WebResponseStream.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. //
  2. // WebResponseStream.cs
  3. //
  4. // Author:
  5. // Martin Baulig <[email protected]>
  6. //
  7. // Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.com)
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. using System.IO;
  27. using System.Text;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. using System.Threading;
  31. using System.Threading.Tasks;
  32. using System.Runtime.ExceptionServices;
  33. using System.Net.Sockets;
  34. namespace System.Net
  35. {
  36. class WebResponseStream : WebConnectionStream
  37. {
  38. BufferOffsetSize readBuffer;
  39. long contentLength;
  40. long totalRead;
  41. bool nextReadCalled;
  42. int stream_length; // -1 when CL not present
  43. TaskCompletionSource<int> readTcs;
  44. object locker = new object ();
  45. int nestedRead;
  46. bool read_eof;
  47. public WebRequestStream RequestStream {
  48. get;
  49. }
  50. public WebHeaderCollection Headers {
  51. get;
  52. private set;
  53. }
  54. public HttpStatusCode StatusCode {
  55. get;
  56. private set;
  57. }
  58. public string StatusDescription {
  59. get;
  60. private set;
  61. }
  62. public Version Version {
  63. get;
  64. private set;
  65. }
  66. public bool KeepAlive {
  67. get;
  68. private set;
  69. }
  70. internal readonly string ME;
  71. public WebResponseStream (WebRequestStream request)
  72. : base (request.Connection, request.Operation, request.InnerStream)
  73. {
  74. RequestStream = request;
  75. request.InnerStream.ReadTimeout = ReadTimeout;
  76. #if MONO_WEB_DEBUG
  77. ME = $"WRP(Cnc={Connection.ID}, Op={Operation.ID})";
  78. #endif
  79. }
  80. public override long Length {
  81. get {
  82. return stream_length;
  83. }
  84. }
  85. public override bool CanRead => true;
  86. public override bool CanWrite => false;
  87. protected bool ChunkedRead {
  88. get;
  89. private set;
  90. }
  91. protected MonoChunkStream ChunkStream {
  92. get;
  93. private set;
  94. }
  95. public override async Task<int> ReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
  96. {
  97. WebConnection.Debug ($"{ME} READ ASYNC");
  98. cancellationToken.ThrowIfCancellationRequested ();
  99. if (buffer == null)
  100. throw new ArgumentNullException (nameof (buffer));
  101. int length = buffer.Length;
  102. if (offset < 0 || length < offset)
  103. throw new ArgumentOutOfRangeException (nameof (offset));
  104. if (size < 0 || (length - offset) < size)
  105. throw new ArgumentOutOfRangeException (nameof (size));
  106. if (Interlocked.CompareExchange (ref nestedRead, 1, 0) != 0)
  107. throw new InvalidOperationException ("Invalid nested call.");
  108. var myReadTcs = new TaskCompletionSource<int> ();
  109. while (!cancellationToken.IsCancellationRequested) {
  110. /*
  111. * 'readTcs' is set by ReadAllAsync().
  112. */
  113. var oldReadTcs = Interlocked.CompareExchange (ref readTcs, myReadTcs, null);
  114. WebConnection.Debug ($"{ME} READ ASYNC #1: {oldReadTcs != null}");
  115. if (oldReadTcs == null)
  116. break;
  117. await oldReadTcs.Task.ConfigureAwait (false);
  118. }
  119. WebConnection.Debug ($"{ME} READ ASYNC #2: {totalRead} {contentLength}");
  120. int oldBytes = 0, nbytes = 0;
  121. Exception throwMe = null;
  122. try {
  123. // FIXME: NetworkStream.ReadAsync() does not support cancellation.
  124. (oldBytes, nbytes) = await HttpWebRequest.RunWithTimeout (
  125. ct => ProcessRead (buffer, offset, size, ct),
  126. ReadTimeout, () => {
  127. Operation.Abort ();
  128. InnerStream.Dispose ();
  129. }).ConfigureAwait (false);
  130. } catch (Exception e) {
  131. throwMe = GetReadException (WebExceptionStatus.ReceiveFailure, e, "ReadAsync");
  132. }
  133. WebConnection.Debug ($"{ME} READ ASYNC #3: {totalRead} {contentLength} - {oldBytes} {nbytes} {throwMe?.Message}");
  134. if (throwMe != null) {
  135. lock (locker) {
  136. myReadTcs.TrySetException (throwMe);
  137. readTcs = null;
  138. nestedRead = 0;
  139. }
  140. closed = true;
  141. Operation.CompleteResponseRead (false, throwMe);
  142. throw throwMe;
  143. }
  144. lock (locker) {
  145. readTcs.TrySetResult (oldBytes + nbytes);
  146. readTcs = null;
  147. nestedRead = 0;
  148. }
  149. if (totalRead >= contentLength && !nextReadCalled) {
  150. WebConnection.Debug ($"{ME} READ ASYNC - READ COMPLETE: {oldBytes} {nbytes} - {totalRead} {contentLength} {nextReadCalled}");
  151. if (!nextReadCalled) {
  152. nextReadCalled = true;
  153. Operation.CompleteResponseRead (true);
  154. }
  155. }
  156. return oldBytes + nbytes;
  157. }
  158. async Task<(int, int)> ProcessRead (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
  159. {
  160. WebConnection.Debug ($"{ME} PROCESS READ: {totalRead} {contentLength}");
  161. cancellationToken.ThrowIfCancellationRequested ();
  162. if (totalRead >= contentLength) {
  163. read_eof = true;
  164. contentLength = totalRead;
  165. return (0, 0);
  166. }
  167. int oldBytes = 0;
  168. int remaining = readBuffer?.Size ?? 0;
  169. if (remaining > 0) {
  170. int copy = (remaining > size) ? size : remaining;
  171. Buffer.BlockCopy (readBuffer.Buffer, readBuffer.Offset, buffer, offset, copy);
  172. readBuffer.Offset += copy;
  173. readBuffer.Size -= copy;
  174. offset += copy;
  175. size -= copy;
  176. totalRead += copy;
  177. if (totalRead >= contentLength) {
  178. contentLength = totalRead;
  179. read_eof = true;
  180. }
  181. if (size == 0 || totalRead >= contentLength)
  182. return (0, copy);
  183. oldBytes = copy;
  184. }
  185. if (contentLength != Int64.MaxValue && contentLength - totalRead < size)
  186. size = (int)(contentLength - totalRead);
  187. WebConnection.Debug ($"{ME} PROCESS READ #1: {oldBytes} {size} {read_eof}");
  188. if (read_eof) {
  189. contentLength = totalRead;
  190. return (oldBytes, 0);
  191. }
  192. var ret = await InnerReadAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
  193. if (ret <= 0) {
  194. read_eof = true;
  195. contentLength = totalRead;
  196. return (oldBytes, 0);
  197. }
  198. totalRead += ret;
  199. return (oldBytes, ret);
  200. }
  201. internal async Task<int> InnerReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
  202. {
  203. WebConnection.Debug ($"{ME} INNER READ ASYNC");
  204. Operation.ThrowIfDisposed (cancellationToken);
  205. int nbytes = 0;
  206. bool done = false;
  207. if (!ChunkedRead || (!ChunkStream.DataAvailable && ChunkStream.WantMore)) {
  208. nbytes = await InnerStream.ReadAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
  209. WebConnection.Debug ($"{ME} INNER READ ASYNC #1: {nbytes} {ChunkedRead}");
  210. if (!ChunkedRead)
  211. return nbytes;
  212. done = nbytes == 0;
  213. }
  214. try {
  215. ChunkStream.WriteAndReadBack (buffer, offset, size, ref nbytes);
  216. WebConnection.Debug ($"{ME} INNER READ ASYNC #1: {done} {nbytes} {ChunkStream.WantMore}");
  217. if (!done && nbytes == 0 && ChunkStream.WantMore)
  218. nbytes = await EnsureReadAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
  219. } catch (Exception e) {
  220. if (e is WebException || e is OperationCanceledException)
  221. throw;
  222. throw new WebException ("Invalid chunked data.", e, WebExceptionStatus.ServerProtocolViolation, null);
  223. }
  224. if ((done || nbytes == 0) && ChunkStream.ChunkLeft != 0) {
  225. // HandleError (WebExceptionStatus.ReceiveFailure, null, "chunked EndRead");
  226. throw new WebException ("Read error", null, WebExceptionStatus.ReceiveFailure, null);
  227. }
  228. return nbytes;
  229. }
  230. async Task<int> EnsureReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
  231. {
  232. byte[] morebytes = null;
  233. int nbytes = 0;
  234. while (nbytes == 0 && ChunkStream.WantMore && !cancellationToken.IsCancellationRequested) {
  235. int localsize = ChunkStream.ChunkLeft;
  236. if (localsize <= 0) // not read chunk size yet
  237. localsize = 1024;
  238. else if (localsize > 16384)
  239. localsize = 16384;
  240. if (morebytes == null || morebytes.Length < localsize)
  241. morebytes = new byte[localsize];
  242. int nread = await InnerStream.ReadAsync (morebytes, 0, localsize, cancellationToken).ConfigureAwait (false);
  243. if (nread <= 0)
  244. return 0; // Error
  245. ChunkStream.Write (morebytes, 0, nread);
  246. nbytes += ChunkStream.Read (buffer, offset + nbytes, size - nbytes);
  247. }
  248. return nbytes;
  249. }
  250. bool CheckAuthHeader (string headerName)
  251. {
  252. var authHeader = Headers[headerName];
  253. return (authHeader != null && authHeader.IndexOf ("NTLM", StringComparison.Ordinal) != -1);
  254. }
  255. bool IsNtlmAuth ()
  256. {
  257. bool isProxy = (Request.Proxy != null && !Request.Proxy.IsBypassed (Request.Address));
  258. if (isProxy && CheckAuthHeader ("Proxy-Authenticate"))
  259. return true;
  260. return CheckAuthHeader ("WWW-Authenticate");
  261. }
  262. bool ExpectContent {
  263. get {
  264. if (Request.Method == "HEAD")
  265. return false;
  266. return ((int)StatusCode >= 200 && (int)StatusCode != 204 && (int)StatusCode != 304);
  267. }
  268. }
  269. async Task Initialize (BufferOffsetSize buffer, CancellationToken cancellationToken)
  270. {
  271. WebConnection.Debug ($"{ME} INIT: status={(int)StatusCode} bos={buffer.Offset}/{buffer.Size}");
  272. string contentType = Headers["Transfer-Encoding"];
  273. bool chunkedRead = (contentType != null && contentType.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
  274. string clength = Headers["Content-Length"];
  275. if (!chunkedRead && !string.IsNullOrEmpty (clength)) {
  276. if (!long.TryParse (clength, out contentLength))
  277. contentLength = Int64.MaxValue;
  278. } else {
  279. contentLength = Int64.MaxValue;
  280. }
  281. if (Version == HttpVersion.Version11 && RequestStream.KeepAlive) {
  282. KeepAlive = true;
  283. var cncHeader = Headers[ServicePoint.UsesProxy ? "Proxy-Connection" : "Connection"];
  284. if (cncHeader != null) {
  285. cncHeader = cncHeader.ToLower ();
  286. KeepAlive = cncHeader.IndexOf ("keep-alive", StringComparison.Ordinal) != -1;
  287. if (cncHeader.IndexOf ("close", StringComparison.Ordinal) != -1)
  288. KeepAlive = false;
  289. }
  290. }
  291. // Negative numbers?
  292. if (!Int32.TryParse (clength, out stream_length))
  293. stream_length = -1;
  294. string me = "WebResponseStream.Initialize()";
  295. string tencoding = null;
  296. if (ExpectContent)
  297. tencoding = Headers["Transfer-Encoding"];
  298. ChunkedRead = (tencoding != null && tencoding.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
  299. if (!ChunkedRead) {
  300. readBuffer = buffer;
  301. try {
  302. if (contentLength > 0 && readBuffer.Size >= contentLength) {
  303. if (!IsNtlmAuth ())
  304. await ReadAllAsync (false, cancellationToken).ConfigureAwait (false);
  305. }
  306. } catch (Exception e) {
  307. throw GetReadException (WebExceptionStatus.ReceiveFailure, e, me);
  308. }
  309. } else if (ChunkStream == null) {
  310. try {
  311. ChunkStream = new MonoChunkStream (buffer.Buffer, buffer.Offset, buffer.Offset + buffer.Size, Headers);
  312. } catch (Exception e) {
  313. throw GetReadException (WebExceptionStatus.ServerProtocolViolation, e, me);
  314. }
  315. } else {
  316. ChunkStream.ResetBuffer ();
  317. try {
  318. ChunkStream.Write (buffer.Buffer, buffer.Offset, buffer.Size);
  319. } catch (Exception e) {
  320. throw GetReadException (WebExceptionStatus.ServerProtocolViolation, e, me);
  321. }
  322. }
  323. WebConnection.Debug ($"{ME} INIT #1: - {ExpectContent} {closed} {nextReadCalled}");
  324. if (!ExpectContent) {
  325. if (!closed && !nextReadCalled) {
  326. if (contentLength == Int64.MaxValue)
  327. contentLength = 0;
  328. nextReadCalled = true;
  329. }
  330. Operation.CompleteResponseRead (true);
  331. }
  332. }
  333. internal async Task ReadAllAsync (bool resending, CancellationToken cancellationToken)
  334. {
  335. WebConnection.Debug ($"{ME} READ ALL ASYNC: resending={resending} eof={read_eof} total={totalRead} " +
  336. "length={contentLength} nextReadCalled={nextReadCalled}");
  337. if (read_eof || totalRead >= contentLength || nextReadCalled) {
  338. if (!nextReadCalled) {
  339. nextReadCalled = true;
  340. Operation.CompleteResponseRead (true);
  341. }
  342. return;
  343. }
  344. var timeoutTask = Task.Delay (ReadTimeout);
  345. var myReadTcs = new TaskCompletionSource<int> ();
  346. while (true) {
  347. /*
  348. * 'readTcs' is set by ReadAsync().
  349. */
  350. cancellationToken.ThrowIfCancellationRequested ();
  351. var oldReadTcs = Interlocked.CompareExchange (ref readTcs, myReadTcs, null);
  352. if (oldReadTcs == null)
  353. break;
  354. // ReadAsync() is in progress.
  355. var anyTask = await Task.WhenAny (oldReadTcs.Task, timeoutTask).ConfigureAwait (false);
  356. if (anyTask == timeoutTask)
  357. throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
  358. }
  359. WebConnection.Debug ($"{ME} READ ALL ASYNC #1");
  360. cancellationToken.ThrowIfCancellationRequested ();
  361. try {
  362. if (totalRead >= contentLength)
  363. return;
  364. byte[] b = null;
  365. int new_size;
  366. if (contentLength == Int64.MaxValue && !ChunkedRead) {
  367. WebConnection.Debug ($"{ME} READ ALL ASYNC - NEITHER LENGTH NOR CHUNKED");
  368. /*
  369. * This is a violation of the HTTP Spec - the server neither send a
  370. * "Content-Length:" nor a "Transfer-Encoding: chunked" header.
  371. *
  372. * When we're redirecting or resending for NTLM, then we can simply close
  373. * the connection here.
  374. *
  375. * However, if it's the final reply, then we need to try our best to read it.
  376. */
  377. if (resending) {
  378. Close ();
  379. return;
  380. }
  381. KeepAlive = false;
  382. }
  383. if (contentLength == Int64.MaxValue) {
  384. MemoryStream ms = new MemoryStream ();
  385. BufferOffsetSize buffer = null;
  386. if (readBuffer != null && readBuffer.Size > 0) {
  387. ms.Write (readBuffer.Buffer, readBuffer.Offset, readBuffer.Size);
  388. readBuffer.Offset = 0;
  389. readBuffer.Size = readBuffer.Buffer.Length;
  390. if (readBuffer.Buffer.Length >= 8192)
  391. buffer = readBuffer;
  392. }
  393. if (buffer == null)
  394. buffer = new BufferOffsetSize (new byte[8192], false);
  395. int read;
  396. while ((read = await InnerReadAsync (buffer.Buffer, buffer.Offset, buffer.Size, cancellationToken)) != 0)
  397. ms.Write (buffer.Buffer, buffer.Offset, read);
  398. new_size = (int)ms.Length;
  399. contentLength = new_size;
  400. readBuffer = new BufferOffsetSize (ms.GetBuffer (), 0, new_size, false);
  401. } else {
  402. new_size = (int)(contentLength - totalRead);
  403. b = new byte[new_size];
  404. int readSize = 0;
  405. if (readBuffer != null && readBuffer.Size > 0) {
  406. readSize = readBuffer.Size;
  407. if (readSize > new_size)
  408. readSize = new_size;
  409. Buffer.BlockCopy (readBuffer.Buffer, readBuffer.Offset, b, 0, readSize);
  410. }
  411. int remaining = new_size - readSize;
  412. int r = -1;
  413. while (remaining > 0 && r != 0) {
  414. r = await InnerReadAsync (b, readSize, remaining, cancellationToken);
  415. remaining -= r;
  416. readSize += r;
  417. }
  418. }
  419. readBuffer = new BufferOffsetSize (b, 0, new_size, false);
  420. totalRead = 0;
  421. nextReadCalled = true;
  422. myReadTcs.TrySetResult (new_size);
  423. } catch (Exception ex) {
  424. WebConnection.Debug ($"{ME} READ ALL ASYNC EX: {ex.Message}");
  425. myReadTcs.TrySetException (ex);
  426. throw;
  427. } finally {
  428. WebConnection.Debug ($"{ME} READ ALL ASYNC #2");
  429. readTcs = null;
  430. }
  431. Operation.CompleteResponseRead (true);
  432. }
  433. public override Task WriteAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
  434. {
  435. return Task.FromException (new NotSupportedException (SR.net_readonlystream));
  436. }
  437. protected override void Close_internal (ref bool disposed)
  438. {
  439. WebConnection.Debug ($"{ME} CLOSE: {disposed} {closed} {nextReadCalled}");
  440. if (!closed && !nextReadCalled) {
  441. nextReadCalled = true;
  442. if (totalRead >= contentLength) {
  443. disposed = true;
  444. Operation.CompleteResponseRead (true);
  445. } else {
  446. // If we have not read all the contents
  447. closed = true;
  448. disposed = true;
  449. Operation.CompleteResponseRead (false);
  450. }
  451. }
  452. }
  453. WebException GetReadException (WebExceptionStatus status, Exception error, string where)
  454. {
  455. error = GetException (error);
  456. string msg = $"Error getting response stream ({where}): {status}";
  457. if (error == null)
  458. return new WebException ($"Error getting response stream ({where}): {status}", status);
  459. if (error is WebException wexc)
  460. return wexc;
  461. if (Operation.Aborted || error is OperationCanceledException || error is ObjectDisposedException)
  462. return HttpWebRequest.CreateRequestAbortedException ();
  463. return new WebException ($"Error getting response stream ({where}): {status} {error.Message}", status,
  464. WebExceptionInternalStatus.RequestFatal, error);
  465. }
  466. internal async Task InitReadAsync (CancellationToken cancellationToken)
  467. {
  468. WebConnection.Debug ($"{ME} INIT READ ASYNC");
  469. var buffer = new BufferOffsetSize (new byte[4096], false);
  470. var state = ReadState.None;
  471. int position = 0;
  472. while (true) {
  473. Operation.ThrowIfClosedOrDisposed (cancellationToken);
  474. WebConnection.Debug ($"{ME} INIT READ ASYNC LOOP: {state} {position} - {buffer.Offset}/{buffer.Size}");
  475. var nread = await InnerStream.ReadAsync (
  476. buffer.Buffer, buffer.Offset, buffer.Size, cancellationToken).ConfigureAwait (false);
  477. WebConnection.Debug ($"{ME} INIT READ ASYNC LOOP #1: {state} {position} - {buffer.Offset}/{buffer.Size} - {nread}");
  478. if (nread == 0)
  479. throw GetReadException (WebExceptionStatus.ReceiveFailure, null, "ReadDoneAsync2");
  480. if (nread < 0)
  481. throw GetReadException (WebExceptionStatus.ServerProtocolViolation, null, "ReadDoneAsync3");
  482. buffer.Offset += nread;
  483. buffer.Size -= nread;
  484. if (state == ReadState.None) {
  485. try {
  486. var oldPos = position;
  487. if (!GetResponse (buffer, ref position, ref state))
  488. position = oldPos;
  489. } catch (Exception e) {
  490. WebConnection.Debug ($"{ME} INIT READ ASYNC FAILED: {e.Message}\n{e}");
  491. throw GetReadException (WebExceptionStatus.ServerProtocolViolation, e, "ReadDoneAsync4");
  492. }
  493. }
  494. if (state == ReadState.Aborted)
  495. throw GetReadException (WebExceptionStatus.RequestCanceled, null, "ReadDoneAsync5");
  496. if (state == ReadState.Content) {
  497. buffer.Size = buffer.Offset - position;
  498. buffer.Offset = position;
  499. break;
  500. }
  501. int est = nread * 2;
  502. if (est > buffer.Size) {
  503. var newBuffer = new byte [buffer.Buffer.Length + est];
  504. Buffer.BlockCopy (buffer.Buffer, 0, newBuffer, 0, buffer.Offset);
  505. buffer = new BufferOffsetSize (newBuffer, buffer.Offset, newBuffer.Length - buffer.Offset, false);
  506. }
  507. state = ReadState.None;
  508. position = 0;
  509. }
  510. WebConnection.Debug ($"{ME} INIT READ ASYNC LOOP DONE: {buffer.Offset} {buffer.Size}");
  511. try {
  512. Operation.ThrowIfDisposed (cancellationToken);
  513. await Initialize (buffer, cancellationToken).ConfigureAwait (false);
  514. } catch (Exception e) {
  515. throw GetReadException (WebExceptionStatus.ReceiveFailure, e, "ReadDoneAsync6");
  516. }
  517. }
  518. bool GetResponse (BufferOffsetSize buffer, ref int pos, ref ReadState state)
  519. {
  520. string line = null;
  521. bool lineok = false;
  522. bool isContinue = false;
  523. bool emptyFirstLine = false;
  524. do {
  525. if (state == ReadState.Aborted)
  526. throw GetReadException (WebExceptionStatus.RequestCanceled, null, "GetResponse");
  527. if (state == ReadState.None) {
  528. lineok = WebConnection.ReadLine (buffer.Buffer, ref pos, buffer.Offset, ref line);
  529. if (!lineok)
  530. return false;
  531. if (line == null) {
  532. emptyFirstLine = true;
  533. continue;
  534. }
  535. emptyFirstLine = false;
  536. state = ReadState.Status;
  537. string[] parts = line.Split (' ');
  538. if (parts.Length < 2)
  539. throw GetReadException (WebExceptionStatus.ServerProtocolViolation, null, "GetResponse");
  540. if (String.Compare (parts[0], "HTTP/1.1", true) == 0) {
  541. Version = HttpVersion.Version11;
  542. ServicePoint.SetVersion (HttpVersion.Version11);
  543. } else {
  544. Version = HttpVersion.Version10;
  545. ServicePoint.SetVersion (HttpVersion.Version10);
  546. }
  547. StatusCode = (HttpStatusCode)UInt32.Parse (parts[1]);
  548. if (parts.Length >= 3)
  549. StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
  550. else
  551. StatusDescription = string.Empty;
  552. if (pos >= buffer.Size)
  553. return true;
  554. }
  555. emptyFirstLine = false;
  556. if (state == ReadState.Status) {
  557. state = ReadState.Headers;
  558. Headers = new WebHeaderCollection ();
  559. var headerList = new List<string> ();
  560. bool finished = false;
  561. while (!finished) {
  562. if (WebConnection.ReadLine (buffer.Buffer, ref pos, buffer.Offset, ref line) == false)
  563. break;
  564. if (line == null) {
  565. // Empty line: end of headers
  566. finished = true;
  567. continue;
  568. }
  569. if (line.Length > 0 && (line[0] == ' ' || line[0] == '\t')) {
  570. int count = headerList.Count - 1;
  571. if (count < 0)
  572. break;
  573. string prev = headerList[count] + line;
  574. headerList[count] = prev;
  575. } else {
  576. headerList.Add (line);
  577. }
  578. }
  579. if (!finished)
  580. return false;
  581. // .NET uses ParseHeaders or ParseHeadersStrict which is much better
  582. foreach (string s in headerList) {
  583. int pos_s = s.IndexOf (':');
  584. if (pos_s == -1)
  585. throw new ArgumentException ("no colon found", "header");
  586. var header = s.Substring (0, pos_s);
  587. var value = s.Substring (pos_s + 1).Trim ();
  588. if (WebHeaderCollection.AllowMultiValues (header)) {
  589. Headers.AddInternal (header, value);
  590. } else {
  591. Headers.SetInternal (header, value);
  592. }
  593. }
  594. if (StatusCode == HttpStatusCode.Continue) {
  595. ServicePoint.SendContinue = true;
  596. if (pos >= buffer.Offset)
  597. return true;
  598. if (Request.ExpectContinue) {
  599. Request.DoContinueDelegate ((int)StatusCode, Headers);
  600. // Prevent double calls when getting the
  601. // headers in several packets.
  602. Request.ExpectContinue = false;
  603. }
  604. state = ReadState.None;
  605. isContinue = true;
  606. } else {
  607. state = ReadState.Content;
  608. return true;
  609. }
  610. }
  611. } while (emptyFirstLine || isContinue);
  612. throw GetReadException (WebExceptionStatus.ServerProtocolViolation, null, "GetResponse");
  613. }
  614. }
  615. }