2010-05-05 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpResponseStream.jvm.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         //
42         // HttpResponseStream implements the "OutputStream" from HttpResponse
43         //
44         // The MS implementation is broken in that it does not hook up this
45         // to HttpResponse, so calling "Flush" on Response.OutputStream does not
46         // flush the contents and produce the headers.
47         //
48         // You must call HttpResponse.Flush which does the actual header generation
49         // and actual data flushing
50         //
51         internal class HttpResponseStream : Stream
52         {
53                 Bucket first_bucket;
54                 Bucket cur_bucket;
55                 HttpResponse response;
56                 bool dirty = false;
57
58                 Stream filter;
59
60                 public HttpResponseStream (HttpResponse response)
61                 {
62                         this.response = response;
63                 }
64
65                 internal bool HaveFilter
66                 {
67                         get { return filter != null; }
68                 }
69
70                 public Stream Filter
71                 {
72                         get
73                         {
74                                 if (filter == null)
75                                         filter = new OutputFilterStream (this);
76                                 return filter;
77                         }
78                         set
79                         {
80                                 filter = value;
81                         }
82                 }
83                 abstract class Bucket
84                 {
85                         public Bucket Next;
86
87                         public virtual void Dispose ()
88                         {
89                         }
90
91                         public abstract void Send (HttpWorkerRequest wr);
92                         public abstract void Send (Stream stream);
93                         public abstract int Length { get; }
94                         public abstract int FreeSpace { get; }
95                 }
96
97                 class ByteBucket : Bucket
98                 {
99
100                         const int _preferredLength = 16 * 1024;
101
102                         int _position = 0;
103                         int _freeSpace = _preferredLength;
104                         byte [] buffer = new byte [_preferredLength];
105
106                         public ByteBucket ()
107                         {
108                         }
109
110                         public override int Length
111                         {
112                                 get { return _position; }
113                         }
114
115                         public override int FreeSpace
116                         {
117                                 get { return _freeSpace; }
118                         }
119
120                         public int Write (byte [] buf, int offset, int count)
121                         {
122                                 if (count > _freeSpace)
123                                         throw new InvalidOperationException ("Out of bucket space");
124
125                                 Array.Copy (buf, offset, buffer, _position, count);
126                                 _position += count;
127                                 _freeSpace = _preferredLength - _position;
128                                 return count;
129                         }
130
131                         public override void Dispose ()
132                         {
133                                 buffer = null;
134                         }
135
136                         public override void Send (HttpWorkerRequest wr)
137                         {
138                                 if (_position == 0)
139                                         return;
140
141                                 wr.SendResponseFromMemory (buffer, _position);
142                         }
143
144                         public override void Send (Stream stream)
145                         {
146                                 if (_position == 0)
147                                         return;
148
149                                 stream.Write (buffer, 0, _position);
150                         }
151                 }
152
153                 class CharBucket : Bucket
154                 {
155                         const int _preferredLength = 8 * 1024;
156
157                         int _position = 0;
158                         int _freeSpace = _preferredLength;
159                         char [] buffer = new char [_preferredLength];
160                         readonly Encoding _encoding;
161
162                         public CharBucket (Encoding encoding)
163                         {
164                                 _encoding = encoding;
165                         }
166
167                         public override int Length
168                         {
169                                 get
170                                 {
171                                         HttpContext current = HttpContext.Current;
172                                         Encoding enc = (current != null) ? current.Response.ContentEncoding : Encoding.UTF8;
173                                         return enc.GetByteCount (buffer, 0, _position);
174                                 }
175                         }
176
177                         public override int FreeSpace
178                         {
179                                 get { return _freeSpace; }
180                         }
181
182                         public int Write (string buf, int offset, int count)
183                         {
184                                 if (count > _freeSpace)
185                                         throw new InvalidOperationException ("Out of bucket space");
186
187                                 buf.CopyTo (offset, buffer, _position, count);
188                                 _position += count;
189
190                                 _freeSpace = _preferredLength - _position;
191                                 return count;
192                         }
193
194                         public int Write (char [] buf, int offset, int count)
195                         {
196                                 if (count > _freeSpace)
197                                         throw new InvalidOperationException ("Out of bucket space");
198
199                                 if (count == 1)
200                                         buffer [_position] = buf [offset];
201                                 else
202                                         Array.Copy (buf, offset, buffer, _position, count);
203
204                                 _position += count;
205                                 _freeSpace = _preferredLength - _position;
206                                 return count;
207                         }
208
209                         public override void Dispose ()
210                         {
211                                 buffer = null;
212                         }
213
214                         public override void Send (HttpWorkerRequest wr)
215                         {
216                                 if (_position == 0)
217                                         return;
218
219                                 wr.SendResponseFromMemory (buffer, 0, _position, _encoding);
220                         }
221
222                         public override void Send (Stream stream)
223                         {
224                                 if (_position == 0)
225                                         return;
226
227                                 byte[] bytesToWrite =_encoding.GetBytes (buffer, 0, _position);
228                                 stream.Write (bytesToWrite, 0, bytesToWrite.Length);
229                         }
230                 }
231
232                 class BufferedFileBucket : Bucket
233                 {
234                         string file;
235                         long offset;
236                         long length;
237
238                         public BufferedFileBucket (string f, long off, long len)
239                         {
240                                 file = f;
241                                 offset = off;
242                                 length = len;
243                         }
244
245                         public override int Length
246                         {
247                                 get { return (int) length; }
248                         }
249
250                         public override int FreeSpace
251                         {
252                                 get { return int.MaxValue; }
253                         }
254
255                         public override void Send (HttpWorkerRequest wr)
256                         {
257                                 wr.SendResponseFromFile (file, offset, length);
258                         }
259
260                         public override void Send (Stream stream)
261                         {
262                                 using (FileStream fs = File.OpenRead (file)) {
263                                         byte [] buffer = new byte [Math.Min (fs.Length, 32 * 1024)];
264
265                                         long remain = fs.Length;
266                                         int n;
267                                         while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32 * 1024))) != 0) {
268                                                 remain -= n;
269                                                 stream.Write (buffer, 0, n);
270                                         }
271                                 }
272                         }
273
274                         public override string ToString ()
275                         {
276                                 return String.Format ("file {0} {1} bytes from position {2}", file, length, offset);
277                         }
278                 }
279
280                 void AppendBucket (Bucket b)
281                 {
282                         if (first_bucket == null) {
283                                 cur_bucket = first_bucket = b;
284                                 return;
285                         }
286
287                         cur_bucket.Next = b;
288                         cur_bucket = b;
289                 }
290
291                 //
292                 // Nothing happens here, broken by requirement.
293                 // See note at the start
294                 //
295                 public override void Flush ()
296                 {
297                 }
298
299                 internal void Flush (HttpWorkerRequest wr, bool final_flush)
300                 {
301                         if (!dirty && !final_flush)
302                                 return;
303
304                         for (Bucket b = first_bucket; b != null; b = b.Next) {
305                                 b.Send (wr);
306                         }
307
308                         wr.FlushResponse (final_flush);
309                         Clear ();
310                 }
311
312                 internal int GetTotalLength ()
313                 {
314                         int size = 0;
315                         for (Bucket b = first_bucket; b != null; b = b.Next)
316                                 size += b.Length;
317
318                         return size;
319                 }
320
321                 internal MemoryStream GetData ()
322                 {
323                         MemoryStream stream = new MemoryStream ();
324                         for (Bucket b = first_bucket; b != null; b = b.Next)
325                                 b.Send (stream);
326                         return stream;
327                 }
328
329                 public void WriteFile (string f, long offset, long length)
330                 {
331                         if (length == 0)
332                                 return;
333
334                         dirty = true;
335
336                         AppendBucket (new BufferedFileBucket (f, offset, length));
337                         // Flush () is called from HttpResponse if needed (WriteFile/TransmitFile)
338                 }
339
340                 bool filtering;
341                 internal void ApplyFilter (bool close)
342                 {
343                         if (filter == null)
344                                 return;
345
346                         filtering = true;
347                         Bucket one = first_bucket;
348                         first_bucket = null; // This will recreate new buckets for the filtered content
349                         cur_bucket = null;
350                         dirty = false;
351                         for (Bucket b = one; b != null; b = b.Next)
352                                 b.Send (filter);
353
354                         for (Bucket b = one; b != null; b = b.Next)
355                                 b.Dispose ();
356
357                         if (close) {
358                                 filter.Flush ();
359                                 filter.Close ();
360                                 filter = null;
361                         }
362                         else {
363                                 filter.Flush ();
364                         }
365                         filtering = false;
366                 }
367
368                 public void Write (char [] buffer, int offset, int count)
369                 {
370                         bool buffering = response.BufferOutput;
371
372                         if (buffering) {
373                                 // It does not matter whether we're in ApplyFilter or not
374                                 AppendBuffer (buffer, offset, count);
375                         }
376                         else if (filter == null || filtering) {
377                                 response.WriteHeaders (false);
378                                 HttpWorkerRequest wr = response.WorkerRequest;
379                                 // Direct write because not buffering
380                                 wr.SendResponseFromMemory (buffer, offset, count, response.ContentEncoding);
381                                 wr.FlushResponse (false);
382                         }
383                         else {
384                                 // Write to the filter, which will call us back, and then Flush
385                                 filtering = true;
386                                 try {
387                                         byte [] bytesToWrite = response.ContentEncoding.GetBytes (buffer, offset, count);
388                                         filter.Write (bytesToWrite, 0, bytesToWrite.Length);
389                                 }
390                                 finally {
391                                         filtering = false;
392                                 }
393                                 Flush (response.WorkerRequest, false);
394                         }
395                 }
396
397                 public void Write (string s, int offset, int count)
398                 {
399                         bool buffering = response.BufferOutput;
400
401                         if (buffering) {
402                                 // It does not matter whether we're in ApplyFilter or not
403                                 AppendBuffer (s, offset, count);
404                         }
405                         else if (filter == null || filtering) {
406                                 response.WriteHeaders (false);
407                                 HttpWorkerRequest wr = response.WorkerRequest;
408                                 // Direct write because not buffering
409                                 wr.SendResponseFromMemory (s, offset, count, response.ContentEncoding);
410                                 wr.FlushResponse (false);
411                         }
412                         else {
413                                 // Write to the filter, which will call us back, and then Flush
414                                 filtering = true;
415                                 try {
416                                         byte [] bytesToWrite = response.ContentEncoding.GetBytes (s.ToCharArray (), offset, count);
417                                         filter.Write (bytesToWrite, 0, bytesToWrite.Length);
418                                 }
419                                 finally {
420                                         filtering = false;
421                                 }
422                                 Flush (response.WorkerRequest, false);
423                         }
424                 }
425
426                 public override void Write (byte [] buffer, int offset, int count)
427                 {
428                         bool buffering = response.BufferOutput;
429
430                         if (buffering) {
431                                 // It does not matter whether we're in ApplyFilter or not
432                                 AppendBuffer (buffer, offset, count);
433                         }
434                         else if (filter == null || filtering) {
435                                 response.WriteHeaders (false);
436                                 HttpWorkerRequest wr = response.WorkerRequest;
437                                 // Direct write because not buffering
438                                 if (offset == 0) {
439                                         wr.SendResponseFromMemory (buffer, count);
440                                 }
441                                 else {
442                                         UnsafeWrite (wr, buffer, offset, count);
443                                 }
444                                 wr.FlushResponse (false);
445                         }
446                         else {
447                                 // Write to the filter, which will call us back, and then Flush
448                                 filtering = true;
449                                 try {
450                                         filter.Write (buffer, offset, count);
451                                 }
452                                 finally {
453                                         filtering = false;
454                                 }
455                                 Flush (response.WorkerRequest, false);
456                         }
457                 }
458
459 #if TARGET_JVM
460                 void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
461                 {
462                         if (count <= 0)
463                                 return;
464
465                         byte [] copy = new byte [count];
466                         Array.Copy (buffer, offset, copy, 0, count);
467                         wr.SendResponseFromMemory (copy, count);
468                 }
469 #else
470                 unsafe void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
471                 {
472                         fixed (byte *ptr = buffer) {
473                                 wr.SendResponseFromMemory ((IntPtr) (ptr + offset), count);
474                         }
475                 }
476 #endif
477                 void AppendBuffer (byte [] buffer, int offset, int count)
478                 {
479                         if (!(cur_bucket is ByteBucket))
480                                 AppendBucket (new ByteBucket ());
481
482                         dirty = true;
483
484                         while (count > 0) {
485                                 if (cur_bucket.FreeSpace == 0)
486                                         AppendBucket (new ByteBucket ());
487
488                                 int len = count;
489                                 int freeSpace = cur_bucket.FreeSpace;
490
491                                 if (len > freeSpace)
492                                         len = freeSpace;
493
494                                 ((ByteBucket) cur_bucket).Write (buffer, offset, len);
495                                 offset += len;
496                                 count -= len;
497                         }
498
499                 }
500
501                 void AppendBuffer (char [] buffer, int offset, int count)
502                 {
503                         if (!(cur_bucket is CharBucket))
504                                 AppendBucket (new CharBucket (response.ContentEncoding));
505
506                         dirty = true;
507
508                         while (count > 0) {
509                                 if (cur_bucket.FreeSpace == 0)
510                                         AppendBucket (new CharBucket (response.ContentEncoding));
511
512                                 int len = count;
513                                 int freeSpace = cur_bucket.FreeSpace;
514
515                                 if (len > freeSpace)
516                                         len = freeSpace;
517
518                                 ((CharBucket) cur_bucket).Write (buffer, offset, len);
519                                 offset += len;
520                                 count -= len;
521                         }
522                 }
523
524                 void AppendBuffer (string buffer, int offset, int count)
525                 {
526                         if (!(cur_bucket is CharBucket))
527                                 AppendBucket (new CharBucket (response.ContentEncoding));
528
529                         dirty = true;
530
531                         while (count > 0) {
532                                 if (cur_bucket.FreeSpace == 0)
533                                         AppendBucket (new CharBucket (response.ContentEncoding));
534
535                                 int len = count;
536                                 int freeSpace = cur_bucket.FreeSpace;
537
538                                 if (len > freeSpace)
539                                         len = freeSpace;
540
541                                 ((CharBucket) cur_bucket).Write (buffer, offset, len);
542                                 offset += len;
543                                 count -= len;
544                         }
545                 }
546
547                 //
548                 // This should not flush/close or anything else, its called
549                 // just to free any memory we might have allocated (when we later
550                 // implement something with unmanaged memory).
551                 //
552                 internal void ReleaseResources (bool close_filter)
553                 {
554                         if (close_filter && filter != null) {
555                                 filter.Close ();
556                                 filter = null;
557                         }
558
559                         for (Bucket b = first_bucket; b != null; b = b.Next)
560                                 b.Dispose ();
561
562                         first_bucket = null;
563                         cur_bucket = null;
564                 }
565
566                 public void Clear ()
567                 {
568                         //
569                         // IMPORTANT: you must dispose *AFTER* using all the buckets Byte chunks might be
570                         // split across two buckets if there is a file between the data.
571                         //
572                         ReleaseResources (false);
573                         dirty = false;
574                 }
575
576                 public override bool CanRead
577                 {
578                         get
579                         {
580                                 return false;
581                         }
582                 }
583
584                 public override bool CanSeek
585                 {
586                         get
587                         {
588                                 return false;
589                         }
590                 }
591                 public override bool CanWrite
592                 {
593                         get
594                         {
595                                 return true;
596                         }
597                 }
598
599                 const string notsupported = "HttpResponseStream is a forward, write-only stream";
600
601                 public override long Length
602                 {
603                         get
604                         {
605                                 throw new InvalidOperationException (notsupported);
606                         }
607                 }
608
609                 public override long Position
610                 {
611                         get
612                         {
613                                 throw new InvalidOperationException (notsupported);
614                         }
615                         set
616                         {
617                                 throw new InvalidOperationException (notsupported);
618                         }
619                 }
620
621                 public override long Seek (long offset, SeekOrigin origin)
622                 {
623                         throw new InvalidOperationException (notsupported);
624                 }
625
626                 public override void SetLength (long value)
627                 {
628                         throw new InvalidOperationException (notsupported);
629                 }
630
631                 public override int Read (byte [] buffer, int offset, int count)
632                 {
633                         throw new InvalidOperationException (notsupported);
634                 }
635         }
636 }
637