2010-03-12 Jb Evain <jbevain@novell.com>
[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-2009 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         class HttpResponseStream : Stream
51         {
52                 Bucket first_bucket;
53                 Bucket cur_bucket;
54                 HttpResponse response;
55                 internal long total;
56                 Stream filter;
57                 byte [] chunk_buffer = new byte [24];
58
59                 public HttpResponseStream (HttpResponse response)
60                 {
61                         this.response = response;
62                 }
63
64                 internal bool HaveFilter {
65                         get { return filter != null; }
66                 }
67
68                 public Stream Filter {
69                         get {
70                                 if (filter == null)
71                                         filter = new OutputFilterStream (this);
72                                 return filter;
73                         }
74                         set {
75                                 filter = value;
76                         }
77                 }
78 #if TARGET_JVM
79
80                 class BlockManager {
81                         const int PreferredLength = 16 * 1024;
82                         static readonly byte[] EmptyBuffer = new byte[0];
83
84                         byte[] buffer = EmptyBuffer;
85                         int position;
86
87                         public BlockManager () {
88                         }
89
90                         public int Position {
91                                 get { return position; }
92                         }
93
94                         void EnsureCapacity (int capacity) {
95                                 if (buffer.Length >= capacity)
96                                         return;
97
98                                 capacity += PreferredLength;
99                                 capacity = (capacity / PreferredLength) * PreferredLength;
100                                 byte[] temp = new byte[capacity];
101                                 Array.Copy(buffer, 0, temp, 0, buffer.Length);
102                                 buffer = temp;
103                         }
104
105                         public void Write (byte [] buffer, int offset, int count) {
106                                 if (count == 0)
107                                         return;
108
109                                 EnsureCapacity (position + count);
110                                 Array.Copy(buffer, offset, this.buffer, position, count);
111                                 position += count;
112                         }
113
114                         public void Send (HttpWorkerRequest wr, int start, int end) {
115                                 int length = end - start;
116                                 if (length <= 0)
117                                         return;
118
119                                 if (length > buffer.Length - start)
120                                         length = buffer.Length - start;
121
122                                 if (start > 0) {
123                                         byte[] temp = new byte[length];
124                                         Array.Copy(buffer, start, temp, 0, length);
125                                         buffer = temp;
126                                 }
127                                 wr.SendResponseFromMemory(buffer, length);
128                         }
129
130                         public void Send (Stream stream, int start, int end) {
131                                 int length = end - start;
132                                 if (length <= 0)
133                                         return;
134
135                                 if (length > buffer.Length - start)
136                                         length = buffer.Length - start;
137
138                                 stream.Write(buffer, start, length);
139                         }
140
141                         public void Dispose () {
142                                 buffer = null;
143                         }
144                 }
145
146 #else // TARGET_JVM
147                 unsafe sealed class BlockManager {
148                         const int PreferredLength = 128 * 1024;
149                         byte *data;
150                         int position;
151                         int block_size;
152
153                         public BlockManager ()
154                         {
155                         }
156
157                         public int Position {
158                                 get { return position; }
159                         }
160
161                         void EnsureCapacity (int capacity)
162                         {
163                                 if (block_size >= capacity)
164                                         return;
165
166                                 capacity += PreferredLength;
167                                 capacity = (capacity / PreferredLength) * PreferredLength;
168
169                                 data = data == null
170                                         ? (byte *) Marshal.AllocHGlobal (capacity)
171                                         : (byte *) Marshal.ReAllocHGlobal ((IntPtr) data, (IntPtr) capacity);
172                                 block_size = capacity;
173                         }
174
175                         public void Write (byte [] buffer, int offset, int count)
176                         {
177                                 if (count == 0)
178                                         return;
179                                 
180                                 EnsureCapacity (position + count);
181                                 Marshal.Copy (buffer, offset, (IntPtr) (data + position), count);
182                                 position += count;
183                         }
184
185                         public void Write (IntPtr ptr, int count)
186                         {
187                                 if (count == 0)
188                                         return;
189                                 
190                                 EnsureCapacity (position + count);
191                                 byte *src = (byte *) ptr.ToPointer ();
192                                 memcpy (data + position, src, count);
193                                 position += count;
194                         }
195
196                         public void Send (HttpWorkerRequest wr, int start, int end)
197                         {
198                                 if (end - start <= 0)
199                                         return;
200
201                                 wr.SendResponseFromMemory ((IntPtr) (data + start), end - start);
202                         }
203
204                         public void Send (Stream stream, int start, int end)
205                         {
206                                 int len = end - start;
207                                 if (len <= 0)
208                                         return;
209
210                                 byte [] buffer = new byte [Math.Min (len, 32 * 1024)];
211                                 int size = buffer.Length;
212                                 while (len > 0) {
213                                         Marshal.Copy ((IntPtr) (data + start), buffer, 0, size);
214                                         stream.Write (buffer, 0, size);
215                                         start += size;
216                                         len -= size;
217                                         if (len > 0 && len < size)
218                                                 size = len;
219                                 }
220                         }
221                         
222                         public void Dispose ()
223                         {
224                                 if ((IntPtr) data != IntPtr.Zero) {
225                                         Marshal.FreeHGlobal ((IntPtr) data);
226                                         data = (byte *) IntPtr.Zero;
227                                 }
228                         }
229                 }
230
231 #endif
232                 abstract class Bucket {
233                         public Bucket Next;
234
235                         public virtual void Dispose ()
236                         {
237                         }
238
239                         public abstract void Send (HttpWorkerRequest wr);
240                         public abstract void Send (Stream stream);
241                         public abstract int Length { get; }
242                 }
243
244 #if !TARGET_JVM
245                 unsafe
246 #endif
247                 class ByteBucket : Bucket {
248                         int start;
249                         int length;
250                         public BlockManager blocks;
251                         public bool Expandable = true;
252
253                         public ByteBucket () : this (null)
254                         {
255                         }
256
257                         public ByteBucket (BlockManager blocks)
258                         {
259                                 if (blocks == null)
260                                         blocks = new BlockManager ();
261
262                                 this.blocks = blocks;
263                                 start = blocks.Position;
264                         }
265
266                         public override int Length {
267                                 get { return length; }
268                         }
269
270                         public unsafe int Write (byte [] buf, int offset, int count)
271                         {
272                                 if (Expandable == false)
273                                         throw new Exception ("This should not happen.");
274
275                                 fixed (byte *p = &buf[0]) {
276                                         IntPtr p2 = new IntPtr (p + offset);
277                                         blocks.Write (p2, count);
278                                 }
279
280                                 length += count;
281                                 return count;
282                         }
283
284                         public int Write (IntPtr ptr, int count)
285                         {
286                                 if (Expandable == false)
287                                         throw new Exception ("This should not happen.");
288
289                                 blocks.Write (ptr, count);
290                                 length += count;
291                                 return count;
292                         }
293
294                         public override void Dispose ()
295                         {
296                                 blocks.Dispose ();
297                         }
298
299                         public override void Send (HttpWorkerRequest wr)
300                         {
301                                 if (length == 0)
302                                         return;
303
304                                 blocks.Send (wr, start, length);
305                         }
306
307                         public override void Send (Stream stream)
308                         {
309                                 if (length == 0)
310                                         return;
311
312                                 blocks.Send (stream, start, length);
313                         }
314                 }
315
316                 class BufferedFileBucket : Bucket {
317                         string file;
318                         long offset;
319                         long length;
320         
321                         public BufferedFileBucket (string f, long off, long len)
322                         {
323                                 file = f;
324                                 offset = off;
325                                 length = len;
326                         }
327
328                         public override int Length {
329                                 get { return (int) length; }
330                         }
331
332                         public override void Send (HttpWorkerRequest wr)
333                         {
334                                 wr.SendResponseFromFile (file, offset, length);
335                         }
336
337                         public override void Send (Stream stream)
338                         {
339                                 using (FileStream fs = File.OpenRead (file)) {
340                                         byte [] buffer = new byte [Math.Min (fs.Length, 32*1024)];
341
342                                         long remain = fs.Length;
343                                         int n;
344                                         while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
345                                                 remain -= n;
346                                                 stream.Write (buffer, 0, n);
347                                         }
348                                 }
349                         }
350
351                         public override string ToString ()
352                         {
353                                 return "file " + file + " " + length.ToString () + " bytes from position " + offset.ToString ();
354                         }       
355                 }
356         
357                 void AppendBucket (Bucket b)
358                 {
359                         if (first_bucket == null) {
360                                 cur_bucket = first_bucket = b;
361                                 return;
362                         }
363         
364                         cur_bucket.Next = b;
365                         cur_bucket = b;
366                 }
367         
368                 //
369                 // Nothing happens here, broken by requirement.
370                 // See note at the start
371                 //
372                 public override void Flush () 
373                 {
374                 }
375
376                 void SendChunkSize (long l, bool last)
377                 {
378                         if (l == 0 && !last)
379                                 return;
380
381                         int i = 0;
382                         if (l >= 0) {
383                                 string s = l.ToString ("x");
384                                 for (; i < s.Length; i++)
385                                         chunk_buffer [i] = (byte) s [i];
386                         }
387
388                         chunk_buffer [i++] = 13;
389                         chunk_buffer [i++] = 10;
390                         if (last) {
391                                 chunk_buffer [i++] = 13;
392                                 chunk_buffer [i++] = 10;
393                         }
394
395                         response.WorkerRequest.SendResponseFromMemory (chunk_buffer, i);
396                 }
397
398                 internal void Flush (HttpWorkerRequest wr, bool final_flush)
399                 {
400                         if (total == 0 && !final_flush)
401                                 return;
402
403                         if (response.use_chunked) 
404                                 SendChunkSize (total, false);
405
406                         for (Bucket b = first_bucket; b != null; b = b.Next) {
407                                 b.Send (wr);
408                         }
409
410                         if (response.use_chunked) {
411                                 SendChunkSize (-1, false);
412                                 if (final_flush)
413                                         SendChunkSize (0, true);
414                         }
415
416                         wr.FlushResponse (final_flush);
417
418                         Clear ();
419                 }
420
421                 internal int GetTotalLength ()
422                 {
423                         int size = 0;
424                         for (Bucket b = first_bucket; b != null; b = b.Next)
425                                 size += b.Length;
426
427                         return size;
428                 }
429
430                 internal MemoryStream GetData ()
431                 {
432                         MemoryStream stream = new MemoryStream ();
433                         for (Bucket b = first_bucket; b != null; b = b.Next)
434                                 b.Send (stream);
435                         return stream;
436                 }
437
438                 public void WriteFile (string f, long offset, long length)
439                 {
440                         if (length == 0)
441                                 return;
442
443                         ByteBucket bb = cur_bucket as ByteBucket;
444
445                         if (bb != null) {
446                                 bb.Expandable = false;
447                                 bb = new ByteBucket (bb.blocks);
448                         }
449
450                         total += length;
451                         
452                         AppendBucket (new BufferedFileBucket (f, offset, length));
453                         if (bb != null)
454                                 AppendBucket (bb);
455                         // Flush () is called from HttpResponse if needed (WriteFile/TransmitFile)
456                 }
457
458                 bool filtering;
459                 internal void ApplyFilter (bool close)
460                 {
461                         if (filter == null)
462                                 return;
463
464                         filtering = true;
465                         Bucket one = first_bucket;
466                         first_bucket = null; // This will recreate new buckets for the filtered content
467                         cur_bucket = null;
468                         total = 0;
469                         for (Bucket b = one; b != null; b = b.Next)
470                                 b.Send (filter);
471
472                         for (Bucket b = one; b != null; b = b.Next)
473                                 b.Dispose ();
474
475                         if (close) {
476                                 filter.Flush ();
477                                 filter.Close ();
478                                 filter = null;
479                         } else {
480                                 filter.Flush ();
481                         }
482                         filtering = false;
483                 }
484
485                 public void WritePtr (IntPtr ptr, int length)
486                 {
487                         if (length == 0)
488                                 return;
489
490                         bool buffering = response.BufferOutput;
491
492                         if (buffering) {
493                                 // It does not matter whether we're in ApplyFilter or not
494                                 AppendBuffer (ptr, length);
495                         } else if (filter == null || filtering) {
496                                 response.WriteHeaders (false);
497                                 HttpWorkerRequest wr = response.WorkerRequest;
498                                 // Direct write because not buffering
499                                 wr.SendResponseFromMemory (ptr, length);
500                                 wr.FlushResponse (false);
501                         } else {
502                                 // Write to the filter, which will call us back, and then Flush
503                                 filtering = true;
504                                 try {
505                                         byte [] bytes = new byte [length];
506                                         Marshal.Copy (ptr, bytes, 0, length);
507                                         filter.Write (bytes, 0, length);
508                                         bytes = null;
509                                 } finally {
510                                         filtering = false;
511                                 }
512                                 Flush (response.WorkerRequest, false);
513                         }
514
515                 }
516
517                 public override void Write (byte [] buffer, int offset, int count)
518                 {
519                         bool buffering = response.BufferOutput;
520                         if (buffer == null)
521                                 throw new ArgumentNullException ("buffer");
522
523                         int max_count = buffer.Length - offset;
524                         if (offset < 0 || max_count <= 0)
525                                 throw new ArgumentOutOfRangeException ("offset");
526                         if (count < 0)
527                                 throw new ArgumentOutOfRangeException ("count");
528                         if (count > max_count)
529                                 count = max_count;
530
531                         if (buffering) {
532                                 // It does not matter whether we're in ApplyFilter or not
533                                 AppendBuffer (buffer, offset, count);
534                         } else if (filter == null || filtering) {
535                                 response.WriteHeaders (false);
536                                 HttpWorkerRequest wr = response.WorkerRequest;
537                                 // Direct write because not buffering
538                                 if (offset == 0) {
539                                         wr.SendResponseFromMemory (buffer, count);
540                                 } else {
541                                         UnsafeWrite (wr, buffer, offset, count);
542                                 }
543                                 wr.FlushResponse (false);
544                         } else {
545                                 // Write to the filter, which will call us back, and then Flush
546                                 filtering = true;
547                                 try {
548                                         filter.Write (buffer, offset, count);
549                                 } finally {
550                                         filtering = false;
551                                 }
552                                 Flush (response.WorkerRequest, false);
553                         }
554                 }
555
556 #if TARGET_JVM
557                 void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
558                 {
559                         if (count <= 0)
560                                 return;
561
562                         byte[] copy = new byte[count];
563                         Array.Copy(buffer, offset, copy, 0, count);
564                         wr.SendResponseFromMemory (copy, count);
565                 }
566 #else
567                 unsafe void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
568                 {
569                         fixed (byte *ptr = buffer) {
570                                 wr.SendResponseFromMemory ((IntPtr) (ptr + offset), count);
571                         }
572                 }
573 #endif
574                 void AppendBuffer (byte [] buffer, int offset, int count)
575                 {
576                         if (!(cur_bucket is ByteBucket))
577                                 AppendBucket (new ByteBucket ());
578
579                         total += count;
580                         ((ByteBucket) cur_bucket).Write (buffer, offset, count);
581                 }
582
583                 void AppendBuffer (IntPtr ptr, int count)
584                 {
585                         if (!(cur_bucket is ByteBucket))
586                                 AppendBucket (new ByteBucket ());
587
588                         total += count;
589                         ((ByteBucket) cur_bucket).Write (ptr, count);
590                 }
591
592                 //
593                 // This should not flush/close or anything else, its called
594                 // just to free any memory we might have allocated (when we later
595                 // implement something with unmanaged memory).
596                 //
597                 internal void ReleaseResources (bool close_filter)
598                 {
599                         if (close_filter && filter != null) {
600                                 filter.Close ();
601                                 filter = null;
602                         }
603
604                         for (Bucket b = first_bucket; b != null; b = b.Next)
605                                 b.Dispose ();
606
607                         first_bucket = null;
608                         cur_bucket = null;
609                 }
610
611                 public void Clear ()
612                 {
613                         //
614                         // IMPORTANT: you must dispose *AFTER* using all the buckets Byte chunks might be
615                         // split across two buckets if there is a file between the data.
616                         //
617                         ReleaseResources (false);
618                         total = 0;
619                 }
620                 
621                 // Do not use directly. Use memcpy.
622                 static unsafe void memcpy4 (byte *dest, byte *src, int size) {
623                         /*while (size >= 32) {
624                                 // using long is better than int and slower than double
625                                 // FIXME: enable this only on correct alignment or on platforms
626                                 // that can tolerate unaligned reads/writes of doubles
627                                 ((double*)dest) [0] = ((double*)src) [0];
628                                 ((double*)dest) [1] = ((double*)src) [1];
629                                 ((double*)dest) [2] = ((double*)src) [2];
630                                 ((double*)dest) [3] = ((double*)src) [3];
631                                 dest += 32;
632                                 src += 32;
633                                 size -= 32;
634                         }*/
635                         while (size >= 16) {
636                                 ((int*)dest) [0] = ((int*)src) [0];
637                                 ((int*)dest) [1] = ((int*)src) [1];
638                                 ((int*)dest) [2] = ((int*)src) [2];
639                                 ((int*)dest) [3] = ((int*)src) [3];
640                                 dest += 16;
641                                 src += 16;
642                                 size -= 16;
643                         }
644                         while (size >= 4) {
645                                 ((int*)dest) [0] = ((int*)src) [0];
646                                 dest += 4;
647                                 src += 4;
648                                 size -= 4;
649                         }
650                         while (size > 0) {
651                                 ((byte*)dest) [0] = ((byte*)src) [0];
652                                 dest += 1;
653                                 src += 1;
654                                 --size;
655                         }
656                 }
657
658                 // Do not use directly. Use memcpy.
659                 static unsafe void memcpy2 (byte *dest, byte *src, int size) {
660                         while (size >= 8) {
661                                 ((short*)dest) [0] = ((short*)src) [0];
662                                 ((short*)dest) [1] = ((short*)src) [1];
663                                 ((short*)dest) [2] = ((short*)src) [2];
664                                 ((short*)dest) [3] = ((short*)src) [3];
665                                 dest += 8;
666                                 src += 8;
667                                 size -= 8;
668                         }
669                         while (size >= 2) {
670                                 ((short*)dest) [0] = ((short*)src) [0];
671                                 dest += 2;
672                                 src += 2;
673                                 size -= 2;
674                         }
675                         if (size > 0)
676                                 ((byte*)dest) [0] = ((byte*)src) [0];
677                 }
678
679                 // Do not use directly. Use memcpy.
680                 static unsafe void memcpy1 (byte *dest, byte *src, int size) {
681                         while (size >= 8) {
682                                 ((byte*)dest) [0] = ((byte*)src) [0];
683                                 ((byte*)dest) [1] = ((byte*)src) [1];
684                                 ((byte*)dest) [2] = ((byte*)src) [2];
685                                 ((byte*)dest) [3] = ((byte*)src) [3];
686                                 ((byte*)dest) [4] = ((byte*)src) [4];
687                                 ((byte*)dest) [5] = ((byte*)src) [5];
688                                 ((byte*)dest) [6] = ((byte*)src) [6];
689                                 ((byte*)dest) [7] = ((byte*)src) [7];
690                                 dest += 8;
691                                 src += 8;
692                                 size -= 8;
693                         }
694                         while (size >= 2) {
695                                 ((byte*)dest) [0] = ((byte*)src) [0];
696                                 ((byte*)dest) [1] = ((byte*)src) [1];
697                                 dest += 2;
698                                 src += 2;
699                                 size -= 2;
700                         }
701                         if (size > 0)
702                                 ((byte*)dest) [0] = ((byte*)src) [0];
703                 }
704
705                 static unsafe void memcpy (byte *dest, byte *src, int size) {
706                         // FIXME: if pointers are not aligned, try to align them
707                         // so a faster routine can be used. Handle the case where
708                         // the pointers can't be reduced to have the same alignment
709                         // (just ignore the issue on x86?)
710                         if ((((int)dest | (int)src) & 3) != 0) {
711                                 if (((int)dest & 1) != 0 && ((int)src & 1) != 0 && size >= 1) {
712                                         dest [0] = src [0];
713                                         ++dest;
714                                         ++src;
715                                         --size;
716                                 }
717                                 if (((int)dest & 2) != 0 && ((int)src & 2) != 0 && size >= 2) {
718                                         ((short*)dest) [0] = ((short*)src) [0];
719                                         dest += 2;
720                                         src += 2;
721                                         size -= 2;
722                                 }
723                                 if ((((int)dest | (int)src) & 1) != 0) {
724                                         memcpy1 (dest, src, size);
725                                         return;
726                                 }
727                                 if ((((int)dest | (int)src) & 2) != 0) {
728                                         memcpy2 (dest, src, size);
729                                         return;
730                                 }
731                         }
732                         memcpy4 (dest, src, size);
733                 }
734
735                 public override bool CanRead {
736                         get {
737                                 return false;
738                         }
739                 }
740
741                 public override bool CanSeek {
742                         get {
743                                 return false;
744                         }
745                 }
746
747                 public override bool CanWrite {
748                         get {
749                                 return true;
750                         }
751                 }
752
753                 const string notsupported = "HttpResponseStream is a forward, write-only stream";
754
755                 public override long Length {
756                         get {
757                                 throw new NotSupportedException (notsupported);
758                         }
759                 }
760         
761                 public override long Position {
762                         get {
763                                 throw new NotSupportedException (notsupported);
764                         }
765                         set {
766                                 throw new NotSupportedException (notsupported);
767                         }
768                 }
769                 
770                 public override long Seek (long offset, SeekOrigin origin)
771                 {
772                         throw new NotSupportedException (notsupported);
773                 }
774
775                 public override void SetLength (long value) 
776                 {
777                         throw new NotSupportedException (notsupported);
778                 }
779         
780                 public override int Read (byte [] buffer, int offset, int count)
781                 {
782                         throw new NotSupportedException (notsupported);
783                 }
784         }
785 }