context: //
  // System.Net.WebConnection
  //
  // Authors:
  //	Gonzalo Paniagua Javier (gonzalo@ximian.com)
  //
  // (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.
  //
  
  #if SECURITY_DEP
  
  #if MONOTOUCH || MONODROID
  using Mono.Security.Protocol.Tls;
  #else
  extern alias MonoSecurity;
  using MonoSecurity::Mono.Security.Protocol.Tls;
  #endif
  
  #endif
  
  using System.IO;
  using System.Collections;
  using System.Net.Sockets;
  using System.Reflection;
  using System.Security.Cryptography.X509Certificates;
  using System.Text;
  using System.Threading;
  using System.Diagnostics;
  
  namespace System.Net
  {
  	enum ReadState
  	{
  		None,
  		Status,
  		Headers,
  		Content,
  		Aborted
  	}
  
  	class WebConnection
  	{
  		ServicePoint sPoint;
  		Stream nstream;
  		internal Socket socket;
  		object socketLock = new object ();
  		IWebConnectionState state;
  		WebExceptionStatus status;
  		WaitCallback initConn;
  		bool keepAlive;
  		byte [] buffer;
  		static AsyncCallback readDoneDelegate = new AsyncCallback (ReadDone);
  		EventHandler abortHandler;
  		AbortHelper abortHelper;
  		internal WebConnectionData Data;
  		bool chunkedRead;
  		ChunkStream chunkStream;
  		Queue queue;
  		bool reused;
  		int position;
  		HttpWebRequest priority_request;		
  		NetworkCredential ntlm_credentials;
  		bool ntlm_authenticated;
  		bool unsafe_sharing;
  
  		enum NtlmAuthState
  		{
  			None,
  			Challenge,
  			Response
  		}
  		NtlmAuthState connect_ntlm_auth_state;
  		HttpWebRequest connect_request;
  
  		bool ssl;
  		bool certsAvailable;
  		Exception connect_exception;
  		static object classLock = new object ();
  		static Type sslStream;
  #if !MONOTOUCH && !MONODROID
  		static PropertyInfo piClient;
  		static PropertyInfo piServer;
  		static PropertyInfo piTrustFailure;
  #endif
  
  #if MONOTOUCH
  		[System.Runtime.InteropServices.DllImport ("__Internal")]
  		static extern void xamarin_start_wwan (string uri);
  #endif
  
  		internal ChunkStream ChunkStream {
  			get { return chunkStream; }
  		}
  
  		public WebConnection (IWebConnectionState wcs, ServicePoint sPoint)
  		{
  			this.state = wcs;
  			this.sPoint = sPoint;
  			buffer = new byte [4096];
  			Data = new WebConnectionData ();
  			initConn = new WaitCallback (state => {
  				try {
  					InitConnection (state);
  				} catch {}
  				});
  			queue = wcs.Group.Queue;
  			abortHelper = new AbortHelper ();
  			abortHelper.Connection = this;
  			abortHandler = new EventHandler (abortHelper.Abort);
  		}
  
  		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);
  			}
  		}
  
  		bool CanReuse ()
  		{
  			// 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 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
  					xamarin_start_wwan (sPoint.Address.ToString ());
  					hostEntry = sPoint.HostEntry;
  					if (hostEntry == null) {
  #endif
  						status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
  									    WebExceptionStatus.NameResolutionFailure;
  						return;
  #if MONOTOUCH
  					}
  #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;
  						}
  					}
  				}
  			}
  		}
  
  		static void EnsureSSLStreamAvailable ()
  		{
  			lock (classLock) {
  				if (sslStream != null)
  					return;
  
  #if NET_2_1 && SECURITY_DEP
  				sslStream = typeof (HttpsClientStream);
  #else
  				// HttpsClientStream is an internal glue class in Mono.Security.dll
  				sslStream = Type.GetType ("Mono.Security.Protocol.Tls.HttpsClientStream, " +
  							Consts.AssemblyMono_Security, false);
  
  				if (sslStream == null) {
  					string msg = "Missing Mono.Security.dll assembly. " +
  							"Support for SSL/TLS is unavailable.";
  
  					throw new NotSupportedException (msg);
  				}
  #endif
  #if !MONOTOUCH && !MONODROID
  				piClient = sslStream.GetProperty ("SelectedClientCertificate");
  				piServer = sslStream.GetProperty ("ServerCertificate");
  				piTrustFailure = sslStream.GetProperty ("TrustFailure");
  #endif
  			}
  		}
  
  		bool CreateTunnel (HttpWebRequest request, Uri connectUri,
  		                   Stream stream, out byte[] buffer)
  		{
  			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;
  				}
  
  				for (int i = 0; i < challenge.Length; i++) {
  					var auth = AuthenticationManager.Authenticate (challenge [i], connect_request, creds);
  					if (auth == null)
  						continue;
  					ntlm = (auth.Module.AuthenticationType == "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_internal ("Proxy-Authenticate", false);
  				return false;
  			} else if (status != 200) {
  				string msg = String.Format ("The remote server returned a {0} status code.", status);
  				HandleError (WebExceptionStatus.SecureChannelFailure, null, msg);
  				return false;
  			}
  
  			return (result != null);
  		}
  
  		WebHeaderCollection ReadHeaders (Stream stream, out byte [] retBuffer, out int status)
  		{
  			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]);
  					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;
  				}
  			}
  		}
  
  		bool CreateStream (HttpWebRequest request)
  		{
  			try {
  				NetworkStream serverStream = new NetworkStream (socket, false);
  
  				if (request.Address.Scheme == Uri.UriSchemeHttps) {
  					ssl = true;
  					EnsureSSLStreamAvailable ();
  					if (!reused || nstream == null || nstream.GetType () != sslStream) {
  						byte [] buffer = null;
  						if (sPoint.UseConnect) {
  							bool ok = CreateTunnel (request, sPoint.Address, serverStream, out buffer);
  							if (!ok)
  								return false;
  						}
  #if SECURITY_DEP
  #if MONOTOUCH || MONODROID
  						nstream = new HttpsClientStream (serverStream, request.ClientCertificates, request, buffer);
  #else
  						object[] args = new object [4] { serverStream,
  							request.ClientCertificates,
  							request, buffer};
  						nstream = (Stream) Activator.CreateInstance (sslStream, args);
  #endif
  						SslClientStream scs = (SslClientStream) nstream;
  						var helper = new ServicePointManager.ChainValidationHelper (request, request.Address.Host);
  						scs.ServerCertValidation2 += new CertificateValidationCallback2 (helper.ValidateChain);
  #endif
  						certsAvailable = false;
  					}
  					// 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 {
  					ssl = false;
  					nstream = serverStream;
  				}
  			} catch (Exception) {
  				if (!request.Aborted)
  					status = WebExceptionStatus.ConnectFailure;
  				return false;
  			}
  
  			return true;
  		}
  		
  		void HandleError (WebExceptionStatus st, Exception e, string where)
  		{
  			status = st;
  			lock (this) {
  				if (st == WebExceptionStatus.RequestCanceled)
  					Data = new WebConnectionData ();
  			}
  
  			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;
  				}
  			}
  
  			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);
  			}
  		}
  		
  		static void ReadDone (IAsyncResult result)
  		{
  			WebConnection cnc = (WebConnection)result.AsyncState;
  			WebConnectionData data = cnc.Data;
  			Stream ns = cnc.nstream;
  			if (ns == null) {
  				cnc.Close (true);
  				return;
  			}
  
  			int nread = -1;
  			try {
  				nread = ns.EndRead (result);
  			} catch (ObjectDisposedException) {
  				return;
  			} catch (Exception e) {
  				if (e.InnerException is ObjectDisposedException)
  					return;
  
  				cnc.HandleError (WebExceptionStatus.ReceiveFailure, e, "ReadDone1");
  				return;
  			}
  
  			if (nread == 0) {
  				cnc.HandleError (WebExceptionStatus.ReceiveFailure, null, "ReadDone2");
  				return;
  			}
  
  			if (nread < 0) {
  				cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, null, "ReadDone3");
  				return;
  			}
  
  			int pos = -1;
  			nread += cnc.position;
  			if (data.ReadState == ReadState.None) { 
  				Exception exc = null;
  				try {
  					pos = GetResponse (data, cnc.sPoint, cnc.buffer, nread);
  				} catch (Exception e) {
  					exc = e;
  				}
  
  				if (exc != null || pos == -1) {
  					cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc, "ReadDone4");
  					return;
  				}
  			}
  
  			if (data.ReadState == ReadState.Aborted) {
  				cnc.HandleError (WebExceptionStatus.RequestCanceled, null, "ReadDone");
  				return;
  			}
  
  			if (data.ReadState != ReadState.Content) {
  				int est = nread * 2;
  				int max = (est < cnc.buffer.Length) ? cnc.buffer.Length : est;
  				byte [] newBuffer = new byte [max];
  				Buffer.BlockCopy (cnc.buffer, 0, newBuffer, 0, nread);
  				cnc.buffer = newBuffer;
  				cnc.position = nread;
  				data.ReadState = ReadState.None;
  				InitRead (cnc);
  				return;
  			}
  
  			cnc.position = 0;
  
  			WebConnectionStream stream = new WebConnectionStream (cnc, data);
  			bool expect_content = ExpectContent (data.StatusCode, data.request.Method);
  			string tencoding = null;
  			if (expect_content)
  				tencoding = data.Headers ["Transfer-Encoding"];
  
  			cnc.chunkedRead = (tencoding != null && tencoding.IndexOf ("chunked", StringComparison.OrdinalIgnoreCase) != -1);
  			if (!cnc.chunkedRead) {
  				stream.ReadBuffer = cnc.buffer;
  				stream.ReadBufferOffset = pos;
  				stream.ReadBufferSize = nread;
  				try {
  					stream.CheckResponseInBuffer ();
  				} catch (Exception e) {
  					cnc.HandleError (WebExceptionStatus.ReceiveFailure, e, "ReadDone7");
  				}
  			} else if (cnc.chunkStream == null) {
  				try {
  					cnc.chunkStream = new ChunkStream (cnc.buffer, pos, nread, data.Headers);
  				} catch (Exception e) {
  					cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, e, "ReadDone5");
  					return;
  				}
  			} else {
  				cnc.chunkStream.ResetBuffer ();
  				try {
  					cnc.chunkStream.Write (cnc.buffer, pos, nread);
  				} catch (Exception e) {
  					cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, e, "ReadDone6");
  					return;
  				}
  			}
  
  			data.stream = stream;
  			
  			if (!expect_content)
  				stream.ForceCompletion ();
  
  			data.request.SetResponseData (data);
  		}
  
  		static bool ExpectContent (int statusCode, string method)
  		{
  			if (method == "HEAD")
  				return false;
  			return (statusCode >= 200 && statusCode != 204 && statusCode != 304);
  		}
  
  		internal void GetCertificates (Stream stream) 
  		{
  			// here the SSL negotiation have been done
  #if SECURITY_DEP && (MONOTOUCH || MONODROID)
  			HttpsClientStream s = (stream as HttpsClientStream);
  			X509Certificate client = s.SelectedClientCertificate;
  			X509Certificate server = s.ServerCertificate;
  #else
  			X509Certificate client = (X509Certificate) piClient.GetValue (stream, null);
  			X509Certificate server = (X509Certificate) piServer.GetValue (stream, null);
  #endif
  			sPoint.SetCertificates (client, server);
  			certsAvailable = (server != null);
  		}
  
  		internal static void InitRead (object state)
  		{
  			WebConnection cnc = (WebConnection) state;
  			Stream ns = cnc.nstream;
  
  			try {
  				int size = cnc.buffer.Length - cnc.position;
  				ns.BeginRead (cnc.buffer, cnc.position, size, readDoneDelegate, cnc);
  			} catch (Exception e) {
  				cnc.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;
  
  
gt: 				if (data.ReadState == ReadState.None) {