//
// 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.
//

using System.IO;
using System.Collections;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

namespace System.Net
{
	enum ReadState
	{
		None,
		Status,
		Headers,
		Content
	}
	
	class WebConnection
	{
		ServicePoint sPoint;
		Stream nstream;
		Socket socket;
		object socketLock = new object ();
		WebExceptionStatus status;
		WaitCallback initConn;
		bool keepAlive;
		byte [] buffer;
		static AsyncCallback readDoneDelegate = new AsyncCallback (ReadDone);
		EventHandler abortHandler;
		ReadState readState;
		internal WebConnectionData Data;
		bool chunkedRead;
		ChunkStream chunkStream;
		Queue queue;
		bool reused;
		int position;
		bool busy;

		bool ssl;
		bool certsAvailable;
		static object classLock = new object ();
		static Type sslStream;
		static PropertyInfo piClient;
		static PropertyInfo piServer;
		static PropertyInfo piTrustFailure;

		public WebConnection (WebConnectionGroup group, ServicePoint sPoint)
		{
			this.sPoint = sPoint;
			buffer = new byte [4096];
			readState = ReadState.None;
			Data = new WebConnectionData ();
			initConn = new WaitCallback (InitConnection);
			abortHandler = new EventHandler (Abort);
			queue = group.Queue;
		}

		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 ()
		{
			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) {
					status = sPoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
								    WebExceptionStatus.NameResolutionFailure;
					return;
				}

