2 // System.Web.HttpResponseStream.cs
6 // Miguel de Icaza (miguel@novell.com)
7 // Ben Maurer (bmaurer@ximian.com)
10 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
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:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
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.
35 using System.Globalization;
36 using System.Runtime.InteropServices;
42 // HttpResponseStream implements the "OutputStream" from HttpResponse
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.
48 // You must call HttpResponse.Flush which does the actual header generation
49 // and actual data flushing
51 internal class HttpResponseStream : Stream
55 HttpResponse response;
60 public HttpResponseStream (HttpResponse response)
62 this.response = response;
65 internal bool HaveFilter
67 get { return filter != null; }
75 filter = new OutputFilterStream (this);
87 public virtual void Dispose ()
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; }
97 class ByteBucket : Bucket
100 const int _preferredLength = 16 * 1024;
103 int _freeSpace = _preferredLength;
104 byte [] buffer = new byte [_preferredLength];
110 public override int Length
112 get { return _position; }
115 public override int FreeSpace
117 get { return _freeSpace; }
120 public int Write (byte [] buf, int offset, int count)
122 if (count > _freeSpace)
123 throw new InvalidOperationException ("Out of bucket space");
125 Array.Copy (buf, offset, buffer, _position, count);
127 _freeSpace = _preferredLength - _position;
131 public override void Dispose ()
136 public override void Send (HttpWorkerRequest wr)
141 wr.SendResponseFromMemory (buffer, _position);
144 public override void Send (Stream stream)
149 stream.Write (buffer, 0, _position);
153 class CharBucket : Bucket
155 const int _preferredLength = 8 * 1024;
158 int _freeSpace = _preferredLength;
159 char [] buffer = new char [_preferredLength];
160 readonly Encoding _encoding;
162 public CharBucket (Encoding encoding)
164 _encoding = encoding;
167 public override int Length
171 HttpContext current = HttpContext.Current;
172 Encoding enc = (current != null) ? current.Response.ContentEncoding : Encoding.UTF8;
173 return enc.GetByteCount (buffer, 0, _position);
177 public override int FreeSpace
179 get { return _freeSpace; }
182 public int Write (string buf, int offset, int count)
184 if (count > _freeSpace)
185 throw new InvalidOperationException ("Out of bucket space");
187 buf.CopyTo (offset, buffer, _position, count);
190 _freeSpace = _preferredLength - _position;
194 public int Write (char [] buf, int offset, int count)
196 if (count > _freeSpace)
197 throw new InvalidOperationException ("Out of bucket space");
200 buffer [_position] = buf [offset];
202 Array.Copy (buf, offset, buffer, _position, count);
205 _freeSpace = _preferredLength - _position;
209 public override void Dispose ()
214 public override void Send (HttpWorkerRequest wr)
219 wr.SendResponseFromMemory (buffer, 0, _position, _encoding);
222 public override void Send (Stream stream)
227 StreamWriter writer = new StreamWriter (stream);
228 writer.Write (buffer, 0, _position);
232 class BufferedFileBucket : Bucket
238 public BufferedFileBucket (string f, long off, long len)
245 public override int Length
247 get { return (int) length; }
250 public override int FreeSpace
252 get { return int.MaxValue; }
255 public override void Send (HttpWorkerRequest wr)
257 wr.SendResponseFromFile (file, offset, length);
260 public override void Send (Stream stream)
262 using (FileStream fs = File.OpenRead (file)) {
263 byte [] buffer = new byte [Math.Min (fs.Length, 32 * 1024)];
265 long remain = fs.Length;
267 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32 * 1024))) != 0) {
269 stream.Write (buffer, 0, n);
274 public override string ToString ()
276 return String.Format ("file {0} {1} bytes from position {2}", file, length, offset);
280 void AppendBucket (Bucket b)
282 if (first_bucket == null) {
283 cur_bucket = first_bucket = b;
292 // Nothing happens here, broken by requirement.
293 // See note at the start
295 public override void Flush ()
299 internal void Flush (HttpWorkerRequest wr, bool final_flush)
301 if (!dirty && !final_flush)
304 for (Bucket b = first_bucket; b != null; b = b.Next) {
308 wr.FlushResponse (final_flush);
312 internal int GetTotalLength ()
315 for (Bucket b = first_bucket; b != null; b = b.Next)
321 internal MemoryStream GetData ()
323 MemoryStream stream = new MemoryStream ();
324 for (Bucket b = first_bucket; b != null; b = b.Next)
329 public void WriteFile (string f, long offset, long length)
336 AppendBucket (new BufferedFileBucket (f, offset, length));
337 // Flush () is called from HttpResponse if needed (WriteFile/TransmitFile)
341 internal void ApplyFilter (bool close)
347 Bucket one = first_bucket;
348 first_bucket = null; // This will recreate new buckets for the filtered content
351 for (Bucket b = one; b != null; b = b.Next)
354 for (Bucket b = one; b != null; b = b.Next)
368 public void Write (char [] buffer, int offset, int count)
370 bool buffering = response.BufferOutput;
373 // It does not matter whether we're in ApplyFilter or not
374 AppendBuffer (buffer, offset, count);
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);
384 // Write to the filter, which will call us back, and then Flush
387 StreamWriter wr = new StreamWriter (filter, response.ContentEncoding);
388 wr.Write (buffer, offset, count);
393 Flush (response.WorkerRequest, false);
397 public void Write (string s, int offset, int count)
399 bool buffering = response.BufferOutput;
402 // It does not matter whether we're in ApplyFilter or not
403 AppendBuffer (s, offset, count);
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);
413 // Write to the filter, which will call us back, and then Flush
416 StreamWriter wr = new StreamWriter (filter, response.ContentEncoding);
417 wr.Write (s, offset, count);
422 Flush (response.WorkerRequest, false);
426 public override void Write (byte [] buffer, int offset, int count)
428 bool buffering = response.BufferOutput;
431 // It does not matter whether we're in ApplyFilter or not
432 AppendBuffer (buffer, offset, count);
434 else if (filter == null || filtering) {
435 response.WriteHeaders (false);
436 HttpWorkerRequest wr = response.WorkerRequest;
437 // Direct write because not buffering
439 wr.SendResponseFromMemory (buffer, count);
442 UnsafeWrite (wr, buffer, offset, count);
444 wr.FlushResponse (false);
447 // Write to the filter, which will call us back, and then Flush
450 filter.Write (buffer, offset, count);
455 Flush (response.WorkerRequest, false);
460 void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
465 byte [] copy = new byte [count];
466 Array.Copy (buffer, offset, copy, 0, count);
467 wr.SendResponseFromMemory (copy, count);
470 unsafe void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
472 fixed (byte *ptr = buffer) {
473 wr.SendResponseFromMemory ((IntPtr) (ptr + offset), count);
477 void AppendBuffer (byte [] buffer, int offset, int count)
479 if (!(cur_bucket is ByteBucket))
480 AppendBucket (new ByteBucket ());
485 if (cur_bucket.FreeSpace == 0)
486 AppendBucket (new ByteBucket ());
489 int freeSpace = cur_bucket.FreeSpace;
494 ((ByteBucket) cur_bucket).Write (buffer, offset, len);
501 void AppendBuffer (char [] buffer, int offset, int count)
503 if (!(cur_bucket is CharBucket))
504 AppendBucket (new CharBucket (response.ContentEncoding));
509 if (cur_bucket.FreeSpace == 0)
510 AppendBucket (new CharBucket (response.ContentEncoding));
513 int freeSpace = cur_bucket.FreeSpace;
518 ((CharBucket) cur_bucket).Write (buffer, offset, len);
524 void AppendBuffer (string buffer, int offset, int count)
526 if (!(cur_bucket is CharBucket))
527 AppendBucket (new CharBucket (response.ContentEncoding));
532 if (cur_bucket.FreeSpace == 0)
533 AppendBucket (new CharBucket (response.ContentEncoding));
536 int freeSpace = cur_bucket.FreeSpace;
541 ((CharBucket) cur_bucket).Write (buffer, offset, len);
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).
552 internal void ReleaseResources (bool close_filter)
554 if (close_filter && filter != null) {
559 for (Bucket b = first_bucket; b != null; b = b.Next)
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.
572 ReleaseResources (false);
576 public override bool CanRead
584 public override bool CanSeek
591 public override bool CanWrite
599 const string notsupported = "HttpResponseStream is a forward, write-only stream";
601 public override long Length
605 throw new InvalidOperationException (notsupported);
609 public override long Position
613 throw new InvalidOperationException (notsupported);
617 throw new InvalidOperationException (notsupported);
621 public override long Seek (long offset, SeekOrigin origin)
623 throw new InvalidOperationException (notsupported);
626 public override void SetLength (long value)
628 throw new InvalidOperationException (notsupported);
631 public override int Read (byte [] buffer, int offset, int count)
633 throw new InvalidOperationException (notsupported);