/// <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 { } }
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> /// 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> /// 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> /// 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]); }
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> /// 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"); }
internal void SendTo(HttpSocket hs) { hs.WriteAsciiLine(StatusLine); }
/// <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 (GhostLib.Network.Proxy.IoBroken) { /* ignore */ } catch (Exception e) { // log.Error(e); // log.Debug("Closing socket on error"); } CloseSocket(state); }
/* 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> /// 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 = 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); DestinationHostName = hostname; DestinationPort = port; //log.Debug("SocketPS connected to " + hostname + ":" + port); }
/// <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; }
internal void SendTo(HttpSocket hs) { hs.WriteAsciiLine(RequestLine); }