internal HttpStatusLine(HttpSocket hs) { string line; do line = hs.ReadAsciiLine().Trim(); while (line.Length == 0); string[] items = line.Split(sp, StringSplitOptions.RemoveEmptyEntries); // Note: the status line has three items: the HTTP protocol // version, the status code, and the reason phrase. // Only the reason phrase may be empty. if (items.Length < 2) throw new HttpProtocolBroken("Unrecognized status line '" + line + "'"); ProtocolVersion = ParserHelper.ParseProtocolVersion( items[0]); string code = items[1]; if (code.Length != 3 || !char.IsDigit(code[0])) // we only test the first character throw new HttpProtocolBroken("Invalid status code '" + code + "'"); //string Reason = rest of the string; // we don't need it StatusCode = int.Parse(code); StatusLine = line; }
/// <summary> /// Transfer data from this socket to the destination socket /// until this socket closes /// </summary> /// <returns>The number of bytes sent</returns> public uint TunnelDataTo(HttpSocket dest, System.Threading.CancellationToken cancelToken) { uint total_sent = 0; try { if (AvailableData == 0) { ReadRaw(); } while (AvailableData > 0 && !cancelToken.IsCancellationRequested) { uint sent = dest.WriteBinary(Buffer, BufferPosition, AvailableData); if (sent < AvailableData) { throw new IoBroken(); } total_sent += sent; ReadRaw(); } } catch (SocketException) { /* ignore */ } return(total_sent); }
internal void SendTo(HttpSocket hs) { hs.WriteAsciiLine(HeadersInOrder); // Note: HeadersInOrder contains one trailing newline, so // WriteAsciiLine() will send two newlines (which is what we // want). }
/// <summary> /// Transfer data from this socket to the destination socket /// until this socket closes /// </summary> /// <returns>The number of bytes sent</returns> public uint TunnelDataTo(HttpSocket dest) { uint total_sent = 0; try { if (AvailableData == 0) { ReadRaw(); } while (AvailableData > 0) { uint sent = dest.WriteBinary(Buffer, BufferPosition, AvailableData); if (sent < AvailableData) { throw new IoBroken(); } total_sent += sent; ReadRaw(); } } catch (SocketException) { /* ignore */ } return(total_sent); }
/// <summary> /// Close broken sockets /// </summary> /// <remarks> /// This function is called regularly to clean up the list of /// connected sockets. /// </remarks> void CheckSockets(object eventState) { try { lock (ConnectedSockets) { foreach (var kv in ConnectedSockets) { try { int id = kv.Key; HttpSocket state = kv.Value; if (state == null || state.IsSocketDead()) { ConnectedSockets.Remove(id); } } catch (Exception e) { log.Error(e); } } } } catch { } }
/// <summary> /// Read <c>nb_bytes</c> bytes from the socket, /// and send it to the destination socket /// </summary> /// <returns>The number of bytes sent</returns> public uint TunnelDataTo(HttpSocket dest, uint nb_bytes) { return(TunnelDataTo((byte[] b, uint o, uint s) => { if (dest.WriteBinary(b, o, s) < s) { throw new IoBroken(); } }, nb_bytes)); }
/// <summary> /// Tunnel a HTTP-chunked blob of data /// </summary> /// <param name="dest">The destination socket</param> /// <remarks> /// The tunneling stops when the last chunk, identified by a /// size of 0, arrives. The optional trailing entities are also /// transmitted (but otherwise ignored). /// </remarks> public void TunnelChunkedDataTo(HttpSocket dest) { TunnelChunkedDataTo(dest, (byte[] b, uint o, uint s) => { if (dest.WriteBinary(b, o, s) < s) { throw new IoBroken(); } }); }
/// <summary> /// Parse a HTTP header after sending it through the TCP channel /// </summary> /// <returns>A parsed version of <param>rq_line</param></returns> /// <remarks> /// This method must be called between Setup() and TearDown(). /// </remarks> public static HttpRequestLine ParseHttpRequestLine(string rq_line) { byte[] to_send = System.Text.Encoding.ASCII.GetBytes(rq_line); var es = new EchoSocket(bUseIPv6, to_send); using (var hs = new HttpSocket(es.socket)) { es.socket.Shutdown(SocketShutdown.Send); return new HttpRequestLine(hs); } }
/// <summary> /// Parse a HTTP header after sending it through the TCP channel /// </summary> /// <returns>A parsed version of <param>header</param></returns> /// <remarks> /// This method must be called between Setup() and TearDown(). /// </remarks> public static HttpHeaders ParseHttpHeaders(string header) { byte[] to_send = System.Text.Encoding.ASCII.GetBytes(header); var es = new EchoSocket(bUseIPv6, to_send); using (var hs = new HttpSocket(es.socket)) { es.socket.Shutdown(SocketShutdown.Send); return new TrotiNet.HttpHeaders(hs); } }
/// <summary> /// Send a message through the TCP channel, and expect it to /// return unchanged /// </summary> /// <remarks> /// Useful for testing I/O levels 1 and 2. /// This method must be called between Setup() and TearDown(). /// </remarks> public static void DoMsgRoundTrip(string msg_send, string msg_expect) { byte[] to_send = System.Text.Encoding.ASCII.GetBytes(msg_send); using (var hs = new HttpSocket( new EchoSocket(bUseIPv6, to_send).socket)) { var msg_receive = hs.ReadAsciiLine(); Assert.AreEqual(msg_expect, msg_receive); hs.CloseSocket(); } }
/// <summary> /// Remove the socket contained in the given state object /// from the connected array list and hash table, then close the /// socket /// </summary> virtual protected void CloseSocket(HttpSocket state) { HttpSocket actual_state; lock (ConnectedSockets) { if (!ConnectedSockets.TryGetValue(state.id, out actual_state)) return; System.Diagnostics.Debug.Assert(actual_state == state); ConnectedSockets.Remove(state.id); } state.CloseSocket(); }
internal HttpRequestLine(HttpSocket hs) { string line; do line = hs.ReadAsciiLine().Trim(); while (line.Length == 0); string[] items = line.Split(sp, StringSplitOptions.RemoveEmptyEntries); if (items.Length != 3) throw new HttpProtocolBroken("Unrecognized request line '" + line + "'"); RequestLine = line; Method = items[0]; URI = items[1]; ProtocolVersion = ParserHelper.ParseProtocolVersion(items[2]); }
/// <summary> /// Close broken sockets /// </summary> /// <remarks> /// This function is called regularly to clean up the list of /// connected sockets. /// </remarks> void CheckSockets(object eventState) { Console.WriteLine("Starting Clean Sockets: {0}", ConnectedSockets.Count); try { lock (ConnectedSockets) { var toRemove = new List <int>(); foreach (var kv in ConnectedSockets) { try { int id = kv.Key; HttpSocket state = kv.Value; if (DateTime.Now - state.CreationTime > TimeSpan.FromMinutes(10)) { //Console.WriteLine("Closing Old Socket: {0} {1}", id, state.CreationTime); state.CloseSocket(); } if (state == null || state.IsSocketDead()) { toRemove.Add(id); } } catch (Exception e) { log.Error(e); } } foreach (var id in toRemove) { ConnectedSockets.Remove(id); } } } catch { } Console.WriteLine("Finished Clean Sockets: {0}", ConnectedSockets.Count); }
internal HttpRequestLine(HttpSocket hs) { string line; do { line = hs.ReadAsciiLine().Trim(); }while (line.Length == 0); string[] items = line.Split(sp, StringSplitOptions.RemoveEmptyEntries); if (items.Length != 3) { throw new HttpProtocolBroken("Unrecognized request line '" + line + "'"); } RequestLine = line; Method = items[0]; URI = items[1]; ProtocolVersion = ParserHelper.ParseProtocolVersion(items[2]); }
internal HttpStatusLine(HttpSocket hs) { string line; do { line = hs.ReadAsciiLine().Trim(); }while (line.Length == 0); string[] items = line.Split(sp, StringSplitOptions.RemoveEmptyEntries); // Note: the status line has three items: the HTTP protocol // version, the status code, and the reason phrase. // Only the reason phrase may be empty. if (items.Length < 2) { throw new HttpProtocolBroken("Unrecognized status line '" + line + "'"); } ProtocolVersion = ParserHelper.ParseProtocolVersion( items[0]); string code = items[1]; if (code.Length != 3 || !char.IsDigit(code[0])) // we only test the first character { throw new HttpProtocolBroken("Invalid status code '" + code + "'"); } //string Reason = rest of the string; // we don't need it StatusCode = int.Parse(code); StatusLine = line; }
/* Helper function */ void TunnelChunkedDataTo(HttpSocket dest, MessagePacketHandler mph) { // (RFC 2616, sections 3.6.1, 19.4.6) while (true) { string chunk_header = ReadAsciiLine(); if (chunk_header.Length == 0) throw new HttpProtocolBroken( "Expected chunk header missing"); int sc = chunk_header.IndexOfAny(c_ChunkSizeEnd); string hexa_size; if (sc > -1) // We have chunk extensions: ignore them hexa_size = chunk_header.Substring(0, sc); else hexa_size = chunk_header; uint size; try { size = Convert.ToUInt32(hexa_size, 16); } catch { string s = chunk_header.Length > 20 ? (chunk_header.Substring(0, 17) + "...") : chunk_header; throw new HttpProtocolBroken( "Could not parse chunk size in: " + s); } if (dest != null) dest.WriteAsciiLine(chunk_header); if (size == 0) break; TunnelDataTo(mph, size); // Read/write one more CRLF string new_line = ReadAsciiLine(); System.Diagnostics.Debug.Assert(new_line.Length == 0); if (dest != null) dest.WriteAsciiLine(new_line); } string line; do { // Tunnel any trailing entity headers line = ReadAsciiLine(); if (dest != null) dest.WriteAsciiLine(line); } while (line.Length != 0); }
/// <summary> /// Read <c>nb_bytes</c> bytes from the socket, /// and send it to the destination socket /// </summary> /// <returns>The number of bytes sent</returns> public uint TunnelDataTo(HttpSocket dest, uint nb_bytes) { return TunnelDataTo((byte[] b, uint o, uint s) => { if (dest.WriteBinary(b, o, s) < s) throw new IoBroken(); }, nb_bytes); }
/// <summary> /// Transfer data from this socket to the destination socket /// until this socket closes /// </summary> /// <returns>The number of bytes sent</returns> public uint TunnelDataTo(HttpSocket dest) { uint total_sent = 0; try { if (AvailableData == 0) ReadRaw(); while (AvailableData > 0) { uint sent = dest.WriteBinary(Buffer, BufferPosition, AvailableData); if (sent < AvailableData) throw new IoBroken(); total_sent += sent; ReadRaw(); } } catch (SocketException) { /* ignore */ } return total_sent; }
/// <summary> /// Tunnel a HTTP-chunked blob of data /// </summary> /// <param name="dest">The destination socket</param> /// <remarks> /// The tunneling stops when the last chunk, identified by a /// size of 0, arrives. The optional trailing entities are also /// transmitted (but otherwise ignored). /// </remarks> public void TunnelChunkedDataTo(HttpSocket dest) { TunnelChunkedDataTo(dest, (byte[] b, uint o, uint s) => { if (dest.WriteBinary(b, o, s) < s) throw new IoBroken(); }); }
/// <summary> /// Static constructor with <c>PrintEchoPrefix = false</c> /// </summary> public static AbstractProxyLogic CreateMirrorProxy(HttpSocket socketBP) { return new ProxyDummyEcho(socketBP, false); }
/// <summary> /// Common constructor for proxies; one proxy instance is created /// per client connection /// </summary> /// <param name="socketBP">Client socket</param> protected AbstractProxyLogic(HttpSocket socketBP) { System.Diagnostics.Debug.Assert(socketBP != null); SocketBP = socketBP; SocketPS = null; }
/// <summary> /// Callback method for accepting new connections /// </summary> void AcceptCallback(IAsyncResult ar) { if (IsShuttingDown) return; // Have we really changed thread? if (ListeningThread.ManagedThreadId == System.Threading.Thread.CurrentThread.ManagedThreadId) { // No! Give me a new thread! new Thread(() => AcceptCallback(ar)).Start(); return; } // Get the socket that handles the client request Socket listener = (Socket)ar.AsyncState; Socket handler = listener.EndAccept(ar); // Signal the main thread to continue ListenThreadSwitch.Set(); #if DEBUG_ACCEPT_CONNECTION log.Debug("\tAcceptCallback sent signal"); #endif // Create the state object HttpSocket state = new HttpSocket(handler); state.id = ++LastClientId; lock (ConnectedSockets) ConnectedSockets[state.id] = state; AbstractProxyLogic proxy = null; try { proxy = OnClientStart(state); } catch (Exception e) { log.Error(e); } if (proxy == null) { CloseSocket(state); return; } // No need for asynchronous I/O from now on try { while (proxy.LogicLoop()) if (IsShuttingDown || state.IsSocketDead()) break; log.Debug("Shutting down socket"); } catch (System.Net.Sockets.SocketException) { /* ignore */ } catch (TrotiNet.IoBroken) { /* ignore */ } catch (Exception e) { log.Error(e); log.Debug("Closing socket on error"); } CloseSocket(state); }
internal void SendTo(HttpSocket hs) { hs.WriteAsciiLine(RequestLine); }
/// <summary> /// SocketBPからインスタンスを初期化。 /// 接続(AcceptCallback)の都度インスタンスが生成される。 /// </summary> /// <param name="clientSocket">Browser-Proxy間Socket。SocketBP。</param> public TransparentProxyLogic(HttpSocket clientSocket) : base(clientSocket) { }
/// <summary> /// TcpServerがインスタンスを生成する際に使用するメソッド。 /// 接続(AcceptCallback)の都度呼び出され、インスタンスが生成される。 /// </summary> /// <param name="clientSocket">Browser-Proxy間Socket。SocketBP。</param> /// <returns>ProxyLogicインスタンス。</returns> public new static TransparentProxyLogic CreateProxy(HttpSocket clientSocket) => new TransparentProxyLogic(clientSocket);
/// <summary> /// Static constructor /// </summary> public static AbstractProxyLogic CreateProxy(HttpSocket socketBP) { return new ProxyLogic(socketBP); }
/// <summary> /// Instantiate a transparent proxy /// </summary> /// <param name="socketBP">Client browser-proxy socket</param> public ProxyLogic(HttpSocket socketBP) : base(socketBP) { }
/// <summary> /// Remove the socket contained in the given state object /// from the connected array list and hash table, then close the /// socket /// </summary> protected virtual void CloseSocket(HttpSocket state) { HttpSocket actual_state; lock (ConnectedSockets) { if (!ConnectedSockets.TryGetValue(state.id, out actual_state)) return; System.Diagnostics.Debug.Assert(actual_state == state); ConnectedSockets.Remove(state.id); } state.CloseSocket(); }
/// <summary> /// Read and parse HTTP headers from a connected socket /// </summary> public HttpHeaders(HttpSocket source) : this() { StringBuilder sb = new StringBuilder(512); while (true) { var line = source.ReadAsciiLine(); if (line.Length == 0) { break; } sb.Append(line); sb.Append("\r\n"); // Note: if the header newline was // incorrectly formatted (i.e. LF instead of CRLF), // we correct it here. This is one point where our // proxy is not fully transparent. var iSplit = line.IndexOf(':'); if (iSplit <= 0) { throw new HttpProtocolBroken("No colon in HTTP header"); } // Header names are case-insensitive, but only some header // values are. string HeaderName = line.Substring(0, iSplit).Trim().ToLower(); string HeaderValue = line.Substring(iSplit + 1).Trim(); if (IsHeaderValueCaseInsensitive(HeaderName)) { HeaderValue = HeaderValue.ToLower(); } string previous_value = null; if (Headers.TryGetValue(HeaderName, out previous_value)) { // Duplicate headers: concatenate them // (RFC 2616, section 4.2) // However, this should only occur if the value of that // header is a comma-separated list. In the real world, // it has been observed that headers with // non-comma-separated values, such as Content-Length, *are* // in some rare cases repeated, so we should not concatenate // the values. if (!HeaderName.Equals("content-length")) { Headers[HeaderName] = previous_value + "," + HeaderValue; } } else { Headers[HeaderName] = HeaderValue; } } HeadersInOrder = sb.ToString(); // Parse a subset of the header values. // If headers are added, don't forget to update RemoveHeader // as well. Connection = ParseMultipleStringValues("connection"); ContentEncoding = ParseStringValue("content-encoding"); ContentLength = ParseIntValue("content-length"); Host = ParseStringValue("host"); ProxyConnection = ParseMultipleStringValues("proxy-connection"); Referer = ParseStringValue("referer"); TransferEncoding = ParseMultipleStringValues("transfer-encoding"); }
/// <summary> /// If necessary, connect the remote <c>SocketPS</c> socket /// to the given host and port /// </summary> /// <param name="hostname">Remote host name</param> /// <param name="port">Remote port</param> /// <remarks> /// If SocketPS is already connected to the right host and port, /// the socket is reused as is. /// </remarks> protected void Connect(string hostname, int port) { System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(hostname)); System.Diagnostics.Debug.Assert(port > 0); if (DestinationHostName != null && DestinationHostName.Equals(hostname) && DestinationPort == port && (SocketPS != null && !SocketPS.IsSocketDead())) // Nothing to do, just reuse the socket return; if (SocketPS != null) { log.Debug("Changing hostname/port from " + DestinationHostName + ":" + DestinationPort + " to " + hostname + ":" + port); // We have a socket connected to the wrong host (or port) SocketPS.CloseSocket(); SocketPS = null; } IPAddress[] ips = Dns.GetHostAddresses(hostname); Socket socket = null; Exception e = null; foreach (var ip in ips) { try { socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); socket.Connect(ip, port); break; } catch (Exception ee) { if (ip.Equals(IPAddress.IPv6Loopback)) // Do not log that continue; if (e == null) e = ee; if (socket != null) { socket.Close(); socket = null; } log.Error(ee); } } if (socket == null) throw e; // Checked up, and good to go SocketPS = new HttpSocket(socket); DestinationHostName = hostname; DestinationPort = port; log.Debug("SocketPS connected to " + hostname + ":" + port); }
/* Helper function */ void TunnelChunkedDataTo(HttpSocket dest, MessagePacketHandler mph) { // (RFC 2616, sections 3.6.1, 19.4.6) while (true) { string chunk_header = ReadAsciiLine(); if (chunk_header.Length == 0) { throw new HttpProtocolBroken( "Expected chunk header missing"); } int sc = chunk_header.IndexOfAny(c_ChunkSizeEnd); string hexa_size; if (sc > -1) { // We have chunk extensions: ignore them hexa_size = chunk_header.Substring(0, sc); } else { hexa_size = chunk_header; } uint size; try { size = Convert.ToUInt32(hexa_size, 16); } catch { string s = chunk_header.Length > 20 ? (chunk_header.Substring(0, 17) + "...") : chunk_header; throw new HttpProtocolBroken( "Could not parse chunk size in: " + s); } if (dest != null) { dest.WriteAsciiLine(chunk_header); } if (size == 0) { break; } TunnelDataTo(mph, size); // Read/write one more CRLF string new_line = ReadAsciiLine(); System.Diagnostics.Debug.Assert(new_line.Length == 0); if (dest != null) { dest.WriteAsciiLine(new_line); } } string line; do { // Tunnel any trailing entity headers line = ReadAsciiLine(); if (dest != null) { dest.WriteAsciiLine(line); } } while (line.Length != 0); }
/// <summary> /// Instantiate a dummy proxy that echoes what it reads on the /// socket back to it /// </summary> /// <param name="socketBP">Client socket</param> /// <param name="PrintEchoPrefix">If true, the proxy will add an /// "Echo" prefix for each message</param> public ProxyDummyEcho(HttpSocket socketBP, bool PrintEchoPrefix) : base(socketBP) { bPrintEchoPrefix = PrintEchoPrefix; }
internal void SendTo(HttpSocket hs) { hs.WriteAsciiLine(StatusLine); }
/// <summary> /// Static constructor with <c>PrintEchoPrefix = true</c> /// </summary> public static AbstractProxyLogic CreateEchoProxy(HttpSocket socketBP) { return new ProxyDummyEcho(socketBP, true); }
/// <summary> /// Callback method for accepting new connections /// </summary> void AcceptCallback(IAsyncResult ar) { // Have we really changed thread? if (ListeningThread.ManagedThreadId == System.Threading.Thread.CurrentThread.ManagedThreadId) { // No! Give me a new thread! new Thread(() => AcceptCallback(ar)).Start(); return; } if (IsShuttingDown) return; // Get the socket that handles the client request Socket listener = (Socket)ar.AsyncState; Socket handler = listener.EndAccept(ar); // Signal the main thread to continue ListenThreadSwitch.Set(); #if DEBUG_ACCEPT_CONNECTION log.Debug("\tAcceptCallback sent signal"); #endif // Create the state object HttpSocket state = new HttpSocket(handler); state.id = ++LastClientId; log.DebugFormat("Accepted connection: [S{0}] {1} -> {2}", state.id, handler.RemoteEndPoint, handler.LocalEndPoint); lock (ConnectedSockets) ConnectedSockets[state.id] = state; AbstractProxyLogic proxy = null; try { proxy = OnClientStart(state); } catch (Exception e) { log.Error(e); } if (proxy == null) { CloseSocket(state); return; } // No need for asynchronous I/O from now on try { while (proxy.LogicLoop()) if (IsShuttingDown || state.IsSocketDead()) break; log.Debug("Shutting down socket"); } catch (System.Net.Sockets.SocketException) { /* ignore */ } catch (TrotiNet.IoBroken) { /* ignore */ } catch (Exception e) { log.Error(e); log.Debug("Closing socket on error"); } CloseSocket(state); }
/// <summary> /// Transfer data from the socket to the specified packet handler with Async Mode /// This is Just For A SSL Tunneling /// </summary> /// <returns>The number of bytes sent</returns> public IAsyncResult TunnelDataAsyncTo(HttpSocket dest) { byte[] buffer = new byte[4096]; IAsyncResult Result = LowLevelSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) => { try { int Ret = (int)ar.AsyncState; Ret = LowLevelSocket.EndReceive(ar); if (Ret > 0) { dest.WriteBinary(buffer, 0, (uint)Ret); } else { CloseSocket(); } } catch { } }), new int()); return Result; }
/// <summary> /// If necessary, connect the remote <c>SocketPS</c> socket /// to the given host and port /// </summary> /// <param name="hostname">Remote host name</param> /// <param name="port">Remote port</param> /// <remarks> /// If SocketPS is already connected to the right host and port, /// the socket is reused as is. /// </remarks> protected void Connect(string hostname, int port, bool secure) { System.Diagnostics.Debug.Assert(!String.IsNullOrEmpty(hostname)); System.Diagnostics.Debug.Assert(port > 0); if (DestinationHostName != null && DestinationHostName.Equals(hostname) && DestinationPort == port && (SocketPS != null && !SocketPS.IsSocketDead())) { // Nothing to do, just reuse the socket return; } if (SocketPS != null) { log.Debug("Changing hostname/port from " + DestinationHostName + ":" + DestinationPort + " to " + hostname + ":" + port); // We have a socket connected to the wrong host (or port) SocketPS.CloseSocket(); SocketPS = null; } IPAddress[] ips = Resolve(hostname); Socket socket = null; Exception e = null; foreach (var ip in ips) { try { socket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp); socket.Connect(ip, port); break; } catch (Exception ee) { if (ip.Equals(IPAddress.IPv6Loopback)) { // Do not log that continue; } if (e == null) { e = ee; } if (socket != null) { socket.Close(); socket = null; } log.Error(ee); } } if (socket == null) { throw e; } // Checked up, and good to go SocketPS = new HttpSocket(socket); if (secure) { SocketPS.MakeSecureClient(hostname); } DestinationHostName = hostname; DestinationPort = port; log.Debug("SocketPS connected to " + hostname + ":" + port); }