Selaa lähdekoodia

New PR for the new web stack (#6125)

Martin Baulig 8 vuotta sitten
vanhempi
sitoutus
cd09ddbc55

+ 1 - 1
mcs/class/System.Net.Http/System.Net.Http/HttpClientHandler.cs

@@ -387,7 +387,7 @@ namespace System.Net.Http
 							}
 						}
 
-						wrequest.ResendContentFactory = content.CopyTo;
+						wrequest.ResendContentFactory = content.CopyToAsync;
 
 						using (var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false)) {
 							await request.Content.CopyToAsync (stream).ConfigureAwait (false);

+ 15 - 8
mcs/class/System/Mono.Net.Security/MonoTlsStream.cs

@@ -52,7 +52,7 @@ namespace Mono.Net.Security
 {
 	class MonoTlsStream
 	{
-#if SECURITY_DEP		
+#if SECURITY_DEP
 		readonly MonoTlsProvider provider;
 		readonly NetworkStream networkStream;		
 		readonly HttpWebRequest request;
@@ -99,9 +99,11 @@ namespace Mono.Net.Security
 #endif
 		}
 
-		internal Stream CreateStream (byte[] buffer)
+		internal async Task<Stream> CreateStream (WebConnectionTunnel tunnel, CancellationToken cancellationToken)
 		{
 #if SECURITY_DEP
+			var socket = networkStream.InternalSocket;
+			WebConnection.Debug ($"MONO TLS STREAM CREATE STREAM: {socket.ID}");
 			sslStream = provider.CreateSslStream (networkStream, false, settings);
 
 			try {
@@ -112,16 +114,21 @@ namespace Mono.Net.Security
 						host = host.Substring (0, pos);
 				}
 
-				sslStream.AuthenticateAsClient (
+				await sslStream.AuthenticateAsClientAsync (
 					host, request.ClientCertificates,
 					(SslProtocols)ServicePointManager.SecurityProtocol,
-					ServicePointManager.CheckCertificateRevocationList);
+					ServicePointManager.CheckCertificateRevocationList).ConfigureAwait (false);
 
 				status = WebExceptionStatus.Success;
-			} catch {
-				status = WebExceptionStatus.SecureChannelFailure;
+			} catch (Exception ex) {
+				WebConnection.Debug ($"MONO TLS STREAM ERROR: {socket.ID} {socket.CleanedUp} {ex.Message}");
+				if (socket.CleanedUp)
+					status = WebExceptionStatus.RequestCanceled;
+				else
+					status = WebExceptionStatus.SecureChannelFailure;
 				throw;
 			} finally {
+				WebConnection.Debug ($"MONO TLS STREAM CREATE STREAM DONE: {socket.ID} {socket.CleanedUp}");
 				if (CertificateValidationFailed)
 					status = WebExceptionStatus.TrustFailure;
 
@@ -134,8 +141,8 @@ namespace Mono.Net.Security
 			}
 
 			try {
-				if (buffer != null)
-					sslStream.Write (buffer, 0, buffer.Length);
+				if (tunnel?.Data != null)
+					await sslStream.WriteAsync (tunnel.Data, 0, tunnel.Data.Length, cancellationToken).ConfigureAwait (false);
 			} catch {
 				status = WebExceptionStatus.SendFailure;
 				sslStream = null;

+ 2 - 0
mcs/class/System/ReferenceSources/SR2.cs

@@ -15,4 +15,6 @@ partial class SR
 	public const string net_log_set_socketoption_reuseport_default_on = "net_log_set_socketoption_reuseport_default_on";
 	public const string net_log_set_socketoption_reuseport_not_supported = "net_log_set_socketoption_reuseport_not_supported";
 	public const string net_log_set_socketoption_reuseport = "net_log_set_socketoption_reuseport";
+
+	public const string net_reqaborted = "The request was aborted: The request was canceled.";
 }

+ 9 - 2
mcs/class/System/System.Net.Sockets/Socket.cs

@@ -47,7 +47,7 @@ using System.Text;
 using System.Timers;
 using System.Net.NetworkInformation;
 
-namespace System.Net.Sockets 
+namespace System.Net.Sockets
 {
 	public partial class Socket : IDisposable
 	{
@@ -91,7 +91,14 @@ namespace System.Net.Sockets
 		int m_IntCleanedUp;
 		internal bool connect_in_progress;
 
-#region Constructors
+#if MONO_WEB_DEBUG
+		static int nextId;
+		internal readonly int ID = ++nextId;
+#else
+		internal readonly int ID;
+#endif
+
+		#region Constructors
 
 
 		public Socket (SocketInformation socketInformation)

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 411 - 262
mcs/class/System/System.Net/HttpWebRequest.cs


+ 27 - 27
mcs/class/System/System.Net/HttpWebResponse.cs

@@ -39,6 +39,8 @@ using System.IO;
 using System.IO.Compression;
 using System.Net.Sockets;
 using System.Runtime.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
 using System.Text;
 
 namespace System.Net 
@@ -60,16 +62,28 @@ namespace System.Net
 		Stream stream;
 		
 		// Constructors
+
+		internal HttpWebResponse (Uri uri, string method, HttpStatusCode status, WebHeaderCollection headers)
+		{
+			this.uri = uri;
+			this.method = method;
+			this.statusCode = status;
+			this.statusDescription = HttpStatusDescription.Get (status);
+			this.webHeaders = headers;
+			version = HttpVersion.Version10;
+			contentLength = -1;
+		}
 		
-		internal HttpWebResponse (Uri uri, string method, WebConnectionData data, CookieContainer container)
+		internal HttpWebResponse (Uri uri, string method, WebResponseStream stream, CookieContainer container)
 		{
 			this.uri = uri;
 			this.method = method;
-			webHeaders = data.Headers;
-			version = data.Version;
-			statusCode = (HttpStatusCode) data.StatusCode;
-			statusDescription = data.StatusDescription;
-			stream = data.stream;
+			this.stream = stream;
+
+			webHeaders = stream.Headers ?? new WebHeaderCollection ();
+			version = stream.Version;
+			statusCode = stream.StatusCode;
+			statusDescription = stream.StatusDescription ?? HttpStatusDescription.Get (statusCode);
 			contentLength = -1;
 
 			try {
@@ -86,12 +100,12 @@ namespace System.Net
 			}
 
 			string content_encoding = webHeaders ["Content-Encoding"];
-			if (content_encoding == "gzip" && (data.request.AutomaticDecompression & DecompressionMethods.GZip) != 0) {
-				stream = new GZipStream (stream, CompressionMode.Decompress);
+			if (content_encoding == "gzip" && (stream.Request.AutomaticDecompression & DecompressionMethods.GZip) != 0) {
+				this.stream = new GZipStream (stream, CompressionMode.Decompress);
 				webHeaders.Remove (HttpRequestHeader.ContentEncoding);
 			}
-			else if (content_encoding == "deflate" && (data.request.AutomaticDecompression & DecompressionMethods.Deflate) != 0) {
-				stream = new DeflateStream (stream, CompressionMode.Decompress);
+			else if (content_encoding == "deflate" && (stream.Request.AutomaticDecompression & DecompressionMethods.Deflate) != 0) {
+				this.stream = new DeflateStream (stream, CompressionMode.Decompress);
 				webHeaders.Remove (HttpRequestHeader.ContentEncoding);
 			}
 		}
@@ -263,17 +277,6 @@ namespace System.Net
 			return (value != null) ? value : "";
 		}
 
-		internal void ReadAll ()
-		{
-			WebConnectionStream wce = stream as WebConnectionStream;
-			if (wce == null)
-				return;
-				
-			try {
-				wce.ReadAll ();
-			} catch {}
-		}
-
 		public override Stream GetResponseStream ()
 		{
 			CheckDisposed ();
@@ -310,12 +313,9 @@ namespace System.Net
 
 		public override void Close ()
 		{
-			if (stream != null) {
-				Stream st = stream;
-				stream = null;
-				if (st != null)
-					st.Close ();
-			}
+			var st = Interlocked.Exchange (ref stream, null);
+			if (st != null)
+				st.Close ();
 		}
 		
 		void IDisposable.Dispose ()

+ 0 - 52
mcs/class/System/System.Net/IWebConnectionState.cs

@@ -1,52 +0,0 @@
-//
-// IWebConnectionState.cs
-//
-// Author:
-//       Martin Baulig <[email protected]>
-//
-// Copyright (c) 2014 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;
-using System.Threading;
-
-namespace System.Net
-{
-	interface IWebConnectionState {
-		WebConnectionGroup Group {
-			get;
-		}
-
-		ServicePoint ServicePoint {
-			get;
-		}
-
-		bool Busy {
-			get;
-		}
-
-		DateTime IdleSince {
-			get;
-		}
-
-		bool TrySetBusy ();
-
-		void SetIdle ();
-	}
-}

+ 48 - 191
mcs/class/System/System.Net/ServicePoint.cs

@@ -38,20 +38,15 @@ using System.Net.Sockets;
 using System.Security.Cryptography.X509Certificates;
 using System.Threading;
 
-namespace System.Net 
+namespace System.Net
 {
 	public class ServicePoint
 	{
 		readonly Uri uri;
-		int connectionLimit;
-		int maxIdleTime;
-		int currentConnections;
-		DateTime idleSince;
 		DateTime lastDnsResolve;
 		Version protocolVersion;
 		IPHostEntry host;
 		bool usesProxy;
-		Dictionary<string,WebConnectionGroup> groups;
 		bool sendContinue = true;
 		bool useConnect;
 		object hostE = new object ();
@@ -60,21 +55,22 @@ namespace System.Net
 		bool tcp_keepalive;
 		int tcp_keepalive_time;
 		int tcp_keepalive_interval;
-		Timer idleTimer;
 
 		// Constructors
 
 		internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
 		{
-			this.uri = uri;  
-			this.connectionLimit = connectionLimit;
-			this.maxIdleTime = maxIdleTime;	
-			this.currentConnections = 0;
-			this.idleSince = DateTime.UtcNow;
+			this.uri = uri;
+
+			Scheduler = new ServicePointScheduler (this, connectionLimit, maxIdleTime);
+		}
+
+		internal ServicePointScheduler Scheduler {
+			get;
 		}
-		
+
 		// Properties
-		
+
 		public Uri Address {
 			get { return uri; }
 		}
@@ -84,15 +80,13 @@ namespace System.Net
 			return new NotImplementedException ();
 		}
 
-		public BindIPEndPoint BindIPEndPointDelegate
-		{
+		public BindIPEndPoint BindIPEndPointDelegate {
 			get { return endPointCallback; }
 			set { endPointCallback = value; }
 		}
-		
+
 		[MonoTODO]
-		public int ConnectionLeaseTimeout
-		{
+		public int ConnectionLeaseTimeout {
 			get {
 				throw GetMustImplement ();
 			}
@@ -100,54 +94,39 @@ namespace System.Net
 				throw GetMustImplement ();
 			}
 		}
-		
-		public int ConnectionLimit {
-			get { return connectionLimit; }
-			set {
-				if (value <= 0)
-					throw new ArgumentOutOfRangeException ();
 
-				connectionLimit = value;
-			}
+		public int ConnectionLimit {
+			get { return Scheduler.ConnectionLimit; }
+			set { Scheduler.ConnectionLimit = value; }
 		}
-		
+
 		public string ConnectionName {
 			get { return uri.Scheme; }
 		}
 
 		public int CurrentConnections {
 			get {
-				return currentConnections;
+				return Scheduler.CurrentConnections;
 			}
 		}
 
 		public DateTime IdleSince {
 			get {
-				return idleSince.ToLocalTime ();
+				return Scheduler.IdleSince.ToLocalTime ();
 			}
 		}
 
 		public int MaxIdleTime {
-			get { return maxIdleTime; }
-			set { 
-				if (value < Timeout.Infinite || value > Int32.MaxValue)
-					throw new ArgumentOutOfRangeException ();
-
-				lock (this) {
-					maxIdleTime = value;
-					if (idleTimer != null)
-						idleTimer.Change (maxIdleTime, maxIdleTime);
-				}
-			}
+			get { return Scheduler.MaxIdleTime; }
+			set { Scheduler.MaxIdleTime = value; }
 		}
-		
+
 		public virtual Version ProtocolVersion {
 			get { return protocolVersion; }
 		}
 
 		[MonoTODO]
-		public int ReceiveBufferSize
-		{
+		public int ReceiveBufferSize {
 			get {
 				throw GetMustImplement ();
 			}
@@ -155,7 +134,7 @@ namespace System.Net
 				throw GetMustImplement ();
 			}
 		}
-		
+
 		public bool SupportsPipelining {
 			get { return HttpVersion.Version11.Equals (protocolVersion); }
 		}
@@ -172,8 +151,10 @@ namespace System.Net
 		}
 
 		internal bool SendContinue {
-			get { return sendContinue &&
-				     (protocolVersion == null || protocolVersion == HttpVersion.Version11); }
+			get {
+				return sendContinue &&
+				       (protocolVersion == null || protocolVersion == HttpVersion.Version11);
+			}
 			set { sendContinue = value; }
 		}
 		// Methods
@@ -197,25 +178,25 @@ namespace System.Net
 			if (!tcp_keepalive)
 				return;
 
-			byte [] bytes = new byte [12];
-			PutBytes (bytes, (uint) (tcp_keepalive ? 1 : 0), 0);
-			PutBytes (bytes, (uint) tcp_keepalive_time, 4);
-			PutBytes (bytes, (uint) tcp_keepalive_interval, 8);
+			byte[] bytes = new byte[12];
+			PutBytes (bytes, (uint)(tcp_keepalive ? 1 : 0), 0);
+			PutBytes (bytes, (uint)tcp_keepalive_time, 4);
+			PutBytes (bytes, (uint)tcp_keepalive_interval, 8);
 			socket.IOControl (IOControlCode.KeepAliveValues, bytes, null);
 		}
 
-		static void PutBytes (byte [] bytes, uint v, int offset)
+		static void PutBytes (byte[] bytes, uint v, int offset)
 		{
 			if (BitConverter.IsLittleEndian) {
-				bytes [offset] = (byte) (v & 0x000000ff);
-				bytes [offset + 1] = (byte) ((v & 0x0000ff00) >> 8);
-				bytes [offset + 2] = (byte) ((v & 0x00ff0000) >> 16);
-				bytes [offset + 3] = (byte) ((v & 0xff000000) >> 24);
+				bytes[offset] = (byte)(v & 0x000000ff);
+				bytes[offset + 1] = (byte)((v & 0x0000ff00) >> 8);
+				bytes[offset + 2] = (byte)((v & 0x00ff0000) >> 16);
+				bytes[offset + 3] = (byte)((v & 0xff000000) >> 24);
 			} else {
-				bytes [offset + 3] = (byte) (v & 0x000000ff);
-				bytes [offset + 2] = (byte) ((v & 0x0000ff00) >> 8);
-				bytes [offset + 1] = (byte) ((v & 0x00ff0000) >> 16);
-				bytes [offset] = (byte) ((v & 0xff000000) >> 24);
+				bytes[offset + 3] = (byte)(v & 0x000000ff);
+				bytes[offset + 2] = (byte)((v & 0x0000ff00) >> 8);
+				bytes[offset + 1] = (byte)((v & 0x00ff0000) >> 16);
+				bytes[offset] = (byte)((v & 0xff000000) >> 24);
 			}
 		}
 
@@ -231,107 +212,7 @@ namespace System.Net
 			set { useConnect = value; }
 		}
 
-		WebConnectionGroup GetConnectionGroup (string name)
-		{
-			if (name == null)
-				name = "";
-
-			/*
-			 * Optimization:
-			 * 
-			 * In the vast majority of cases, we only have one single WebConnectionGroup per ServicePoint, so we
-			 * don't need to allocate a dictionary.
-			 * 
-			 */
-
-			WebConnectionGroup group;
-			if (groups != null && groups.TryGetValue (name, out group))
-				return group;
-
-			group = new WebConnectionGroup (this, name);
-			group.ConnectionClosed += (s, e) => currentConnections--;
-
-			if (groups == null)
-				groups = new Dictionary<string, WebConnectionGroup> ();
-			groups.Add (name, group);
-
-			return group;
-		}
-
-		void RemoveConnectionGroup (WebConnectionGroup group)
-		{
-			if (groups == null || groups.Count == 0)
-				throw new InvalidOperationException ();
-
-			groups.Remove (group.Name);
-		}
-
-		bool CheckAvailableForRecycling (out DateTime outIdleSince)
-		{
-			outIdleSince = DateTime.MinValue;
-
-			TimeSpan idleTimeSpan;
-			List<WebConnectionGroup> groupList = null, removeList = null;
-			lock (this) {
-				if (groups == null || groups.Count == 0) {
-					idleSince = DateTime.MinValue;
-					return true;
-				}
-
-				idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
-
-				/*
-				 * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
-				 * copy the group dictionary if it exists.
-				 * 
-				 * In most cases, we only have a single connection group, so we can simply store
-				 * that in a local variable instead of copying a collection.
-				 * 
-				 */
-
-				groupList = new List<WebConnectionGroup> (groups.Values);
-			}
-
-			foreach (var group in groupList) {
-				if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
-					continue;
-				if (removeList == null)
-					removeList = new List<WebConnectionGroup> ();
-				removeList.Add (group);
-			}
-
-			lock (this) {
-				idleSince = outIdleSince;
-
-				if (removeList != null && groups != null) {
-					foreach (var group in removeList)
-						if (groups.ContainsKey (group.Name))
-							RemoveConnectionGroup (group);
-				}
-
-				if (groups != null && groups.Count == 0)
-					groups = null;
-
-				if (groups == null) {
-					if (idleTimer != null) {
-						idleTimer.Dispose ();
-						idleTimer = null;
-					}
-					return true;
-				}
-
-				return false;
-			}
-		}
-
-		void IdleTimerCallback (object obj)
-		{
-			DateTime dummy;
-			CheckAvailableForRecycling (out dummy);
-		}
-
-		private bool HasTimedOut
-		{
+		private bool HasTimedOut {
 			get {
 				int timeout = ServicePointManager.DnsRefreshTimeout;
 				return timeout != Timeout.Infinite &&
@@ -339,8 +220,7 @@ namespace System.Net
 			}
 		}
 
-		internal IPHostEntry HostEntry
-		{
+		internal IPHostEntry HostEntry {
 			get {
 				lock (hostE) {
 					string uriHost = uri.Host;
@@ -356,7 +236,7 @@ namespace System.Net
 						}
 
 						// Creates IPHostEntry
-						host = new IPHostEntry();
+						host = new IPHostEntry ();
 						host.AddressList = new IPAddress[] { IPAddress.Parse (uriHost) };
 						return host;
 					}
@@ -382,41 +262,18 @@ namespace System.Net
 			protocolVersion = version;
 		}
 
-		internal EventHandler SendRequest (HttpWebRequest request, string groupName)
+		internal void SendRequest (WebOperation operation, string groupName)
 		{
-			WebConnection cnc;
-			
 			lock (this) {
-				bool created;
-				WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
-				cnc = cncGroup.GetConnection (request, out created);
-				if (created) {
-					++currentConnections;
-					if (idleTimer == null)
-						idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
-				}
+				Scheduler.SendRequest (operation, groupName);
 			}
-			
-			return cnc.SendRequest (request);
 		}
+
 		public bool CloseConnectionGroup (string connectionGroupName)
 		{
-			WebConnectionGroup cncGroup = null;
-
 			lock (this) {
-				cncGroup = GetConnectionGroup (connectionGroupName);
-				if (cncGroup != null) {
-					RemoveConnectionGroup (cncGroup);
-				}
+				return Scheduler.CloseConnectionGroup (connectionGroupName);
 			}
-
-			// WebConnectionGroup.Close() must *not* be called inside the lock
-			if (cncGroup != null) {
-				cncGroup.Close ();
-				return true;
-			}
-
-			return false;
 		}
 
 		//

+ 621 - 0
mcs/class/System/System.Net/ServicePointScheduler.cs

@@ -0,0 +1,621 @@
+//
+// ServicePointScheduler.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.Collections;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.ExceptionServices;
+using System.Diagnostics;
+
+namespace System.Net
+{
+	class ServicePointScheduler
+	{
+		public ServicePoint ServicePoint {
+			get;
+		}
+
+		public int MaxIdleTime {
+			get { return maxIdleTime; }
+			set {
+				if (value < Timeout.Infinite || value > Int32.MaxValue)
+					throw new ArgumentOutOfRangeException ();
+				if (value == maxIdleTime)
+					return;
+				maxIdleTime = value;
+				Debug ($"MAX IDLE TIME = {value}");
+				Run ();
+			}
+		}
+
+		public int ConnectionLimit {
+			get { return connectionLimit; }
+			set {
+				if (value <= 0)
+					throw new ArgumentOutOfRangeException ();
+
+				if (value == connectionLimit)
+					return;
+				connectionLimit = value;
+				Debug ($"CONNECTION LIMIT = {value}");
+				Run ();
+			}
+		}
+
+		public ServicePointScheduler (ServicePoint servicePoint, int connectionLimit, int maxIdleTime)
+		{
+			ServicePoint = servicePoint;
+			this.connectionLimit = connectionLimit;
+			this.maxIdleTime = maxIdleTime;
+
+			schedulerEvent = new AsyncManualResetEvent (false);
+			defaultGroup = new ConnectionGroup (this, string.Empty);
+			operations = new LinkedList<(ConnectionGroup, WebOperation)> ();
+			idleConnections = new LinkedList<(ConnectionGroup, WebConnection, Task)> ();
+			idleSince = DateTime.UtcNow;
+		}
+
+		[Conditional ("MONO_WEB_DEBUG")]
+		void Debug (string message, params object[] args)
+		{
+			WebConnection.Debug ($"SPS({ID}): {string.Format (message, args)}");
+		}
+
+		[Conditional ("MONO_WEB_DEBUG")]
+		void Debug (string message)
+		{
+			WebConnection.Debug ($"SPS({ID}): {message}");
+		}
+
+		int running;
+		int maxIdleTime = 100000;
+		AsyncManualResetEvent schedulerEvent;
+		ConnectionGroup defaultGroup;
+		Dictionary<string, ConnectionGroup> groups;
+		LinkedList<(ConnectionGroup, WebOperation)> operations;
+		LinkedList<(ConnectionGroup, WebConnection, Task)> idleConnections;
+		int currentConnections;
+		int connectionLimit;
+		DateTime idleSince;
+
+		public int CurrentConnections {
+			get {
+				return currentConnections;
+			}
+		}
+
+		public DateTime IdleSince {
+			get {
+				return idleSince;
+			}
+		}
+
+		static int nextId;
+		public readonly int ID = ++nextId;
+
+		internal string ME {
+			get;
+		}
+
+		public void Run ()
+		{
+			lock (ServicePoint) {
+				if (Interlocked.CompareExchange (ref running, 1, 0) == 0)
+					StartScheduler ();
+
+				schedulerEvent.Set ();
+			}
+		}
+
+		async void StartScheduler ()
+		{
+			idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
+
+			while (true) {
+				Debug ($"MAIN LOOP");
+
+				// Gather list of currently running operations.
+				ValueTuple<ConnectionGroup, WebOperation>[] operationArray;
+				ValueTuple<ConnectionGroup, WebConnection, Task>[] idleArray;
+				var taskList = new List<Task> ();
+				lock (ServicePoint) {
+					Cleanup ();
+					if (groups == null && defaultGroup.IsEmpty () && operations.Count == 0 && idleConnections.Count == 0) {
+						Debug ($"MAIN LOOP DONE");
+						running = 0;
+						idleSince = DateTime.UtcNow;
+						schedulerEvent.Reset ();
+						return;
+					}
+
+					operationArray = new ValueTuple<ConnectionGroup, WebOperation>[operations.Count];
+					operations.CopyTo (operationArray, 0);
+					idleArray = new ValueTuple<ConnectionGroup, WebConnection, Task>[idleConnections.Count];
+					idleConnections.CopyTo (idleArray, 0);
+
+					taskList.Add (schedulerEvent.WaitAsync (maxIdleTime));
+					foreach (var item in operationArray)
+						taskList.Add (item.Item2.WaitForCompletion (true));
+					foreach (var item in idleArray)
+						taskList.Add (item.Item3);
+				}
+
+				Debug ($"MAIN LOOP #1: operations={operationArray.Length} idle={idleArray.Length}");
+
+				var ret = await Task.WhenAny (taskList).ConfigureAwait (false);
+
+				lock (ServicePoint) {
+					if (ret == taskList[0]) {
+						RunSchedulerIteration ();
+						continue;
+					}
+
+					int idx = -1;
+					for (int i = 0; i < operationArray.Length; i++) {
+						if (ret == taskList[i + 1]) {
+							idx = i;
+							break;
+						}
+					}
+
+					if (idx >= 0) {
+						var item = operationArray[idx];
+						Debug ($"MAIN LOOP #2: {idx} group={item.Item1.ID} Op={item.Item2.ID}");
+						operations.Remove (item);
+
+						var opTask = (Task<ValueTuple<bool, WebOperation>>)ret;
+						var runLoop = OperationCompleted (item.Item1, item.Item2, opTask);
+						Debug ($"MAIN LOOP #2 DONE: {idx} {runLoop}");
+						if (runLoop)
+							RunSchedulerIteration ();
+						continue;
+					}
+
+					for (int i = 0; i < idleArray.Length; i++) {
+						if (ret == taskList[i + 1 + operationArray.Length]) {
+							idx = i;
+							break;
+						}
+					}
+
+					if (idx >= 0) {
+						var item = idleArray[idx];
+						Debug ($"MAIN LOOP #3: {idx} group={item.Item1.ID} Cnc={item.Item2.ID}");
+						idleConnections.Remove (item);
+						CloseIdleConnection (item.Item1, item.Item2);
+					}
+				}
+			}
+		}
+
+		void Cleanup ()
+		{
+			if (groups != null) {
+				var keys = new string[groups.Count];
+				groups.Keys.CopyTo (keys, 0);
+				foreach (var groupName in keys) {
+					if (!groups.ContainsKey (groupName))
+						continue;
+					var group = groups[groupName];
+					if (group.IsEmpty ()) {
+						Debug ($"CLEANUP - REMOVING group={group.ID}");
+						groups.Remove (groupName);
+					}
+				}
+				if (groups.Count == 0)
+					groups = null;
+			}
+		}
+
+		void RunSchedulerIteration ()
+		{
+			schedulerEvent.Reset ();
+
+			bool repeat;
+			do {
+				Debug ($"ITERATION");
+
+				repeat = SchedulerIteration (defaultGroup);
+
+				Debug ($"ITERATION #1: {repeat} {groups != null}");
+
+				if (groups != null) {
+					foreach (var group in groups) {
+						Debug ($"ITERATION #2: group={group.Value.ID}");
+						repeat |= SchedulerIteration (group.Value);
+					}
+				}
+
+				Debug ($"ITERATION #3: {repeat}");
+			} while (repeat);
+		}
+
+		bool OperationCompleted (ConnectionGroup group, WebOperation operation, Task<(bool, WebOperation)> task)
+		{
+#if MONO_WEB_DEBUG
+			var me = $"{nameof (OperationCompleted)}(group={group.ID}, Op={operation.ID}, Cnc={operation.Connection.ID})";
+#else
+			string me = null;
+#endif
+
+			var (ok, next) = task.Status == TaskStatus.RanToCompletion ? task.Result : (false, null);
+			Debug ($"{me}: {task.Status} {ok} {next?.ID}");
+
+			if (!ok || !operation.Connection.Continue (next)) {
+				group.RemoveConnection (operation.Connection);
+				if (next == null) {
+					Debug ($"{me}: closed connection and done.");
+					return true;
+				}
+				ok = false;
+			}
+
+			if (next == null) {
+				if (ok) {
+					var idleTask = Task.Delay (MaxIdleTime);
+					idleConnections.AddLast ((group, operation.Connection, idleTask));
+					Debug ($"{me} keeping connection open for {MaxIdleTime} milliseconds.");
+				} else {
+					Debug ($"{me}: closed connection and done.");
+				}
+				return true;
+			}
+
+			Debug ($"{me} got new operation next={next.ID}.");
+			operations.AddLast ((group, next));
+
+			if (ok) {
+				Debug ($"{me} continuing next={next.ID} on same connection.");
+				RemoveIdleConnection (operation.Connection);
+				return false;
+			}
+
+			group.Cleanup ();
+
+			var (connection, created) = group.CreateOrReuseConnection (next, true);
+			Debug ($"{me} created new connection Cnc={connection.ID} next={next.ID}.");
+			return false;
+		}
+
+		void CloseIdleConnection (ConnectionGroup group, WebConnection connection)
+		{
+			Debug ($"{nameof (CloseIdleConnection)}(group={group.ID}, Cnc={connection.ID}) closing idle connection.");
+
+			group.RemoveConnection (connection);
+			RemoveIdleConnection (connection);
+		}
+
+		bool SchedulerIteration (ConnectionGroup group)
+		{
+#if MONO_WEB_DEBUG
+			var me = $"{nameof (SchedulerIteration)}(group={group.ID})";
+#else
+			string me = null;
+#endif
+			Debug ($"{me}");
+
+			// First, let's clean up.
+			group.Cleanup ();
+
+			// Is there anything in the queue?
+			var next = group.GetNextOperation ();
+			Debug ($"{me} no pending operations.");
+			if (next == null)
+				return false;
+
+			Debug ($"{me} found pending operation Op={next.ID}");
+
+			var (connection, created) = group.CreateOrReuseConnection (next, false);
+			if (connection == null) {
+				// All connections are currently busy, need to keep it in the queue for now.
+				Debug ($"{me} all connections busy, keeping operation in queue.");
+				return false;
+			}
+
+			Debug ($"{me} started operation: Op={next.ID} Cnc={connection.ID}");
+			operations.AddLast ((group, next));
+			RemoveIdleConnection (connection);
+			return true;
+		}
+
+		void RemoveOperation (WebOperation operation)
+		{
+			var iter = operations.First;
+			while (iter != null) {
+				var node = iter;
+				iter = iter.Next;
+
+				if (node.Value.Item2 == operation)
+					operations.Remove (node);
+			}
+		}
+
+		void RemoveIdleConnection (WebConnection connection)
+		{
+			var iter = idleConnections.First;
+			while (iter != null) {
+				var node = iter;
+				iter = iter.Next;
+
+				if (node.Value.Item2 == connection)
+					idleConnections.Remove (node);
+			}
+		}
+
+		public void SendRequest (WebOperation operation, string groupName)
+		{
+			lock (ServicePoint) {
+				var group = GetConnectionGroup (groupName);
+				Debug ($"SEND REQUEST: Op={operation.ID} group={group.ID}");
+				group.EnqueueOperation (operation);
+				Run ();
+				Debug ($"SEND REQUEST DONE: Op={operation.ID} group={group.ID}");
+			}
+		}
+
+		public bool CloseConnectionGroup (string groupName)
+		{
+			lock (ServicePoint) {
+				ConnectionGroup group;
+				if (string.IsNullOrEmpty (groupName))
+					group = defaultGroup;
+				else if (groups == null || !groups.TryGetValue (groupName, out group))
+					return false;
+
+				Debug ($"CLOSE CONNECTION GROUP: group={group.ID}");
+
+				if (group != defaultGroup) {
+					groups.Remove (groupName);
+					if (groups.Count == 0)
+						groups = null;
+				}
+
+				group.Close ();
+				Run ();
+				return true;
+			}
+		}
+
+		ConnectionGroup GetConnectionGroup (string name)
+		{
+			lock (ServicePoint) {
+				if (string.IsNullOrEmpty (name))
+					return defaultGroup;
+
+				if (groups == null)
+					groups = new Dictionary<string, ConnectionGroup> (); 
+
+				if (groups.TryGetValue (name, out ConnectionGroup group))
+					return group;
+
+				group = new ConnectionGroup (this, name);
+				groups.Add (name, group);
+				return group;
+			}
+		}
+
+		void OnConnectionCreated (WebConnection connection)
+		{
+			Interlocked.Increment (ref currentConnections);
+		}
+
+		void OnConnectionClosed (WebConnection connection)
+		{
+			RemoveIdleConnection (connection);
+			Interlocked.Decrement (ref currentConnections);
+		}
+
+		class ConnectionGroup
+		{
+			public ServicePointScheduler Scheduler {
+				get;
+			}
+
+			public string Name {
+				get;
+			}
+
+			public bool IsDefault => string.IsNullOrEmpty (Name);
+
+			static int nextId;
+			public readonly int ID = ++nextId;
+			LinkedList<WebConnection> connections;
+			LinkedList<WebOperation> queue;
+
+			public ConnectionGroup (ServicePointScheduler scheduler, string name)
+			{
+				Scheduler = scheduler;
+				Name = name;
+
+				connections = new LinkedList<WebConnection> ();
+				queue = new LinkedList<WebOperation> (); 
+			}
+
+			public bool IsEmpty ()
+			{
+				return connections.Count == 0 && queue.Count == 0;
+			}
+
+			public void RemoveConnection (WebConnection connection)
+			{
+				Scheduler.Debug ($"REMOVING CONNECTION: group={ID} cnc={connection.ID}");
+				connections.Remove (connection);
+				connection.Dispose ();
+				Scheduler.OnConnectionClosed (connection);
+			}
+
+			public void Cleanup ()
+			{
+				var iter = connections.First;
+				while (iter != null) {
+					var connection = iter.Value;
+					var node = iter;
+					iter = iter.Next;
+
+					if (connection.Closed) {
+						Scheduler.Debug ($"REMOVING CONNECTION: group={ID} cnc={connection.ID}");
+						connections.Remove (node);
+						Scheduler.OnConnectionClosed (connection);
+					}
+				}
+			}
+
+			public void Close ()
+			{
+				foreach (var operation in queue) {
+					operation.Abort ();
+					Scheduler.RemoveOperation (operation);
+				}
+				queue.Clear ();
+
+				foreach (var connection in connections) {
+					connection.Dispose ();
+					Scheduler.OnConnectionClosed (connection);
+				}
+				connections.Clear ();
+			}
+
+			public void EnqueueOperation (WebOperation operation)
+			{
+				queue.AddLast (operation);
+			}
+
+			public WebOperation GetNextOperation ()
+			{
+				// Is there anything in the queue?
+				var iter = queue.First;
+				while (iter != null) {
+					var operation = iter.Value;
+					var node = iter;
+					iter = iter.Next;
+
+					if (operation.Aborted) {
+						queue.Remove (node);
+						Scheduler.RemoveOperation (operation);
+						continue;
+					}
+
+					return operation;
+				}
+
+				return null;
+			}
+
+			public WebConnection FindIdleConnection (WebOperation operation)
+			{
+				// First let's find the ideal candidate.
+				WebConnection candidate = null;
+				foreach (var connection in connections) {
+					if (connection.CanReuseConnection (operation)) {
+						if (candidate == null || connection.IdleSince > candidate.IdleSince)
+							candidate = connection;
+					}
+				}
+
+				// Found one?  Make sure it's actually willing to run it.
+				if (candidate != null && candidate.StartOperation (operation, true)) {
+					queue.Remove (operation);
+					return candidate;
+				}
+
+				// Ok, let's loop again and pick the first one that accepts the new operation.
+				foreach (var connection in connections) {
+					if (connection.StartOperation (operation, true)) {
+						queue.Remove (operation);
+						return connection;
+					}
+				}
+
+				return null;
+			}
+
+			public (WebConnection connection, bool created) CreateOrReuseConnection (WebOperation operation, bool force)
+			{
+				var connection = FindIdleConnection (operation);
+				if (connection != null)
+					return (connection, false);
+
+				if (force || Scheduler.ServicePoint.ConnectionLimit > connections.Count || connections.Count == 0) {
+					connection = new WebConnection (Scheduler.ServicePoint);
+					connection.StartOperation (operation, false);
+					connections.AddFirst (connection);
+					Scheduler.OnConnectionCreated (connection);
+					queue.Remove (operation);
+					return (connection, true);
+				}
+
+				return (null, false);
+			}
+		}
+
+		// https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/
+		class AsyncManualResetEvent
+		{
+			volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool> ();
+
+			public Task WaitAsync () { return m_tcs.Task; }
+
+			public bool WaitOne (int millisecondTimeout)
+			{
+				WebConnection.Debug ($"AMRE WAIT ONE: {millisecondTimeout}");
+				return m_tcs.Task.Wait (millisecondTimeout);
+			}
+
+			public async Task<bool> WaitAsync (int millisecondTimeout)
+			{
+				var timeoutTask = Task.Delay (millisecondTimeout);
+				var ret = await Task.WhenAny (m_tcs.Task, timeoutTask).ConfigureAwait (false);
+				return ret != timeoutTask;
+			}
+
+			public void Set ()
+			{
+				var tcs = m_tcs;
+				Task.Factory.StartNew (s => ((TaskCompletionSource<bool>)s).TrySetResult (true),
+				    tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
+				tcs.Task.Wait ();
+			}
+
+			public void Reset ()
+			{
+				while (true) {
+					var tcs = m_tcs;
+					if (!tcs.Task.IsCompleted ||
+					    Interlocked.CompareExchange (ref m_tcs, new TaskCompletionSource<bool> (), tcs) == tcs)
+						return;
+				}
+			}
+
+			public AsyncManualResetEvent (bool state)
+			{
+				if (state)
+					Set ();
+			}
+		}
+	}
+}

+ 0 - 232
mcs/class/System/System.Net/SimpleAsyncResult.cs

@@ -1,232 +0,0 @@
-//
-// SimpleAsyncResult.cs
-//
-// Authors:
-//	Gonzalo Paniagua Javier ([email protected])
-//      Martin Baulig ([email protected])
-//
-// (C) 2003 Ximian, Inc (http://www.ximian.com)
-// Copyright (c) 2014 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.Threading;
-
-namespace System.Net
-{
-	delegate void SimpleAsyncCallback (SimpleAsyncResult result);
-
-	class SimpleAsyncResult : IAsyncResult
-	{
-		ManualResetEvent handle;
-		bool synch;
-		bool isCompleted;
-		readonly SimpleAsyncCallback cb;
-		object state;
-		bool callbackDone;
-		Exception exc;
-		object locker = new object ();
-
-		SimpleAsyncResult (SimpleAsyncCallback cb)
-		{
-			this.cb = cb;
-		}
-
-		protected SimpleAsyncResult (AsyncCallback cb, object state)
-		{
-			this.state = state;
-			this.cb = result => {
-				if (cb != null)
-					cb (this);
-			};
-		}
-
-		public static void Run (Func<SimpleAsyncResult, bool> func, SimpleAsyncCallback callback)
-		{
-			var result = new SimpleAsyncResult (callback);
-			try {
-				if (!func (result))
-					result.SetCompleted (true);
-			} catch (Exception ex) {
-				result.SetCompleted (true, ex);
-			}
-		}
-
-		public static void RunWithLock (object locker, Func<SimpleAsyncResult, bool> func, SimpleAsyncCallback callback)
-		{
-			Run (inner => {
-				bool running = func (inner);
-				if (running)
-					Monitor.Exit (locker);
-				return running;
-			}, inner => {
-				if (inner.GotException) {
-					if (inner.synch)
-						Monitor.Exit (locker);
-					callback (inner);
-					return;
-				}
-
-				try {
-					if (!inner.synch)
-						Monitor.Enter (locker);
-
-					callback (inner);
-				} finally {
-					Monitor.Exit (locker);
-				}
-			});
-		}
-
-		protected void Reset_internal ()
-		{
-			callbackDone = false;
-			exc = null;
-			lock (locker) {
-				isCompleted = false;
-				if (handle != null)
-					handle.Reset ();
-			}
-		}
-
-		internal void SetCompleted (bool synch, Exception e)
-		{
-			SetCompleted_internal (synch, e);
-			DoCallback_private ();
-		}
-
-		internal void SetCompleted (bool synch)
-		{
-			SetCompleted_internal (synch);
-			DoCallback_private ();
-		}
-
-		void SetCompleted_internal (bool synch, Exception e)
-		{
-			this.synch = synch;
-			exc = e;
-			lock (locker) {
-				isCompleted = true;
-				if (handle != null)
-					handle.Set ();
-			}
-		}
-
-		protected void SetCompleted_internal (bool synch)
-		{
-			SetCompleted_internal (synch, null);
-		}
-
-		void DoCallback_private ()
-		{
-			if (callbackDone)
-				return;
-			callbackDone = true;
-			if (cb == null)
-				return;
-			cb (this);
-		}
-
-		protected void DoCallback_internal ()
-		{
-			if (!callbackDone && cb != null) {
-				callbackDone = true;
-				cb (this);
-			}
-		}
-
-		internal void WaitUntilComplete ()
-		{
-			if (IsCompleted)
-				return;
-
-			AsyncWaitHandle.WaitOne ();
-		}
-
-		internal bool WaitUntilComplete (int timeout, bool exitContext)
-		{
-			if (IsCompleted)
-				return true;
-
-			return AsyncWaitHandle.WaitOne (timeout, exitContext);
-		}
-
-		public object AsyncState {
-			get { return state; }
-		}
-
-		public WaitHandle AsyncWaitHandle {
-			get {
-				lock (locker) {
-					if (handle == null)
-						handle = new ManualResetEvent (isCompleted);
-				}
-
-				return handle;
-			}
-		}
-
-		bool? user_read_synch;
-
-		public bool CompletedSynchronously {
-			get {
-				//
-				// CompletedSynchronously (for System.Net networking stack) means "was the operation completed before the first time
-				// that somebody asked if it was completed synchronously"? They do this because some of their asynchronous operations
-				// (particularly those in the Socket class) will avoid the cost of capturing and transferring the ExecutionContext
-				// to the callback thread by checking CompletedSynchronously, and calling the callback from within BeginXxx instead of
-				// on the completion port thread if the native winsock call completes quickly.
-				//
-				// TODO: racy
-				if (user_read_synch != null)
-					return user_read_synch.Value;
-
-				user_read_synch	= synch;
-				return user_read_synch.Value;
-			}
-		}
-
-		internal bool CompletedSynchronouslyPeek {
-			get {
-				return synch;
-			}
-		}
-
-		public bool IsCompleted {
-			get {
-				lock (locker) {
-					return isCompleted;
-				}
-			}
-		}
-
-		internal bool GotException {
-			get { return (exc != null); }
-		}
-
-		internal Exception Exception {
-			get { return exc; }
-		}
-	}
-}
-

+ 0 - 132
mcs/class/System/System.Net/WebAsyncResult.cs

@@ -1,132 +0,0 @@
-//
-// System.Net.WebAsyncResult
-//
-// Authors:
-//	Gonzalo Paniagua Javier ([email protected])
-//
-// (C) 2003 Ximian, Inc (http://www.ximian.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.Threading;
-
-namespace System.Net
-{
-	class WebAsyncResult : SimpleAsyncResult
-	{
-		int nbytes;
-		IAsyncResult innerAsyncResult;
-		HttpWebResponse response;
-		Stream writeStream;
-		byte [] buffer;
-		int offset;
-		int size;
-		public bool EndCalled;
-		public bool AsyncWriteAll;
-		public HttpWebRequest AsyncObject;
-
-		public WebAsyncResult (AsyncCallback cb, object state)
-			: base (cb, state)
-		{
-		}
-
-		public WebAsyncResult (HttpWebRequest request, AsyncCallback cb, object state)
-			: base (cb, state)
-		{
-			this.AsyncObject = request;
-		}
-
-		public WebAsyncResult (AsyncCallback cb, object state, byte [] buffer, int offset, int size)
-			: base (cb, state)
-		{
-			this.buffer = buffer;
-			this.offset = offset;
-			this.size = size;
-		}
-
-		internal void Reset ()
-		{
-			this.nbytes = 0;
-			this.response = null;
-			this.buffer = null;
-			this.offset = 0;
-			this.size = 0;
-			Reset_internal ();
-		}
-
-		internal void SetCompleted (bool synch, int nbytes)
-		{
-			this.nbytes = nbytes;
-			SetCompleted_internal (synch);
-		}
-		
-		internal void SetCompleted (bool synch, Stream writeStream)
-		{
-			this.writeStream = writeStream;
-			SetCompleted_internal (synch);
-		}
-		
-		internal void SetCompleted (bool synch, HttpWebResponse response)
-		{
-			this.response = response;
-			SetCompleted_internal (synch);
-		}
-
-		internal void DoCallback ()
-		{
-			DoCallback_internal ();
-		}
-		
-		internal int NBytes {
-			get { return nbytes; }
-			set { nbytes = value; }
-		}
-
-		internal IAsyncResult InnerAsyncResult {
-			get { return innerAsyncResult; }
-			set { innerAsyncResult = value; }
-		}
-
-		internal Stream WriteStream {
-			get { return writeStream; }
-		}
-
-		internal HttpWebResponse Response {
-			get { return response; }
-		}
-
-		internal byte [] Buffer {
-			get { return buffer; }
-		}
-
-		internal int Offset {
-			get { return offset; }
-		}
-
-		internal int Size {
-			get { return size; }
-		}
-	}
-}
-

+ 268 - 986
mcs/class/System/System.Net/WebConnection.cs

@@ -3,10 +3,11 @@
 //
 // Authors:
 //	Gonzalo Paniagua Javier ([email protected])
+//      Martin Baulig <[email protected]>
 //
 // (C) 2003 Ximian, Inc (http://www.ximian.com)
+// 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
@@ -27,13 +28,14 @@
 // 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.Collections;
 using System.Net.Sockets;
 using System.Security.Cryptography.X509Certificates;
 using System.Text;
 using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.ExceptionServices;
 using System.Diagnostics;
 using Mono.Net.Security;
 
@@ -48,788 +50,245 @@ namespace System.Net
 		Aborted
 	}
 
-	class WebConnection
+	class WebConnection : IDisposable
 	{
-		ServicePoint sPoint;
-		Stream nstream;
-		internal Socket socket;
-		object socketLock = new object ();
-		IWebConnectionState state;
-		WebExceptionStatus status;
-		bool keepAlive;
-		byte [] buffer;
-		EventHandler abortHandler;
-		AbortHelper abortHelper;
-		internal WebConnectionData Data;
-		bool chunkedRead;
-		MonoChunkStream chunkStream;
-		Queue queue;
-		bool reused;
-		int position;
-		HttpWebRequest priority_request;		
 		NetworkCredential ntlm_credentials;
 		bool ntlm_authenticated;
 		bool unsafe_sharing;
+		Stream networkStream;
+		Socket socket;
+		MonoTlsStream monoTlsStream;
+		WebConnectionTunnel tunnel;
+		int disposed;
 
-		enum NtlmAuthState
-		{
-			None,
-			Challenge,
-			Response
+		public ServicePoint ServicePoint {
+			get;
 		}
-		NtlmAuthState connect_ntlm_auth_state;
-		HttpWebRequest connect_request;
-
-		Exception connect_exception;
-		MonoTlsStream tlsStream;
 
 #if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
 		[System.Runtime.InteropServices.DllImport ("__Internal")]
 		static extern void xamarin_start_wwan (string uri);
 #endif
 
-		internal MonoChunkStream MonoChunkStream {
-			get { return chunkStream; }
-		}
-
-		public WebConnection (IWebConnectionState wcs, ServicePoint sPoint)
+		public WebConnection (ServicePoint sPoint)
 		{
-			this.state = wcs;
-			this.sPoint = sPoint;
-			buffer = new byte [4096];
-			Data = new WebConnectionData ();
-			queue = wcs.Group.Queue;
-			abortHelper = new AbortHelper ();
-			abortHelper.Connection = this;
-			abortHandler = new EventHandler (abortHelper.Abort);
+			ServicePoint = sPoint;
 		}
 
-		class AbortHelper {
-			public WebConnection Connection;
-
-			public void Abort (object sender, EventArgs args)
-			{
-				WebConnection other = ((HttpWebRequest) sender).WebConnection;
-				if (other == null)
-					other = Connection;
-				other.Abort (sender, args);
-			}
+#if MONO_WEB_DEBUG
+		internal static bool EnableWebDebug {
+			get; set;
 		}
 
-		bool CanReuse ()
+		static WebConnection ()
 		{
-			// The real condition is !(socket.Poll (0, SelectMode.SelectRead) || socket.Available != 0)
-			// but if there's data pending to read (!) we won't reuse the socket.
-			return (socket.Poll (0, SelectMode.SelectRead) == false);
+			if (Environment.GetEnvironmentVariable ("MONO_WEB_DEBUG") != null)
+				EnableWebDebug = true;
 		}
-		
-		void Connect (HttpWebRequest request)
-		{
-			lock (socketLock) {
-				if (socket != null && socket.Connected && status == WebExceptionStatus.Success) {
-					// Take the chunked stream to the expected state (State.None)
-					if (CanReuse () && CompleteChunkedRead ()) {
-						reused = true;
-						return;
-					}
-				}
-
-				reused = false;
-				if (socket != null) {
-					socket.Close();
-					socket = null;
-				}
-
-				chunkStream = null;
-				IPHostEntry hostEntry = sPoint.HostEntry;
-
-				if (hostEntry == null) {
-#if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
-					xamarin_start_wwan (sPoint.Address.ToString ());
-					hostEntry = sPoint.HostEntry;
-					if (hostEntry == null) {
-#endif
-						status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
-									    WebExceptionStatus.NameResolutionFailure;
-						return;
-#if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
-					}
 #endif
-				}
-
-				//WebConnectionData data = Data;
-				foreach (IPAddress address in hostEntry.AddressList) {
-					try {
-						socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
-					} catch (Exception se) {
-						// The Socket ctor can throw if we run out of FD's
-						if (!request.Aborted)
-								status = WebExceptionStatus.ConnectFailure;
-						connect_exception = se;
-						return;
-					}
-					IPEndPoint remote = new IPEndPoint (address, sPoint.Address.Port);
-					socket.NoDelay = !sPoint.UseNagleAlgorithm;
-					try {
-						sPoint.KeepAliveSetup (socket);
-					} catch {
-						// Ignore. Not supported in all platforms.
-					}
 
-					if (!sPoint.CallEndPointDelegate (socket, remote)) {
-						socket.Close ();
-						socket = null;
-						status = WebExceptionStatus.ConnectFailure;
-					} else {
-						try {
-							if (request.Aborted)
-								return;
-							socket.Connect (remote);
-							status = WebExceptionStatus.Success;
-							break;
-						} catch (ThreadAbortException) {
-							// program exiting...
-							Socket s = socket;
-							socket = null;
-							if (s != null)
-								s.Close ();
-							return;
-						} catch (ObjectDisposedException) {
-							// socket closed from another thread
-							return;
-						} catch (Exception exc) {
-							Socket s = socket;
-							socket = null;
-							if (s != null)
-								s.Close ();
-							if (!request.Aborted)
-								status = WebExceptionStatus.ConnectFailure;
-							connect_exception = exc;
-						}
-					}
-				}
-			}
+		[Conditional ("MONO_WEB_DEBUG")]
+		internal static void Debug (string message, params object[] args)
+		{
+#if MONO_WEB_DEBUG
+			if (EnableWebDebug)
+				Console.Error.WriteLine (string.Format (message, args));
+#endif
 		}
 
-		bool CreateTunnel (HttpWebRequest request, Uri connectUri,
-		                   Stream stream, out byte[] buffer)
+		[Conditional ("MONO_WEB_DEBUG")]
+		internal static void Debug (string message)
 		{
-			StringBuilder sb = new StringBuilder ();
-			sb.Append ("CONNECT ");
-			sb.Append (request.Address.Host);
-			sb.Append (':');
-			sb.Append (request.Address.Port);
-			sb.Append (" HTTP/");
-			if (request.ServicePoint.ProtocolVersion == HttpVersion.Version11)
-				sb.Append ("1.1");
-			else
-				sb.Append ("1.0");
-
-			sb.Append ("\r\nHost: ");
-			sb.Append (request.Address.Authority);
-
-			bool ntlm = false;
-			var challenge = Data.Challenge;
-			Data.Challenge = null;
-			var auth_header = request.Headers ["Proxy-Authorization"];
-			bool have_auth = auth_header != null;
-			if (have_auth) {
-				sb.Append ("\r\nProxy-Authorization: ");
-				sb.Append (auth_header);
-				ntlm = auth_header.ToUpper ().Contains ("NTLM");
-			} else if (challenge != null && Data.StatusCode == 407) {
-				ICredentials creds = request.Proxy.Credentials;
-				have_auth = true;
-
-				if (connect_request == null) {
-					// create a CONNECT request to use with Authenticate
-					connect_request = (HttpWebRequest)WebRequest.Create (
-						connectUri.Scheme + "://" + connectUri.Host + ":" + connectUri.Port + "/");
-					connect_request.Method = "CONNECT";
-					connect_request.Credentials = creds;
-				}
-
-				if (creds != null) {
-					for (int i = 0; i < challenge.Length; i++) {
-						var auth = AuthenticationManager.Authenticate (challenge [i], connect_request, creds);
-						if (auth == null)
-							continue;
-						ntlm = (auth.ModuleAuthenticationType == "NTLM");
-						sb.Append ("\r\nProxy-Authorization: ");
-						sb.Append (auth.Message);
-						break;
-					}
-				}
-			}
-
-			if (ntlm) {
-				sb.Append ("\r\nProxy-Connection: keep-alive");
-				connect_ntlm_auth_state++;
-			}
-
-			sb.Append ("\r\n\r\n");
-
-			Data.StatusCode = 0;
-			byte [] connectBytes = Encoding.Default.GetBytes (sb.ToString ());
-			stream.Write (connectBytes, 0, connectBytes.Length);
-
-			int status;
-			WebHeaderCollection result = ReadHeaders (stream, out buffer, out status);
-			if ((!have_auth || connect_ntlm_auth_state == NtlmAuthState.Challenge) &&
-			    result != null && status == 407) { // Needs proxy auth
-				var connectionHeader = result ["Connection"];
-				if (socket != null && !string.IsNullOrEmpty (connectionHeader) &&
-				    connectionHeader.ToLower() == "close") {
-					// The server is requesting that this connection be closed
-					socket.Close();
-					socket = null;
-				}
-
-				Data.StatusCode = status;
-				Data.Challenge = result.GetValues ("Proxy-Authenticate");
-				Data.Headers = result;
-				return false;
-			}
-
-			if (status != 200) {
-				Data.StatusCode = status;
-				Data.Headers = result;
-				return false;
-			}
-
-			return (result != null);
+#if MONO_WEB_DEBUG
+			if (EnableWebDebug)
+				Console.Error.WriteLine (message);
+#endif
 		}
 
-		WebHeaderCollection ReadHeaders (Stream stream, out byte [] retBuffer, out int status)
+		bool CanReuse ()
 		{
-			retBuffer = null;
-			status = 200;
-
-			byte [] buffer = new byte [1024];
-			MemoryStream ms = new MemoryStream ();
-
-			while (true) {
-				int n = stream.Read (buffer, 0, 1024);
-				if (n == 0) {
-					HandleError (WebExceptionStatus.ServerProtocolViolation, null, "ReadHeaders");
-					return null;
-				}
-				
-				ms.Write (buffer, 0, n);
-				int start = 0;
-				string str = null;
-				bool gotStatus = false;
-				WebHeaderCollection headers = new WebHeaderCollection ();
-				while (ReadLine (ms.GetBuffer (), ref start, (int) ms.Length, ref str)) {
-					if (str == null) {
-						int contentLen = 0;
-						try	{
-							contentLen = int.Parse(headers["Content-Length"]);
-						}
-						catch {
-							contentLen = 0;
-						}
-
-						if (ms.Length - start - contentLen > 0)	{
-							// we've read more data than the response header and conents,
-							// give back extra data to the caller
-							retBuffer = new byte[ms.Length - start - contentLen];
-							Buffer.BlockCopy(ms.GetBuffer(), start + contentLen, retBuffer, 0, retBuffer.Length);
-						}
-						else {
-							// haven't read in some or all of the contents for the response, do so now
-							FlushContents(stream, contentLen - (int)(ms.Length - start));
-						}
-
-						return headers;
-					}
-
-					if (gotStatus) {
-						headers.Add (str);
-						continue;
-					}
-
-					string[] parts = str.Split (' ');
-					if (parts.Length < 2) {
-						HandleError (WebExceptionStatus.ServerProtocolViolation, null, "ReadHeaders2");
-						return null;
-					}
-
-					if (String.Compare (parts [0], "HTTP/1.1", true) == 0)
-						Data.ProxyVersion = HttpVersion.Version11;
-					else if (String.Compare (parts [0], "HTTP/1.0", true) == 0)
-						Data.ProxyVersion = HttpVersion.Version10;
-					else {
-						HandleError (WebExceptionStatus.ServerProtocolViolation, null, "ReadHeaders2");
-						return null;
-					}
-
-					status = (int)UInt32.Parse (parts [1]);
-					if (parts.Length >= 3)
-						Data.StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
-
-					gotStatus = true;
-				}
-			}
+			// The real condition is !(socket.Poll (0, SelectMode.SelectRead) || socket.Available != 0)
+			// but if there's data pending to read (!) we won't reuse the socket.
+			return (socket.Poll (0, SelectMode.SelectRead) == false);
 		}
 
-		void FlushContents(Stream stream, int contentLength)
+		bool CheckReusable ()
 		{
-			while (contentLength > 0) {
-				byte[] contentBuffer = new byte[contentLength];
-				int bytesRead = stream.Read(contentBuffer, 0, contentLength);
-				if (bytesRead > 0) {
-					contentLength -= bytesRead;
-				}
-				else {
-					break;
-				}
+			if (socket != null && socket.Connected) {
+				try {
+					if (CanReuse ())
+						return true;
+				} catch { }
 			}
+
+			return false;
 		}
 
-		bool CreateStream (HttpWebRequest request)
+		async Task Connect (WebOperation operation, CancellationToken cancellationToken)
 		{
-			try {
-				NetworkStream serverStream = new NetworkStream (socket, false);
-
-				if (request.Address.Scheme == Uri.UriSchemeHttps) {
-#if SECURITY_DEP
-					if (!reused || nstream == null || tlsStream == null) {
-						byte [] buffer = null;
-						if (sPoint.UseConnect) {
-							bool ok = CreateTunnel (request, sPoint.Address, serverStream, out buffer);
-							if (!ok)
-								return false;
-						}
-						tlsStream = new MonoTlsStream (request, serverStream);
-						nstream = tlsStream.CreateStream (buffer);
+			IPHostEntry hostEntry = ServicePoint.HostEntry;
+
+			if (hostEntry == null || hostEntry.AddressList.Length == 0) {
+#if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
+					xamarin_start_wwan (ServicePoint.Address.ToString ());
+					hostEntry = ServicePoint.HostEntry;
+					if (hostEntry == null) {
+#endif
+				throw GetException (ServicePoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
+						    WebExceptionStatus.NameResolutionFailure, null);
+#if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
 					}
-					// we also need to set ServicePoint.Certificate 
-					// and ServicePoint.ClientCertificate but this can
-					// only be done later (after handshake - which is
-					// done only after a read operation).
-#else
-					throw new NotSupportedException ();
 #endif
-				} else {
-					nstream = serverStream;
-				}
-			} catch (Exception ex) {
-				if (tlsStream != null)
-					status = tlsStream.ExceptionStatus;
-				else if (!request.Aborted)
-					status = WebExceptionStatus.ConnectFailure;
-				connect_exception = ex;
-				return false;
 			}
 
-			return true;
-		}
-		
-		void HandleError (WebExceptionStatus st, Exception e, string where)
-		{
-			status = st;
-			lock (this) {
-				if (st == WebExceptionStatus.RequestCanceled)
-					Data = new WebConnectionData ();
-			}
+			foreach (IPAddress address in hostEntry.AddressList) {
+				operation.ThrowIfDisposed (cancellationToken);
 
-			if (e == null) { // At least we now where it comes from
 				try {
-					throw new Exception (new System.Diagnostics.StackTrace ().ToString ());
-				} catch (Exception e2) {
-					e = e2;
+					socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+				} catch (Exception se) {
+					// The Socket ctor can throw if we run out of FD's
+					throw GetException (WebExceptionStatus.ConnectFailure, se);
 				}
-			}
-
-			HttpWebRequest req = null;
-			if (Data != null && Data.request != null)
-				req = Data.request;
-
-			Close (true);
-			if (req != null) {
-				req.FinishedReading = true;
-				req.SetResponseError (st, e, where);
-			}
-		}
-		
-		void ReadDone (IAsyncResult result)
-		{
-			WebConnectionData data = Data;
-			Stream ns = nstream;
-			if (ns == null) {
-				Close (true);
-				return;
-			}
-
-			int nread = -1;
-			try {
-				nread = ns.EndRead (result);
-			} catch (ObjectDisposedException) {
-				return;
-			} catch (Exception e) {
-				if (e.InnerException is ObjectDisposedException)
-					return;
-
-				HandleError (WebExceptionStatus.ReceiveFailure, e, "ReadDone1");
-				return;
-			}
-
-			if (nread == 0) {
-				HandleError (WebExceptionStatus.ReceiveFailure, null, "ReadDone2");
-				return;
-			}
-
-			if (nread < 0) {
-				HandleError (WebExceptionStatus.ServerProtocolViolation, null, "ReadDone3");
-				return;
-			}
-
-			int pos = -1;
-			nread += position;
-			if (data.ReadState == ReadState.None) { 
-				Exception exc = null;
+				IPEndPoint remote = new IPEndPoint (address, ServicePoint.Address.Port);
+				socket.NoDelay = !ServicePoint.UseNagleAlgorithm;
 				try {
-					pos = GetResponse (data, sPoint, buffer, nread);
-				} catch (Exception e) {
-					exc = e;
+					ServicePoint.KeepAliveSetup (socket);
+				} catch {
+					// Ignore. Not supported in all platforms.
 				}
 
-				if (exc != null || pos == -1) {
-					HandleError (WebExceptionStatus.ServerProtocolViolation, exc, "ReadDone4");
-					return;
+				if (!ServicePoint.CallEndPointDelegate (socket, remote)) {
+					Interlocked.Exchange (ref socket, null)?.Close ();
+					continue;
+				} else {
+					try {
+						operation.ThrowIfDisposed (cancellationToken);
+						await socket.ConnectAsync (remote).ConfigureAwait (false);
+					} catch (ObjectDisposedException) {
+						throw;
+					} catch (Exception exc) {
+						Interlocked.Exchange (ref socket, null)?.Close ();
+						throw GetException (WebExceptionStatus.ConnectFailure, exc);
+					}
 				}
-			}
-
-			if (data.ReadState == ReadState.Aborted) {
-				HandleError (WebExceptionStatus.RequestCanceled, null, "ReadDone");
-				return;
-			}
-
-			if (data.ReadState != ReadState.Content) {
-				int est = nread * 2;
-				int max = (est < buffer.Length) ? buffer.Length : est;
-				byte [] newBuffer = new byte [max];
-				Buffer.BlockCopy (buffer, 0, newBuffer, 0, nread);
-				buffer = newBuffer;
-				position = nread;
-				data.ReadState = ReadState.None;
-				InitRead ();
-				return;
-			}
-
-			position = 0;
-
-			WebConnectionStream stream = new WebConnectionStream (this, data);
-			bool expect_content = ExpectContent (data.StatusCode, data.request.Method);
-			string tencoding = null;
-			if (expect_content)
-				tencoding = data.Headers ["Transfer-Encoding"];
 
-			chunkedRead = (tencoding != null && tencoding.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
-			if (!chunkedRead) {
-				stream.ReadBuffer = buffer;
-				stream.ReadBufferOffset = pos;
-				stream.ReadBufferSize = nread;
-				try {
-					stream.CheckResponseInBuffer ();
-				} catch (Exception e) {
-					HandleError (WebExceptionStatus.ReceiveFailure, e, "ReadDone7");
-				}
-			} else if (chunkStream == null) {
-				try {
-					chunkStream = new MonoChunkStream (buffer, pos, nread, data.Headers);
-				} catch (Exception e) {
-					HandleError (WebExceptionStatus.ServerProtocolViolation, e, "ReadDone5");
-					return;
-				}
-			} else {
-				chunkStream.ResetBuffer ();
-				try {
-					chunkStream.Write (buffer, pos, nread);
-				} catch (Exception e) {
-					HandleError (WebExceptionStatus.ServerProtocolViolation, e, "ReadDone6");
+				if (socket != null)
 					return;
-				}
 			}
 
-			data.stream = stream;
-			
-			if (!expect_content)
-				stream.ForceCompletion ();
-
-			data.request.SetResponseData (data);
+			throw GetException (WebExceptionStatus.ConnectFailure, null);
 		}
 
-		static bool ExpectContent (int statusCode, string method)
-		{
-			if (method == "HEAD")
-				return false;
-			return (statusCode >= 200 && statusCode != 204 && statusCode != 304);
-		}
+#if MONO_WEB_DEBUG
+		static int nextID, nextRequestID;
+		readonly int id = ++nextID;
+		public int ID => disposed != 0 ? -id : id;
+#else
+		internal readonly int ID;
+#endif
 
-		internal void InitRead ()
+		async Task<bool> CreateStream (WebOperation operation, bool reused, CancellationToken cancellationToken)
 		{
-			Stream ns = nstream;
+#if MONO_WEB_DEBUG
+			var requestID = ++nextRequestID;
+#else
+			var requestID = 0;
+#endif
 
 			try {
-				int size = buffer.Length - position;
-				ns.BeginRead (buffer, position, size, ReadDone, null);
-			} catch (Exception e) {
-				HandleError (WebExceptionStatus.ReceiveFailure, e, "InitRead");
-			}
-		}
-		
-		static int GetResponse (WebConnectionData data, ServicePoint sPoint,
-		                        byte [] buffer, int max)
-		{
-			int pos = 0;
-			string line = null;
-			bool lineok = false;
-			bool isContinue = false;
-			bool emptyFirstLine = false;
-			do {
-				if (data.ReadState == ReadState.Aborted)
-					return -1;
-
-				if (data.ReadState == ReadState.None) {
-					lineok = ReadLine (buffer, ref pos, max, ref line);
-					if (!lineok)
-						return 0;
-
-					if (line == null) {
-						emptyFirstLine = true;
-						continue;
-					}
-					emptyFirstLine = false;
-					data.ReadState = ReadState.Status;
-
-					string [] parts = line.Split (' ');
-					if (parts.Length < 2)
-						return -1;
-
-					if (String.Compare (parts [0], "HTTP/1.1", true) == 0) {
-						data.Version = HttpVersion.Version11;
-						sPoint.SetVersion (HttpVersion.Version11);
-					} else {
-						data.Version = HttpVersion.Version10;
-						sPoint.SetVersion (HttpVersion.Version10);
-					}
-
-					data.StatusCode = (int) UInt32.Parse (parts [1]);
-					if (parts.Length >= 3)
-						data.StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
-					else
-						data.StatusDescription = "";
-
-					if (pos >= max)
-						return pos;
-				}
-
-				emptyFirstLine = false;
-				if (data.ReadState == ReadState.Status) {
-					data.ReadState = ReadState.Headers;
-					data.Headers = new WebHeaderCollection ();
-					ArrayList headers = new ArrayList ();
-					bool finished = false;
-					while (!finished) {
-						if (ReadLine (buffer, ref pos, max, 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 = headers.Count - 1;
-							if (count < 0)
-								break;
-
-							string prev = (string) headers [count] + line;
-							headers [count] = prev;
-						} else {
-							headers.Add (line);
-						}
-					}
-
-					if (!finished)
-						return 0;
-
-					// .NET uses ParseHeaders or ParseHeadersStrict which is much better
-					foreach (string s in headers) {
+				var stream = new NetworkStream (socket, false);
 
-						int pos_s = s.IndexOf (':');
-						if (pos_s == -1)
-							throw new ArgumentException ("no colon found", "header");
+				Debug ($"WC CREATE STREAM: Cnc={ID} {requestID} {reused} socket={socket.ID}");
 
-						var header = s.Substring (0, pos_s);
-						var value = s.Substring (pos_s + 1).Trim ();
-
-						var h = data.Headers;
-						if (WebHeaderCollection.AllowMultiValues (header)) {
-							h.AddInternal (header, value);
-						} else  {
-							h.SetInternal (header, value);
-						}
-					}
-
-					if (data.StatusCode == (int) HttpStatusCode.Continue) {
-						sPoint.SendContinue = true;
-						if (pos >= max)
-							return pos;
-
-						if (data.request.ExpectContinue) {
-							data.request.DoContinueDelegate (data.StatusCode, data.Headers);
-							// Prevent double calls when getting the
-							// headers in several packets.
-							data.request.ExpectContinue = false;
+				if (operation.Request.Address.Scheme == Uri.UriSchemeHttps) {
+					if (!reused || monoTlsStream == null) {
+						if (ServicePoint.UseConnect) {
+							if (tunnel == null)
+								tunnel = new WebConnectionTunnel (operation.Request, ServicePoint.Address);
+							await tunnel.Initialize (stream, cancellationToken).ConfigureAwait (false);
+							if (!tunnel.Success)
+								return false;
 						}
-
-						data.ReadState = ReadState.None;
-						isContinue = true;
-					}
-					else {
-						data.ReadState = ReadState.Content;
-						return pos;
+						monoTlsStream = new MonoTlsStream (operation.Request, stream);
+						networkStream = await monoTlsStream.CreateStream (tunnel, cancellationToken).ConfigureAwait (false);
 					}
+					return true;
 				}
-			} while (emptyFirstLine || isContinue);
 
-			return -1;
+				networkStream = stream;
+				return true;
+			} catch (Exception ex) {
+				ex = HttpWebRequest.FlattenException (ex);
+				Debug ($"WC CREATE STREAM EX: Cnc={ID} {requestID} {operation.Aborted} - {ex.Message}");
+				if (operation.Aborted || monoTlsStream == null)
+					throw GetException (WebExceptionStatus.ConnectFailure, ex);
+				throw GetException (monoTlsStream.ExceptionStatus, ex);
+			} finally {
+				Debug ($"WC CREATE STREAM DONE: Cnc={ID} {requestID}");
+			}
 		}
-		
-		void InitConnection (HttpWebRequest request)
-		{
-			request.WebConnection = this;
-			if (request.ReuseConnection)
-				request.StoredConnection = this;
 
-			if (request.Aborted)
-				return;
+		internal async Task<WebRequestStream> InitConnection (WebOperation operation, CancellationToken cancellationToken)
+		{
+			Debug ($"WC INIT CONNECTION: Cnc={ID} Req={operation.Request.ID} Op={operation.ID}");
 
-			keepAlive = request.KeepAlive;
-			Data = new WebConnectionData (request);
+			bool reset = true;
 		retry:
-			Connect (request);
-			if (request.Aborted)
-				return;
-
-			if (status != WebExceptionStatus.Success) {
-				if (!request.Aborted) {
-					request.SetWriteStreamError (status, connect_exception);
-					Close (true);
+			operation.ThrowIfClosedOrDisposed (cancellationToken);
+
+			var reused = CheckReusable ();
+			Debug ($"WC INIT CONNECTION #1: Cnc={ID} Op={operation.ID} - {reused} - {operation.WriteBuffer != null} {operation.IsNtlmChallenge}");
+			if (!reused) {
+				CloseSocket ();
+				if (reset)
+					Reset ();
+				try {
+					await Connect (operation, cancellationToken).ConfigureAwait (false);
+					Debug ($"WC INIT CONNECTION #2: Cnc={ID} Op={operation.ID} {socket.LocalEndPoint}");
+				} catch (Exception ex) {
+					Debug ($"WC INIT CONNECTION #2 FAILED: Cnc={ID} Op={operation.ID} - {ex.Message}\n{ex}");
+					throw;
 				}
-				return;
 			}
-			
-			if (!CreateStream (request)) {
-				if (request.Aborted)
-					return;
 
-				WebExceptionStatus st = status;
-				if (Data.Challenge != null)
-					goto retry;
+			var success = await CreateStream (operation, reused, cancellationToken).ConfigureAwait (false);
 
-				Exception cnc_exc = connect_exception;
-				if (cnc_exc == null && (Data.StatusCode == 401 || Data.StatusCode == 407)) {
-					st = WebExceptionStatus.ProtocolError;
-					if (Data.Headers == null)
-						Data.Headers = new WebHeaderCollection ();
+			Debug ($"WC INIT CONNECTION #3: Cnc={ID} Op={operation.ID} - {success}");
+			if (!success) {
+				if (tunnel?.Challenge == null)
+					throw GetException (WebExceptionStatus.ProtocolError, null);
 
-					var webResponse = new HttpWebResponse (sPoint.Address, "CONNECT", Data, null);
-					cnc_exc = new WebException (Data.StatusCode == 407 ? "(407) Proxy Authentication Required" : "(401) Unauthorized", null, st, webResponse);
-				}
-			
-				connect_exception = null;
-				request.SetWriteStreamError (st, cnc_exc);
-				Close (true);
-				return;
+				if (tunnel.CloseConnection)
+					CloseSocket ();
+				reset = false;
+				goto retry;
 			}
 
-			request.SetWriteStream (new WebConnectionStream (this, request));
+			return new WebRequestStream (this, operation, networkStream, tunnel);
 		}
 
-#if MONOTOUCH
-		static bool warned_about_queue = false;
-#endif
-
-		internal EventHandler SendRequest (HttpWebRequest request)
-		{
-			if (request.Aborted)
-				return null;
-
-			lock (this) {
-				if (state.TrySetBusy ()) {
-					status = WebExceptionStatus.Success;
-					ThreadPool.QueueUserWorkItem (o => { try { InitConnection ((HttpWebRequest) o); } catch {} }, request);
-				} else {
-					lock (queue) {
-#if MONOTOUCH
-						if (!warned_about_queue) {
-							warned_about_queue = true;
-							Console.WriteLine ("WARNING: An HttpWebRequest was added to the ConnectionGroup queue because the connection limit was reached.");
-						}
-#endif
-						queue.Enqueue (request);
-					}
-				}
-			}
-
-			return abortHandler;
-		}
-		
-		void SendNext ()
+		internal static WebException GetException (WebExceptionStatus status, Exception error)
 		{
-			lock (queue) {
-				if (queue.Count > 0) {
-					SendRequest ((HttpWebRequest) queue.Dequeue ());
-				}
-			}
+			if (error == null)
+				return new WebException ($"Error: {status}", status);
+			if (error is WebException wex)
+				return wex;
+			return new WebException ($"Error: {status} ({error.Message})", status,
+						 WebExceptionInternalStatus.RequestFatal, error);
 		}
 
-		internal void NextRead ()
-		{
-			lock (this) {
-				if (Data.request != null)
-					Data.request.FinishedReading = true;
-				string header = (sPoint.UsesProxy) ? "Proxy-Connection" : "Connection";
-				string cncHeader = (Data.Headers != null) ? Data.Headers [header] : null;
-				bool keepAlive = (Data.Version == HttpVersion.Version11 && this.keepAlive);
-				if (Data.ProxyVersion != null && Data.ProxyVersion != HttpVersion.Version11)
-					keepAlive = false;
-				if (cncHeader != null) {
-					cncHeader = cncHeader.ToLower ();
-					keepAlive = (this.keepAlive && cncHeader.IndexOf ("keep-alive", StringComparison.Ordinal) != -1);
-				}
-
-				if ((socket != null && !socket.Connected) ||
-				   (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close", StringComparison.Ordinal) != -1))) {
-					Close (false);
-				}
-
-				state.SetIdle ();
-				if (priority_request != null) {
-					SendRequest (priority_request);
-					priority_request = null;
-				} else {
-					SendNext ();
-				}
-			}
-		}
-		
-		static bool ReadLine (byte [] buffer, ref int start, int max, ref string output)
+		internal static bool ReadLine (byte[] buffer, ref int start, int max, ref string output)
 		{
 			bool foundCR = false;
 			StringBuilder text = new StringBuilder ();
 
 			int c = 0;
 			while (start < max) {
-				c = (int) buffer [start++];
+				c = (int)buffer[start++];
 
-				if (c == '\n') {			// newline
-					if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
+				if (c == '\n') {                        // newline
+					if ((text.Length > 0) && (text[text.Length - 1] == '\r'))
 						text.Length--;
 
 					foundCR = false;
@@ -841,9 +300,9 @@ namespace System.Net
 
 				if (c == '\r')
 					foundCR = true;
-					
 
-				text.Append ((char) c);
+
+				text.Append ((char)c);
 			}
 
 			if (c != '\n' && c != '\r')
@@ -861,357 +320,180 @@ namespace System.Net
 			return true;
 		}
 
-
-		internal IAsyncResult BeginRead (HttpWebRequest request, byte [] buffer, int offset, int size, AsyncCallback cb, object state)
+		internal bool CanReuseConnection (WebOperation operation)
 		{
-			Stream s = null;
 			lock (this) {
-				if (Data.request != request)
-					throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-				if (nstream == null)
-					return null;
-				s = nstream;
-			}
-
-			IAsyncResult result = null;
-			if (!chunkedRead || (!chunkStream.DataAvailable && chunkStream.WantMore)) {
-				try {
-					result = s.BeginRead (buffer, offset, size, cb, state);
-					cb = null;
-				} catch (Exception) {
-					HandleError (WebExceptionStatus.ReceiveFailure, null, "chunked BeginRead");
-					throw;
-				}
-			}
-
-			if (chunkedRead) {
-				WebAsyncResult wr = new WebAsyncResult (cb, state, buffer, offset, size);
-				wr.InnerAsyncResult = result;
-				if (result == null) {
-					// Will be completed from the data in MonoChunkStream
-					wr.SetCompleted (true, (Exception) null);
-					wr.DoCallback ();
-				}
-				return wr;
-			}
+				if (Closed || currentOperation != null)
+					return false;
+				if (!NtlmAuthenticated)
+					return true;
 
-			return result;
-		}
-		
-		internal int EndRead (HttpWebRequest request, IAsyncResult result)
-		{
-			Stream s = null;
-			lock (this) {
-				if (request.Aborted)
-					throw new WebException ("Request aborted", WebExceptionStatus.RequestCanceled);
-				if (Data.request != request)
-					throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-				if (nstream == null)
-					throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-				s = nstream;
-			}
+				NetworkCredential cnc_cred = NtlmCredential;
+				var request = operation.Request;
 
-			int nbytes = 0;
-			bool done = false;
-			WebAsyncResult wr = null;
-			IAsyncResult nsAsync = ((WebAsyncResult) result).InnerAsyncResult;
-			if (chunkedRead && (nsAsync is WebAsyncResult)) {
-				wr = (WebAsyncResult) nsAsync;
-				IAsyncResult inner = wr.InnerAsyncResult;
-				if (inner != null && !(inner is WebAsyncResult)) {
-					nbytes = s.EndRead (inner);
-					done = nbytes == 0;
-				}
-			} else if (!(nsAsync is WebAsyncResult)) {
-				nbytes = s.EndRead (nsAsync);
-				wr = (WebAsyncResult) result;
-				done = nbytes == 0;
-			}
+				bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
+				ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
+				NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
 
-			if (chunkedRead) {
-				try {
-					chunkStream.WriteAndReadBack (wr.Buffer, wr.Offset, wr.Size, ref nbytes);
-					if (!done && nbytes == 0 && chunkStream.WantMore)
-						nbytes = EnsureRead (wr.Buffer, wr.Offset, wr.Size);
-				} catch (Exception e) {
-					if (e is WebException)
-						throw e;
-
-					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);
+				if (cnc_cred == null || req_cred == null ||
+					cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
+					cnc_cred.Password != req_cred.Password) {
+					return false;
 				}
-			}
-
-			return (nbytes != 0) ? nbytes : -1;
-		}
 
-		// To be called on chunkedRead when we can read no data from the MonoChunkStream yet
-		int EnsureRead (byte [] buffer, int offset, int size)
-		{
-			byte [] morebytes = null;
-			int nbytes = 0;
-			while (nbytes == 0 && chunkStream.WantMore) {
-				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 = nstream.Read (morebytes, 0, localsize);
-				if (nread <= 0)
-					return 0; // Error
-
-				chunkStream.Write (morebytes, 0, nread);
-				nbytes += chunkStream.Read (buffer, offset + nbytes, size - nbytes);
+				bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
+				bool cnc_sharing = UnsafeAuthenticatedConnectionSharing;
+				return !(req_sharing == false || req_sharing != cnc_sharing);
 			}
-
-			return nbytes;
 		}
 
-		bool CompleteChunkedRead()
+		bool PrepareSharingNtlm (WebOperation operation)
 		{
-			if (!chunkedRead || chunkStream == null)
+			if (operation == null || !NtlmAuthenticated)
 				return true;
 
-			while (chunkStream.WantMore) {
-				int nbytes = nstream.Read (buffer, 0, buffer.Length);
-				if (nbytes <= 0)
-					return false; // Socket was disconnected
+			bool needs_reset = false;
+			NetworkCredential cnc_cred = NtlmCredential;
+			var request = operation.Request;
 
-				chunkStream.Write (buffer, 0, nbytes);
-			}
-
-			return true;
-  		}
+			bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
+			ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
+			NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
 
-		internal IAsyncResult BeginWrite (HttpWebRequest request, byte [] buffer, int offset, int size, AsyncCallback cb, object state)
-		{
-			Stream s = null;
-			lock (this) {
-				if (Data.request != request)
-					throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-				if (nstream == null)
-					return null;
-				s = nstream;
+			if (cnc_cred == null || req_cred == null ||
+				cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
+				cnc_cred.Password != req_cred.Password) {
+				needs_reset = true;
 			}
 
-			IAsyncResult result = null;
-			try {
-				result = s.BeginWrite (buffer, offset, size, cb, state);
-			} catch (ObjectDisposedException) {
-				lock (this) {
-					if (Data.request != request)
-						return null;
-				}
-				throw;
-			} catch (IOException e) {
-				SocketException se = e.InnerException as SocketException;
-				if (se != null && se.SocketErrorCode == SocketError.NotConnected) {
-					return null;
-				}
-				throw;
-			} catch (Exception) {
-				status = WebExceptionStatus.SendFailure;
-				throw;
+			if (!needs_reset) {
+				bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
+				bool cnc_sharing = UnsafeAuthenticatedConnectionSharing;
+				needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
 			}
 
-			return result;
+			return needs_reset;
 		}
 
-		internal bool EndWrite (HttpWebRequest request, bool throwOnError, IAsyncResult result)
+		void Reset ()
 		{
-			Stream s = null;
 			lock (this) {
-				if (status == WebExceptionStatus.RequestCanceled)
-					return true;
-				if (Data.request != request)
-					throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-				if (nstream == null)
-					throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-				s = nstream;
-			}
-
-			try {
-				s.EndWrite (result);
-				return true;
-			} catch (Exception exc) {
-				status = WebExceptionStatus.SendFailure;
-				if (throwOnError && exc.InnerException != null)
-					throw exc.InnerException;
-				return false;
+				tunnel = null;
+				ResetNtlm ();
 			}
 		}
 
-		internal int Read (HttpWebRequest request, byte [] buffer, int offset, int size)
+		void Close (bool reset)
 		{
-			Stream s = null;
 			lock (this) {
-				if (Data.request != request)
-					throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-				if (nstream == null)
-					return 0;
-				s = nstream;
+				CloseSocket ();
+				if (reset)
+					Reset ();
 			}
+		}
 
-			int result = 0;
-			try {
-				bool done = false;
-				if (!chunkedRead) {
-					result = s.Read (buffer, offset, size);
-					done = (result == 0);
+		void CloseSocket ()
+		{
+			lock (this) {
+				if (networkStream != null) {
+					try {
+						networkStream.Dispose ();
+					} catch { }
+					networkStream = null;
 				}
 
-				if (chunkedRead) {
+				if (socket != null) {
 					try {
-						chunkStream.WriteAndReadBack (buffer, offset, size, ref result);
-						if (!done && result == 0 && chunkStream.WantMore)
-							result = EnsureRead (buffer, offset, size);
-					} catch (Exception e) {
-						HandleError (WebExceptionStatus.ReceiveFailure, e, "chunked Read1");
-						throw;
-					}
-
-					if ((done || result == 0) && chunkStream.WantMore) {
-						HandleError (WebExceptionStatus.ReceiveFailure, null, "chunked Read2");
-						throw new WebException ("Read error", null, WebExceptionStatus.ReceiveFailure, null);
-					}
+						socket.Dispose ();
+					} catch { }
+					socket = null;
 				}
-			} catch (Exception e) {
-				HandleError (WebExceptionStatus.ReceiveFailure, e, "Read");
+
+				monoTlsStream = null;
 			}
+		}
+
+		DateTime idleSince;
+		WebOperation currentOperation;
 
-			return result;
+		public bool Closed => disposed != 0;
+
+		public bool Busy {
+			get { return currentOperation != null; }
+		}
+
+		public DateTime IdleSince {
+			get { return idleSince; }
 		}
 
-		internal bool Write (HttpWebRequest request, byte [] buffer, int offset, int size, ref string err_msg)
+		public bool StartOperation (WebOperation operation, bool reused)
 		{
-			err_msg = null;
-			Stream s = null;
 			lock (this) {
-				if (Data.request != request)
-					throw new ObjectDisposedException (typeof (NetworkStream).FullName);
-				s = nstream;
-				if (s == null)
+				if (Closed)
 					return false;
-			}
-
-			try {
-				s.Write (buffer, offset, size);
-			} catch (Exception e) {
-				err_msg = e.Message;
-				WebExceptionStatus wes = WebExceptionStatus.SendFailure;
-				string msg = "Write: " + err_msg;
-				if (e is WebException) {
-					HandleError (wes, e, msg);
+				if (Interlocked.CompareExchange (ref currentOperation, operation, null) != null)
 					return false;
+
+				idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
+
+				if (reused && !PrepareSharingNtlm (operation)) {
+					Debug ($"WC START - CAN'T REUSE: Cnc={ID} Op={operation.ID}");
+					Close (true);
 				}
 
-				HandleError (wes, e, msg);
-				return false;
+				operation.RegisterRequest (ServicePoint, this);
+				Debug ($"WC START: Cnc={ID} Op={operation.ID}");
 			}
+
+			operation.Run ();
 			return true;
 		}
 
-		internal void Close (bool sendNext)
+		public bool Continue (WebOperation next)
 		{
 			lock (this) {
-				if (Data != null && Data.request != null && Data.request.ReuseConnection) {
-					Data.request.ReuseConnection = false;
-					return;
-				}
+				if (Closed)
+					return false;
 
-				if (nstream != null) {
-					try {
-						nstream.Close ();
-					} catch {}
-					nstream = null;
+				Debug ($"WC CONTINUE: Cnc={ID} connected={socket?.Connected} next={next?.ID} current={currentOperation?.ID}");
+				if (socket == null || !socket.Connected || !PrepareSharingNtlm (next)) {
+					Close (true);
+					return false;
 				}
 
-				if (socket != null) {
-					try {
-						socket.Close ();
-					} catch {}
-					socket = null;
-				}
+				currentOperation = next;
 
-				if (ntlm_authenticated)
-					ResetNtlm ();
-				if (Data != null) {
-					lock (Data) {
-						Data.ReadState = ReadState.Aborted;
-					}
-				}
-				state.SetIdle ();
-				Data = new WebConnectionData ();
-				if (sendNext)
-					SendNext ();
-				
-				connect_request = null;
-				connect_ntlm_auth_state = NtlmAuthState.None;
+				if (next == null)
+					return true;
+
+				// Ok, we got another connection.  Let's run it!
+				next.RegisterRequest (ServicePoint, this);
 			}
+
+			next.Run ();
+			return true;
 		}
 
-		void Abort (object sender, EventArgs args)
+		void Dispose (bool disposing)
 		{
-			lock (this) {
-				lock (queue) {
-					HttpWebRequest req = (HttpWebRequest) sender;
-					if (Data.request == req || Data.request == null) {
-						if (!req.FinishedReading) {
-							status = WebExceptionStatus.RequestCanceled;
-							Close (false);
-							if (queue.Count > 0) {
-								Data.request = (HttpWebRequest) queue.Dequeue ();
-								SendRequest (Data.request);
-							}
-						}
-						return;
-					}
+			if (Interlocked.CompareExchange (ref disposed, 1, 0) != 0)
+				return;
+			Debug ($"WC DISPOSE: Cnc={ID}");
+			Close (true);
+		}
 
-					req.FinishedReading = true;
-					req.SetResponseError (WebExceptionStatus.RequestCanceled, null, "User aborted");
-					if (queue.Count > 0 && queue.Peek () == sender) {
-						queue.Dequeue ();
-					} else if (queue.Count > 0) {
-						object [] old = queue.ToArray ();
-						queue.Clear ();
-						for (int i = old.Length - 1; i >= 0; i--) {
-							if (old [i] != sender)
-								queue.Enqueue (old [i]);
-						}
-					}
-				}
-			}
+		public void Dispose ()
+		{
+			Dispose (true);
 		}
 
-		internal void ResetNtlm ()
+		void ResetNtlm ()
 		{
 			ntlm_authenticated = false;
 			ntlm_credentials = null;
 			unsafe_sharing = false;
 		}
 
-		internal bool Connected {
-			get {
-				lock (this) {
-					return (socket != null && socket.Connected);
-				}
-			}
-		}
-
-		// -Used for NTLM authentication
-		internal HttpWebRequest PriorityRequest {
-			set { priority_request = value; }
-		}
-
 		internal bool NtlmAuthenticated {
 			get { return ntlm_authenticated; }
 			set { ntlm_authenticated = value; }

+ 0 - 80
mcs/class/System/System.Net/WebConnectionData.cs

@@ -1,80 +0,0 @@
-//
-// System.Net.WebConnectionData
-//
-// Authors:
-//	Gonzalo Paniagua Javier ([email protected])
-//
-// (C) 2003 Ximian, Inc (http://www.ximian.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;
-
-namespace System.Net
-{
-	class WebConnectionData
-	{
-		HttpWebRequest _request;
-		public int StatusCode;
-		public string StatusDescription;
-		public WebHeaderCollection Headers;
-		public Version Version;
-		public Version ProxyVersion;
-		public Stream stream;
-		public string[] Challenge;
-		ReadState _readState;
-
-		public WebConnectionData ()
-		{
-			_readState = ReadState.None;
-		}
-
-		public WebConnectionData (HttpWebRequest request)
-		{
-			this._request = request;
-		}
-
-		public HttpWebRequest request {
-			get {
-				return _request;
-			}
-			set {
-				_request = value;
-			}
-		}
-
-		public ReadState ReadState {
-			get {
-				return _readState;
-			}
-			set {
-				lock (this) {
-					if ((_readState == ReadState.Aborted) && (value != ReadState.Aborted))
-						throw new WebException ("Aborted", WebExceptionStatus.RequestCanceled);
-					_readState = value;
-				}
-			}
-		}
-	}
-}
-

+ 0 - 292
mcs/class/System/System.Net/WebConnectionGroup.cs

@@ -1,292 +0,0 @@
-//
-// System.Net.WebConnectionGroup
-//
-// Authors:
-//	Gonzalo Paniagua Javier ([email protected])
-//      Martin Baulig ([email protected])
-//
-// (C) 2003 Ximian, Inc (http://www.ximian.com)
-// Copyright 2011-2014 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;
-using System.Threading;
-using System.Collections;
-using System.Collections.Generic;
-using System.Net.Configuration;
-using System.Net.Sockets;
-using System.Diagnostics;
-
-namespace System.Net
-{
-	class WebConnectionGroup
-	{
-		ServicePoint sPoint;
-		string name;
-		LinkedList<ConnectionState> connections;
-		Queue queue;
-		bool closing;
-
-		public WebConnectionGroup (ServicePoint sPoint, string name)
-		{
-			this.sPoint = sPoint;
-			this.name = name;
-			connections = new LinkedList<ConnectionState> ();
-			queue = new Queue ();
-		}
-
-		public event EventHandler ConnectionClosed;
-
-		void OnConnectionClosed ()
-		{
-			if (ConnectionClosed != null)
-				ConnectionClosed (this, null);
-		}
-
-		public void Close ()
-		{
-			List<WebConnection> connectionsToClose = null;
-
-			//TODO: what do we do with the queue? Empty it out and abort the requests?
-			//TODO: abort requests or wait for them to finish
-			lock (sPoint) {
-				closing = true;
-				var iter = connections.First;
-				while (iter != null) {
-					var cnc = iter.Value.Connection;
-					var node = iter;
-					iter = iter.Next;
-
-					// Closing connections inside the lock leads to a deadlock.
-					if (connectionsToClose == null)
-						connectionsToClose = new List<WebConnection>();
-
-					connectionsToClose.Add (cnc);
-					connections.Remove (node);
-				}
-			}
-
-			if (connectionsToClose != null) {
-				foreach (var cnc in connectionsToClose) {
-					cnc.Close (false);
-					OnConnectionClosed ();
-				}
-			}
-		}
-
-		public WebConnection GetConnection (HttpWebRequest request, out bool created)
-		{
-			lock (sPoint) {
-				return CreateOrReuseConnection (request, out created);
-			}
-		}
-
-		static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
-		{
-			if (!cnc.NtlmAuthenticated)
-				return;
-
-			bool needs_reset = false;
-			NetworkCredential cnc_cred = cnc.NtlmCredential;
-
-			bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
-			ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
-			NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
-
-			if (cnc_cred == null || req_cred == null ||
-				cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
-				cnc_cred.Password != req_cred.Password) {
-				needs_reset = true;
-			}
-
-			if (!needs_reset) {
-				bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
-				bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
-				needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
-			}
-			if (needs_reset) {
-				cnc.Close (false); // closes the authenticated connection
-				cnc.ResetNtlm ();
-			}
-		}
-
-		ConnectionState FindIdleConnection ()
-		{
-			foreach (var cnc in connections) {
-				if (cnc.Busy)
-					continue;
-
-				connections.Remove (cnc);
-				connections.AddFirst (cnc);
-				return cnc;
-			}
-
-			return null;
-		}
-
-		WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
-		{
-			var cnc = FindIdleConnection ();
-			if (cnc != null) {
-				created = false;
-				PrepareSharingNtlm (cnc.Connection, request);
-				return cnc.Connection;
-			}
-
-			if (sPoint.ConnectionLimit > connections.Count || connections.Count == 0) {
-				created = true;
-				cnc = new ConnectionState (this);
-				connections.AddFirst (cnc);
-				return cnc.Connection;
-			}
-
-			created = false;
-			cnc = connections.Last.Value;
-			connections.Remove (cnc);
-			connections.AddFirst (cnc);
-			return cnc.Connection;
-		}
-
-		public string Name {
-			get { return name; }
-		}
-
-		internal Queue Queue {
-			get { return queue; }
-		}
-
-		internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
-		{
-			var now = DateTime.UtcNow;
-
-		again:
-			bool recycled;
-			List<WebConnection> connectionsToClose = null;
-
-			lock (sPoint) {
-				if (closing) {
-					idleSince = DateTime.MinValue;
-					return true;
-				}
-
-				int count = 0;
-				var iter = connections.First;
-				while (iter != null) {
-					var cnc = iter.Value;
-					var node = iter;
-					iter = iter.Next;
-
-					++count;
-					if (cnc.Busy)
-						continue;
-
-					if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
-						if (cnc.IdleSince > idleSince)
-							idleSince = cnc.IdleSince;
-						continue;
-					}
-
-					/*
-					 * Do not call WebConnection.Close() while holding the ServicePoint lock
-					 * as this could deadlock when attempting to take the WebConnection lock.
-					 * 
-					 */
-
-					if (connectionsToClose == null)
-						connectionsToClose = new List<WebConnection> ();
-					connectionsToClose.Add (cnc.Connection);
-					connections.Remove (node);
-				}
-
-				recycled = connections.Count == 0;
-			}
-
-			// Did we find anything that can be closed?
-			if (connectionsToClose == null)
-				return recycled;
-
-			// Ok, let's get rid of these!
-			foreach (var cnc in connectionsToClose)
-				cnc.Close (false);
-
-			// Re-take the lock, then remove them from the connection list.
-			goto again;
-		}
-
-		class ConnectionState : IWebConnectionState {
-			public WebConnection Connection {
-				get;
-				private set;
-			}
-
-			public WebConnectionGroup Group {
-				get;
-				private set;
-			}
-
-			public ServicePoint ServicePoint {
-				get { return Group.sPoint; }
-			}
-
-			bool busy;
-			DateTime idleSince;
-
-			public bool Busy {
-				get { return busy; }
-			}
-
-			public DateTime IdleSince {
-				get { return idleSince; }
-			}
-
-			public bool TrySetBusy ()
-			{
-				lock (ServicePoint) {
-					if (busy)
-						return false;
-					busy = true;
-					idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
-					return true;
-				}
-			}
-
-			public void SetIdle ()
-			{
-				lock (ServicePoint) {
-					busy = false;
-					idleSince = DateTime.UtcNow;
-				}
-			}
-
-			public ConnectionState (WebConnectionGroup group)
-			{
-				Group = group;
-				idleSince = DateTime.UtcNow;
-				Connection = new WebConnection (this, group.sPoint);
-			}
-		}
-		
-	}
-}
-

+ 94 - 696
mcs/class/System/System.Net/WebConnectionStream.cs

@@ -3,9 +3,11 @@
 //
 // Authors:
 //	Gonzalo Paniagua Javier ([email protected])
+//      Martin Baulig <[email protected]>
 //
 // (C) 2003 Ximian, Inc (http://www.ximian.com)
 // (C) 2004 Novell, Inc (http://www.novell.com)
+// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.com)
 //
 
 //
@@ -28,127 +30,53 @@
 // 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.Threading;
+using System.Threading.Tasks;
+using System.Runtime.ExceptionServices;
+using System.Net.Sockets;
 
 namespace System.Net
 {
-	class WebConnectionStream : Stream
+	abstract class WebConnectionStream : Stream
 	{
-		static byte [] crlf = new byte [] { 13, 10 };
-		bool isRead;
-		WebConnection cnc;
-		HttpWebRequest request;
-		byte [] readBuffer;
-		int readBufferOffset;
-		int readBufferSize;
-		int stream_length; // -1 when CL not present
-		long contentLength;
-		long totalRead;
-		internal long totalWritten;
-		bool nextReadCalled;
-		int pendingReads;
-		int pendingWrites;
-		ManualResetEvent pending;
-		bool allowBuffering;
-		bool sendChunked;
-		MemoryStream writeBuffer;
-		bool requestWritten;
-		byte [] headers;
+		protected bool closed;
 		bool disposed;
-		bool headersSent;
 		object locker = new object ();
-		bool initRead;
-		bool read_eof;
-		bool complete_request_written;
 		int read_timeout;
 		int write_timeout;
-		AsyncCallback cb_wrapper; // Calls to ReadCallbackWrapper or WriteCallbacWrapper
 		internal bool IgnoreIOErrors;
 
-		public WebConnectionStream (WebConnection cnc, WebConnectionData data)
-		{          
-			if (data == null)
-				throw new InvalidOperationException ("data was not initialized");
-			if (data.Headers == null)
-				throw new InvalidOperationException ("data.Headers was not initialized");
-			if (data.request == null)
-				throw new InvalidOperationException ("data.request was not initialized");
-			isRead = true;
-			cb_wrapper = new AsyncCallback (ReadCallbackWrapper);
-			pending = new ManualResetEvent (true);
-			this.request = data.request;
-			read_timeout = request.ReadWriteTimeout;
-			write_timeout = read_timeout;
-			this.cnc = cnc;
-			string contentType = data.Headers ["Transfer-Encoding"];
-			bool chunkedRead = (contentType != null && contentType.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
-			string clength = data.Headers ["Content-Length"];
-			if (!chunkedRead && clength != null && clength != "") {
-				try {
-					contentLength = Int32.Parse (clength);
-					if (contentLength == 0 && !IsNtlmAuth ()) {
-						ReadAll ();
-					}
-				} catch {
-					contentLength = Int64.MaxValue;
-				}
-			} else {
-				contentLength = Int64.MaxValue;
-			}
-
-			// Negative numbers?
-			if (!Int32.TryParse (clength, out stream_length))
-				stream_length = -1;
-		}
-
-		public WebConnectionStream (WebConnection cnc, HttpWebRequest request)
+		protected WebConnectionStream (WebConnection cnc, WebOperation operation, Stream stream)
 		{
-			read_timeout = request.ReadWriteTimeout;
+			Connection = cnc;
+			Operation = operation;
+			Request = operation.Request;
+			InnerStream = stream;
+
+			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 (sendChunked)
-				pending = new ManualResetEvent (true);
-			else if (allowBuffering)
-				writeBuffer = new MemoryStream ();
 		}
 
-		bool CheckAuthHeader (string headerName)
-		{
-			var authHeader = cnc.Data.Headers [headerName];
-			return (authHeader != null && authHeader.IndexOf ("NTLM", StringComparison.Ordinal) != -1);
+		internal HttpWebRequest Request {
+			get;
 		}
 
-		bool IsNtlmAuth ()
-		{
-			bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.Address));
-			if (isProxy && CheckAuthHeader ("Proxy-Authenticate"))
-				return true;
-			return CheckAuthHeader ("WWW-Authenticate");
+		internal WebConnection Connection {
+			get;
 		}
 
-		internal void CheckResponseInBuffer ()
-		{
-			if (contentLength > 0 && (readBufferSize - readBufferOffset) >= contentLength) {
-				if (!IsNtlmAuth ())
-					ReadAll ();
-			}
+		internal WebOperation Operation {
+			get;
 		}
 
-		internal HttpWebRequest Request {
-			get { return request; }
-		}
+		internal ServicePoint ServicePoint => Connection.ServicePoint;
 
-		internal WebConnection Connection {
-			get { return cnc; }
+		internal Stream InnerStream {
+			get;
 		}
+
 		public override bool CanTimeout {
 			get { return true; }
 		}
@@ -177,579 +105,126 @@ namespace System.Net
 			}
 		}
 
-		internal bool CompleteRequestWritten {
-			get { return complete_request_written; }
-		}
-
-		internal bool SendChunked {
-			set { sendChunked = value; }
-		}
-
-		internal byte [] ReadBuffer {
-			set { readBuffer = value; }
-		}
-
-		internal int ReadBufferOffset {
-			set { readBufferOffset = value; }
-		}
-		
-		internal int ReadBufferSize {
-			set { readBufferSize = value; }
-		}
-		
-		internal byte[] WriteBuffer {
-			get { return writeBuffer.GetBuffer (); }
-		}
-
-		internal int WriteBufferLength {
-			get { return writeBuffer != null ? (int) writeBuffer.Length : (-1); }
-		}
-
-		internal void ForceCompletion ()
-		{
-			if (!nextReadCalled) {
-				if (contentLength == Int64.MaxValue)
-					contentLength = 0;
-				nextReadCalled = true;
-				cnc.NextRead ();
-			}
-		}
-
-		internal void CheckComplete ()
+		protected Exception GetException (Exception e)
 		{
-			bool nrc = nextReadCalled;
-			if (!nrc && readBufferSize - readBufferOffset == contentLength) {
-				nextReadCalled = true;
-				cnc.NextRead ();
-			}
+			e = HttpWebRequest.FlattenException (e);
+			if (e is WebException)
+				return e;
+			if (Operation.Aborted || e is OperationCanceledException || e is ObjectDisposedException)
+				return HttpWebRequest.CreateRequestAbortedException ();
+			return e;
 		}
 
-		internal void ReadAll ()
+		public override int Read (byte[] buffer, int offset, int size)
 		{
-			if (!isRead || read_eof || totalRead >= contentLength || nextReadCalled) {
-				if (isRead && !nextReadCalled) {
-					nextReadCalled = true;
-					cnc.NextRead ();
-				}
-				return;
-			}
-
-			if (!pending.WaitOne (ReadTimeout))
-				throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
-			lock (locker) {
-				if (totalRead >= contentLength)
-					return;
-				
-				byte [] b = null;
-				int diff = readBufferSize - readBufferOffset;
-				int new_size;
-
-				if (contentLength == Int64.MaxValue) {
-					MemoryStream ms = new MemoryStream ();
-					byte [] buffer = null;
-					if (readBuffer != null && diff > 0) {
-						ms.Write (readBuffer, readBufferOffset, diff);
-						if (readBufferSize >= 8192)
-							buffer = readBuffer;
-					}
-
-					if (buffer == null)
-						buffer = new byte [8192];
-
-					int read;
-					while ((read = cnc.Read (request, buffer, 0, buffer.Length)) != 0)
-						ms.Write (buffer, 0, read);
-
-					b = ms.GetBuffer ();
-					new_size = (int) ms.Length;
-					contentLength = new_size;
-				} else {
-					new_size = (int) (contentLength - totalRead);
-					b = new byte [new_size];
-					if (readBuffer != null && diff > 0) {
-						if (diff > new_size)
-							diff = new_size;
-
-						Buffer.BlockCopy (readBuffer, readBufferOffset, b, 0, diff);
-					}
-					
-					int remaining = new_size - diff;
-					int r = -1;
-					while (remaining > 0 && r != 0) {
-						r = cnc.Read (request, b, diff, remaining);
-						remaining -= r;
-						diff += r;
-					}
-				}
-
-				readBuffer = b;
-				readBufferOffset = 0;
-				readBufferSize = new_size;
-				totalRead = 0;
-				nextReadCalled = true;
-			}
+			if (!CanRead)
+				throw new NotSupportedException (SR.net_writeonlystream);
+			Operation.ThrowIfClosedOrDisposed ();
 
-			cnc.NextRead ();
-		}
-
-	   	void WriteCallbackWrapper (IAsyncResult r)
-		{
-			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 {
-				try {
-					EndWrite (r);
-				} catch {
-				}
-			}
-		}
+			if (buffer == null)
+				throw new ArgumentNullException (nameof (buffer));
 
-	   	void ReadCallbackWrapper (IAsyncResult r)
-		{
-			WebAsyncResult result;
-			if (r.AsyncState != null) {
-				result = (WebAsyncResult) r.AsyncState;
-				result.InnerAsyncResult = r;
-				result.DoCallback ();
-			} else {
-				try {
-					EndRead (r);
-				} catch {
-				}
-			}
-		}
+			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));
 
-		public override int Read (byte [] buffer, int offset, int size)
-		{
-			AsyncCallback cb = cb_wrapper;
-			WebAsyncResult res = (WebAsyncResult) BeginRead (buffer, offset, size, cb, null);
-			if (!res.IsCompleted && !res.WaitUntilComplete (ReadTimeout, false)) {
-				nextReadCalled = true;
-				cnc.Close (true);
-				throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
+			try {
+				return ReadAsync (buffer, offset, size, CancellationToken.None).Result;
+			} catch (Exception e) {
+				throw GetException (e);
 			}
-
-			return EndRead (res);
 		}
 
-		public override IAsyncResult BeginRead (byte [] buffer, int offset, int size,
+		public override IAsyncResult BeginRead (byte[] buffer, int offset, int size,
 							AsyncCallback cb, object state)
 		{
-			if (!isRead)
-				throw new NotSupportedException ("this stream does not allow reading");
+			if (!CanRead)
+				throw new NotSupportedException (SR.net_writeonlystream);
+			Operation.ThrowIfClosedOrDisposed ();
 
 			if (buffer == null)
-				throw new ArgumentNullException ("buffer");
+				throw new ArgumentNullException (nameof (buffer));
 
 			int length = buffer.Length;
 			if (offset < 0 || length < offset)
-				throw new ArgumentOutOfRangeException ("offset");
+				throw new ArgumentOutOfRangeException (nameof (offset));
 			if (size < 0 || (length - offset) < size)
-				throw new ArgumentOutOfRangeException ("size");
+				throw new ArgumentOutOfRangeException (nameof (size));
 
-			lock (locker) {
-				pendingReads++;
-				pending.Reset ();
-			}
-
-			WebAsyncResult result = new WebAsyncResult (cb, state, buffer, offset, size);
-			if (totalRead >= contentLength) {
-				result.SetCompleted (true, -1);
-				result.DoCallback ();
-				return result;
-			}
-			
-			int remaining = readBufferSize - readBufferOffset;
-			if (remaining > 0) {
-				int copy = (remaining > size) ? size : remaining;
-				Buffer.BlockCopy (readBuffer, readBufferOffset, buffer, offset, copy);
-				readBufferOffset += copy;
-				offset += copy;
-				size -= copy;
-				totalRead += copy;
-				if (size == 0 || totalRead >= contentLength) {
-					result.SetCompleted (true, copy);
-					result.DoCallback ();
-					return result;
-				}
-				result.NBytes = copy;
-			}
-
-			if (cb != null)
-				cb = cb_wrapper;
-
-			if (contentLength != Int64.MaxValue && contentLength - totalRead < size)
-				size = (int)(contentLength - totalRead);
-
-			if (!read_eof) {
-				result.InnerAsyncResult = cnc.BeginRead (request, buffer, offset, size, cb, result);
-			} else {
-				result.SetCompleted (true, result.NBytes);
-				result.DoCallback ();
-			}
-			return result;
+			var task = ReadAsync (buffer, offset, size, CancellationToken.None);
+			return TaskToApm.Begin (task, cb, state);
 		}
 
 		public override int EndRead (IAsyncResult r)
 		{
-			WebAsyncResult result = (WebAsyncResult) r;
-			if (result.EndCalled) {
-				int xx = result.NBytes;
-				return (xx >= 0) ? xx : 0;
-			}
-
-			result.EndCalled = true;
-
-			if (!result.IsCompleted) {
-				int nbytes = -1;
-				try {
-					nbytes = cnc.EndRead (request, result);
-				} catch (Exception exc) {
-					lock (locker) {
-						pendingReads--;
-						if (pendingReads == 0)
-							pending.Set ();
-					}
-
-					nextReadCalled = true;
-					cnc.Close (true);
-					result.SetCompleted (false, exc);
-					result.DoCallback ();
-					throw;
-				}
-
-				if (nbytes < 0) {
-					nbytes = 0;
-					read_eof = true;
-				}
-
-				totalRead += nbytes;
-				result.SetCompleted (false, nbytes + result.NBytes);
-				result.DoCallback ();
-				if (nbytes == 0)
-					contentLength = totalRead;
-			}
-
-			lock (locker) {
-				pendingReads--;
-				if (pendingReads == 0)
-					pending.Set ();
-			}
-
-			if (totalRead >= contentLength && !nextReadCalled)
-				ReadAll ();
-
-			int nb = result.NBytes;
-			return (nb >= 0) ? nb : 0;
-		}
-
-	   	void WriteAsyncCB (IAsyncResult r)
-		{
-			WebAsyncResult result = (WebAsyncResult) r.AsyncState;
-			result.InnerAsyncResult = null;
+			if (r == null)
+				throw new ArgumentNullException (nameof (r));
 
 			try {
-				cnc.EndWrite (request, true, r);
-				result.SetCompleted (false, 0);
-				if (!initRead) {
-					initRead = true;
-					cnc.InitRead ();
-				}
+				return TaskToApm.End<int> (r);
 			} 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);
+				throw GetException (e);
 			}
-
-			if (allowBuffering && !sendChunked && request.ContentLength > 0 && totalWritten == request.ContentLength)
-				complete_request_written = true;
-
-			result.DoCallback ();
 		}
 
-		public override IAsyncResult BeginWrite (byte [] buffer, int offset, int size,
-							AsyncCallback cb, object state)
+		public override IAsyncResult BeginWrite (byte[] buffer, int offset, int size,
+							 AsyncCallback cb, object state)
 		{
-			if (request.Aborted)
-				throw new WebException ("The request was canceled.", WebExceptionStatus.RequestCanceled);
-
-			if (isRead)
-				throw new NotSupportedException ("this stream does not allow writing");
+			if (!CanWrite)
+				throw new NotSupportedException (SR.net_readonlystream);
+			Operation.ThrowIfClosedOrDisposed ();
 
 			if (buffer == null)
-				throw new ArgumentNullException ("buffer");
+				throw new ArgumentNullException (nameof (buffer));
 
 			int length = buffer.Length;
 			if (offset < 0 || length < offset)
-				throw new ArgumentOutOfRangeException ("offset");
+				throw new ArgumentOutOfRangeException (nameof (offset));
 			if (size < 0 || (length - offset) < size)
-				throw new ArgumentOutOfRangeException ("size");
-
-			if (sendChunked) {
-				lock (locker) {
-					pendingWrites++;
-					pending.Reset ();
-				}
-			}
-
-			WebAsyncResult result = new WebAsyncResult (cb, state);
-			AsyncCallback callback = new AsyncCallback (WriteAsyncCB);
-
-			if (sendChunked) {
-				requestWritten = true;
-
-				string cSize = String.Format ("{0:X}\r\n", size);
-				byte[] head = Encoding.ASCII.GetBytes (cSize);
-				int chunkSize = 2 + size + head.Length;
-				byte[] newBuffer = new byte [chunkSize];
-				Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
-				Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
-				Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
-
-				if (allowBuffering) {
-					if (writeBuffer == null)
-						writeBuffer = new MemoryStream ();
-					writeBuffer.Write (buffer, offset, size);
-					totalWritten += size;
-				}
-
-				buffer = newBuffer;
-				offset = 0;
-				size = chunkSize;
-			} else {
-				CheckWriteOverflow (request.ContentLength, totalWritten, size);
-
-				if (allowBuffering) {
-					if (writeBuffer == null)
-						writeBuffer = new MemoryStream ();
-					writeBuffer.Write (buffer, offset, size);
-					totalWritten += size;
-
-					if (request.ContentLength <= 0 || totalWritten < request.ContentLength) {
-						result.SetCompleted (true, 0);
-						result.DoCallback ();
-						return result;
-					}
-
-					result.AsyncWriteAll = true;
-					requestWritten = true;
-					buffer = writeBuffer.GetBuffer ();
-					offset = 0;
-					size = (int)totalWritten;
-				}
-			}
+				throw new ArgumentOutOfRangeException (nameof (size));
 
-			try {
-				result.InnerAsyncResult = cnc.BeginWrite (request, buffer, offset, size, callback, result);
-				if (result.InnerAsyncResult == null) {
-					if (!result.IsCompleted)
-						result.SetCompleted (true, 0);
-					result.DoCallback ();
-				}
-			} 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.");
-			}
+			var task = WriteAsync (buffer, offset, size, CancellationToken.None);
+			return TaskToApm.Begin (task, cb, state);
 		}
 
 		public override void EndWrite (IAsyncResult r)
 		{
 			if (r == null)
-				throw new ArgumentNullException ("r");
-
-			WebAsyncResult result = r as WebAsyncResult;
-			if (result == null)
-				throw new ArgumentException ("Invalid IAsyncResult");
-
-			if (result.EndCalled)
-				return;
-
-			if (sendChunked) {
-				lock (locker) {
-					pendingWrites--;
-					if (pendingWrites <= 0)
-						pending.Set ();
-				}
-			}
+				throw new ArgumentNullException (nameof (r));
 
-			result.EndCalled = true;
-			if (result.AsyncWriteAll) {
-				result.WaitUntilComplete ();
-				if (result.GotException)
-					throw result.Exception;
-				return;
-			}
-
-			if (allowBuffering && !sendChunked)
-				return;
-
-			if (result.GotException)
-				throw result.Exception;
-		}
-		
-		public override void Write (byte [] buffer, int offset, int size)
-		{
-			AsyncCallback cb = cb_wrapper;
-			WebAsyncResult res = (WebAsyncResult) BeginWrite (buffer, offset, size, cb, null);
-			if (!res.IsCompleted && !res.WaitUntilComplete (WriteTimeout, false)) {
-				KillBuffer ();
-				nextReadCalled = true;
-				cnc.Close (true);
-				throw new IOException ("Write timed out.");
+			try {
+				TaskToApm.End (r);
+			} catch (Exception e) {
+				throw GetException (e);
 			}
-
-			EndWrite (res);
-		}
-
-		public override void Flush ()
-		{
-		}
-
-		internal void SetHeadersAsync (bool setInternalLength, SimpleAsyncCallback callback)
-		{
-			SimpleAsyncResult.Run (r => SetHeadersAsync (r, setInternalLength), callback);
 		}
 
-		bool SetHeadersAsync (SimpleAsyncResult result, bool setInternalLength)
+		public override void Write (byte[] buffer, int offset, int size)
 		{
-			if (headersSent)
-				return false;
-
-			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 (setInternalLength && !no_writestream && writeBuffer != null)
-				request.InternalContentLength = writeBuffer.Length;
+			if (!CanWrite)
+				throw new NotSupportedException (SR.net_readonlystream);
+			Operation.ThrowIfClosedOrDisposed ();
 
-			bool has_content = !no_writestream && (writeBuffer == null || request.ContentLength > -1);
-			if (!(sendChunked || has_content || no_writestream || webdav))
-				return false;
-
-			headersSent = true;
-			headers = request.GetRequestHeaders ();
-
-			var innerResult = cnc.BeginWrite (request, headers, 0, headers.Length, r => {
-				try {
-					cnc.EndWrite (request, true, r);
-					if (!initRead) {
-						initRead = true;
-						cnc.InitRead ();
-					}
-					var cl = request.ContentLength;
-					if (!sendChunked && cl == 0)
-						requestWritten = true;
-					result.SetCompleted (false);
-				} catch (WebException e) {
-					result.SetCompleted (false, e);
-				} catch (Exception e) {
-					result.SetCompleted (false, new WebException ("Error writing headers", WebExceptionStatus.SendFailure, WebExceptionInternalStatus.RequestFatal, e));
-				}
-			}, null);
-
-			return innerResult != null;
-		}
+			if (buffer == null)
+				throw new ArgumentNullException (nameof (buffer));
 
-		internal bool RequestWritten {
-			get { return requestWritten; }
-		}
+			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));
 
-		internal SimpleAsyncResult WriteRequestAsync (SimpleAsyncCallback callback)
-		{
-			var result = WriteRequestAsync (callback);
 			try {
-				if (!WriteRequestAsync (result))
-					result.SetCompleted (true);
-			} catch (Exception ex) {
-				result.SetCompleted (true, ex);
+				WriteAsync (buffer, offset, size).Wait ();
+			} catch (Exception e) {
+				throw GetException (e);
 			}
-			return result;
 		}
 
-		internal bool WriteRequestAsync (SimpleAsyncResult result)
+		public override void Flush ()
 		{
-			if (requestWritten)
-				return false;
-
-			requestWritten = true;
-			if (sendChunked || !allowBuffering || writeBuffer == null)
-				return false;
-
-			// Keep the call for a potential side-effect of GetBuffer
-			var bytes = writeBuffer.GetBuffer ();
-			var 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);
-			}
-
-			SetHeadersAsync (true, inner => {
-				if (inner.GotException) {
-					result.SetCompleted (inner.CompletedSynchronouslyPeek, inner.Exception);
-					return;
-				}
-
-				if (cnc.Data.StatusCode != 0 && cnc.Data.StatusCode != 100) {
-					result.SetCompleted (inner.CompletedSynchronouslyPeek);
-					return;
-				}
-
-				if (!initRead) {
-					initRead = true;
-					cnc.InitRead ();
-				}
-
-				if (length == 0) {
-					complete_request_written = true;
-					result.SetCompleted (inner.CompletedSynchronouslyPeek);
-					return;
-				}
-
-				cnc.BeginWrite (request, bytes, 0, length, r => {
-					try {
-						complete_request_written = cnc.EndWrite (request, false, r);
-						result.SetCompleted (false);
-					} catch (Exception exc) {
-						result.SetCompleted (false, exc);
-					}
-				}, null);
-			});
-
-			return true;
 		}
 
 		internal void InternalClose ()
@@ -757,103 +232,26 @@ namespace System.Net
 			disposed = true;
 		}
 
-		internal bool GetResponseOnClose {
-			get; set;
-		}
+		protected abstract void Close_internal (ref bool disposed);
 
 		public override void Close ()
 		{
-			if (GetResponseOnClose) {
-				if (disposed)
-					return;
-				disposed = true;
-				var response = (HttpWebResponse)request.GetResponse ();
-				response.ReadAll ();
-				response.Close ();
-				return;
-			}
-
-			if (sendChunked) {
-				if (disposed)
-					return;
-				disposed = true;
-				if (!pending.WaitOne (WriteTimeout)) {
-					throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
-				}
-				byte [] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
-				string err_msg = null;
-				cnc.Write (request, chunk, 0, chunk.Length, ref err_msg);
-				return;
-			}
-
-			if (isRead) {
-				if (!nextReadCalled) {
-					CheckComplete ();
-					// If we have not read all the contents
-					if (!nextReadCalled) {
-						nextReadCalled = true;
-						cnc.Close (true);
-					}
-				}
-				return;
-			} else if (!allowBuffering) {
-				complete_request_written = true;
-				if (!initRead) {
-					initRead = true;
-					cnc.InitRead ();
-				}
-				return;
-			}
-
-			if (disposed || requestWritten)
-				return;
-
-			long length = request.ContentLength;
-
-			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.", WebExceptionStatus.RequestCanceled, WebExceptionInternalStatus.RequestFatal, io);
-			}
-
-			// Commented out the next line to fix xamarin bug #1512
-			//WriteRequest ();
-			disposed = true;
-		}
-
-		internal void KillBuffer ()
-		{
-			writeBuffer = null;
+			Close_internal (ref disposed);
 		}
 
 		public override long Seek (long a, SeekOrigin b)
 		{
 			throw new NotSupportedException ();
 		}
-		
+
 		public override void SetLength (long a)
 		{
 			throw new NotSupportedException ();
 		}
-		
-		public override bool CanSeek {
-			get { return false; }
-		}
-
-		public override bool CanRead {
-			get { return !disposed && isRead; }
-		}
 
-		public override bool CanWrite {
-			get { return !disposed && !isRead; }
-		}
-
-		public override long Length {
+		public override bool CanSeek {
 			get {
-				if (!isRead)
-					throw new NotSupportedException ();
-				return stream_length;
+				return false;
 			}
 		}
 

+ 271 - 0
mcs/class/System/System.Net/WebConnectionTunnel.cs

@@ -0,0 +1,271 @@
+//
+// System.Net.WebConnectionTunnel
+//
+// Authors:
+//	Gonzalo Paniagua Javier ([email protected])
+//      Martin Baulig <[email protected]>
+//
+// (C) 2003 Ximian, Inc (http://www.ximian.com)
+// 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.Collections;
+using System.Net.Sockets;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.ExceptionServices;
+using System.Diagnostics;
+using Mono.Net.Security;
+
+namespace System.Net
+{
+	class WebConnectionTunnel
+	{
+		public HttpWebRequest Request {
+			get;
+		}
+
+		public Uri ConnectUri {
+			get;
+		}
+
+		public WebConnectionTunnel (HttpWebRequest request, Uri connectUri)
+		{
+			Request = request;
+			ConnectUri = connectUri;
+		}
+
+		enum NtlmAuthState
+		{
+			None,
+			Challenge,
+			Response
+		}
+
+		HttpWebRequest connectRequest;
+		NtlmAuthState ntlmAuthState;
+
+		public bool Success {
+			get;
+			private set;
+		}
+
+		public bool CloseConnection {
+			get;
+			private set;
+		}
+
+		public int StatusCode {
+			get;
+			private set;
+		}
+
+		public string StatusDescription {
+			get;
+			private set;
+		}
+
+		public string[] Challenge {
+			get;
+			private set;
+		}
+
+		public WebHeaderCollection Headers {
+			get;
+			private set;
+		}
+
+		public Version ProxyVersion {
+			get;
+			private set;
+		}
+
+		public byte[] Data {
+			get;
+			private set;
+		}
+
+		internal async Task Initialize (Stream stream, CancellationToken cancellationToken)
+		{
+			StringBuilder sb = new StringBuilder ();
+			sb.Append ("CONNECT ");
+			sb.Append (Request.Address.Host);
+			sb.Append (':');
+			sb.Append (Request.Address.Port);
+			sb.Append (" HTTP/");
+			if (Request.ProtocolVersion == HttpVersion.Version11)
+				sb.Append ("1.1");
+			else
+				sb.Append ("1.0");
+
+			sb.Append ("\r\nHost: ");
+			sb.Append (Request.Address.Authority);
+
+			bool ntlm = false;
+			var challenge = Challenge;
+			Challenge = null;
+			var auth_header = Request.Headers["Proxy-Authorization"];
+			bool have_auth = auth_header != null;
+			if (have_auth) {
+				sb.Append ("\r\nProxy-Authorization: ");
+				sb.Append (auth_header);
+				ntlm = auth_header.ToUpper ().Contains ("NTLM");
+			} else if (challenge != null && StatusCode == 407) {
+				ICredentials creds = Request.Proxy.Credentials;
+				have_auth = true;
+
+				if (connectRequest == null) {
+					// create a CONNECT request to use with Authenticate
+					connectRequest = (HttpWebRequest)WebRequest.Create (
+						ConnectUri.Scheme + "://" + ConnectUri.Host + ":" + ConnectUri.Port + "/");
+					connectRequest.Method = "CONNECT";
+					connectRequest.Credentials = creds;
+				}
+
+				if (creds != null) {
+					for (int i = 0; i < challenge.Length; i++) {
+						var auth = AuthenticationManager.Authenticate (challenge[i], connectRequest, creds);
+						if (auth == null)
+							continue;
+						ntlm = (auth.ModuleAuthenticationType == "NTLM");
+						sb.Append ("\r\nProxy-Authorization: ");
+						sb.Append (auth.Message);
+						break;
+					}
+				}
+			}
+
+			if (ntlm) {
+				sb.Append ("\r\nProxy-Connection: keep-alive");
+				ntlmAuthState++;
+			}
+
+			sb.Append ("\r\n\r\n");
+
+			StatusCode = 0;
+			byte[] connectBytes = Encoding.Default.GetBytes (sb.ToString ());
+			await stream.WriteAsync (connectBytes, 0, connectBytes.Length, cancellationToken).ConfigureAwait (false);
+
+			(Headers, Data, StatusCode) = await ReadHeaders (stream, cancellationToken).ConfigureAwait (false);
+
+			if ((!have_auth || ntlmAuthState == NtlmAuthState.Challenge) && Headers != null && StatusCode == 407) { // Needs proxy auth
+				var connectionHeader = Headers["Connection"];
+				if (!string.IsNullOrEmpty (connectionHeader) && connectionHeader.ToLower () == "close") {
+					// The server is requesting that this connection be closed
+					CloseConnection = true;
+				}
+
+				Challenge = Headers.GetValues ("Proxy-Authenticate");
+				Success = false;
+			} else {
+				Success = StatusCode == 200 && Headers != null;
+			}
+
+			if (Challenge == null && (StatusCode == 401 || StatusCode == 407)) {
+				var response = new HttpWebResponse (ConnectUri, "CONNECT", (HttpStatusCode)StatusCode, Headers);
+				throw new WebException (
+					StatusCode == 407 ? "(407) Proxy Authentication Required" : "(401) Unauthorized",
+					null, WebExceptionStatus.ProtocolError, response);
+			}
+		}
+
+		async Task<(WebHeaderCollection, byte[], int)> ReadHeaders (Stream stream, CancellationToken cancellationToken)
+		{
+			byte[] retBuffer = null;
+			int status = 200;
+
+			byte[] buffer = new byte[1024];
+			MemoryStream ms = new MemoryStream ();
+
+			while (true) {
+				cancellationToken.ThrowIfCancellationRequested ();
+				int n = await stream.ReadAsync (buffer, 0, 1024, cancellationToken).ConfigureAwait (false);
+				if (n == 0)
+					throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
+
+				ms.Write (buffer, 0, n);
+				int start = 0;
+				string str = null;
+				bool gotStatus = false;
+				WebHeaderCollection headers = new WebHeaderCollection ();
+				while (WebConnection.ReadLine (ms.GetBuffer (), ref start, (int)ms.Length, ref str)) {
+					if (str == null) {
+						int contentLen;
+						var clengthHeader = headers["Content-Length"];
+						if (string.IsNullOrEmpty (clengthHeader) || !int.TryParse (clengthHeader, out contentLen))
+							contentLen = 0;
+
+						if (ms.Length - start - contentLen > 0) {
+							// we've read more data than the response header and conents,
+							// give back extra data to the caller
+							retBuffer = new byte[ms.Length - start - contentLen];
+							Buffer.BlockCopy (ms.GetBuffer (), start + contentLen, retBuffer, 0, retBuffer.Length);
+						} else {
+							// haven't read in some or all of the contents for the response, do so now
+							FlushContents (stream, contentLen - (int)(ms.Length - start));
+						}
+
+						return (headers, retBuffer, status);
+					}
+
+					if (gotStatus) {
+						headers.Add (str);
+						continue;
+					}
+
+					string[] parts = str.Split (' ');
+					if (parts.Length < 2)
+						throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
+
+					if (String.Compare (parts[0], "HTTP/1.1", true) == 0)
+						ProxyVersion = HttpVersion.Version11;
+					else if (String.Compare (parts[0], "HTTP/1.0", true) == 0)
+						ProxyVersion = HttpVersion.Version10;
+					else
+						throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
+
+					status = (int)UInt32.Parse (parts[1]);
+					if (parts.Length >= 3)
+						StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
+
+					gotStatus = true;
+				}
+			}
+		}
+
+		void FlushContents (Stream stream, int contentLength)
+		{
+			while (contentLength > 0) {
+				byte[] contentBuffer = new byte[contentLength];
+				int bytesRead = stream.Read (contentBuffer, 0, contentLength);
+				if (bytesRead > 0) {
+					contentLength -= bytesRead;
+				} else {
+					break;
+				}
+			}
+		}
+	}
+}

+ 344 - 0
mcs/class/System/System.Net/WebOperation.cs

@@ -0,0 +1,344 @@
+//
+// WebOperation.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.Collections;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.ExceptionServices;
+using System.Diagnostics;
+
+namespace System.Net
+{
+	class WebOperation
+	{
+		public HttpWebRequest Request {
+			get;
+		}
+
+		public WebConnection Connection {
+			get;
+			private set;
+		}
+
+		public ServicePoint ServicePoint {
+			get;
+			private set;
+		}
+
+		public BufferOffsetSize WriteBuffer {
+			get;
+		}
+
+		public bool IsNtlmChallenge {
+			get;
+		}
+
+#if MONO_WEB_DEBUG
+		static int nextID;
+		internal readonly int ID = ++nextID;
+#else
+		internal readonly int ID;
+#endif
+
+		public WebOperation (HttpWebRequest request, BufferOffsetSize writeBuffer, bool isNtlmChallenge, CancellationToken cancellationToken)
+		{
+			Request = request;
+			WriteBuffer = writeBuffer;
+			IsNtlmChallenge = isNtlmChallenge;
+			cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken);
+			requestTask = new TaskCompletionSource<WebRequestStream> ();
+			requestWrittenTask = new TaskCompletionSource<WebRequestStream> ();
+			completeResponseReadTask = new TaskCompletionSource<bool> ();
+			responseTask = new TaskCompletionSource<WebResponseStream> ();
+			finishedTask = new TaskCompletionSource<(bool, WebOperation)> (); 
+		}
+
+		CancellationTokenSource cts;
+		TaskCompletionSource<WebRequestStream> requestTask;
+		TaskCompletionSource<WebRequestStream> requestWrittenTask;
+		TaskCompletionSource<WebResponseStream> responseTask;
+		TaskCompletionSource<bool> completeResponseReadTask;
+		TaskCompletionSource<(bool, WebOperation)> finishedTask;
+		WebRequestStream writeStream;
+		WebResponseStream responseStream;
+		ExceptionDispatchInfo disposedInfo;
+		ExceptionDispatchInfo closedInfo;
+		WebOperation priorityRequest;
+		volatile bool finishedReading;
+		int requestSent;
+
+		public bool Aborted {
+			get {
+				if (disposedInfo != null || Request.Aborted)
+					return true;
+				if (cts != null && cts.IsCancellationRequested)
+					return true;
+				return false;
+			}
+		}
+
+		public bool Closed {
+			get {
+				return Aborted || closedInfo != null;
+			}
+		}
+
+		public void Abort ()
+		{
+			var (exception, disposed) = SetDisposed (ref disposedInfo);
+			if (!disposed)
+				return;
+			cts?.Cancel ();
+			SetCanceled ();
+			Close ();
+		}
+
+		public void Close ()
+		{
+			var (exception, closed) = SetDisposed (ref closedInfo);
+			if (!closed)
+				return;
+
+			var stream = Interlocked.Exchange (ref writeStream, null);
+			if (stream != null) {
+				try {
+					stream.Close ();
+				} catch { }
+			}
+		}
+
+		void SetCanceled ()
+		{
+			requestTask.TrySetCanceled ();
+			requestWrittenTask.TrySetCanceled ();
+			responseTask.TrySetCanceled ();
+			completeResponseReadTask.TrySetCanceled ();
+		}
+
+		void SetError (Exception error)
+		{
+			requestTask.TrySetException (error);
+			requestWrittenTask.TrySetException (error);
+			responseTask.TrySetException (error);
+			completeResponseReadTask.TrySetException (error);
+		}
+
+		(ExceptionDispatchInfo, bool) SetDisposed (ref ExceptionDispatchInfo field)
+		{
+			var wexc = new WebException (SR.GetString (SR.net_webstatus_RequestCanceled), WebExceptionStatus.RequestCanceled);
+			var exception = ExceptionDispatchInfo.Capture (wexc);
+			var old = Interlocked.CompareExchange (ref field, exception, null);
+			return (old ?? exception, old == null);
+		}
+
+		internal void ThrowIfDisposed ()
+		{
+			ThrowIfDisposed (CancellationToken.None);
+		}
+
+		internal void ThrowIfDisposed (CancellationToken cancellationToken)
+		{
+			if (Aborted || cancellationToken.IsCancellationRequested)
+				ThrowDisposed (ref disposedInfo);
+		}
+
+		internal void ThrowIfClosedOrDisposed ()
+		{
+			ThrowIfClosedOrDisposed (CancellationToken.None);
+		}
+
+		internal void ThrowIfClosedOrDisposed (CancellationToken cancellationToken)
+		{
+			if (Closed || cancellationToken.IsCancellationRequested)
+				ThrowDisposed (ref closedInfo);
+		}
+
+		void ThrowDisposed (ref ExceptionDispatchInfo field)
+		{
+			var (exception, disposed) = SetDisposed (ref field);
+			if (disposed)
+				cts?.Cancel ();
+			exception.Throw ();
+		}
+
+		internal void RegisterRequest (ServicePoint servicePoint, WebConnection connection)
+		{
+			if (servicePoint == null)
+				throw new ArgumentNullException (nameof (servicePoint));
+			if (connection == null)
+				throw new ArgumentNullException (nameof (connection));
+
+			lock (this) {
+				if (Interlocked.CompareExchange (ref requestSent, 1, 0) != 0)
+					throw new InvalidOperationException ("Invalid nested call.");
+				ServicePoint = servicePoint;
+				Connection = connection;
+			}
+
+			cts.Token.Register (() => {
+				Request.FinishedReading = true;
+				SetDisposed (ref disposedInfo);
+			});
+		}
+
+		public void SetPriorityRequest (WebOperation operation)
+		{
+			lock (this) {
+				if (requestSent != 1 || ServicePoint == null || finishedReading)
+					throw new InvalidOperationException ("Should never happen.");
+				if (Interlocked.CompareExchange (ref priorityRequest, operation, null) != null)
+					throw new InvalidOperationException ("Invalid nested request.");
+			}
+		}
+
+		public Task<WebRequestStream> GetRequestStream ()
+		{
+			return requestTask.Task;
+		}
+
+		public Task WaitUntilRequestWritten ()
+		{
+			return requestWrittenTask.Task;
+		}
+
+		public WebRequestStream WriteStream {
+			get {
+				ThrowIfDisposed ();
+				return writeStream;
+			}
+		}
+
+		public Task<WebResponseStream> GetResponseStream ()
+		{
+			return responseTask.Task;
+		}
+
+		internal async Task<(bool, WebOperation)> WaitForCompletion (bool ignoreErrors)
+		{
+			try {
+				return await finishedTask.Task.ConfigureAwait (false);
+			} catch {
+				if (ignoreErrors)
+					return (false, null);
+				throw;
+			}
+		}
+
+		internal async void Run ()
+		{
+			try {
+				FinishReading ();
+
+				ThrowIfClosedOrDisposed ();
+
+				var requestStream = await Connection.InitConnection (this, cts.Token).ConfigureAwait (false);
+
+				ThrowIfClosedOrDisposed ();
+
+				writeStream = requestStream;
+
+				await requestStream.Initialize (cts.Token).ConfigureAwait (false);
+
+				ThrowIfClosedOrDisposed ();
+
+				requestTask.TrySetResult (requestStream);
+
+				var stream = new WebResponseStream (requestStream);
+				responseStream = stream;
+
+				await stream.InitReadAsync (cts.Token).ConfigureAwait (false);
+
+				responseTask.TrySetResult (stream);
+			} catch (OperationCanceledException) {
+				SetCanceled ();
+			} catch (Exception e) {
+				SetError (e);
+			}
+		}
+
+		async void FinishReading ()
+		{
+			bool ok = false;
+			Exception error = null;
+
+			try {
+				ok = await completeResponseReadTask.Task.ConfigureAwait (false);
+			} catch (Exception e) {
+				error = e;
+			}
+
+			WebResponseStream stream;
+			WebOperation next;
+
+			lock (this) {
+				finishedReading = true;
+				stream = Interlocked.Exchange (ref responseStream, null);
+				next = Interlocked.Exchange (ref priorityRequest, null);
+				Request.FinishedReading = true;
+			}
+
+			if (error != null) {
+				if (next != null)
+					next.SetError (error);
+				finishedTask.TrySetException (error);
+				return;
+			}
+
+			WebConnection.Debug ($"WO FINISH READING: Cnc={Connection?.ID} Op={ID} ok={ok} error={error != null} stream={stream != null} next={next != null}");
+
+			var keepAlive = !Aborted && ok && (stream?.KeepAlive ?? false);
+			if (next != null && next.Aborted) {
+				next = null;
+				keepAlive = false;
+			}
+
+			finishedTask.TrySetResult ((keepAlive, next));
+
+			WebConnection.Debug ($"WO FINISH READING DONE: Cnc={Connection.ID} Op={ID} - {keepAlive} next={next?.ID}");
+		}
+
+		internal void CompleteRequestWritten (WebRequestStream stream, Exception error = null)
+		{
+			WebConnection.Debug ($"WO COMPLETE REQUEST WRITTEN: Op={ID} {error != null}");
+
+			if (error != null)
+				SetError (error);
+			else
+				requestWrittenTask.TrySetResult (stream);
+		}
+
+		internal void CompleteResponseRead (bool ok, Exception error = null)
+		{
+			WebConnection.Debug ($"WO COMPLETE RESPONSE READ: Op={ID} {ok} {error?.GetType ()}");
+
+			if (error != null)
+				completeResponseReadTask.TrySetException (error);
+			else
+				completeResponseReadTask.TrySetResult (ok);
+		}
+	}
+}

+ 436 - 0
mcs/class/System/System.Net/WebRequestStream.cs

@@ -0,0 +1,436 @@
+//
+// WebRequestStream.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.Threading;
+using System.Threading.Tasks;
+using System.Runtime.ExceptionServices;
+using System.Net.Sockets;
+
+namespace System.Net
+{
+	class WebRequestStream : WebConnectionStream
+	{
+		static byte[] crlf = new byte[] { 13, 10 };
+		MemoryStream writeBuffer;
+		bool requestWritten;
+		bool allowBuffering;
+		bool sendChunked;
+		TaskCompletionSource<int> pendingWrite;
+		long totalWritten;
+		byte[] headers;
+		bool headersSent;
+		int completeRequestWritten;
+		int chunkTrailerWritten;
+
+		internal readonly string ME;
+
+		public WebRequestStream (WebConnection connection, WebOperation operation,
+					 Stream stream, WebConnectionTunnel tunnel)
+			: base (connection, operation, stream)
+		{
+			allowBuffering = operation.Request.InternalAllowBuffering;
+			sendChunked = operation.Request.SendChunked && operation.WriteBuffer == null;
+			if (!sendChunked && allowBuffering && operation.WriteBuffer == null)
+				writeBuffer = new MemoryStream ();
+
+			KeepAlive = Request.KeepAlive;
+			if (tunnel?.ProxyVersion != null && tunnel?.ProxyVersion != HttpVersion.Version11)
+				KeepAlive = false;
+
+#if MONO_WEB_DEBUG
+			ME = $"WRQ(Cnc={Connection.ID}, Op={Operation.ID})";
+#endif
+		}
+
+		public bool KeepAlive {
+			get;
+		}
+
+		public override long Length {
+			get {
+				throw new NotSupportedException ();
+			}
+		}
+
+		public override bool CanRead => false;
+
+		public override bool CanWrite => true;
+
+		internal bool SendChunked {
+			get { return sendChunked; }
+			set { sendChunked = value; }
+		}
+
+		internal bool HasWriteBuffer {
+			get {
+				return Operation.WriteBuffer != null || writeBuffer != null;
+			}
+		}
+
+		internal int WriteBufferLength {
+			get {
+				if (Operation.WriteBuffer != null)
+					return Operation.WriteBuffer.Size;
+				if (writeBuffer != null)
+					return (int)writeBuffer.Length;
+				return -1;
+			}
+		}
+
+		internal BufferOffsetSize GetWriteBuffer ()
+		{
+			if (Operation.WriteBuffer != null)
+				return Operation.WriteBuffer;
+			if (writeBuffer == null || writeBuffer.Length == 0)
+				return null;
+			var buffer = writeBuffer.GetBuffer ();
+			return new BufferOffsetSize (buffer, 0, (int)writeBuffer.Length, false);
+		}
+
+		async Task FinishWriting (CancellationToken cancellationToken)
+		{
+			if (Interlocked.CompareExchange (ref completeRequestWritten, 1, 0) != 0)
+				return;
+
+			WebConnection.Debug ($"{ME} FINISH WRITING: {sendChunked}");
+			try {
+				Operation.ThrowIfClosedOrDisposed (cancellationToken);
+				if (sendChunked)
+					await WriteChunkTrailer_inner (cancellationToken).ConfigureAwait (false);
+			} catch (Exception ex) {
+				Operation.CompleteRequestWritten (this, ex);
+				throw;
+			} finally {
+				WebConnection.Debug ($"{ME} FINISH WRITING DONE");
+			}
+
+			Operation.CompleteRequestWritten (this);
+		}
+
+		public override async Task WriteAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
+		{
+			WebConnection.Debug ($"{ME} WRITE ASYNC: {buffer.Length}/{offset}/{size}");
+
+			Operation.ThrowIfClosedOrDisposed (cancellationToken);
+
+			if (Operation.WriteBuffer != null)
+				throw new InvalidOperationException ();
+
+			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));
+
+			var myWriteTcs = new TaskCompletionSource<int> ();
+			if (Interlocked.CompareExchange (ref pendingWrite, myWriteTcs, null) != null)
+				throw new InvalidOperationException (SR.GetString (SR.net_repcall));
+
+			try {
+				await ProcessWrite (buffer, offset, size, cancellationToken).ConfigureAwait (false);
+
+				WebConnection.Debug ($"{ME} WRITE ASYNC #1: {allowBuffering} {sendChunked} {Request.ContentLength} {totalWritten}");
+
+				if (Request.ContentLength > 0 && totalWritten == Request.ContentLength)
+					await FinishWriting (cancellationToken);
+
+				pendingWrite = null;
+				myWriteTcs.TrySetResult (0);
+			} catch (Exception ex) {
+				KillBuffer ();
+				closed = true;
+
+				WebConnection.Debug ($"{ME} WRITE ASYNC EX: {ex.Message}");
+
+				if (ex is SocketException)
+					ex = new IOException ("Error writing request", ex);
+
+				Operation.CompleteRequestWritten (this, ex);
+
+				pendingWrite = null;
+				myWriteTcs.TrySetException (ex);
+				throw;
+			}
+		}
+
+		async Task ProcessWrite (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
+		{
+			Operation.ThrowIfClosedOrDisposed (cancellationToken);
+
+			if (sendChunked) {
+				requestWritten = true;
+
+				string cSize = String.Format ("{0:X}\r\n", size);
+				byte[] head = Encoding.ASCII.GetBytes (cSize);
+				int chunkSize = 2 + size + head.Length;
+				byte[] newBuffer = new byte[chunkSize];
+				Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
+				Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
+				Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
+
+				if (allowBuffering) {
+					if (writeBuffer == null)
+						writeBuffer = new MemoryStream ();
+					writeBuffer.Write (buffer, offset, size);
+				}
+
+				totalWritten += size;
+
+				buffer = newBuffer;
+				offset = 0;
+				size = chunkSize;
+			} else {
+				CheckWriteOverflow (Request.ContentLength, totalWritten, size);
+
+				if (allowBuffering) {
+					if (writeBuffer == null)
+						writeBuffer = new MemoryStream ();
+					writeBuffer.Write (buffer, offset, size);
+					totalWritten += size;
+
+					if (Request.ContentLength <= 0 || totalWritten < Request.ContentLength)
+						return;
+
+					requestWritten = true;
+					buffer = writeBuffer.GetBuffer ();
+					offset = 0;
+					size = (int)totalWritten;
+				} else {
+					totalWritten += size;
+				}
+			}
+
+			try {
+				await InnerStream.WriteAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
+			} catch {
+				if (!IgnoreIOErrors)
+					throw;
+			}
+		}
+
+		void CheckWriteOverflow (long contentLength, long totalWritten, long size)
+		{
+			if (contentLength == -1)
+				return;
+
+			long avail = contentLength - totalWritten;
+			if (size > avail) {
+				KillBuffer ();
+				closed = true;
+				var throwMe = new ProtocolViolationException (
+					"The number of bytes to be written is greater than " +
+					"the specified ContentLength.");
+				Operation.CompleteRequestWritten (this, throwMe);
+				throw throwMe;
+			}
+		}
+
+		internal async Task Initialize (CancellationToken cancellationToken)
+		{
+			Operation.ThrowIfClosedOrDisposed (cancellationToken);
+
+			WebConnection.Debug ($"{ME} INIT: {Operation.WriteBuffer != null}");
+
+			if (Operation.WriteBuffer != null) {
+				if (Operation.IsNtlmChallenge)
+					Request.InternalContentLength = 0;
+				else
+					Request.InternalContentLength = Operation.WriteBuffer.Size;
+			}
+
+			await SetHeadersAsync (false, cancellationToken).ConfigureAwait (false);
+
+			Operation.ThrowIfClosedOrDisposed (cancellationToken);
+
+			if (Operation.WriteBuffer != null && !Operation.IsNtlmChallenge) {
+				await WriteRequestAsync (cancellationToken);
+				Close ();
+			}
+		}
+
+		async Task SetHeadersAsync (bool setInternalLength, CancellationToken cancellationToken)
+		{
+			Operation.ThrowIfClosedOrDisposed (cancellationToken);
+
+			if (headersSent)
+				return;
+
+			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 (Operation.IsNtlmChallenge)
+				no_writestream = true;
+
+			if (setInternalLength && !no_writestream && HasWriteBuffer)
+				Request.InternalContentLength = WriteBufferLength;
+
+			bool has_content = !no_writestream && (!HasWriteBuffer || Request.ContentLength > -1);
+			if (!(sendChunked || has_content || no_writestream || webdav))
+				return;
+
+			headersSent = true;
+			headers = Request.GetRequestHeaders ();
+
+			WebConnection.Debug ($"{ME} SET HEADERS: {Request.ContentLength}");
+
+			try {
+				await InnerStream.WriteAsync (headers, 0, headers.Length, cancellationToken).ConfigureAwait (false);
+				var cl = Request.ContentLength;
+				if (!sendChunked && cl == 0)
+					requestWritten = true;
+			} catch (Exception e) {
+				if (e is WebException || e is OperationCanceledException)
+					throw;
+				throw new WebException ("Error writing headers", WebExceptionStatus.SendFailure, WebExceptionInternalStatus.RequestFatal, e);
+			}
+		}
+
+		internal async Task WriteRequestAsync (CancellationToken cancellationToken)
+		{
+			Operation.ThrowIfClosedOrDisposed (cancellationToken);
+
+			WebConnection.Debug ($"{ME} WRITE REQUEST: {requestWritten} {sendChunked} {allowBuffering} {HasWriteBuffer}");
+
+			if (requestWritten)
+				return;
+
+			requestWritten = true;
+			if (sendChunked || !HasWriteBuffer)
+				return;
+
+			BufferOffsetSize buffer = GetWriteBuffer ();
+			if (buffer != null && !Operation.IsNtlmChallenge && Request.ContentLength != -1 && Request.ContentLength < buffer.Size) {
+				closed = true;
+				var throwMe = new WebException ("Specified Content-Length is less than the number of bytes to write", null,
+					WebExceptionStatus.ServerProtocolViolation, null);
+				Operation.CompleteRequestWritten (this, throwMe);
+				throw throwMe;
+			}
+
+			await SetHeadersAsync (true, cancellationToken).ConfigureAwait (false);
+
+			WebConnection.Debug ($"{ME} WRITE REQUEST #1: {buffer != null}");
+
+			Operation.ThrowIfClosedOrDisposed (cancellationToken);
+			if (buffer != null && buffer.Size > 0)
+				await InnerStream.WriteAsync (buffer.Buffer, 0, buffer.Size, cancellationToken);
+
+			await FinishWriting (cancellationToken);
+		}
+
+		async Task WriteChunkTrailer_inner (CancellationToken cancellationToken)
+		{
+			if (Interlocked.CompareExchange (ref chunkTrailerWritten, 1, 0) != 0)
+				return;
+			Operation.ThrowIfClosedOrDisposed (cancellationToken);
+			byte[] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
+			await InnerStream.WriteAsync (chunk, 0, chunk.Length, cancellationToken).ConfigureAwait (false);
+		}
+
+		async Task WriteChunkTrailer ()
+		{
+			using (var cts = new CancellationTokenSource ()) {
+				cts.CancelAfter (WriteTimeout);
+				var timeoutTask = Task.Delay (WriteTimeout);
+				while (true) {
+					var myWriteTcs = new TaskCompletionSource<int> ();
+					var oldTcs = Interlocked.CompareExchange (ref pendingWrite, myWriteTcs, null);
+					if (oldTcs == null)
+						break;
+					var ret = await Task.WhenAny (timeoutTask, oldTcs.Task).ConfigureAwait (false);
+					if (ret == timeoutTask)
+						throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
+				}
+
+				try {
+					await WriteChunkTrailer_inner (cts.Token).ConfigureAwait (false);
+				} catch {
+					// Intentionally eating exceptions.
+				} finally {
+					pendingWrite = null;
+				}
+			}
+		}
+
+		internal void KillBuffer ()
+		{
+			writeBuffer = null;
+		}
+
+		public override Task<int> ReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
+		{
+			return Task.FromException<int> (new NotSupportedException (SR.net_writeonlystream));
+		}
+
+		protected override void Close_internal (ref bool disposed)
+		{
+			WebConnection.Debug ($"{ME} CLOSE: {disposed} {requestWritten} {allowBuffering}");
+
+			if (disposed)
+				return;
+			disposed = true;
+
+			if (sendChunked) {
+				// Don't use FinishWriting() here, we need to block on 'pendingWrite' to ensure that
+				// any pending WriteAsync() has been completed.
+				//
+				// FIXME: I belive .NET simply aborts if you call Close() or Dispose() while writing,
+				//        need to check this.  2017/07/17 Martin.
+				WriteChunkTrailer ().Wait ();
+				return;
+			}
+
+			if (!allowBuffering || requestWritten) {
+				Operation.CompleteRequestWritten (this);
+				return;
+			}
+
+			long length = Request.ContentLength;
+
+			if (!sendChunked && !Operation.IsNtlmChallenge && length != -1 && totalWritten != length) {
+				IOException io = new IOException ("Cannot close the stream until all bytes are written");
+				closed = true;
+				disposed = true;
+				var throwMe = new WebException ("Request was cancelled.", WebExceptionStatus.RequestCanceled, WebExceptionInternalStatus.RequestFatal, io);
+				Operation.CompleteRequestWritten (this, throwMe);
+				throw throwMe;
+			}
+
+			// Commented out the next line to fix xamarin bug #1512
+			//WriteRequest ();
+			disposed = true;
+			Operation.CompleteRequestWritten (this);
+		}
+	}
+}

+ 733 - 0
mcs/class/System/System.Net/WebResponseStream.cs

@@ -0,0 +1,733 @@
+//
+// 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");
+		}
+
+
+	}
+}

+ 8 - 4
mcs/class/System/Test/System.Net/HttpWebRequestTest.cs

@@ -303,7 +303,8 @@ namespace MonoTests.System.Net
 				request.Method = "GET";
 
 				try {
-					request.BeginGetRequestStream (null, null);
+					var result = request.BeginGetRequestStream (null, null);
+					request.EndGetRequestStream (result);
 					Assert.Fail ("#A1");
 				} catch (ProtocolViolationException ex) {
 					// Cannot send a content-body with this
@@ -316,7 +317,8 @@ namespace MonoTests.System.Net
 				request.Method = "HEAD";
 
 				try {
-					request.BeginGetRequestStream (null, null);
+					var res = request.BeginGetRequestStream (null, null);
+					request.EndGetRequestStream (res);
 					Assert.Fail ("#B1");
 				} catch (ProtocolViolationException ex) {
 					// Cannot send a content-body with this
@@ -358,7 +360,8 @@ namespace MonoTests.System.Net
 				req.AllowWriteStreamBuffering = false;
 
 				try {
-					req.BeginGetRequestStream (null, null);
+					var result = req.BeginGetRequestStream (null, null);
+					req.EndGetRequestStream (result);
 					Assert.Fail ("#A1");
 				} catch (ProtocolViolationException ex) {
 					// When performing a write operation with
@@ -3076,7 +3079,8 @@ namespace MonoTests.System.Net
 				try {
 					Assert.IsTrue (rs.CanWrite, "#1");
 					rs.Close ();
-					Assert.IsFalse (rs.CanWrite, "#2");
+					// CanRead and CanWrite do not change status after closing.
+					Assert.IsTrue (rs.CanWrite, "#2");
 				} finally {
 					rs.Close ();
 					req.Abort ();

+ 0 - 2
mcs/class/System/common.sources

@@ -59,10 +59,8 @@ System.Net/ListenerPrefix.cs
 System.Net/MonoHttpDate.cs
 System.Net/NetConfig.cs
 System.Net/RequestStream.cs
-System.Net/SimpleAsyncResult.cs
 System.Net/SocketPermissionAttribute.cs
 System.Net/SocketPermission.cs
-System.Net/WebAsyncResult.cs
 
 System.Net.Mail/AlternateView.cs
 System.Net.Mail/AlternateViewCollection.cs

+ 5 - 3
mcs/class/System/common_networking.sources

@@ -37,16 +37,18 @@ System.Net/HttpListenerResponse.cs
 System.Net/HttpListenerTimeoutManager.cs
 System.Net/HttpWebRequest.cs
 System.Net/HttpWebResponse.cs
-System.Net/IWebConnectionState.cs
 System.Net/ListenerAsyncResult.cs
 System.Net/ResponseStream.cs
 System.Net/ServicePoint.cs
 System.Net/ServicePointManager.cs
 System.Net/ServicePointManager.extra.cs
+System.Net/ServicePointScheduler.cs
 System.Net/WebConnection.cs
-System.Net/WebConnectionData.cs
-System.Net/WebConnectionGroup.cs
 System.Net/WebConnectionStream.cs
+System.Net/WebConnectionTunnel.cs
+System.Net/WebOperation.cs
+System.Net/WebRequestStream.cs
+System.Net/WebResponseStream.cs
 
 ../referencesource/System/net/System/Net/Sockets/TCPClient.cs
 ../referencesource/System/net/System/Net/Sockets/TCPListener.cs

+ 8 - 23
mcs/class/referencesource/System/net/System/Net/webclient.cs

@@ -1805,20 +1805,9 @@ namespace System.Net {
             OnOpenReadCompleted((OpenReadCompletedEventArgs)arg);
         }
         private void OpenReadAsyncCallback(IAsyncResult result) {
-#if MONO
-            // It can be removed when we are full referencesource
-            AsyncOperation asyncOp = (AsyncOperation) result.AsyncState;
-            WebRequest request;
-            if (result is WebAsyncResult) {
-                request = (WebRequest) ((WebAsyncResult) result).AsyncObject;
-            } else {
-                request = (WebRequest) ((LazyAsyncResult) result).AsyncObject;
-            }
-#else
-            LazyAsyncResult lazyAsyncResult = (LazyAsyncResult) result;
-            AsyncOperation asyncOp = (AsyncOperation) lazyAsyncResult.AsyncState;
-            WebRequest request = (WebRequest) lazyAsyncResult.AsyncObject;
-#endif
+            Tuple<WebRequest,AsyncOperation> userData = (Tuple<WebRequest,AsyncOperation>)result.AsyncState;
+            WebRequest request = userData.Item1;
+            AsyncOperation asyncOp = userData.Item2;
             Stream stream = null;
             Exception exception = null;
             try {
@@ -1857,7 +1846,7 @@ namespace System.Net {
             m_AsyncOp = asyncOp;
             try {
                 WebRequest request = m_WebRequest = GetWebRequest(GetUri(address));
-                request.BeginGetResponse(new AsyncCallback(OpenReadAsyncCallback), asyncOp);
+                request.BeginGetResponse(new AsyncCallback(OpenReadAsyncCallback), new Tuple<WebRequest,AsyncOperation>(request, asyncOp));
             } catch (Exception e) {
                 if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
                     throw;
@@ -1887,13 +1876,9 @@ namespace System.Net {
             OnOpenWriteCompleted((OpenWriteCompletedEventArgs)arg);
         }
         private void OpenWriteAsyncCallback(IAsyncResult result) {
-#if MONO
-            var lazyAsyncResult = (WebAsyncResult) result;
-#else
-            LazyAsyncResult lazyAsyncResult = (LazyAsyncResult) result;
-#endif
-            AsyncOperation asyncOp = (AsyncOperation) lazyAsyncResult.AsyncState;
-            WebRequest request = (WebRequest) lazyAsyncResult.AsyncObject;
+            Tuple<WebRequest,AsyncOperation> userData = (Tuple<WebRequest,AsyncOperation>)result.AsyncState;
+            WebRequest request = userData.Item1;
+            AsyncOperation asyncOp = userData.Item2;
             WebClientWriteStream stream = null;
             Exception exception = null;
 
@@ -1943,7 +1928,7 @@ namespace System.Net {
             try {
                 m_Method = method;
                 WebRequest request = m_WebRequest = GetWebRequest(GetUri(address));
-                request.BeginGetRequestStream(new AsyncCallback(OpenWriteAsyncCallback), asyncOp);
+                request.BeginGetRequestStream(new AsyncCallback(OpenWriteAsyncCallback), new Tuple<WebRequest,AsyncOperation>(request, asyncOp));
             } catch (Exception e) {
                 if (e is ThreadAbortException || e is StackOverflowException || e is OutOfMemoryException) {
                     throw;

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä