In corlib/System.Runtime.InteropServices:
[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 Send (HttpWorkerRequest wr, int start, int end)
185                         {
186                                 if (end - start <= 0)
187                                         return;
188
189                                 wr.SendResponseFromMemory ((IntPtr) (data + start), end - start);
190                         }
191
192                         public void Send (Stream stream, int start, int end)
193                         {
194                                 int len = end - start;
195                                 if (len <= 0)
196                                         return;
197
198                                 byte [] buffer = new byte [Math.Min (len, 32 * 1024)];
199                                 int size = buffer.Length;
200                                 while (len > 0) {
201                                         Marshal.Copy ((IntPtr) (data + start), buffer, 0, size);
202                                         stream.Write (buffer, 0, size);
203                                         start += size;
204                                         len -= size;
205                                         if (len > 0 && len < size)
206                                                 size = len;
207                                 }
208                         }
209                         
210                         public void Dispose ()
211                         {
212                                 if ((IntPtr) data != IntPtr.Zero) {
213                                         Marshal.FreeHGlobal ((IntPtr) data);
214                                         data = (byte *) IntPtr.Zero;
215                                 }
216                         }
217                 }
218
219 #endif
220                 abstract class Bucket {
221                         public Bucket Next;
222
223                         public virtual void Dispose ()
224                         {
225                         }
226
227                         public abstract void Send (HttpWorkerRequest wr);
228                         public abstract void Send (Stream stream);
229                         public abstract int Length { get; }
230                 }
231
232 #if !TARGET_JVM
233                 unsafe
234 #endif
235                 class ByteBucket : Bucket {
236                         int start;
237                         int length;
238                         public BlockManager blocks;
239                         public bool Expandable = true;
240
241                         public ByteBucket () : this (null)
242                         {
243                         }
244
245                         public ByteBucket (BlockManager blocks)
246                         {
247                                 if (blocks == null)
248                                         blocks = new BlockManager ();
249
250                                 this.blocks = blocks;
251                                 start = blocks.Position;
252                         }
253
254                         public override int Length {
255                                 get { return length; }
256                         }
257
258                         public int Write (byte [] buf, int offset, int count)
259                         {
260                                 if (Expandable == false)
261                                         throw new Exception ("This should not happen.");
262
263                                 blocks.Write (buf, offset, count);
264                                 length += count;
265                                 return count;
266                         }
267
268                         public override void Dispose ()
269                         {
270                                 blocks.Dispose ();
271                         }
272
273                         public override void Send (HttpWorkerRequest wr)
274                         {
275                                 if (length == 0)
276                                         return;
277
278                                 blocks.Send (wr, start, length);
279                         }
280
281                         public override void Send (Stream stream)
282                         {
283                                 if (length == 0)
284                                         return;
285
286                                 blocks.Send (stream, start, length);
287                         }
288                 }
289         
290                 class BufferedFileBucket : Bucket {
291                         string file;
292                         long offset;
293                         long length;
294         
295                         public BufferedFileBucket (string f, long off, long len)
296                         {
297                                 file = f;
298                                 offset = off;
299                                 length = len;
300                         }
301
302                         public override int Length {
303                                 get { return (int) length; }
304                         }
305
306                         public override void Send (HttpWorkerRequest wr)
307                         {
308                                 wr.SendResponseFromFile (file, offset, length);
309                         }
310
311                         public override void Send (Stream stream)
312                         {
313                                 using (FileStream fs = File.OpenRead (file)) {
314                                         byte [] buffer = new byte [Math.Min (fs.Length, 32*1024)];
315
316                                         long remain = fs.Length;
317                                         int n;
318                                         while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
319                                                 remain -= n;
320                                                 stream.Write (buffer, 0, n);
321                                         }
322                                 }
323                         }
324
325                         public override string ToString ()
326                         {
327                                 return String.Format ("file {0} {1} bytes from position {2}", file, length, offset);
328                         }       
329                 }
330         
331                 void AppendBucket (Bucket b)
332                 {
333                         if (first_bucket == null) {
334                                 cur_bucket = first_bucket = b;
335                                 return;
336                         }
337         
338                         cur_bucket.Next = b;
339                         cur_bucket = b;
340                 }
341         
342                 //
343                 // Nothing happens here, broken by requirement.
344                 // See note at the start
345                 //
346                 public override void Flush () 
347                 {
348                 }
349
350                 void SendChunkSize (long l, bool last)
351                 {
352                         if (l == 0 && !last)
353                                 return;
354
355                         int i = 0;
356                         if (l >= 0) {
357                                 string s = String.Format ("{0:x}", l);
358                                 for (; i < s.Length; i++)
359                                         chunk_buffer [i] = (byte) s [i];
360                         }
361
362                         chunk_buffer [i++] = 13;
363                         chunk_buffer [i++] = 10;
364                         if (last) {
365                                 chunk_buffer [i++] = 13;
366                                 chunk_buffer [i++] = 10;
367                         }
368
369                         response.WorkerRequest.SendResponseFromMemory (chunk_buffer, i);
370                 }
371
372                 internal void Flush (HttpWorkerRequest wr, bool final_flush)
373                 {
374                         if (total == 0 && !final_flush)
375                                 return;
376
377                         if (response.use_chunked) 
378                                 SendChunkSize (total, false);
379
380                         for (Bucket b = first_bucket; b != null; b = b.Next) {
381                                 b.Send (wr);
382                         }
383
384                         if (response.use_chunked) {
385                                 SendChunkSize (-1, false);
386                                 if (final_flush)
387                                         SendChunkSize (0, true);
388                         }
389
390                         wr.FlushResponse (final_flush);
391
392                         Clear ();
393                 }
394
395                 internal int GetTotalLength ()
396                 {
397                         int size = 0;
398                         for (Bucket b = first_bucket; b != null; b = b.Next)
399                                 size += b.Length;
400
401                         return size;
402                 }
403
404                 internal MemoryStream GetData ()
405                 {
406                         MemoryStream stream = new MemoryStream ();
407                         for (Bucket b = first_bucket; b != null; b = b.Next)
408                                 b.Send (stream);
409                         return stream;
410                 }
411
412                 public void WriteFile (string f, long offset, long length)
413                 {
414                         if (length == 0)
415                                 return;
416
417                         ByteBucket bb = cur_bucket as ByteBucket;
418
419                         if (bb != null) {
420                                 bb.Expandable = false;
421                                 bb = new ByteBucket (bb.blocks);
422                         }
423
424                         total += length;
425                         
426                         AppendBucket (new BufferedFileBucket (f, offset, length));
427                         if (bb != null)
428                                 AppendBucket (bb);
429                         // Flush () is called from HttpResponse if needed (WriteFile/TransmitFile)
430                 }
431
432                 bool filtering;
433                 internal void ApplyFilter (bool close)
434                 {
435                         if (filter == null)
436                                 return;
437
438                         filtering = true;
439                         Bucket one = first_bucket;
440                         first_bucket = null; // This will recreate new buckets for the filtered content
441                         cur_bucket = null;
442                         total = 0;
443                         for (Bucket b = one; b != null; b = b.Next)
444                                 b.Send (filter);
445
446                         for (Bucket b = one; b != null; b = b.Next)
447                                 b.Dispose ();
448
449                         if (close) {
450                                 filter.Flush ();
451                                 filter.Close ();
452                                 filter = null;
453                         } else {
454                                 filter.Flush ();
455                         }
456                         filtering = false;
457                 }
458
459                 public override void Write (byte [] buffer, int offset, int count)
460                 {
461                         bool buffering = response.Buffer;
462
463                         if (buffering) {
464                                 // It does not matter whether we're in ApplyFilter or not
465                                 AppendBuffer (buffer, offset, count);
466                         } else if (filter == null || filtering) {
467                                 response.WriteHeaders (false);
468                                 HttpWorkerRequest wr = response.WorkerRequest;
469                                 // Direct write because not buffering
470                                 if (offset == 0) {
471                                         wr.SendResponseFromMemory (buffer, count);
472                                 } else {
473                                         UnsafeWrite (wr, buffer, offset, count);
474                                 }
475                                 wr.FlushResponse (false);
476                         } else {
477                                 // Write to the filter, which will call us back, and then Flush
478                                 filtering = true;
479                                 try {
480                                         filter.Write (buffer, offset, count);
481                                 } finally {
482                                         filtering = false;
483                                 }
484                                 Flush (response.WorkerRequest, false);
485                         }
486                 }
487
488 #if TARGET_JVM
489                 void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
490                 {
491                         if (count <= 0)
492                                 return;
493
494                         byte[] copy = new byte[count];
495                         Array.Copy(buffer, offset, copy, 0, count);
496                         wr.SendResponseFromMemory (copy, count);
497                 }
498 #else
499                 unsafe void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
500                 {
501                         fixed (byte *ptr = buffer) {
502                                 wr.SendResponseFromMemory ((IntPtr) (ptr + offset), count);
503                         }
504                 }
505 #endif
506                 void AppendBuffer (byte [] buffer, int offset, int count)
507                 {
508                         if (!(cur_bucket is ByteBucket))
509                                 AppendBucket (new ByteBucket ());
510
511                         total += count;
512                         ((ByteBucket) cur_bucket).Write (buffer, offset, count);
513                 }
514
515                 //
516                 // This should not flush/close or anything else, its called
517                 // just to free any memory we might have allocated (when we later
518                 // implement something with unmanaged memory).
519                 //
520                 internal void ReleaseResources (bool close_filter)
521                 {
522                         if (close_filter && filter != null) {
523                                 filter.Close ();
524                                 filter = null;
525                         }
526
527                         for (Bucket b = first_bucket; b != null; b = b.Next)
528                                 b.Dispose ();
529
530                         first_bucket = null;
531                         cur_bucket = null;
532                 }
533
534                 public void Clear ()
535                 {
536                         //
537                         // IMPORTANT: you must dispose *AFTER* using all the buckets Byte chunks might be
538                         // split across two buckets if there is a file between the data.
539                         //
540                         ReleaseResources (false);
541                         total = 0;
542                 }
543                 
544                 public override bool CanRead {
545                         get {
546                                 return false;
547                         }       
548                 }
549                         
550                 public override bool CanSeek {
551                         get {
552                                 return false;
553                         }
554                 }
555                 public override bool CanWrite {
556                         get {
557                                 return true;
558                         }
559                 }
560                 
561                 const string notsupported = "HttpResponseStream is a forward, write-only stream";
562                 
563                 public override long Length {
564                         get {
565                                 throw new InvalidOperationException (notsupported);
566                         }
567                 }
568         
569                 public override long Position {
570                         get {
571                                 throw new InvalidOperationException (notsupported);
572                         }
573                         set {
574                                 throw new InvalidOperationException (notsupported);
575                         }
576                 }
577                 
578                 public override long Seek (long offset, SeekOrigin origin)
579                 {
580                         throw new InvalidOperationException (notsupported);
581                 }
582                 
583                 public override void SetLength (long value) 
584                 {
585                         throw new InvalidOperationException (notsupported);
586                 }
587         
588                 public override int Read (byte [] buffer, int offset, int count)
589                 {
590                         throw new InvalidOperationException (notsupported);
591                 }
592         }
593 }
594