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