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;
49 internal long totalWritten;
53 ManualResetEvent pending;
56 MemoryStream writeBuffer;
61 object locker = new object ();
64 bool complete_request_written;
67 AsyncCallback cb_wrapper; // Calls to ReadCallbackWrapper or WriteCallbacWrapper
68 internal bool IgnoreIOErrors;
70 public WebConnectionStream (WebConnection cnc)
73 cb_wrapper = new AsyncCallback (ReadCallbackWrapper);
74 pending = new ManualResetEvent (true);
75 this.request = cnc.Data.request;
76 read_timeout = request.ReadWriteTimeout;
77 write_timeout = read_timeout;
79 string contentType = cnc.Data.Headers ["Transfer-Encoding"];
80 bool chunkedRead = (contentType != null && contentType.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
81 string clength = cnc.Data.Headers ["Content-Length"];
82 if (!chunkedRead && clength != null && clength != "") {
84 contentLength = Int32.Parse (clength);
85 if (contentLength == 0 && !IsNtlmAuth ()) {
89 contentLength = Int32.MaxValue;
92 contentLength = Int32.MaxValue;
96 public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
98 read_timeout = request.ReadWriteTimeout;
99 write_timeout = read_timeout;
101 cb_wrapper = new AsyncCallback (WriteCallbackWrapper);
103 this.request = request;
104 allowBuffering = request.InternalAllowBuffering;
105 sendChunked = request.SendChunked;
107 pending = new ManualResetEvent (true);
108 else if (allowBuffering)
109 writeBuffer = new MemoryStream ();
114 bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.Address));
115 string header_name = (isProxy) ? "Proxy-Authenticate" : "WWW-Authenticate";
116 string authHeader = cnc.Data.Headers [header_name];
117 return (authHeader != null && authHeader.IndexOf ("NTLM", StringComparison.Ordinal) != -1);
120 internal void CheckResponseInBuffer ()
122 if (contentLength > 0 && (readBufferSize - readBufferOffset) >= contentLength) {
128 internal HttpWebRequest Request {
129 get { return request; }
132 internal WebConnection Connection {
136 public override bool CanTimeout {
151 throw new ArgumentOutOfRangeException ("value");
152 read_timeout = value;
161 return write_timeout;
166 throw new ArgumentOutOfRangeException ("value");
167 write_timeout = value;
171 internal bool CompleteRequestWritten {
172 get { return complete_request_written; }
175 internal bool SendChunked {
176 set { sendChunked = value; }
179 internal byte [] ReadBuffer {
180 set { readBuffer = value; }
183 internal int ReadBufferOffset {
184 set { readBufferOffset = value;}
187 internal int ReadBufferSize {
188 set { readBufferSize = value; }
191 internal byte[] WriteBuffer {
192 get { return writeBuffer.GetBuffer (); }
195 internal int WriteBufferLength {
196 get { return writeBuffer != null ? (int) writeBuffer.Length : (-1); }
199 internal void ForceCompletion ()
201 if (!nextReadCalled) {
202 if (contentLength == Int32.MaxValue)
204 nextReadCalled = true;
209 internal void CheckComplete ()
211 bool nrc = nextReadCalled;
212 if (!nrc && readBufferSize - readBufferOffset == contentLength) {
213 nextReadCalled = true;
218 internal void ReadAll ()
220 if (!isRead || read_eof || totalRead >= contentLength || nextReadCalled) {
221 if (isRead && !nextReadCalled) {
222 nextReadCalled = true;
230 if (totalRead >= contentLength)
234 int diff = readBufferSize - readBufferOffset;
237 if (contentLength == Int32.MaxValue) {
238 MemoryStream ms = new MemoryStream ();
239 byte [] buffer = null;
240 if (readBuffer != null && diff > 0) {
241 ms.Write (readBuffer, readBufferOffset, diff);
242 if (readBufferSize >= 8192)
247 buffer = new byte [8192];
250 while ((read = cnc.Read (request, buffer, 0, buffer.Length)) != 0)
251 ms.Write (buffer, 0, read);
254 new_size = (int) ms.Length;
255 contentLength = new_size;
257 new_size = contentLength - totalRead;
258 b = new byte [new_size];
259 if (readBuffer != null && diff > 0) {
263 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
266 int remaining = new_size - diff;
268 while (remaining > 0 && r != 0) {
269 r = cnc.Read (request, b, diff, remaining);
276 readBufferOffset = 0;
277 readBufferSize = new_size;
279 nextReadCalled = true;
285 void WriteCallbackWrapper (IAsyncResult r)
287 WebAsyncResult result = r as WebAsyncResult;
288 if (result != null && result.AsyncWriteAll)
291 if (r.AsyncState != null) {
292 result = (WebAsyncResult) r.AsyncState;
293 result.InnerAsyncResult = r;
294 result.DoCallback ();
303 void ReadCallbackWrapper (IAsyncResult r)
305 WebAsyncResult result;
306 if (r.AsyncState != null) {
307 result = (WebAsyncResult) r.AsyncState;
308 result.InnerAsyncResult = r;
309 result.DoCallback ();
318 public override int Read (byte [] buffer, int offset, int size)
320 AsyncCallback cb = cb_wrapper;
321 WebAsyncResult res = (WebAsyncResult) BeginRead (buffer, offset, size, cb, null);
322 if (!res.IsCompleted && !res.WaitUntilComplete (ReadTimeout, false)) {
323 nextReadCalled = true;
325 throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
328 return EndRead (res);
331 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
332 AsyncCallback cb, object state)
335 throw new NotSupportedException ("this stream does not allow reading");
338 throw new ArgumentNullException ("buffer");
340 int length = buffer.Length;
341 if (offset < 0 || length < offset)
342 throw new ArgumentOutOfRangeException ("offset");
343 if (size < 0 || (length - offset) < size)
344 throw new ArgumentOutOfRangeException ("size");
351 WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
352 if (totalRead >= contentLength) {
353 result.SetCompleted (true, -1);
354 result.DoCallback ();
358 int remaining = readBufferSize - readBufferOffset;
360 int copy = (remaining > size) ? size : remaining;
361 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
362 readBufferOffset += copy;
366 if (size == 0 || totalRead >= contentLength) {
367 result.SetCompleted (true, copy);
368 result.DoCallback ();
371 result.NBytes = copy;
377 if (contentLength != Int32.MaxValue && contentLength - totalRead < size)
378 size = contentLength - totalRead;
381 result.InnerAsyncResult = cnc.BeginRead (request, buffer, offset, size, cb, result);
383 result.SetCompleted (true, result.NBytes);
384 result.DoCallback ();
389 public override int EndRead (IAsyncResult r)
391 WebAsyncResult result = (WebAsyncResult) r;
392 if (result.EndCalled) {
393 int xx = result.NBytes;
394 return (xx >= 0) ? xx : 0;
397 result.EndCalled = true;
399 if (!result.IsCompleted) {
402 nbytes = cnc.EndRead (request, result);
403 } catch (Exception exc) {
406 if (pendingReads == 0)
410 nextReadCalled = true;
412 result.SetCompleted (false, exc);
413 result.DoCallback ();
423 result.SetCompleted (false, nbytes + result.NBytes);
424 result.DoCallback ();
426 contentLength = totalRead;
431 if (pendingReads == 0)
435 if (totalRead >= contentLength && !nextReadCalled)
438 int nb = result.NBytes;
439 return (nb >= 0) ? nb : 0;
442 void WriteRequestAsyncCB (IAsyncResult r)
444 WebAsyncResult result = (WebAsyncResult) r.AsyncState;
446 cnc.EndWrite2 (request, r);
447 result.SetCompleted (false, 0);
450 WebConnection.InitRead (cnc);
452 } catch (Exception e) {
454 nextReadCalled = true;
456 if (e is System.Net.Sockets.SocketException)
457 e = new IOException ("Error writing request", e);
458 result.SetCompleted (false, e);
460 complete_request_written = true;
461 result.DoCallback ();
464 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
465 AsyncCallback cb, object state)
468 throw new WebException ("The request was canceled.", null, WebExceptionStatus.RequestCanceled);
471 throw new NotSupportedException ("this stream does not allow writing");
474 throw new ArgumentNullException ("buffer");
476 int length = buffer.Length;
477 if (offset < 0 || length < offset)
478 throw new ArgumentOutOfRangeException ("offset");
479 if (size < 0 || (length - offset) < size)
480 throw new ArgumentOutOfRangeException ("size");
489 WebAsyncResult result = new WebAsyncResult (cb, state);
491 CheckWriteOverflow (request.ContentLength, totalWritten, size);
492 if (allowBuffering && !sendChunked) {
493 if (writeBuffer == null)
494 writeBuffer = new MemoryStream ();
495 writeBuffer.Write (buffer, offset, size);
496 totalWritten += size;
497 if (request.ContentLength > 0 && totalWritten == request.ContentLength) {
499 result.AsyncWriteAll = true;
500 result.InnerAsyncResult = WriteRequestAsync (new AsyncCallback (WriteRequestAsyncCB), result);
501 if (result.InnerAsyncResult == null) {
502 if (!result.IsCompleted)
503 result.SetCompleted (true, 0);
504 result.DoCallback ();
506 } catch (Exception exc) {
507 result.SetCompleted (true, exc);
508 result.DoCallback ();
511 result.SetCompleted (true, 0);
512 result.DoCallback ();
517 AsyncCallback callback = null;
519 callback = cb_wrapper;
524 string cSize = String.Format ("{0:X}\r\n", size);
525 byte [] head = Encoding.ASCII.GetBytes (cSize);
526 int chunkSize = 2 + size + head.Length;
527 byte [] newBuffer = new byte [chunkSize];
528 Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
529 Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
530 Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
538 result.InnerAsyncResult = cnc.BeginWrite (request, buffer, offset, size, callback, result);
539 } catch (Exception) {
542 result.SetCompleted (true, 0);
543 result.DoCallback ();
545 totalWritten += size;
549 void CheckWriteOverflow (long contentLength, long totalWritten, long size)
551 if (contentLength == -1)
554 long avail = contentLength - totalWritten;
557 nextReadCalled = true;
559 throw new ProtocolViolationException (
560 "The number of bytes to be written is greater than " +
561 "the specified ContentLength.");
565 public override void EndWrite (IAsyncResult r)
568 throw new ArgumentNullException ("r");
570 WebAsyncResult result = r as WebAsyncResult;
572 throw new ArgumentException ("Invalid IAsyncResult");
574 if (result.EndCalled)
577 result.EndCalled = true;
578 if (result.AsyncWriteAll) {
579 result.WaitUntilComplete ();
580 if (result.GotException)
581 throw result.Exception;
585 if (allowBuffering && !sendChunked)
588 if (result.GotException)
589 throw result.Exception;
592 cnc.EndWrite2 (request, result.InnerAsyncResult);
593 result.SetCompleted (false, 0);
594 result.DoCallback ();
595 } catch (Exception e) {
597 result.SetCompleted (false, 0);
599 result.SetCompleted (false, e);
600 result.DoCallback ();
607 if (pendingWrites == 0)
614 public override void Write (byte [] buffer, int offset, int size)
616 AsyncCallback cb = cb_wrapper;
617 WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null);
618 if (!res.IsCompleted && !res.WaitUntilComplete (WriteTimeout, false)) {
620 nextReadCalled = true;
622 throw new IOException ("Write timed out.");
628 public override void Flush ()
632 internal void SetHeaders (byte [] buffer)
638 long cl = request.ContentLength;
639 string method = request.Method;
640 bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
641 method == "TRACE" || method == "DELETE");
642 if (sendChunked || cl > -1 || no_writestream) {
646 WebConnection.InitRead (cnc);
648 if (!sendChunked && cl == 0)
649 requestWritten = true;
653 internal bool RequestWritten {
654 get { return requestWritten; }
657 IAsyncResult WriteRequestAsync (AsyncCallback cb, object state)
659 requestWritten = true;
660 byte [] bytes = writeBuffer.GetBuffer ();
661 int length = (int) writeBuffer.Length;
662 // Headers already written to the stream
663 return (length > 0) ? cnc.BeginWrite (request, bytes, 0, length, cb, state) : null;
672 string err_msg = null;
673 if (!cnc.Write (request, headers, 0, headers.Length, ref err_msg))
674 throw new WebException ("Error writing request: " + err_msg, null, WebExceptionStatus.SendFailure, null);
677 internal void WriteRequest ()
682 requestWritten = true;
686 if (!allowBuffering || writeBuffer == null)
689 byte [] bytes = writeBuffer.GetBuffer ();
690 int length = (int) writeBuffer.Length;
691 if (request.ContentLength != -1 && request.ContentLength < length) {
692 nextReadCalled = true;
694 throw new WebException ("Specified Content-Length is less than the number of bytes to write", null,
695 WebExceptionStatus.ServerProtocolViolation, null);
699 string method = request.Method;
700 bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
701 method == "TRACE" || method == "DELETE");
703 request.InternalContentLength = length;
704 request.SendRequestHeaders (true);
707 if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
710 IAsyncResult result = null;
712 result = cnc.BeginWrite (request, bytes, 0, length, null, null);
716 WebConnection.InitRead (cnc);
720 complete_request_written = cnc.EndWrite (request, result);
722 complete_request_written = true;
725 internal void InternalClose ()
730 public override void Close ()
737 byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
738 string err_msg = null;
739 cnc.Write (request, chunk, 0, chunk.Length, ref err_msg);
744 if (!nextReadCalled) {
746 // If we have not read all the contents
747 if (!nextReadCalled) {
748 nextReadCalled = true;
753 } else if (!allowBuffering) {
754 complete_request_written = true;
757 WebConnection.InitRead (cnc);
762 if (disposed || requestWritten)
765 long length = request.ContentLength;
767 if (!sendChunked && length != -1 && totalWritten != length) {
768 IOException io = new IOException ("Cannot close the stream until all bytes are written");
769 nextReadCalled = true;
771 throw new WebException ("Request was cancelled.", io, WebExceptionStatus.RequestCanceled);
778 internal void KillBuffer ()
783 public override long Seek (long a, SeekOrigin b)
785 throw new NotSupportedException ();
788 public override void SetLength (long a)
790 throw new NotSupportedException ();
793 public override bool CanSeek {
794 get { return false; }
797 public override bool CanRead {
798 get { return !disposed && isRead; }
801 public override bool CanWrite {
802 get { return !disposed && !isRead; }
805 public override long Length {
806 get { throw new NotSupportedException (); }
809 public override long Position {
810 get { throw new NotSupportedException (); }
811 set { throw new NotSupportedException (); }