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