using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.IO; using System.Threading; using MarketData; namespace Tor.Proxy { /// /// A class containing a reference to a connection proxy client. /// internal sealed class Connection : IDisposable { private readonly Client client; private volatile bool disposed; private ConnectionDisposedCallback disposedCallback; private Dictionary headers; private string host; private string http; private string method; private int port; private byte[] post; private Socket socket; /// /// Initializes a new instance of the class. /// /// The client hosting the proxy . /// The socket belonging to the connection. /// A callback method raised when the connection is disposed. public Connection(Client client, Socket socket, ConnectionDisposedCallback disposeCallback) { this.client = client; this.disposed = false; this.disposedCallback = disposeCallback; this.headers = null; this.host = null; this.http = "HTTP/1.1"; this.method = null; this.port = 80; this.post = null; this.socket = socket; this.GetHeaderData(); } #region Properties /// /// Gets the header values provided with the request. /// public Dictionary Headers { get { return headers; } } /// /// Gets the target host of the HTTP request. /// public string Host { get { return host; } } /// /// Gets the HTTP version sent with the request. /// public string HTTP { get { return http; } } /// /// Gets the method requested for the HTTP request (GET, POST, PUT, DELETE, CONNECT). /// public string Method { get { return method; } } /// /// Gets the target port number of the HTTP request. /// public int Port { get { return port; } } /// /// Gets the POST data. /// public byte[] Post { get { return post; } } /// /// Gets the socket connected to the proxy client. /// public Socket Socket { get { return socket; } } #endregion #region System.IDisposable /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases unmanaged and - optionally - managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. private void Dispose(bool disposing) { if (disposed) return; if (disposing) { if (socket != null) { try { socket.Shutdown(SocketShutdown.Both); } catch { } socket.Dispose(); socket = null; } disposed = true; if (disposedCallback != null) disposedCallback(this); } } #endregion /// /// Gets the header block which was dispatched with the original socket request. /// /// A containing the header data. public string GetHeader() { StringBuilder header = new StringBuilder(); header.Append(method); header.Append("\r\n"); foreach (KeyValuePair value in headers) { if (value.Key.StartsWith("proxy", StringComparison.CurrentCultureIgnoreCase)) continue; header.AppendFormat("{0}: {1}\r\n", value.Key, value.Value); } return header.Append("\r\n").ToString(); } /// /// Process the connection request by reading the header information from the HTTP request, and connecting to the tor server /// and dispatching the request to the relevant host. /// private void GetHeaderData() { try { // MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Reading headers from LocalEndPoint:{0}/RemoteEndPoint:{1}",Socket.LocalEndPoint,Socket.RemoteEndPoint)); StringBuilder builder = new StringBuilder(); using (StreamReader reader = new StreamReader(new NetworkStream(socket, false))) { for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) { builder.Append(line); builder.Append("\r\n"); if (line.Trim().Length == 0) break; } } using (StringReader reader = new StringReader(builder.ToString())) { method = reader.ReadLine(); if (method == null) { throw new InvalidOperationException("The proxy connection did not supply a valid HTTP header"); } headers = new Dictionary(); for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) { string trimmed = line.Trim(); if (trimmed.Length == 0) break; string[] parts = trimmed.Split(new[] { ':' }, 2); if (parts.Length == 1) continue; headers[parts[0].Trim()] = parts[1].Trim(); } if (method.StartsWith("POST", StringComparison.CurrentCultureIgnoreCase)) { if (!headers.ContainsKey("Content-Length")) { throw new InvalidOperationException("The proxy connection is a POST method but contains no content length"); } long contentLength = long.Parse(headers["Content-Length"]); using (MemoryStream memory = new MemoryStream()) { long read = 0; byte[] buffer = new byte[512]; while (contentLength > read) { int received = socket.Receive(buffer, 0, buffer.Length, SocketFlags.None); if (received <= 0) throw new InvalidOperationException("The proxy connection was terminated while reading POST data"); memory.Write(buffer, 0, received); read += received; } post = memory.ToArray(); } } if (method.StartsWith("CONNECT", StringComparison.CurrentCultureIgnoreCase)) { string[] connectTargets = method.Split(' '); if (connectTargets.Length < 3) throw new InvalidOperationException("The proxy connection supplied a CONNECT command with insufficient parameters"); http = connectTargets[2]; string connectTarget = connectTargets[1]; string[] connectParams = connectTarget.Split(':'); if (connectParams.Length == 2) { host = connectParams[0]; port = int.Parse(connectParams[1]); } else { host = connectParams[0]; port = 443; } } else { if (!headers.ContainsKey("Host")) throw new InvalidOperationException("The proxy connection did not supply a connection host"); string connectTarget = headers["Host"]; string[] connectParams = connectTarget.Split(':'); if (connectParams.Length == 1) host = connectParams[0]; else { host = connectParams[0]; port = int.Parse(connectParams[1]); } } if(null!=host) { MDTrace.WriteLine(LogLevel.DEBUG,String.Format("Host: {0}:{1}",host,port)); } return; } } catch (Exception) { headers = new Dictionary(); return; // throw new TorException("The proxy connection failed to process", exception); } } /// /// Writes a buffer of data to the connected client socket. /// /// The data to send to the client socket. public void Write(string data) { Write(Encoding.ASCII.GetBytes(data)); } /// /// Writes a buffer of data to the connected client socket. /// /// The data to send to the client socket. /// An optional list of parameters to format into the data. public void Write(string data, params object[] parameters) { data = string.Format(data, parameters); Write(Encoding.ASCII.GetBytes(data)); } /// /// Writes a buffer of data to the connected client socket. /// /// The data to send to the client socket. public void Write(byte[] buffer) { socket.Send(buffer, 0, buffer.Length, SocketFlags.None); } } /// /// A delegate event handler representing a method raised when a connection is disposed. /// /// The connection which was disposed. internal delegate void ConnectionDisposedCallback(Connection connection); }