| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733 |
- //
- // WebResponseStream.cs
- //
- // Author:
- // Martin Baulig <[email protected]>
- //
- // Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.com)
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to deal
- // in the Software without restriction, including without limitation the rights
- // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- // copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- // THE SOFTWARE.
- using System.IO;
- using System.Text;
- using System.Collections;
- using System.Collections.Generic;
- using System.Threading;
- using System.Threading.Tasks;
- using System.Runtime.ExceptionServices;
- using System.Net.Sockets;
- namespace System.Net
- {
- class WebResponseStream : WebConnectionStream
- {
- BufferOffsetSize readBuffer;
- long contentLength;
- long totalRead;
- bool nextReadCalled;
- int stream_length; // -1 when CL not present
- TaskCompletionSource<int> readTcs;
- object locker = new object ();
- int nestedRead;
- bool read_eof;
- public WebRequestStream RequestStream {
- get;
- }
- public WebHeaderCollection Headers {
- get;
- private set;
- }
- public HttpStatusCode StatusCode {
- get;
- private set;
- }
- public string StatusDescription {
- get;
- private set;
- }
- public Version Version {
- get;
- private set;
- }
- public bool KeepAlive {
- get;
- private set;
- }
- internal readonly string ME;
- public WebResponseStream (WebRequestStream request)
- : base (request.Connection, request.Operation, request.InnerStream)
- {
- RequestStream = request;
- request.InnerStream.ReadTimeout = ReadTimeout;
- #if MONO_WEB_DEBUG
- ME = $"WRP(Cnc={Connection.ID}, Op={Operation.ID})";
- #endif
- }
- public override long Length {
- get {
- return stream_length;
- }
- }
- public override bool CanRead => true;
- public override bool CanWrite => false;
- protected bool ChunkedRead {
- get;
- private set;
- }
- protected MonoChunkStream ChunkStream {
- get;
- private set;
- }
- public override async Task<int> ReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
- {
- WebConnection.Debug ($"{ME} READ ASYNC");
- cancellationToken.ThrowIfCancellationRequested ();
- if (buffer == null)
- throw new ArgumentNullException (nameof (buffer));
- int length = buffer.Length;
- if (offset < 0 || length < offset)
- throw new ArgumentOutOfRangeException (nameof (offset));
- if (size < 0 || (length - offset) < size)
- throw new ArgumentOutOfRangeException (nameof (size));
- if (Interlocked.CompareExchange (ref nestedRead, 1, 0) != 0)
- throw new InvalidOperationException ("Invalid nested call.");
- var myReadTcs = new TaskCompletionSource<int> ();
- while (!cancellationToken.IsCancellationRequested) {
- /*
- * 'readTcs' is set by ReadAllAsync().
- */
- var oldReadTcs = Interlocked.CompareExchange (ref readTcs, myReadTcs, null);
- WebConnection.Debug ($"{ME} READ ASYNC #1: {oldReadTcs != null}");
- if (oldReadTcs == null)
- break;
- await oldReadTcs.Task.ConfigureAwait (false);
- }
- WebConnection.Debug ($"{ME} READ ASYNC #2: {totalRead} {contentLength}");
- int oldBytes = 0, nbytes = 0;
- Exception throwMe = null;
- try {
- // FIXME: NetworkStream.ReadAsync() does not support cancellation.
- (oldBytes, nbytes) = await HttpWebRequest.RunWithTimeout (
- ct => ProcessRead (buffer, offset, size, ct),
- ReadTimeout, () => {
- Operation.Abort ();
- InnerStream.Dispose ();
- }).ConfigureAwait (false);
- } catch (Exception e) {
- throwMe = GetReadException (WebExceptionStatus.ReceiveFailure, e, "ReadAsync");
- }
- WebConnection.Debug ($"{ME} READ ASYNC #3: {totalRead} {contentLength} - {oldBytes} {nbytes} {throwMe?.Message}");
- if (throwMe != null) {
- lock (locker) {
- myReadTcs.TrySetException (throwMe);
- readTcs = null;
- nestedRead = 0;
- }
- closed = true;
- Operation.CompleteResponseRead (false, throwMe);
- throw throwMe;
- }
- lock (locker) {
- readTcs.TrySetResult (oldBytes + nbytes);
- readTcs = null;
- nestedRead = 0;
- }
- if (totalRead >= contentLength && !nextReadCalled) {
- WebConnection.Debug ($"{ME} READ ASYNC - READ COMPLETE: {oldBytes} {nbytes} - {totalRead} {contentLength} {nextReadCalled}");
- if (!nextReadCalled) {
- nextReadCalled = true;
- Operation.CompleteResponseRead (true);
- }
- }
- return oldBytes + nbytes;
- }
- async Task<(int, int)> ProcessRead (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
- {
- WebConnection.Debug ($"{ME} PROCESS READ: {totalRead} {contentLength}");
- cancellationToken.ThrowIfCancellationRequested ();
- if (totalRead >= contentLength) {
- read_eof = true;
- contentLength = totalRead;
- return (0, 0);
- }
- int oldBytes = 0;
- int remaining = readBuffer?.Size ?? 0;
- if (remaining > 0) {
- int copy = (remaining > size) ? size : remaining;
- Buffer.BlockCopy (readBuffer.Buffer, readBuffer.Offset, buffer, offset, copy);
- readBuffer.Offset += copy;
- readBuffer.Size -= copy;
- offset += copy;
- size -= copy;
- totalRead += copy;
- if (totalRead >= contentLength) {
- contentLength = totalRead;
- read_eof = true;
- }
- if (size == 0 || totalRead >= contentLength)
- return (0, copy);
- oldBytes = copy;
- }
- if (contentLength != Int64.MaxValue && contentLength - totalRead < size)
- size = (int)(contentLength - totalRead);
- WebConnection.Debug ($"{ME} PROCESS READ #1: {oldBytes} {size} {read_eof}");
- if (read_eof) {
- contentLength = totalRead;
- return (oldBytes, 0);
- }
- var ret = await InnerReadAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
- if (ret <= 0) {
- read_eof = true;
- contentLength = totalRead;
- return (oldBytes, 0);
- }
- totalRead += ret;
- return (oldBytes, ret);
- }
- internal async Task<int> InnerReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
- {
- WebConnection.Debug ($"{ME} INNER READ ASYNC");
- Operation.ThrowIfDisposed (cancellationToken);
- int nbytes = 0;
- bool done = false;
- if (!ChunkedRead || (!ChunkStream.DataAvailable && ChunkStream.WantMore)) {
- nbytes = await InnerStream.ReadAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
- WebConnection.Debug ($"{ME} INNER READ ASYNC #1: {nbytes} {ChunkedRead}");
- if (!ChunkedRead)
- return nbytes;
- done = nbytes == 0;
- }
- try {
- ChunkStream.WriteAndReadBack (buffer, offset, size, ref nbytes);
- WebConnection.Debug ($"{ME} INNER READ ASYNC #1: {done} {nbytes} {ChunkStream.WantMore}");
- if (!done && nbytes == 0 && ChunkStream.WantMore)
- nbytes = await EnsureReadAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
- } catch (Exception e) {
- if (e is WebException || e is OperationCanceledException)
- throw;
- throw new WebException ("Invalid chunked data.", e, WebExceptionStatus.ServerProtocolViolation, null);
- }
- if ((done || nbytes == 0) && ChunkStream.ChunkLeft != 0) {
- // HandleError (WebExceptionStatus.ReceiveFailure, null, "chunked EndRead");
- throw new WebException ("Read error", null, WebExceptionStatus.ReceiveFailure, null);
- }
- return nbytes;
- }
- async Task<int> EnsureReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
- {
- byte[] morebytes = null;
- int nbytes = 0;
- while (nbytes == 0 && ChunkStream.WantMore && !cancellationToken.IsCancellationRequested) {
- int localsize = ChunkStream.ChunkLeft;
- if (localsize <= 0) // not read chunk size yet
- localsize = 1024;
- else if (localsize > 16384)
- localsize = 16384;
- if (morebytes == null || morebytes.Length < localsize)
- morebytes = new byte[localsize];
- int nread = await InnerStream.ReadAsync (morebytes, 0, localsize, cancellationToken).ConfigureAwait (false);
- if (nread <= 0)
- return 0; // Error
- ChunkStream.Write (morebytes, 0, nread);
- nbytes += ChunkStream.Read (buffer, offset + nbytes, size - nbytes);
- }
- return nbytes;
- }
- bool CheckAuthHeader (string headerName)
- {
- var authHeader = Headers[headerName];
- return (authHeader != null && authHeader.IndexOf ("NTLM", StringComparison.Ordinal) != -1);
- }
- bool IsNtlmAuth ()
- {
- bool isProxy = (Request.Proxy != null && !Request.Proxy.IsBypassed (Request.Address));
- if (isProxy && CheckAuthHeader ("Proxy-Authenticate"))
- return true;
- return CheckAuthHeader ("WWW-Authenticate");
- }
- bool ExpectContent {
- get {
- if (Request.Method == "HEAD")
- return false;
- return ((int)StatusCode >= 200 && (int)StatusCode != 204 && (int)StatusCode != 304);
- }
- }
- async Task Initialize (BufferOffsetSize buffer, CancellationToken cancellationToken)
- {
- WebConnection.Debug ($"{ME} INIT: status={(int)StatusCode} bos={buffer.Offset}/{buffer.Size}");
- string contentType = Headers["Transfer-Encoding"];
- bool chunkedRead = (contentType != null && contentType.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
- string clength = Headers["Content-Length"];
- if (!chunkedRead && !string.IsNullOrEmpty (clength)) {
- if (!long.TryParse (clength, out contentLength))
- contentLength = Int64.MaxValue;
- } else {
- contentLength = Int64.MaxValue;
- }
- if (Version == HttpVersion.Version11 && RequestStream.KeepAlive) {
- KeepAlive = true;
- var cncHeader = Headers[ServicePoint.UsesProxy ? "Proxy-Connection" : "Connection"];
- if (cncHeader != null) {
- cncHeader = cncHeader.ToLower ();
- KeepAlive = cncHeader.IndexOf ("keep-alive", StringComparison.Ordinal) != -1;
- if (cncHeader.IndexOf ("close", StringComparison.Ordinal) != -1)
- KeepAlive = false;
- }
- }
- // Negative numbers?
- if (!Int32.TryParse (clength, out stream_length))
- stream_length = -1;
- string me = "WebResponseStream.Initialize()";
- string tencoding = null;
- if (ExpectContent)
- tencoding = Headers["Transfer-Encoding"];
- ChunkedRead = (tencoding != null && tencoding.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
- if (!ChunkedRead) {
- readBuffer = buffer;
- try {
- if (contentLength > 0 && readBuffer.Size >= contentLength) {
- if (!IsNtlmAuth ())
- await ReadAllAsync (false, cancellationToken).ConfigureAwait (false);
- }
- } catch (Exception e) {
- throw GetReadException (WebExceptionStatus.ReceiveFailure, e, me);
- }
- } else if (ChunkStream == null) {
- try {
- ChunkStream = new MonoChunkStream (buffer.Buffer, buffer.Offset, buffer.Offset + buffer.Size, Headers);
- } catch (Exception e) {
- throw GetReadException (WebExceptionStatus.ServerProtocolViolation, e, me);
- }
- } else {
- ChunkStream.ResetBuffer ();
- try {
- ChunkStream.Write (buffer.Buffer, buffer.Offset, buffer.Size);
- } catch (Exception e) {
- throw GetReadException (WebExceptionStatus.ServerProtocolViolation, e, me);
- }
- }
- WebConnection.Debug ($"{ME} INIT #1: - {ExpectContent} {closed} {nextReadCalled}");
- if (!ExpectContent) {
- if (!closed && !nextReadCalled) {
- if (contentLength == Int64.MaxValue)
- contentLength = 0;
- nextReadCalled = true;
- }
- Operation.CompleteResponseRead (true);
- }
- }
- internal async Task ReadAllAsync (bool resending, CancellationToken cancellationToken)
- {
- WebConnection.Debug ($"{ME} READ ALL ASYNC: resending={resending} eof={read_eof} total={totalRead} " +
- "length={contentLength} nextReadCalled={nextReadCalled}");
- if (read_eof || totalRead >= contentLength || nextReadCalled) {
- if (!nextReadCalled) {
- nextReadCalled = true;
- Operation.CompleteResponseRead (true);
- }
- return;
- }
- var timeoutTask = Task.Delay (ReadTimeout);
- var myReadTcs = new TaskCompletionSource<int> ();
- while (true) {
- /*
- * 'readTcs' is set by ReadAsync().
- */
- cancellationToken.ThrowIfCancellationRequested ();
- var oldReadTcs = Interlocked.CompareExchange (ref readTcs, myReadTcs, null);
- if (oldReadTcs == null)
- break;
- // ReadAsync() is in progress.
- var anyTask = await Task.WhenAny (oldReadTcs.Task, timeoutTask).ConfigureAwait (false);
- if (anyTask == timeoutTask)
- throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
- }
- WebConnection.Debug ($"{ME} READ ALL ASYNC #1");
- cancellationToken.ThrowIfCancellationRequested ();
- try {
- if (totalRead >= contentLength)
- return;
- byte[] b = null;
- int new_size;
- if (contentLength == Int64.MaxValue && !ChunkedRead) {
- WebConnection.Debug ($"{ME} READ ALL ASYNC - NEITHER LENGTH NOR CHUNKED");
- /*
- * This is a violation of the HTTP Spec - the server neither send a
- * "Content-Length:" nor a "Transfer-Encoding: chunked" header.
- *
- * When we're redirecting or resending for NTLM, then we can simply close
- * the connection here.
- *
- * However, if it's the final reply, then we need to try our best to read it.
- */
- if (resending) {
- Close ();
- return;
- }
- KeepAlive = false;
- }
- if (contentLength == Int64.MaxValue) {
- MemoryStream ms = new MemoryStream ();
- BufferOffsetSize buffer = null;
- if (readBuffer != null && readBuffer.Size > 0) {
- ms.Write (readBuffer.Buffer, readBuffer.Offset, readBuffer.Size);
- readBuffer.Offset = 0;
- readBuffer.Size = readBuffer.Buffer.Length;
- if (readBuffer.Buffer.Length >= 8192)
- buffer = readBuffer;
- }
- if (buffer == null)
- buffer = new BufferOffsetSize (new byte[8192], false);
- int read;
- while ((read = await InnerReadAsync (buffer.Buffer, buffer.Offset, buffer.Size, cancellationToken)) != 0)
- ms.Write (buffer.Buffer, buffer.Offset, read);
- new_size = (int)ms.Length;
- contentLength = new_size;
- readBuffer = new BufferOffsetSize (ms.GetBuffer (), 0, new_size, false);
- } else {
- new_size = (int)(contentLength - totalRead);
- b = new byte[new_size];
- int readSize = 0;
- if (readBuffer != null && readBuffer.Size > 0) {
- readSize = readBuffer.Size;
- if (readSize > new_size)
- readSize = new_size;
- Buffer.BlockCopy (readBuffer.Buffer, readBuffer.Offset, b, 0, readSize);
- }
- int remaining = new_size - readSize;
- int r = -1;
- while (remaining > 0 && r != 0) {
- r = await InnerReadAsync (b, readSize, remaining, cancellationToken);
- remaining -= r;
- readSize += r;
- }
- }
- readBuffer = new BufferOffsetSize (b, 0, new_size, false);
- totalRead = 0;
- nextReadCalled = true;
- myReadTcs.TrySetResult (new_size);
- } catch (Exception ex) {
- WebConnection.Debug ($"{ME} READ ALL ASYNC EX: {ex.Message}");
- myReadTcs.TrySetException (ex);
- throw;
- } finally {
- WebConnection.Debug ($"{ME} READ ALL ASYNC #2");
- readTcs = null;
- }
- Operation.CompleteResponseRead (true);
- }
- public override Task WriteAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
- {
- return Task.FromException (new NotSupportedException (SR.net_readonlystream));
- }
- protected override void Close_internal (ref bool disposed)
- {
- WebConnection.Debug ($"{ME} CLOSE: {disposed} {closed} {nextReadCalled}");
- if (!closed && !nextReadCalled) {
- nextReadCalled = true;
- if (totalRead >= contentLength) {
- disposed = true;
- Operation.CompleteResponseRead (true);
- } else {
- // If we have not read all the contents
- closed = true;
- disposed = true;
- Operation.CompleteResponseRead (false);
- }
- }
- }
- WebException GetReadException (WebExceptionStatus status, Exception error, string where)
- {
- error = GetException (error);
- string msg = $"Error getting response stream ({where}): {status}";
- if (error == null)
- return new WebException ($"Error getting response stream ({where}): {status}", status);
- if (error is WebException wexc)
- return wexc;
- if (Operation.Aborted || error is OperationCanceledException || error is ObjectDisposedException)
- return HttpWebRequest.CreateRequestAbortedException ();
- return new WebException ($"Error getting response stream ({where}): {status} {error.Message}", status,
- WebExceptionInternalStatus.RequestFatal, error);
- }
- internal async Task InitReadAsync (CancellationToken cancellationToken)
- {
- WebConnection.Debug ($"{ME} INIT READ ASYNC");
- var buffer = new BufferOffsetSize (new byte[4096], false);
- var state = ReadState.None;
- int position = 0;
- while (true) {
- Operation.ThrowIfClosedOrDisposed (cancellationToken);
- WebConnection.Debug ($"{ME} INIT READ ASYNC LOOP: {state} {position} - {buffer.Offset}/{buffer.Size}");
- var nread = await InnerStream.ReadAsync (
- buffer.Buffer, buffer.Offset, buffer.Size, cancellationToken).ConfigureAwait (false);
- WebConnection.Debug ($"{ME} INIT READ ASYNC LOOP #1: {state} {position} - {buffer.Offset}/{buffer.Size} - {nread}");
- if (nread == 0)
- throw GetReadException (WebExceptionStatus.ReceiveFailure, null, "ReadDoneAsync2");
- if (nread < 0)
- throw GetReadException (WebExceptionStatus.ServerProtocolViolation, null, "ReadDoneAsync3");
- buffer.Offset += nread;
- buffer.Size -= nread;
- if (state == ReadState.None) {
- try {
- var oldPos = position;
- if (!GetResponse (buffer, ref position, ref state))
- position = oldPos;
- } catch (Exception e) {
- WebConnection.Debug ($"{ME} INIT READ ASYNC FAILED: {e.Message}\n{e}");
- throw GetReadException (WebExceptionStatus.ServerProtocolViolation, e, "ReadDoneAsync4");
- }
- }
- if (state == ReadState.Aborted)
- throw GetReadException (WebExceptionStatus.RequestCanceled, null, "ReadDoneAsync5");
- if (state == ReadState.Content) {
- buffer.Size = buffer.Offset - position;
- buffer.Offset = position;
- break;
- }
- int est = nread * 2;
- if (est > buffer.Size) {
- var newBuffer = new byte [buffer.Buffer.Length + est];
- Buffer.BlockCopy (buffer.Buffer, 0, newBuffer, 0, buffer.Offset);
- buffer = new BufferOffsetSize (newBuffer, buffer.Offset, newBuffer.Length - buffer.Offset, false);
- }
- state = ReadState.None;
- position = 0;
- }
- WebConnection.Debug ($"{ME} INIT READ ASYNC LOOP DONE: {buffer.Offset} {buffer.Size}");
- try {
- Operation.ThrowIfDisposed (cancellationToken);
- await Initialize (buffer, cancellationToken).ConfigureAwait (false);
- } catch (Exception e) {
- throw GetReadException (WebExceptionStatus.ReceiveFailure, e, "ReadDoneAsync6");
- }
- }
- bool GetResponse (BufferOffsetSize buffer, ref int pos, ref ReadState state)
- {
- string line = null;
- bool lineok = false;
- bool isContinue = false;
- bool emptyFirstLine = false;
- do {
- if (state == ReadState.Aborted)
- throw GetReadException (WebExceptionStatus.RequestCanceled, null, "GetResponse");
- if (state == ReadState.None) {
- lineok = WebConnection.ReadLine (buffer.Buffer, ref pos, buffer.Offset, ref line);
- if (!lineok)
- return false;
- if (line == null) {
- emptyFirstLine = true;
- continue;
- }
- emptyFirstLine = false;
- state = ReadState.Status;
- string[] parts = line.Split (' ');
- if (parts.Length < 2)
- throw GetReadException (WebExceptionStatus.ServerProtocolViolation, null, "GetResponse");
- if (String.Compare (parts[0], "HTTP/1.1", true) == 0) {
- Version = HttpVersion.Version11;
- ServicePoint.SetVersion (HttpVersion.Version11);
- } else {
- Version = HttpVersion.Version10;
- ServicePoint.SetVersion (HttpVersion.Version10);
- }
- StatusCode = (HttpStatusCode)UInt32.Parse (parts[1]);
- if (parts.Length >= 3)
- StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
- else
- StatusDescription = string.Empty;
- if (pos >= buffer.Size)
- return true;
- }
- emptyFirstLine = false;
- if (state == ReadState.Status) {
- state = ReadState.Headers;
- Headers = new WebHeaderCollection ();
- var headerList = new List<string> ();
- bool finished = false;
- while (!finished) {
- if (WebConnection.ReadLine (buffer.Buffer, ref pos, buffer.Offset, ref line) == false)
- break;
- if (line == null) {
- // Empty line: end of headers
- finished = true;
- continue;
- }
- if (line.Length > 0 && (line[0] == ' ' || line[0] == '\t')) {
- int count = headerList.Count - 1;
- if (count < 0)
- break;
- string prev = headerList[count] + line;
- headerList[count] = prev;
- } else {
- headerList.Add (line);
- }
- }
- if (!finished)
- return false;
- // .NET uses ParseHeaders or ParseHeadersStrict which is much better
- foreach (string s in headerList) {
- int pos_s = s.IndexOf (':');
- if (pos_s == -1)
- throw new ArgumentException ("no colon found", "header");
- var header = s.Substring (0, pos_s);
- var value = s.Substring (pos_s + 1).Trim ();
- if (WebHeaderCollection.AllowMultiValues (header)) {
- Headers.AddInternal (header, value);
- } else {
- Headers.SetInternal (header, value);
- }
- }
- if (StatusCode == HttpStatusCode.Continue) {
- ServicePoint.SendContinue = true;
- if (pos >= buffer.Offset)
- return true;
- if (Request.ExpectContinue) {
- Request.DoContinueDelegate ((int)StatusCode, Headers);
- // Prevent double calls when getting the
- // headers in several packets.
- Request.ExpectContinue = false;
- }
- state = ReadState.None;
- isContinue = true;
- } else {
- state = ReadState.Content;
- return true;
- }
- }
- } while (emptyFirstLine || isContinue);
- throw GetReadException (WebExceptionStatus.ServerProtocolViolation, null, "GetResponse");
- }
- }
- }
|