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;
47 int stream_length; // -1 when CL not present
50 internal long totalWritten;
54 ManualResetEvent pending;
57 MemoryStream writeBuffer;
62 object locker = new object ();
65 bool complete_request_written;
68 AsyncCallback cb_wrapper; // Calls to ReadCallbackWrapper or WriteCallbacWrapper
69 internal bool IgnoreIOErrors;
71 public WebConnectionStream (WebConnection cnc)
74 cb_wrapper = new AsyncCallback (ReadCallbackWrapper);
75 pending = new ManualResetEvent (true);
76 this.request = cnc.Data.request;
77 read_timeout = request.ReadWriteTimeout;
78 write_timeout = read_timeout;
80 string contentType = cnc.Data.Headers ["Transfer-Encoding"];
81 bool chunkedRead = (contentType != null && contentType.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
82 string clength = cnc.Data.Headers ["Content-Length"];
83 if (!chunkedRead && clength != null && clength != "") {
85 contentLength = Int32.Parse (clength);
86 if (contentLength == 0 && !IsNtlmAuth ()) {
90 contentLength = Int32.MaxValue;
93 contentLength = Int32.MaxValue;
97 if (!Int32.TryParse (clength, out stream_length))
101 public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
103 read_timeout = request.ReadWriteTimeout;
104 write_timeout = read_timeout;
106 cb_wrapper = new AsyncCallback (WriteCallbackWrapper);
108 this.request = request;
109 allowBuffering = request.InternalAllowBuffering;
110 sendChunked = request.SendChunked;
112 pending = new ManualResetEvent (true);
113 else if (allowBuffering)
114 writeBuffer = new MemoryStream ();
119 bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.Address));
120 string header_name = (isProxy) ? "Proxy-Authenticate" : "WWW-Authenticate";
121 string authHeader = cnc.Data.Headers [header_name];
122 return (authHeader != null && authHeader.IndexOf ("NTLM", StringComparison.Ordinal) != -1);
125 internal void CheckResponseInBuffer ()
127 if (contentLength > 0 && (readBufferSize - readBufferOffset) >= contentLength) {
133 internal HttpWebRequest Request {
134 get { return request; }
137 internal WebConnection Connection {
140 public override bool CanTimeout {
144 public override int ReadTimeout {
151 throw new ArgumentOutOfRangeException ("value");
152 read_timeout = value;
156 public override int WriteTimeout {
158 return write_timeout;
163 throw new ArgumentOutOfRangeException ("value");
164 write_timeout = value;
168 internal bool CompleteRequestWritten {
169 get { return complete_request_written; }
172 internal bool SendChunked {
173 set { sendChunked = value; }
176 internal byte [] ReadBuffer {
177 set { readBuffer = value; }
180 internal int ReadBufferOffset {
181 set { readBufferOffset = value;}
184 internal int ReadBufferSize {
185 set { readBufferSize = value; }
188 internal byte[] WriteBuffer {
189 get { return writeBuffer.GetBuffer (); }
192 internal int WriteBufferLength {
193 get { return writeBuffer != null ? (int) writeBuffer.Length : (-1); }
196 internal void ForceCompletion ()
198 if (!nextReadCalled) {
199 if (contentLength == Int32.MaxValue)
201 nextReadCalled = true;
206 internal void CheckComplete ()
208 bool nrc = nextReadCalled;
209 if (!nrc && readBufferSize - readBufferOffset == contentLength) {
210 nextReadCalled = true;
215 internal void ReadAll ()
217 if (!isRead || read_eof || totalRead >= contentLength || nextReadCalled) {
218 if (isRead && !nextReadCalled) {
219 nextReadCalled = true;
227 if (totalRead >= contentLength)
231 int diff = readBufferSize - readBufferOffset;
234 if (contentLength == Int32.MaxValue) {
235 MemoryStream ms = new MemoryStream ();
236 byte [] buffer = null;
237 if (readBuffer != null && diff > 0) {
238 ms.Write (readBuffer, readBufferOffset, diff);
239 if (readBufferSize >= 8192)
244 buffer = new byte [8192];
247 while ((read = cnc.Read (request, buffer, 0, buffer.Length)) != 0)
248 ms.Write (buffer, 0, read);
251 new_size = (int) ms.Length;
252 contentLength = new_size;
254 new_size = contentLength - totalRead;
255 b = new byte [new_size];
256 if (readBuffer != null && diff > 0) {
260 Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
263 int remaining = new_size - diff;
265 while (remaining > 0 && r != 0) {
266 r = cnc.Read (request, b, diff, remaining);
273 readBufferOffset = 0;
274 readBufferSize = new_size;
276 nextReadCalled = true;
282 void WriteCallbackWrapper (IAsyncResult r)
284 WebAsyncResult result = r as WebAsyncResult;
285 if (result != null && result.AsyncWriteAll)
288 if (r.AsyncState != null) {
289 result = (WebAsyncResult) r.AsyncState;
290 result.InnerAsyncResult = r;
291 result.DoCallback ();
300 void ReadCallbackWrapper (IAsyncResult r)
302 WebAsyncResult result;
303 if (r.AsyncState != null) {
304 result = (WebAsyncResult) r.AsyncState;
305 result.InnerAsyncResult = r;
306 result.DoCallback ();
315 public override int Read (byte [] buffer, int offset, int size)
317 AsyncCallback cb = cb_wrapper;
318 WebAsyncResult res = (WebAsyncResult) BeginRead (buffer, offset, size, cb, null);
319 if (!res.IsCompleted && !res.WaitUntilComplete (ReadTimeout, false)) {
320 nextReadCalled = true;
322 throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
325 return EndRead (res);
328 public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
329 AsyncCallback cb, object state)
332 throw new NotSupportedException ("this stream does not allow reading");
335 throw new ArgumentNullException ("buffer");
337 int length = buffer.Length;
338 if (offset < 0 || length < offset)
339 throw new ArgumentOutOfRangeException ("offset");
340 if (size < 0 || (length - offset) < size)
341 throw new ArgumentOutOfRangeException ("size");
348 WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
349 if (totalRead >= contentLength) {
350 result.SetCompleted (true, -1);
351 result.DoCallback ();
355 int remaining = readBufferSize - readBufferOffset;
357 int copy = (remaining > size) ? size : remaining;
358 Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
359 readBufferOffset += copy;
363 if (size == 0 || totalRead >= contentLength) {
364 result.SetCompleted (true, copy);
365 result.DoCallback ();
368 result.NBytes = copy;
374 if (contentLength != Int32.MaxValue && contentLength - totalRead < size)
375 size = contentLength - totalRead;
378 result.InnerAsyncResult = cnc.BeginRead (request, buffer, offset, size, cb, result);
380 result.SetCompleted (true, result.NBytes);
381 result.DoCallback ();
386 public override int EndRead (IAsyncResult r)
388 WebAsyncResult result = (WebAsyncResult) r;
389 if (result.EndCalled) {
390 int xx = result.NBytes;
391 return (xx >= 0) ? xx : 0;
394 result.EndCalled = true;
396 if (!result.IsCompleted) {
399 nbytes = cnc.EndRead (request, result);
400 } catch (Exception exc) {
403 if (pendingReads == 0)
407 nextReadCalled = true;
409 result.SetCompleted (false, exc);
410 result.DoCallback ();
420 result.SetCompleted (false, nbytes + result.NBytes);
421 result.DoCallback ();
423 contentLength = totalRead;
428 if (pendingReads == 0)
432 if (totalRead >= contentLength && !nextReadCalled)
435 int nb = result.NBytes;
436 return (nb >= 0) ? nb : 0;
439 void WriteRequestAsyncCB (IAsyncResult r)
441 WebAsyncResult result = (WebAsyncResult) r.AsyncState;
443 cnc.EndWrite2 (request, r);
444 result.SetCompleted (false, 0);
447 WebConnection.InitRead (cnc);
449 } catch (Exception e) {
451 nextReadCalled = true;
453 if (e is System.Net.Sockets.SocketException)
454 e = new IOException ("Error writing request", e);
455 result.SetCompleted (false, e);
457 complete_request_written = true;
458 result.DoCallback ();
461 public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
462 AsyncCallback cb, object state)
465 throw new WebException ("The request was canceled.", null, WebExceptionStatus.RequestCanceled);
468 throw new NotSupportedException ("this stream does not allow writing");
471 throw new ArgumentNullException ("buffer");
473 int length = buffer.Length;
474 if (offset < 0 || length < offset)
475 throw new ArgumentOutOfRangeException ("offset");
476 if (size < 0 || (length - offset) < size)
477 throw new ArgumentOutOfRangeException ("size");
486 WebAsyncResult result = new WebAsyncResult (cb, state);
488 CheckWriteOverflow (request.ContentLength, totalWritten, size);
489 if (allowBuffering && !sendChunked) {
490 if (writeBuffer == null)
491 writeBuffer = new MemoryStream ();
492 writeBuffer.Write (buffer, offset, size);
493 totalWritten += size;
494 if (request.ContentLength > 0 && totalWritten == request.ContentLength) {
496 result.AsyncWriteAll = true;
497 result.InnerAsyncResult = WriteRequestAsync (new AsyncCallback (WriteRequestAsyncCB), result);
498 if (result.InnerAsyncResult == null) {
499 if (!result.IsCompleted)
500 result.SetCompleted (true, 0);
501 result.DoCallback ();
503 } catch (Exception exc) {
504 result.SetCompleted (true, exc);
505 result.DoCallback ();
508 result.SetCompleted (true, 0);
509 result.DoCallback ();
514 AsyncCallback callback = null;
516 callback = cb_wrapper;
521 string cSize = String.Format ("{0:X}\r\n", size);
522 byte [] head = Encoding.ASCII.GetBytes (cSize);
523 int chunkSize = 2 + size + head.Length;
524 byte [] newBuffer = new byte [chunkSize];
525 Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
526 Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
527 Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
535 result.InnerAsyncResult = cnc.BeginWrite (request, buffer, offset, size, callback, result);
536 } catch (Exception) {
539 result.SetCompleted (true, 0);
540 result.DoCallback ();
542 totalWritten += size;
546 void CheckWriteOverflow (long contentLength, long totalWritten, long size)
548 if (contentLength == -1)
551 long avail = contentLength - totalWritten;
554 nextReadCalled = true;
556 throw new ProtocolViolationException (
557 "The number of bytes to be written is greater than " +
558 "the specified ContentLength.");
562 public override void EndWrite (IAsyncResult r)
565 throw new ArgumentNullException ("r");
567 WebAsyncResult result = r as WebAsyncResult;
569 throw new ArgumentException ("Invalid IAsyncResult");
571 if (result.EndCalled)
574 result.EndCalled = true;
575 if (result.AsyncWriteAll) {
576 result.WaitUntilComplete ();
577 if (result.GotException)
578 throw result.Exception;
582 if (allowBuffering && !sendChunked)
585 if (result.GotException)
586 throw result.Exception;
589 cnc.EndWrite2 (request, result.InnerAsyncResult);
590 result.SetCompleted (false, 0);
591 result.DoCallback ();
592 } catch (Exception e) {
594 result.SetCompleted (false, 0);
596 result.SetCompleted (false, e);
597 result.DoCallback ();
604 if (pendingWrites == 0)
611 public override void Write (byte [] buffer, int offset, int size)
613 AsyncCallback cb = cb_wrapper;
614 WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null);
615 if (!res.IsCompleted && !res.WaitUntilComplete (WriteTimeout, false)) {
617 nextReadCalled = true;
619 throw new IOException ("Write timed out.");
625 public override void Flush ()
629 internal void SetHeaders (byte [] buffer)
635 long cl = request.ContentLength;
636 string method = request.Method;
637 bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
639 if (sendChunked || cl > -1 || no_writestream) {
643 WebConnection.InitRead (cnc);
645 if (!sendChunked && cl == 0)
646 requestWritten = true;
650 internal bool RequestWritten {
651 get { return requestWritten; }
654 IAsyncResult WriteRequestAsync (AsyncCallback cb, object state)
656 requestWritten = true;
657 byte [] bytes = writeBuffer.GetBuffer ();
658 int length = (int) writeBuffer.Length;
659 // Headers already written to the stream
660 return (length > 0) ? cnc.BeginWrite (request, bytes, 0, length, cb, state) : null;
669 string err_msg = null;
670 if (!cnc.Write (request, headers, 0, headers.Length, ref err_msg))
671 throw new WebException ("Error writing request: " + err_msg, null, WebExceptionStatus.SendFailure, null);
674 internal void WriteRequest ()
679 requestWritten = true;
683 if (!allowBuffering || writeBuffer == null)
686 byte [] bytes = writeBuffer.GetBuffer ();
687 int length = (int) writeBuffer.Length;
688 if (request.ContentLength != -1 && request.ContentLength < length) {
689 nextReadCalled = true;
691 throw new WebException ("Specified Content-Length is less than the number of bytes to write", null,
692 WebExceptionStatus.ServerProtocolViolation, null);
696 string method = request.Method;
697 bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
700 request.InternalContentLength = length;
701 request.SendRequestHeaders (true);
704 if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100)
707 IAsyncResult result = null;
709 result = cnc.BeginWrite (request, bytes, 0, length, null, null);
713 WebConnection.InitRead (cnc);
717 complete_request_written = cnc.EndWrite (request, result);
719 complete_request_written = true;
722 internal void InternalClose ()
727 public override void Close ()
734 byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
735 string err_msg = null;
736 cnc.Write (request, chunk, 0, chunk.Length, ref err_msg);
741 if (!nextReadCalled) {
743 // If we have not read all the contents
744 if (!nextReadCalled) {
745 nextReadCalled = true;
750 } else if (!allowBuffering) {
751 complete_request_written = true;
754 WebConnection.InitRead (cnc);
759 if (disposed || requestWritten)
762 long length = request.ContentLength;
764 if (!sendChunked && length != -1 && totalWritten != length) {
765 IOException io = new IOException ("Cannot close the stream until all bytes are written");
766 nextReadCalled = true;
768 throw new WebException ("Request was cancelled.", io, WebExceptionStatus.RequestCanceled);
771 // Commented out the next line to fix xamarin bug #1512
776 internal void KillBuffer ()
781 public override long Seek (long a, SeekOrigin b)
783 throw new NotSupportedException ();
786 public override void SetLength (long a)
788 throw new NotSupportedException ();
791 public override bool CanSeek {
792 get { return false; }
795 public override bool CanRead {
796 get { return !disposed && isRead; }
799 public override bool CanWrite {
800 get { return !disposed && !isRead; }
803 public override long Length {
806 throw new NotSupportedException ();
807 return stream_length;
811 public override long Position {
812 get { throw new NotSupportedException (); }
813 set { throw new NotSupportedException (); }