New test.
[mono.git] / mcs / class / System.Web / System.Web / HttpResponseStream.cs
1 //
2 // System.Web.HttpResponseStream.cs 
3 //
4 // 
5 // Author:
6 //      Miguel de Icaza (miguel@novell.com)
7 //      Ben Maurer (bmaurer@ximian.com)
8 //
9 //
10 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.IO;
34 using System.Text;
35 using System.Globalization;
36 using System.Runtime.InteropServices;
37         
38 namespace System.Web {
39
40         //
41         // HttpResponseStream implements the "OutputStream" from HttpResponse
42         //
43         // The MS implementation is broken in that it does not hook up this
44         // to HttpResponse, so calling "Flush" on Response.OutputStream does not
45         // flush the contents and produce the headers.
46         //
47         // You must call HttpResponse.Flush which does the actual header generation
48         // and actual data flushing
49         //
50         internal class HttpResponseStream : Stream {
51                 Bucket first_bucket;
52                 Bucket cur_bucket;
53                 HttpResponse response;
54                 internal long total;
55                 Stream filter;
56                 byte [] chunk_buffer = new byte [24];
57
58                 public HttpResponseStream (HttpResponse response)
59                 {
60                         this.response = response;
61                 }
62
63                 internal bool HaveFilter {
64                         get { return filter != null; }
65                 }
66
67                 public Stream Filter {
68                         get {
69                                 if (filter == null)
70                                         filter = new OutputFilterStream (this);
71                                 return filter;
72                         }
73                         set {
74                                 filter = value;
75                         }
76                 }
77 #if TARGET_JVM
78
79                 class BlockManager {
80                         const int PreferredLength = 16 * 1024;
81                         static readonly byte[] EmptyBuffer = new byte[0];
82
83                         byte[] buffer = EmptyBuffer;
84                         int position;
85
86                         public BlockManager () {
87                         }
88
89                         public int Position {
90                                 get { return position; }
91                         }
92
93                         void EnsureCapacity (int capacity) {
94                                 if (buffer.Length >= capacity)
95                                         return;
96
97                                 capacity += PreferredLength;
98                                 capacity = (capacity / PreferredLength) * PreferredLength;
99                                 byte[] temp = new byte[capacity];
100                                 Array.Copy(buffer, 0, temp, 0, buffer.Length);
101                                 buffer = temp;
102                         }
103
104                         public void Write (byte [] buffer, int offset, int count) {
105                                 if (count == 0)
106                                         return;
107
108                                 EnsureCapacity (position + count);
109                                 Array.Copy(buffer, offset, this.buffer, position, count);
110                                 position += count;
111                         }
112
113                         public void Send (HttpWorkerRequest wr, int start, int end) {
114                                 int length = end - start;
115                                 if (length <= 0)
116                                         return;
117
118                                 if (length > buffer.Length - start)
119                                         length = buffer.Length - start;
120
121                                 if (start > 0) {
122                                         byte[] temp = new byte[length];
123                                         Array.Copy(buffer, start, temp, 0, length);
124                                         buffer = temp;
125                                 }
126                                 wr.SendResponseFromMemory(buffer, length);
127                         }
128
129                         public void Send (Stream stream, int start, int end) {
130                                 int length = end - start;
131                                 if (length <= 0)
132                                         return;
133
134                                 if (length > buffer.Length - start)
135                                         length = buffer.Length - start;
136
137                                 stream.Write(buffer, start, length);
138                         }
139
140                         public void Dispose () {
141                                 buffer = null;
142                         }
143                 }
144
145 #else // TARGET_JVM
146                 unsafe sealed class BlockManager {
147                         const int PreferredLength = 128 * 1024;
148                         byte *data;
149                         int position;
150                         int block_size;
151
152                         public BlockManager ()
153                         {
154                         }
155
156                         public int Position {
157                                 get { return position; }
158                         }
159
160                         void EnsureCapacity (int capacity)
161                         {
162                                 if (block_size >= capacity)
163                                         return;
164
165                                 capacity += PreferredLength;
166                                 capacity = (capacity / PreferredLength) * PreferredLength;
167                                 data = (byte *) Marshal.ReAllocHGlobal ((IntPtr) data, (IntPtr) capacity);
168                                 block_size = capacity;
169                         }
170
171                         public void Write (byte [] buffer, int offset, int count)
172                         {
173                                 if (count == 0)
174                                         return;
175                                 
176                                 EnsureCapacity (position + count);
177                                 Marshal.Copy (buffer, offset, (IntPtr) (data + position), count);
178                                 position += count;
179                         }
180
181                         public void Send (HttpWorkerRequest wr, int start, int end)
182                         {
183                                 if (end - start <= 0)
184                                         return;
185
186                                 wr.SendResponseFromMemory ((IntPtr) (data + start), end - start);
187                         }
188
189                         public void Send (Stream stream, int start, int end)
190                         {
191                                 int len = end - start;
192                                 if (len <= 0)
193                                         return;
194
195                                 byte [] buffer = new byte [Math.Min (len, 32 * 1024)];
196                                 int size = buffer.Length;
197                                 while (len > 0) {
198                                         Marshal.Copy ((IntPtr) (data + start), buffer, 0, size);
199                                         stream.Write (buffer, 0, size);
200                                         start += size;
201                                         len -= size;
202                                         if (len > 0 && len < size)
203                                                 size = len;
204                                 }
205                         }
206                         
207                         public void Dispose ()
208                         {
209                                 if ((IntPtr) data != IntPtr.Zero) {
210                                         Marshal.FreeHGlobal ((IntPtr) data);
211                                         data = (byte *) IntPtr.Zero;
212                                 }
213                         }
214                 }
215
216 #endif
217                 abstract class Bucket {
218                         public Bucket Next;
219
220                         public virtual void Dispose ()
221                         {
222                         }
223
224                         public abstract void Send (HttpWorkerRequest wr);
225                         public abstract void Send (Stream stream);
226                         public abstract int Length { get; }
227                 }
228
229 #if !TARGET_JVM
230                 unsafe
231 #endif
232                 class ByteBucket : Bucket {
233                         int start;
234                         int length;
235                         public BlockManager blocks;
236                         public bool Expandable = true;
237
238                         public ByteBucket () : this (null)
239                         {
240                         }
241
242                         public ByteBucket (BlockManager blocks)
243                         {
244                                 if (blocks == null)
245                                         blocks = new BlockManager ();
246
247                                 this.blocks = blocks;
248                                 start = blocks.Position;
249                         }
250
251                         public override int Length {
252                                 get { return length; }
253                         }
254
255                         public int Write (byte [] buf, int offset, int count)
256                         {
257                                 if (Expandable == false)
258                                         throw new Exception ("This should not happen.");
259
260                                 blocks.Write (buf, offset, count);
261                                 length += count;
262                                 return count;
263                         }
264
265                         public override void Dispose ()
266                         {
267                                 blocks.Dispose ();
268                         }
269
270                         public override void Send (HttpWorkerRequest wr)
271                         {
272                                 if (length == 0)
273                                         return;
274
275                                 blocks.Send (wr, start, length);
276                         }
277
278                         public override void Send (Stream stream)
279                         {
280                                 if (length == 0)
281                                         return;
282
283                                 blocks.Send (stream, start, length);
284                         }
285                 }
286         
287                 class BufferedFileBucket : Bucket {
288                         string file;
289                         long offset;
290                         long length;
291         
292                         public BufferedFileBucket (string f, long off, long len)
293                         {
294                                 file = f;
295                                 offset = off;
296                                 length = len;
297                         }
298
299                         public override int Length {
300                                 get { return (int) length; }
301                         }
302
303                         public override void Send (HttpWorkerRequest wr)
304                         {
305                                 wr.SendResponseFromFile (file, offset, length);
306                         }
307
308                         public override void Send (Stream stream)
309                         {
310                                 using (FileStream fs = File.OpenRead (file)) {
311                                         byte [] buffer = new byte [Math.Min (fs.Length, 32*1024)];
312
313                                         long remain = fs.Length;
314                                         int n;
315                                         while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
316                                                 remain -= n;
317                                                 stream.Write (buffer, 0, n);
318                                         }
319                                 }
320                         }
321
322                         public override string ToString ()
323                         {
324                                 return String.Format ("file {0} {1} bytes from position {2}", file, length, offset);
325                         }       
326                 }
327         
328                 void AppendBucket (Bucket b)
329                 {
330                         if (first_bucket == null) {
331                                 cur_bucket = first_bucket = b;
332                                 return;
333                         }
334         
335                         cur_bucket.Next = b;
336                         cur_bucket = b;
337                 }
338         
339                 //
340                 // Nothing happens here, broken by requirement.
341                 // See note at the start
342                 //
343                 public override void Flush () 
344                 {
345                 }
346
347                 void SendChunkSize (long l, bool last)
348                 {
349                         if (l == 0 && !last)
350                                 return;
351
352                         int i = 0;
353                         if (l >= 0) {
354                                 string s = String.Format ("{0:x}", l);
355                                 for (; i < s.Length; i++)
356                                         chunk_buffer [i] = (byte) s [i];
357                         }
358
359                         chunk_buffer [i++] = 13;
360                         chunk_buffer [i++] = 10;
361                         if (last) {
362                                 chunk_buffer [i++] = 13;
363                                 chunk_buffer [i++] = 10;
364                         }
365
366                         response.WorkerRequest.SendResponseFromMemory (chunk_buffer, i);
367                 }
368
369                 internal void Flush (HttpWorkerRequest wr, bool final_flush)
370                 {
371                         if (total == 0 && !final_flush)
372                                 return;
373
374                         if (response.use_chunked) 
375                                 SendChunkSize (total, false);
376
377                         for (Bucket b = first_bucket; b != null; b = b.Next) {
378                                 b.Send (wr);
379                         }
380
381                         if (response.use_chunked) {
382                                 SendChunkSize (-1, false);
383                                 if (final_flush)
384                                         SendChunkSize (0, true);
385                         }
386
387                         wr.FlushResponse (final_flush);
388
389                         Clear ();
390                 }
391
392                 internal int GetTotalLength ()
393                 {
394                         int size = 0;
395                         for (Bucket b = first_bucket; b != null; b = b.Next)
396                                 size += b.Length;
397
398                         return size;
399                 }
400
401                 internal MemoryStream GetData ()
402                 {
403                         MemoryStream stream = new MemoryStream ();
404                         for (Bucket b = first_bucket; b != null; b = b.Next)
405                                 b.Send (stream);
406                         return stream;
407                 }
408
409                 public void WriteFile (string f, long offset, long length)
410                 {
411                         if (length == 0)
412                                 return;
413
414                         ByteBucket bb = cur_bucket as ByteBucket;
415
416                         if (bb != null) {
417                                 bb.Expandable = false;
418                                 bb = new ByteBucket (bb.blocks);
419                         }
420
421                         total += length;
422                         
423                         AppendBucket (new BufferedFileBucket (f, offset, length));
424                         if (bb != null)
425                                 AppendBucket (bb);
426                         // Flush () is called from HttpResponse if needed (WriteFile/TransmitFile)
427                 }
428
429                 bool filtering;
430                 internal void ApplyFilter (bool close)
431                 {
432                         if (filter == null)
433                                 return;
434
435                         filtering = true;
436                         Bucket one = first_bucket;
437                         first_bucket = null; // This will recreate new buckets for the filtered content
438                         cur_bucket = null;
439                         total = 0;
440                         for (Bucket b = one; b != null; b = b.Next)
441                                 b.Send (filter);
442
443                         for (Bucket b = one; b != null; b = b.Next)
444                                 b.Dispose ();
445
446                         if (close) {
447                                 filter.Flush ();
448                                 filter.Close ();
449                                 filter = null;
450                         } else {
451                                 filter.Flush ();
452                         }
453                         filtering = false;
454                 }
455
456                 public override void Write (byte [] buffer, int offset, int count)
457                 {
458                         bool buffering = response.Buffer;
459
460                         if (buffering) {
461                                 // It does not matter whether we're in ApplyFilter or not
462                                 AppendBuffer (buffer, offset, count);
463                         } else if (filter == null || filtering) {
464                                 response.WriteHeaders (false);
465                                 HttpWorkerRequest wr = response.WorkerRequest;
466                                 // Direct write because not buffering
467                                 if (offset == 0) {
468                                         wr.SendResponseFromMemory (buffer, count);
469                                 } else {
470                                         UnsafeWrite (wr, buffer, offset, count);
471                                 }
472                                 wr.FlushResponse (false);
473                         } else {
474                                 // Write to the filter, which will call us back, and then Flush
475                                 filtering = true;
476                                 try {
477                                         filter.Write (buffer, offset, count);
478                                 } finally {
479                                         filtering = false;
480                                 }
481                                 Flush (response.WorkerRequest, false);
482                         }
483                 }
484
485 #if TARGET_JVM
486                 void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
487                 {
488                         if (count <= 0)
489                                 return;
490
491                         byte[] copy = new byte[count];
492                         Array.Copy(buffer, offset, copy, 0, count);
493                         wr.SendResponseFromMemory (copy, count);
494                 }
495 #else
496                 unsafe void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
497                 {
498                         fixed (byte *ptr = buffer) {
499                                 wr.SendResponseFromMemory ((IntPtr) (ptr + offset), count);
500                         }
501                 }
502 #endif
503                 void AppendBuffer (byte [] buffer, int offset, int count)
504                 {
505                         if (!(cur_bucket is ByteBucket))
506                                 AppendBucket (new ByteBucket ());
507
508                         total += count;
509                         ((ByteBucket) cur_bucket).Write (buffer, offset, count);
510                 }
511
512                 //
513                 // This should not flush/close or anything else, its called
514                 // just to free any memory we might have allocated (when we later
515                 // implement something with unmanaged memory).
516                 //
517                 internal void ReleaseResources (bool close_filter)
518                 {
519                         if (close_filter && filter != null) {
520                                 filter.Close ();
521                                 filter = null;
522                         }
523
524                         for (Bucket b = first_bucket; b != null; b = b.Next)
525                                 b.Dispose ();
526
527                         first_bucket = null;
528                         cur_bucket = null;
529                 }
530
531                 public void Clear ()
532                 {
533                         //
534                         // IMPORTANT: you must dispose *AFTER* using all the buckets Byte chunks might be
535                         // split across two buckets if there is a file between the data.
536                         //
537                         ReleaseResources (false);
538                         total = 0;
539                 }
540                 
541                 public override bool CanRead {
542                         get {
543                                 return false;
544                         }       
545                 }
546                         
547                 public override bool CanSeek {
548                         get {
549                                 return false;
550                         }
551                 }
552                 public override bool CanWrite {
553                         get {
554                                 return true;
555                         }
556                 }
557                 
558                 const string notsupported = "HttpResponseStream is a forward, write-only stream";
559                 
560                 public override long Length {
561                         get {
562                                 throw new InvalidOperationException (notsupported);
563                         }
564                 }
565         
566                 public override long Position {
567                         get {
568                                 throw new InvalidOperationException (notsupported);
569                         }
570                         set {
571                                 throw new InvalidOperationException (notsupported);
572                         }
573                 }
574                 
575                 public override long Seek (long offset, SeekOrigin origin)
576                 {
577                         throw new InvalidOperationException (notsupported);
578                 }
579                 
580                 public override void SetLength (long value) 
581                 {
582                         throw new InvalidOperationException (notsupported);
583                 }
584         
585                 public override int Read (byte [] buffer, int offset, int count)
586                 {
587                         throw new InvalidOperationException (notsupported);
588                 }
589         }
590 }
591