				foreach (IPAddress address in hostEntry.AddressList) {
					socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
					try {
						socket.Connect (new IPEndPoint(address, sPoint.Address.Port));
						status = WebExceptionStatus.Success;
						break;
					} catch (SocketException) {
						// This might be null if the request is aborted
						if (socket != null) {
							socket.Close();
							socket = null;
							status = WebExceptionStatus.ConnectFailure;
						}
					}
				}
			}
		}

		static void EnsureSSLStreamAvailable ()
		{
			lock (classLock) {
				if (sslStream != null)
					return;

				// 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);
				}
				piClient = sslStream.GetProperty ("SelectedClientCertificate");
				piServer = sslStream.GetProperty ("ServerCertificate");
				piTrustFailure = sslStream.GetProperty ("TrustFailure");
			}
		}

		bool CreateTunnel (HttpWebRequest request, 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);
			string challenge = Data.Challenge;
			Data.Challenge = null;
			bool have_auth = (request.Headers ["Proxy-Authorization"] != null);
			if (have_auth) {
				sb.Append ("\r\nProxy-Authorization: ");
				sb.Append (request.Headers ["Proxy-Authorization"]);
			} else if (challenge != null && Data.StatusCode == 407) {
				have_auth = true;
				ICredentials creds = request.Proxy.Credentials;
				Authorization auth = AuthenticationManager.Authenticate (challenge, request, creds);
				if (auth != null) {
					sb.Append ("\r\nProxy-Authorization: ");
					sb.Append (auth.Message);
				}
			}
			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 (request, stream, out buffer, out status);
			if (!have_auth && result != null && status == 407) { // Needs proxy auth
				Data.StatusCode = status;
				Data.Challenge = result ["Proxy-Authenticate"];
				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 (HttpWebRequest request, Stream stream, out byte [] retBuffer, out int status)
		{
			retBuffer = null;
			status = 200;

			byte [] buffer = new byte [1024];
			MemoryStream ms = new MemoryStream ();
			bool gotStatus = false;
			WebHeaderCollection headers = null;

			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;
				headers = new WebHeaderCollection ();
				while (ReadLine (ms.GetBuffer (), ref start, (int) ms.Length, ref str)) {
					if (str == null) {
						if (ms.Length - start > 0) {
							retBuffer = new byte [ms.Length - start];
							Buffer.BlockCopy (ms.GetBuffer (), start, retBuffer, 0, retBuffer.Length);
						}
						return headers;
					}

					if (gotStatus) {
						headers.Add (str);
						continue;
					}

					int spaceidx = str.IndexOf (' ');
					if (spaceidx == -1) {
						HandleError (WebExceptionStatus.ServerProtocolViolation, null, "ReadHeaders2");
						return null;
					}

					status = (int) UInt32.Parse (str.Substring (spaceidx + 1, 3));
					gotStatus = true;
				}
			}
		}

		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, serverStream, out buffer);
							if (!ok)
								return false;
						}

						object[] args = new object [4] { serverStream,
										request.ClientCertificates,
										request, buffer};
						nstream = (Stream) Activator.CreateInstance (sslStream, args);
						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) {
				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 {
#if TARGET_JVM
					throw new Exception ();
#else
					throw new Exception (new System.Diagnostics.StackTrace ().ToString ());
#endif
				} catch (Exception e2) {
					e = e2;
				}
			}

			HttpWebRequest req = null;
			if (Data != null && Data.request != null)
				req = Data.request;

			Close (true);
			if (req != null)
				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 (Exception e) {
				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 (cnc.readState == ReadState.None) { 
				Exception exc = null;
				try {
					pos = cnc.GetResponse (cnc.buffer, nread);
				} catch (Exception e) {
					exc = e;
				}

				if (exc != null) {
					cnc.HandleError (WebExceptionStatus.ServerProtocolViolation, exc, "ReadDone4");
					return;
				}
			}

			if (cnc.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;
				cnc.readState = ReadState.None;
				InitRead (cnc);
				return;
			}

			cnc.position = 0;

			WebConnectionStream stream = new WebConnectionStream (cnc);

			string contentType = data.Headers ["Transfer-Encoding"];
			cnc.chunkedRead = (contentType != null && contentType.ToLower ().IndexOf ("chunked") != -1);
			if (!cnc.chunkedRead) {
				stream.ReadBuffer = cnc.buffer;
				stream.ReadBufferOffset = pos;
				stream.ReadBufferSize = nread;
			} 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 (!ExpectContent (data.StatusCode))
				stream.ForceCompletion ();

			data.request.SetResponseData (data);
		}

		static bool ExpectContent (int statusCode)
		{
			return (statusCode >= 200 && statusCode != 204 && statusCode != 304);
		}

		internal void GetCertificates () 
		{
			// here the SSL negotiation have been done
			X509Certificate client = (X509Certificate) piClient.GetValue (nstream, null);
			X509Certificate server = (X509Certificate) piServer.GetValue (nstream, null);
			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");
			}
		}
		
		int GetResponse (byte [] buffer, int max)
		{
			int pos = 0;
			string line = null;
			bool lineok = false;
			bool isContinue = false;
			bool emptyFirstLine = false;
			do {
				if (readState == ReadState.None) {
					lineok = ReadLine (buffer, ref pos, max, ref line);
					if (!lineok)
						return -1;

					if (line == null) {
						emptyFirstLine = true;
						continue;
					}
					emptyFirstLine = false;

					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 (readState == ReadState.Status) {
					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 -1;

					foreach (string s in headers)
						Data.Headers.SetInternal (s);

					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;
						}

						readState = ReadState.None;
						isContinue = true;
					}
					else {
						readState = ReadState.Content;
						return pos;
					}
				}
			} while (emptyFirstLine || isContinue);

			return -1;
		}
		
		void InitConnection (object state)
		{
			HttpWebRequest request = (HttpWebRequest) state;

			if (status == WebExceptionStatus.RequestCanceled) {
				lock (this) {
					busy = false;
					Data = new WebConnectionData ();
					SendNext ();
				}
				return;
			}

			keepAlive = request.KeepAlive;
			Data = new WebConnectionData ();
			Data.request = request;
		retry:
			Connect ();
			if (status != WebExceptionStatus.Success) {
				if (status != WebExceptionStatus.RequestCanceled) {
					request.SetWriteStreamError (status);
					Close (true);
				}
				return;
			}
			
			if (!CreateStream (request)) {
				if (Data.Challenge != null)
					goto retry;

				request.SetWriteStreamError (status);
				Close (true);
				return;
			}

			readState = ReadState.None;
			request.SetWriteStream (new WebConnectionStream (this, request));
		}
		
		internal EventHandler SendRequest (HttpWebRequest request)
		{
			lock (this) {
				if (!busy) {
					busy = true;
					status = WebExceptionStatus.Success;
					ThreadPool.QueueUserWorkItem (initConn, request);
				} else {
					lock (queue) {
						queue.Enqueue (request);
					}
				}
			}

			return abortHandler;
		}
		
		void SendNext ()
		{
			lock (queue) {
				if (queue.Count > 0) {
					SendRequest ((HttpWebRequest) queue.Dequeue ());
				}
			}
		}

		internal void NextRead ()
		{
			lock (this) {
				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 (cncHeader != null) {
					cncHeader = cncHeader.ToLower ();
					keepAlive = (this.keepAlive && cncHeader.IndexOf ("keep-alive") != -1);
				}

				if ((socket != null && !socket.Connected) ||
				   (!keepAlive || (cncHeader != null && cncHeader.IndexOf ("close") != -1))) {
					Close (false);
				}

				busy = false;
				SendNext ();
			}
		}
		
		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++];

				if (c == '\n') {			// newline
					if ((text.Length > 0) && (text [text.Length - 1] == '\r'))
						text.Length--;

					foundCR = false;
					break;
				} else if (foundCR) {
					text.Length--;
					break;
				}

				if (c == '\r')
					foundCR = true;
					

				text.Append ((char) c);
			}

			if (c != '\n' && c != '\r')
				return false;

			if (text.Length == 0) {
				output = null;
				return (c == '\n' || c == '\r');
			}

			if (foundCR)
				text.Length--;

			output = text.ToString ();
			return true;
		}


		internal IAsyncResult BeginRead (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
		{
			if (nstream == null)
				return null;

			IAsyncResult result = null;
			if (!chunkedRead || chunkStream.WantMore) {
				try {
					result = nstream.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 ChunkStream
					wr.SetCompleted (true, (Exception) null);
					wr.DoCallback ();
				}
				return wr;
			}

			return result;
		}
		
		internal int EndRead (IAsyncResult result)
		{
			if (nstream == null)
				return 0;

			int nbytes = 0;
			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 = nstream.EndRead (inner);
			} else if (!(nsAsync is WebAsyncResult)) {
				nbytes = nstream.EndRead (nsAsync);
				wr = (WebAsyncResult) result;
			}

			if (chunkedRead) {
				bool done = (nbytes == 0);
				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);
				}
			}

			return (nbytes != 0) ? nbytes : -1;
		}

		// To be called on chunkedRead when we can read no data from the ChunkStream 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);
			}

			return nbytes;
		}

		bool CompleteChunkedRead()
		{
			if (!chunkedRead || chunkStream == null)
				return true;

			while (chunkStream.WantMore) {
				int nbytes = nstream.Read (buffer, 0, buffer.Length);
				if (nbytes <= 0)
					return false; // Socket was disconnected

				chunkStream.Write (buffer, 0, nbytes);
			}

			return true;
  		}
		internal IAsyncResult BeginWrite (byte [] buffer, int offset, int size, AsyncCallback cb, object state)
		{
			IAsyncResult result = null;
			if (nstream == null)
				return null;

			try {
				result = nstream.BeginWrite (buffer, offset, size, cb, state);
			} catch (Exception) {
				status = WebExceptionStatus.SendFailure;
				throw;
			}

			return result;
		}

		internal bool EndWrite (IAsyncResult result)
		{
			if (nstream == null)
				return false;

			try {
				nstream.EndWrite (result);
				return true;
			} catch {
				status = WebExceptionStatus.SendFailure;
				return false;
			}
		}

		internal int Read (byte [] buffer, int offset, int size)
		{
			if (nstream == null)
				return 0;

			int result = 0;
			try {
				bool done = false;
				if (!chunkedRead) {
					result = nstream.Read (buffer, offset, size);
					done = (result == 0);
				}

				if (chunkedRead) {
					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);
					}
				}
			} catch (Exception e) {
				HandleError (WebExceptionStatus.ReceiveFailure, e, "Read");
			}

			return result;
		}

		internal void Write (byte [] buffer, int offset, int size)
		{
			if (nstream == null)
				return;

			try {
				nstream.Write (buffer, offset, size);
				// here SSL handshake should have been done
				if (ssl && !certsAvailable)
					GetCertificates ();
			} catch (Exception e) {
				WebExceptionStatus wes = WebExceptionStatus.SendFailure;
				string msg = "Write";
				if (e is WebException) {
					HandleError (wes, e, msg);
					return;
				}

				// if SSL is in use then check for TrustFailure
				if (ssl && (bool) piTrustFailure.GetValue (nstream, null)) {
					wes = WebExceptionStatus.TrustFailure;
					msg = "Trust failure";
				}

				HandleError (wes, e, msg);
			}
		}

		internal void Close (bool sendNext)
		{
			lock (this) {
				if (nstream != null) {
					try {
						nstream.Close ();
					} catch {}
					nstream = null;
				}

				if (socket != null) {
					try {
						socket.Close ();
					} catch {}
					socket = null;
				}

				busy = false;
				if (sendNext)
					SendNext ();
			}
		}

		void Abort (object sender, EventArgs args)
		{
			lock (this) {
				if (Data.request == sender) {
					HandleError (WebExceptionStatus.RequestCanceled, null, "Abort");
					return;
				}

				lock (queue) {
					if (queue.Count > 0 && queue.Peek () == sender) {
						queue.Dequeue ();
						return;
					}

					object [] old = queue.ToArray ();
					queue.Clear ();
					for (int i = old.Length - 1; i >= 0; i--) {
						if (old [i] != sender)
							queue.Enqueue (old [i]);
					}
				}
			}
		}

		internal bool Busy {
			get { lock (this) return busy; }
		}
		
		internal bool Connected {
			get {
				lock (this) {
					return (socket != null && socket.Connected);
				}
			}
		}
	}
}

