X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FSystem%2FSystem.Net%2FWebConnectionStream.cs;h=ff370e9e8ba78d1e6f6275a49b9f6cb34570def0;hb=8bb9b4409d3f73fdf5ec2afc3b9add6bd33f712f;hp=36a8fc39f08ee605550f131d1b7d50c82413195b;hpb=b67928068dcec9db1f16c4e7ff4c2a3e3c2040c0;p=mono.git diff --git a/mcs/class/System/System.Net/WebConnectionStream.cs b/mcs/class/System/System.Net/WebConnectionStream.cs index 36a8fc39f08..ff370e9e8ba 100644 --- a/mcs/class/System/System.Net/WebConnectionStream.cs +++ b/mcs/class/System/System.Net/WebConnectionStream.cs @@ -44,8 +44,10 @@ namespace System.Net byte [] readBuffer; int readBufferOffset; int readBufferSize; + int stream_length; // -1 when CL not present int contentLength; int totalRead; + internal long totalWritten; bool nextReadCalled; int pendingReads; int pendingWrites; @@ -61,22 +63,33 @@ namespace System.Net bool initRead; bool read_eof; bool complete_request_written; - long max_buffer_size; + int read_timeout; + int write_timeout; + AsyncCallback cb_wrapper; // Calls to ReadCallbackWrapper or WriteCallbacWrapper + internal bool IgnoreIOErrors; public WebConnectionStream (WebConnection cnc) { + if (cnc.Data == null) + throw new InvalidOperationException ("cnc.Data was not initialized"); + if (cnc.Data.Headers == null) + throw new InvalidOperationException ("cnc.Data.Headers was not initialized"); + if (cnc.Data.request == null) + throw new InvalidOperationException ("cnc.Data.request was not initialized"); isRead = true; + cb_wrapper = new AsyncCallback (ReadCallbackWrapper); pending = new ManualResetEvent (true); this.request = cnc.Data.request; + read_timeout = request.ReadWriteTimeout; + write_timeout = read_timeout; this.cnc = cnc; string contentType = cnc.Data.Headers ["Transfer-Encoding"]; - bool chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1); + bool chunkedRead = (contentType != null && contentType.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1); string clength = cnc.Data.Headers ["Content-Length"]; if (!chunkedRead && clength != null && clength != "") { - try { contentLength = Int32.Parse (clength); - if (contentLength == 0) { + if (contentLength == 0 && !IsNtlmAuth ()) { ReadAll (); } } catch { @@ -85,30 +98,78 @@ namespace System.Net } else { contentLength = Int32.MaxValue; } + + // Negative numbers? + if (!Int32.TryParse (clength, out stream_length)) + stream_length = -1; } public WebConnectionStream (WebConnection cnc, HttpWebRequest request) { + read_timeout = request.ReadWriteTimeout; + write_timeout = read_timeout; isRead = false; + cb_wrapper = new AsyncCallback (WriteCallbackWrapper); this.cnc = cnc; this.request = request; allowBuffering = request.InternalAllowBuffering; sendChunked = request.SendChunked; - if (allowBuffering) { + if (sendChunked) + pending = new ManualResetEvent (true); + else if (allowBuffering) writeBuffer = new MemoryStream (); - max_buffer_size = request.ContentLength; - } else { - max_buffer_size = -1; + } + + bool IsNtlmAuth () + { + bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.Address)); + string header_name = (isProxy) ? "Proxy-Authenticate" : "WWW-Authenticate"; + string authHeader = cnc.Data.Headers [header_name]; + return (authHeader != null && authHeader.IndexOf ("NTLM", StringComparison.Ordinal) != -1); + } + + internal void CheckResponseInBuffer () + { + if (contentLength > 0 && (readBufferSize - readBufferOffset) >= contentLength) { + if (!IsNtlmAuth ()) + ReadAll (); } + } - if (sendChunked) - pending = new ManualResetEvent (true); + internal HttpWebRequest Request { + get { return request; } + } + + internal WebConnection Connection { + get { return cnc; } } -#if NET_2_0 public override bool CanTimeout { get { return true; } } -#endif + + public override int ReadTimeout { + get { + return read_timeout; + } + + set { + if (value < -1) + throw new ArgumentOutOfRangeException ("value"); + read_timeout = value; + } + } + + public override int WriteTimeout { + get { + return write_timeout; + } + + set { + if (value < -1) + throw new ArgumentOutOfRangeException ("value"); + write_timeout = value; + } + } internal bool CompleteRequestWritten { get { return complete_request_written; } @@ -135,13 +196,17 @@ namespace System.Net } internal int WriteBufferLength { - get { return (int) writeBuffer.Length; } + get { return writeBuffer != null ? (int) writeBuffer.Length : (-1); } } internal void ForceCompletion () { - nextReadCalled = true; - cnc.NextRead (); + if (!nextReadCalled) { + if (contentLength == Int32.MaxValue) + contentLength = 0; + nextReadCalled = true; + cnc.NextRead (); + } } internal void CheckComplete () @@ -185,7 +250,7 @@ namespace System.Net buffer = new byte [8192]; int read; - while ((read = cnc.Read (buffer, 0, buffer.Length)) != 0) + while ((read = cnc.Read (request, buffer, 0, buffer.Length)) != 0) ms.Write (buffer, 0, read); b = ms.GetBuffer (); @@ -204,7 +269,7 @@ namespace System.Net int remaining = new_size - diff; int r = -1; while (remaining > 0 && r != 0) { - r = cnc.Read (b, diff, remaining); + r = cnc.Read (request, b, diff, remaining); remaining -= r; diff += r; } @@ -222,13 +287,19 @@ namespace System.Net void WriteCallbackWrapper (IAsyncResult r) { - WebAsyncResult result; + WebAsyncResult result = r as WebAsyncResult; + if (result != null && result.AsyncWriteAll) + return; + if (r.AsyncState != null) { result = (WebAsyncResult) r.AsyncState; result.InnerAsyncResult = r; result.DoCallback (); } else { - EndWrite (r); + try { + EndWrite (r); + } catch { + } } } @@ -240,25 +311,21 @@ namespace System.Net result.InnerAsyncResult = r; result.DoCallback (); } else { - EndRead (r); + try { + EndRead (r); + } catch { + } } } public override int Read (byte [] buffer, int offset, int size) { - if (!isRead) - throw new NotSupportedException ("this stream does not allow reading"); - - if (totalRead >= contentLength) - return 0; - - AsyncCallback cb = new AsyncCallback (ReadCallbackWrapper); + AsyncCallback cb = cb_wrapper; WebAsyncResult res = (WebAsyncResult) BeginRead (buffer, offset, size, cb, null); - if (!res.IsCompleted && !res.WaitUntilComplete (request.ReadWriteTimeout, false)) { + if (!res.IsCompleted && !res.WaitUntilComplete (ReadTimeout, false)) { nextReadCalled = true; cnc.Close (true); - throw new WebException ("The operation has timed out.", - WebExceptionStatus.Timeout); + throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout); } return EndRead (res); @@ -274,8 +341,10 @@ namespace System.Net throw new ArgumentNullException ("buffer"); int length = buffer.Length; - if (size < 0 || offset < 0 || length < offset || length - offset < size) - throw new ArgumentOutOfRangeException (); + if (offset < 0 || length < offset) + throw new ArgumentOutOfRangeException ("offset"); + if (size < 0 || (length - offset) < size) + throw new ArgumentOutOfRangeException ("size"); lock (locker) { pendingReads++; @@ -306,13 +375,13 @@ namespace System.Net } if (cb != null) - cb = new AsyncCallback (ReadCallbackWrapper); + cb = cb_wrapper; if (contentLength != Int32.MaxValue && contentLength - totalRead < size) size = contentLength - totalRead; if (!read_eof) { - result.InnerAsyncResult = cnc.BeginRead (buffer, offset, size, cb, result); + result.InnerAsyncResult = cnc.BeginRead (request, buffer, offset, size, cb, result); } else { result.SetCompleted (true, result.NBytes); result.DoCallback (); @@ -333,7 +402,7 @@ namespace System.Net if (!result.IsCompleted) { int nbytes = -1; try { - nbytes = cnc.EndRead (result); + nbytes = cnc.EndRead (request, result); } catch (Exception exc) { lock (locker) { pendingReads--; @@ -344,6 +413,7 @@ namespace System.Net nextReadCalled = true; cnc.Close (true); result.SetCompleted (false, exc); + result.DoCallback (); throw; } @@ -371,10 +441,35 @@ namespace System.Net int nb = result.NBytes; return (nb >= 0) ? nb : 0; } - + + void WriteRequestAsyncCB (IAsyncResult r) + { + WebAsyncResult result = (WebAsyncResult) r.AsyncState; + try { + cnc.EndWrite2 (request, r); + result.SetCompleted (false, 0); + if (!initRead) { + initRead = true; + WebConnection.InitRead (cnc); + } + } catch (Exception e) { + KillBuffer (); + nextReadCalled = true; + cnc.Close (true); + if (e is System.Net.Sockets.SocketException) + e = new IOException ("Error writing request", e); + result.SetCompleted (false, e); + } + complete_request_written = true; + result.DoCallback (); + } + public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state) { + if (request.Aborted) + throw new WebException ("The request was canceled.", null, WebExceptionStatus.RequestCanceled); + if (isRead) throw new NotSupportedException ("this stream does not allow writing"); @@ -382,8 +477,10 @@ namespace System.Net throw new ArgumentNullException ("buffer"); int length = buffer.Length; - if (size < 0 || offset < 0 || length < offset || length - offset < size) - throw new ArgumentOutOfRangeException (); + if (offset < 0 || length < offset) + throw new ArgumentOutOfRangeException ("offset"); + if (size < 0 || (length - offset) < size) + throw new ArgumentOutOfRangeException ("size"); if (sendChunked) { lock (locker) { @@ -393,27 +490,36 @@ namespace System.Net } WebAsyncResult result = new WebAsyncResult (cb, state); - if (allowBuffering) { - if (max_buffer_size >= 0) { - long avail = max_buffer_size - writeBuffer.Length; - if (size > avail) { - if (requestWritten) - throw new ProtocolViolationException ( - "The number of bytes to be written is greater than " + - "the specified ContentLength."); - } - } + if (!sendChunked) + CheckWriteOverflow (request.ContentLength, totalWritten, size); + if (allowBuffering && !sendChunked) { + if (writeBuffer == null) + writeBuffer = new MemoryStream (); writeBuffer.Write (buffer, offset, size); - if (!sendChunked) { + totalWritten += size; + if (request.ContentLength > 0 && totalWritten == request.ContentLength) { + try { + result.AsyncWriteAll = true; + result.InnerAsyncResult = WriteRequestAsync (new AsyncCallback (WriteRequestAsyncCB), result); + if (result.InnerAsyncResult == null) { + if (!result.IsCompleted) + result.SetCompleted (true, 0); + result.DoCallback (); + } + } catch (Exception exc) { + result.SetCompleted (true, exc); + result.DoCallback (); + } + } else { result.SetCompleted (true, 0); result.DoCallback (); - return result; } + return result; } AsyncCallback callback = null; if (cb != null) - callback = new AsyncCallback (WriteCallbackWrapper); + callback = cb_wrapper; if (sendChunked) { WriteRequest (); @@ -431,10 +537,34 @@ namespace System.Net size = chunkSize; } - result.InnerAsyncResult = cnc.BeginWrite (buffer, offset, size, callback, result); + try { + result.InnerAsyncResult = cnc.BeginWrite (request, buffer, offset, size, callback, result); + } catch (Exception) { + if (!IgnoreIOErrors) + throw; + result.SetCompleted (true, 0); + result.DoCallback (); + } + totalWritten += size; return result; } + void CheckWriteOverflow (long contentLength, long totalWritten, long size) + { + if (contentLength == -1) + return; + + long avail = contentLength - totalWritten; + if (size > avail) { + KillBuffer (); + nextReadCalled = true; + cnc.Close (true); + throw new ProtocolViolationException ( + "The number of bytes to be written is greater than " + + "the specified ContentLength."); + } + } + public override void EndWrite (IAsyncResult r) { if (r == null) @@ -448,6 +578,12 @@ namespace System.Net return; result.EndCalled = true; + if (result.AsyncWriteAll) { + result.WaitUntilComplete (); + if (result.GotException) + throw result.Exception; + return; + } if (allowBuffering && !sendChunked) return; @@ -455,30 +591,35 @@ namespace System.Net if (result.GotException) throw result.Exception; - try { - cnc.EndWrite (result.InnerAsyncResult); + try { + cnc.EndWrite2 (request, result.InnerAsyncResult); result.SetCompleted (false, 0); + result.DoCallback (); } catch (Exception e) { - result.SetCompleted (false, e); - } - - if (sendChunked) { - lock (locker) { - pendingWrites--; - if (pendingWrites == 0) - pending.Set (); + if (IgnoreIOErrors) + result.SetCompleted (false, 0); + else + result.SetCompleted (false, e); + result.DoCallback (); + if (!IgnoreIOErrors) + throw; + } finally { + if (sendChunked) { + lock (locker) { + pendingWrites--; + if (pendingWrites == 0) + pending.Set (); + } } } } public override void Write (byte [] buffer, int offset, int size) { - if (isRead) - throw new NotSupportedException ("This stream does not allow writing"); - - AsyncCallback cb = new AsyncCallback (WriteCallbackWrapper); + AsyncCallback cb = cb_wrapper; WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null); - if (!res.IsCompleted && !res.WaitUntilComplete (request.ReadWriteTimeout, false)) { + if (!res.IsCompleted && !res.WaitUntilComplete (WriteTimeout, false)) { + KillBuffer (); nextReadCalled = true; cnc.Close (true); throw new IOException ("Write timed out."); @@ -491,27 +632,27 @@ namespace System.Net { } - internal void SetHeaders (byte [] buffer, int offset, int size) + internal void SetHeaders (byte [] buffer) { if (headersSent) return; - if (!allowBuffering || sendChunked) { - headersSent = true; - if (!cnc.Connected) - throw new WebException ("Not connected", null, WebExceptionStatus.SendFailure, null); - - - if (!cnc.Write (buffer, offset, size)) - throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null); - + headers = buffer; + long cl = request.ContentLength; + string method = request.Method; + bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" || + method == "TRACE"); + bool webdav = (method == "PROPFIND" || method == "PROPPATCH" || method == "MKCOL" || + method == "COPY" || method == "MOVE" || method == "LOCK" || + method == "UNLOCK"); + if (sendChunked || cl > -1 || no_writestream || webdav) { + WriteHeaders (); if (!initRead) { initRead = true; WebConnection.InitRead (cnc); } - } else { - headers = new byte [size]; - Buffer.BlockCopy (buffer, offset, headers, 0, size); + if (!sendChunked && cl == 0) + requestWritten = true; } } @@ -519,16 +660,34 @@ namespace System.Net get { return requestWritten; } } + IAsyncResult WriteRequestAsync (AsyncCallback cb, object state) + { + requestWritten = true; + byte [] bytes = writeBuffer.GetBuffer (); + int length = (int) writeBuffer.Length; + // Headers already written to the stream + return (length > 0) ? cnc.BeginWrite (request, bytes, 0, length, cb, state) : null; + } + + void WriteHeaders () + { + if (headersSent) + return; + + headersSent = true; + string err_msg = null; + if (!cnc.Write (request, headers, 0, headers.Length, ref err_msg)) + throw new WebException ("Error writing request: " + err_msg, null, WebExceptionStatus.SendFailure, null); + } + internal void WriteRequest () { if (requestWritten) return; - if (sendChunked) { - request.SendRequestHeaders (); - requestWritten = true; + requestWritten = true; + if (sendChunked) return; - } if (!allowBuffering || writeBuffer == null) return; @@ -536,56 +695,37 @@ namespace System.Net byte [] bytes = writeBuffer.GetBuffer (); int length = (int) writeBuffer.Length; if (request.ContentLength != -1 && request.ContentLength < length) { + nextReadCalled = true; + cnc.Close (true); throw new WebException ("Specified Content-Length is less than the number of bytes to write", null, WebExceptionStatus.ServerProtocolViolation, null); } - request.InternalContentLength = length; - request.SendRequestHeaders (); - requestWritten = true; - - // - // For small requests, make a copy, it will reduce the traffic, for large - // requests, the NoDelay bit on the socket should take effect (set in WebConnection). - // - if (headers.Length + length < 8192){ - byte[] b = new byte [headers.Length + length]; - - Buffer.BlockCopy (headers, 0, b, 0, headers.Length); - Buffer.BlockCopy (bytes, 0, b, headers.Length, length); - - if (!cnc.Write (b, 0, b.Length)) - throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null); - - headersSent = true; - complete_request_written = true; - - if (!initRead) { - initRead = true; - WebConnection.InitRead (cnc); - } - } else { - if (!cnc.Write (headers, 0, headers.Length)) - throw new WebException ("Error writing request.", null, WebExceptionStatus.SendFailure, null); - - headersSent = true; - if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100) - return; - - IAsyncResult result = null; - if (length > 0) - result = cnc.BeginWrite (bytes, 0, length, null, null); - - if (!initRead) { - initRead = true; - WebConnection.InitRead (cnc); - } + if (!headersSent) { + string method = request.Method; + bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" || + method == "TRACE"); + if (!no_writestream) + request.InternalContentLength = length; + request.SendRequestHeaders (true); + } + WriteHeaders (); + if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100) + return; - if (length > 0) - complete_request_written = cnc.EndWrite (result); - else - complete_request_written = true; + IAsyncResult result = null; + if (length > 0) + result = cnc.BeginWrite (request, bytes, 0, length, null, null); + + if (!initRead) { + initRead = true; + WebConnection.InitRead (cnc); } + + if (length > 0) + complete_request_written = cnc.EndWrite (request, result); + else + complete_request_written = true; } internal void InternalClose () @@ -593,20 +733,16 @@ namespace System.Net disposed = true; } - internal void ForceCloseConnection () - { - if (!disposed) { - disposed = true; - cnc.Close (true); - } - } - public override void Close () { if (sendChunked) { + if (disposed) + return; + disposed = true; pending.WaitOne (); byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n"); - cnc.Write (chunk, 0, chunk.Length); + string err_msg = null; + cnc.Write (request, chunk, 0, chunk.Length, ref err_msg); return; } @@ -629,14 +765,20 @@ namespace System.Net return; } - if (disposed) + if (disposed || requestWritten) return; long length = request.ContentLength; - if (length != -1 && length > writeBuffer.Length) - throw new IOException ("Cannot close the stream until all bytes are written"); - WriteRequest (); + if (!sendChunked && length != -1 && totalWritten != length) { + IOException io = new IOException ("Cannot close the stream until all bytes are written"); + nextReadCalled = true; + cnc.Close (true); + throw new WebException ("Request was cancelled.", io, WebExceptionStatus.RequestCanceled); + } + + // Commented out the next line to fix xamarin bug #1512 + //WriteRequest (); disposed = true; } @@ -660,15 +802,19 @@ namespace System.Net } public override bool CanRead { - get { return isRead; } + get { return !disposed && isRead; } } public override bool CanWrite { - get { return !isRead; } + get { return !disposed && !isRead; } } public override long Length { - get { throw new NotSupportedException (); } + get { + if (!isRead) + throw new NotSupportedException (); + return stream_length; + } } public override long Position {