2 // System.Net.WebConnectionStream
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (C) 2003 Ximian, Inc (http://www.ximian.com)
8 // (C) 2004 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.
34 using System.Threading;
38 class WebConnectionStream : Stream
40 static byte [] crlf = new byte [] { 13, 10 };
43 HttpWebRequest request;
52 ManualResetEvent pending;
55 MemoryStream writeBuffer;
60 object locker = new object ();
63 bool complete_request_written;
66 public WebConnectionStream (WebConnection cnc)
69 pending = new ManualResetEvent (true);
70 this.request = cnc.Data.request;
72 string contentType = cnc.Data.Headers ["Transfer-Encoding"];
73 bool chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
74 string clength = cnc.Data.Headers ["Content-Length"];
75 if (!chunkedRead && clength != null && clength != "") {
78 contentLength = Int32.Parse (clength);
80 contentLength = Int32.MaxValue;
83 contentLength = Int32.MaxValue;
87 public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
91 this.request = request;
92 allowBuffering = request.InternalAllowBuffering;
93 sendChunked = request.SendChunked;
95 writeBuffer = new MemoryStream ();
96 max_buffer_size = request.ContentLength;
102 pending = new ManualResetEvent (true);
105 internal bool CompleteRequestWritten {
106 get { return complete_request_written; }
109 internal bool SendChunked {
110 set { sendChunked = value; }
113 internal byte [] ReadBuffer {
114 set { readBuffer = value; }
117 internal int ReadBufferOffset {
118 set { readBufferOffset = value;}
121 internal int ReadBufferSize {
122 set { readBufferSize = value; }
125 internal byte[] WriteBuffer {
126 get { return writeBuffer.GetBuffer (); }
129 internal int WriteBufferLength {
130 get { return (int) writeBuffer.Length; }
133 internal void ForceCompletion ()
135 nextReadCalled = true;
139 internal void CheckComplete ()
141 bool nrc = nextReadCalled;
142 if (!nrc && readBufferSize - readBufferOffset == contentLength) {
143 nextReadCalled = true;
148 internal void ReadAll ()
150 if (!isRead || read_eof || totalRead >= contentLength || nextReadCalled) {
151 if (isRead && !nextReadCalled) {
152 nextReadCalled = true;
160 if (totalRead >= contentLength)
164 int diff = readBufferSize - readBufferOffset;
167 if (contentLength == Int32.MaxValue) {
168 MemoryStream ms = new MemoryStream ();
169 byte [] buffer = null;
170 if (readBuffer != null && diff > 0) {
171 ms.Write (readBuffer, readBufferOffset, diff);
172 if (readBufferSize >= 8192)
177 buffer = new byte [8192];
180 while ((read = cnc.Read (buffer, 0, buffer.Length)) != 0)
181 ms.Write (buffer, 0, read);
184 new_size = (int) ms.Length;
185 contentLength = new_size;
187 new_size = contentLength - totalRead;
188 b = new byte [new_size];
189 if (readBuffer != null && diff > 0) {
193 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
196 int remaining = new_size - diff;
198 while (remaining > 0 && r != 0) {
199 r = cnc.Read (b, diff, remaining);
206 readBufferOffset = 0;
207 readBufferSize = new_size;
209 nextReadCalled = true;
215 void WriteCallbackWrapper (IAsyncResult r)
217 WebAsyncResult result;
218 if (r.AsyncState != null) {
219 result = (WebAsyncResult) r.AsyncState;
220 result.InnerAsyncResult = r;
221 result.DoCallback ();
227 void ReadCallbackWrapper (IAsyncResult r)
229 WebAsyncResult result;
230 if (r.AsyncState != null) {
231 result = (WebAsyncResult) r.AsyncState;
232 result.InnerAsyncResult = r;
233 result.DoCallback ();
239 public override int Read (byte [] buffer, int offset, int size)
242 throw new NotSupportedException ("this stream does not allow reading");
244 if (totalRead >= contentLength)
247 AsyncCallback cb = new AsyncCallback (ReadCallbackWrapper);
248 WebAsyncResult res = (WebAsyncResult) BeginRead (buffer, offset, size, cb, null);
249 if (!res.IsCompleted && !res.WaitUntilComplete (request.ReadWriteTimeout, false)) {
250 nextReadCalled = true;
252 throw new IOException ("Read timed out.");
255 return EndRead (res);
258 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
259 AsyncCallback cb, object state)
262 throw new NotSupportedException ("this stream does not allow reading");
265 throw new ArgumentNullException ("buffer");
267 int length = buffer.Length;
268 if (size < 0 || offset < 0 || length < offset || length - offset < size)
269 throw new ArgumentOutOfRangeException ();
276 WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
277 if (totalRead >= contentLength) {
278 result.SetCompleted (true, -1);
279 result.DoCallback ();
283 int remaining = readBufferSize - readBufferOffset;
285 int copy = (remaining > size) ? size : remaining;
286 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
287 readBufferOffset += copy;
291 if (size == 0 || totalRead >= contentLength) {
292 result.SetCompleted (true, copy);
293 result.DoCallback ();
296 result.NBytes = copy;
300 cb = new AsyncCallback (ReadCallbackWrapper);
302 if (contentLength != Int32.MaxValue && contentLength - totalRead < size)
303 size = contentLength - totalRead;
306 result.InnerAsyncResult = cnc.BeginRead (buffer, offset, size, cb, result);
308 result.SetCompleted (true, result.NBytes);
309 result.DoCallback ();
314 public override int EndRead (IAsyncResult r)
316 WebAsyncResult result = (WebAsyncResult) r;
317 if (result.EndCalled) {
318 int xx = result.NBytes;
319 return (xx >= 0) ? xx : 0;
322 result.EndCalled = true;
324 if (!result.IsCompleted) {
327 nbytes = cnc.EndRead (result);
328 } catch (Exception exc) {
331 if (pendingReads == 0)
335 nextReadCalled = true;
337 result.SetCompleted (false, exc);
347 result.SetCompleted (false, nbytes + result.NBytes);
348 result.DoCallback ();
350 contentLength = totalRead;
355 if (pendingReads == 0)
359 if (totalRead >= contentLength && !nextReadCalled)
362 int nb = result.NBytes;
363 return (nb >= 0) ? nb : 0;
366 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
367 AsyncCallback cb, object state)
370 throw new NotSupportedException ("this stream does not allow writing");
373 throw new ArgumentNullException ("buffer");
375 int length = buffer.Length;
376 if (size < 0 || offset < 0 || length < offset || length - offset < size)
377 throw new ArgumentOutOfRangeException ();
386 WebAsyncResult result = new WebAsyncResult (cb, state);
387 if (allowBuffering) {
388 if (max_buffer_size >= 0) {
389 long avail = max_buffer_size - writeBuffer.Length;
392 throw new ProtocolViolationException (
393 "The number of bytes to be written is greater than " +
394 "the specified ContentLength.");
397 writeBuffer.Write (buffer, offset, size);
399 result.SetCompleted (true, 0);
400 result.DoCallback ();
405 AsyncCallback callback = null;
407 callback = new AsyncCallback (WriteCallbackWrapper);
412 string cSize = String.Format ("{0:X}\r\n", size);
413 byte [] head = Encoding.ASCII.GetBytes (cSize);
414 int chunkSize = 2 + size + head.Length;
415 byte [] newBuffer = new byte [chunkSize];
416 Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
417 Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
418 Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
425 result.InnerAsyncResult = cnc.BeginWrite (buffer, offset, size, callback, result);
429 public override void EndWrite (IAsyncResult r)
432 throw new ArgumentNullException ("r");
434 WebAsyncResult result = r as WebAsyncResult;
436 throw new ArgumentException ("Invalid IAsyncResult");
438 if (result.EndCalled)
441 result.EndCalled = true;
443 if (allowBuffering && !sendChunked)
446 if (result.GotException)
447 throw result.Exception;
450 cnc.EndWrite (result.InnerAsyncResult);
451 result.SetCompleted (false, 0);
452 } catch (Exception e) {
453 result.SetCompleted (false, e);
459 if (pendingWrites == 0)
465 public override void Write (byte [] buffer, int offset, int size)
468 throw new NotSupportedException ("This stream does not allow writing");
470 AsyncCallback cb = new AsyncCallback (WriteCallbackWrapper);
471 WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null);
472 if (!res.IsCompleted && !res.WaitUntilComplete (request.ReadWriteTimeout, false)) {
473 nextReadCalled = true;
475 throw new IOException ("Write timed out.");
481 public override void Flush ()
485 internal void SetHeaders (byte [] buffer, int offset, int size)
490 if (!allowBuffering || sendChunked) {
493 throw new WebException ("Not connected", null, WebExceptionStatus.SendFailure, null);
495 cnc.Write (buffer, offset, size);
498 WebConnection.InitRead (cnc);
501 headers = new byte [size];
502 Buffer.BlockCopy (buffer, offset, headers, 0, size);
506 internal bool RequestWritten {
507 get { return requestWritten; }
510 internal void WriteRequest ()
516 request.SendRequestHeaders ();
517 requestWritten = true;
521 if (!allowBuffering || writeBuffer == null)
524 byte [] bytes = writeBuffer.GetBuffer ();
525 int length = (int) writeBuffer.Length;
526 if (request.ContentLength != -1 && request.ContentLength < length) {
527 throw new WebException ("Specified Content-Length is less than the number of bytes to write", null,
528 WebExceptionStatus.ServerProtocolViolation, null);
531 request.InternalContentLength = length;
532 request.SendRequestHeaders ();
533 requestWritten = true;
534 cnc.Write (headers, 0, headers.Length);
536 throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null);
539 if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
542 IAsyncResult result = null;
544 result = cnc.BeginWrite (bytes, 0, length, null, null);
548 WebConnection.InitRead (cnc);
552 complete_request_written = cnc.EndWrite (result);
554 complete_request_written = true;
557 internal void InternalClose ()
562 internal void ForceCloseConnection ()
570 public override void Close ()
574 byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
575 cnc.Write (chunk, 0, chunk.Length);
580 if (!nextReadCalled) {
582 // If we have not read all the contents
583 if (!nextReadCalled) {
584 nextReadCalled = true;
589 } else if (!allowBuffering) {
590 complete_request_written = true;
593 WebConnection.InitRead (cnc);
601 long length = request.ContentLength;
602 if (length != -1 && length > writeBuffer.Length)
603 throw new IOException ("Cannot close the stream until all bytes are written");
609 public override long Seek (long a, SeekOrigin b)
611 throw new NotSupportedException ();
614 public override void SetLength (long a)
616 throw new NotSupportedException ();
619 public override bool CanSeek {
620 get { return false; }
623 public override bool CanRead {
624 get { return isRead; }
627 public override bool CanWrite {
628 get { return !isRead; }
631 public override long Length {
632 get { throw new NotSupportedException (); }
635 public override long Position {
636 get { throw new NotSupportedException (); }
637 set { throw new NotSupportedException (); }