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