internal HttpStatusLine(SocketState 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]) == false) { // 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; }
internal HttpRequestLine(SocketState 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]; URL = items[1]; ProtocolVersion = ParserHelper.ParseProtocolVersion(items[2]); }
public QTransfer(SocketState state) { BrowserSocket = state; RemoteSocket = null; }
/* Helper function */ private void TunnelChunkedDataTo(SocketState 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 = sc > -1 ? chunk_header.Substring(0, sc) : 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(); 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(SocketState dest, uint nb_bytes) { return TunnelDataTo((b, o, 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(SocketState dest) { uint total_sent = 0; try { if (AvailableData == 0) { ReadRaw(); } while (AvailableData > 0) { uint sent = dest.WriteBinary(SocketBuffer, 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(SocketState dest) { TunnelChunkedDataTo(dest, (buffer, offset, size) => { if (dest.WriteBinary(buffer, offset, size) < size) { throw new IoBroken(); } }); }
/// <summary> /// Callback method for accepting new connections /// </summary> private void AcceptCallback(IAsyncResult ar) { if (listeningThread.ManagedThreadId == Thread.CurrentThread.ManagedThreadId) { new Thread(() => AcceptCallback(ar)).Start(); return; } if (IsShuttingDown) { return; } // Get the socket that handles the client request var listener = (Socket)ar.AsyncState; Socket handler = listener.EndAccept(ar); // Signal the main thread to continue listenThreadSwitch.Set(); // Create the state object var state = new SocketState(handler); state.guid = Guid.NewGuid(); lock (ConnectedSockets) { ConnectedSockets[state.guid] = state; } QTransfer transfer = null; try { transfer = new QTransfer(state); transfer.OnReceiveRequest = OnReceiveRequest; transfer.OnReceiveResponse = OnReceiveResponse; } catch (Exception e) { Console.WriteLine(e.Message); } if (transfer == null) { CloseSocket(state); return; } // No need for asynchronous I/O from now on try { while (transfer.LogicLoop()) { if (IsShuttingDown || state.IsSocketDead()) { break; } } } catch (SocketException) { /* ignore */ } catch (IoBroken) { /* ignore */ } catch (Exception e) { Console.WriteLine(e.Message); Console.WriteLine("Closing socket on error"); } CloseSocket(state); }
internal void SendTo(SocketState hs) { hs.WriteAsciiLine(RequestLine); }
/// <summary> /// If necessary, connect the remote <c>RemoteSocket</c> socket /// to the given host and port /// </summary> /// <param name="hostname">Remote host name</param> /// <param name="port">Remote port</param> /// <remarks> /// If RemoteSocket is already connected to the right host and port, /// the socket is reused as is. /// </remarks> protected void Connect(string hostname, int port) { Debug.Assert(!String.IsNullOrEmpty(hostname)); Debug.Assert(port > 0); if (DestinationHostName != null && DestinationHostName.Equals(hostname) && DestinationPort == port && (RemoteSocket != null && !RemoteSocket.IsSocketDead())) { // Nothing to do, just reuse the socket return; } if (RemoteSocket != null) { // We have a socket connected to the wrong host (or port) RemoteSocket.CloseSocket(); RemoteSocket = 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; } } } if (socket == null) { throw e; } // Checked up, and good to go RemoteSocket = new SocketState(socket); DestinationHostName = hostname; DestinationPort = port; }
/// <summary> /// Pipeline step: close the connections and stop /// </summary> protected void AbortRequest() { if (RemoteSocket != null) { RemoteSocket.CloseSocket(); RemoteSocket = null; } State.PersistConnectionBrowserSocket = false; State.NextStep = null; }
internal void SendTo(SocketState hs) { hs.WriteAsciiLine(StatusLine); }
/// <summary> /// Read and parse HTTP headers from a connected socket /// </summary> public HttpHeaders(SocketState source) : this() { var 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; 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") == false) { 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"); }
private void CallOnReceiveResponse(string responseMessageChunked) { var transferItem = new TransferItem(); transferItem.Headers = ResponseHeaders; transferItem.HttpRequestLine = RequestLine; transferItem.ResponseStatusLine = ResponseStatusLine; transferItem.BrowserSocket = BrowserSocket; transferItem.RemoteSocket = RemoteSocket; transferItem.State = State; transferItem.Response = GetResponse(responseMessageChunked); OnReceiveResponse(transferItem); if (State.PersistConnectionRemoteSocket == false && RemoteSocket != null) { RemoteSocket.CloseSocket(); RemoteSocket = null; } State.NextStep = null; }
/// <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(SocketState state) { lock (ConnectedSockets) { SocketState actual_state; if (ConnectedSockets.TryGetValue(state.guid, out actual_state) == false) { return; } Debug.Assert(actual_state == state); ConnectedSockets.Remove(state.guid); } state.CloseSocket(); }