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