using System;\r
using System.Globalization;\r
using System.IO;\r
+using System.IO.Compression;\r
using System.Net;\r
using System.Net.Sockets;\r
using System.Text;\r
try\r
{\r
ReceiveRequest();\r
+ if (_session.Request.Method == "CONNECT")\r
+ {\r
+ HandleConnect();\r
+ return;\r
+ }\r
SendRequest();\r
+ ReceiveRequestBody();\r
+ SendRequestBody();\r
ReceiveResponse();\r
SendResponse();\r
Close();\r
AfterSessionComplete?.Invoke(_session);\r
}\r
- catch (SocketException)\r
- {\r
- }\r
- catch (IOException)\r
- {\r
- }\r
- catch (HttpProxyAbort)\r
+ catch (Exception e)\r
{\r
+ File.AppendAllText("debug.log", e.ToString());\r
}\r
finally\r
{\r
var requestLine = _clientStream.ReadLine();\r
_session.Request.RequestLine = requestLine;\r
_session.Request.Headers = _clientStream.ReadHeaders();\r
+ }\r
+\r
+ private void ReceiveRequestBody()\r
+ {\r
if (_session.Request.ContentLength != -1 || _session.Request.TransferEncoding != null)\r
_session.Request.ReadBody(_clientStream);\r
}\r
{\r
_server = ConnectServer();\r
_serverStream = new HttpStream(_server).\r
- WriteLines(_session.Request.RequestLine + _session.Request.ModifiedHeaders).\r
- Write(_session.Request.Body);\r
+ WriteLines(_session.Request.RequestLine + _session.Request.ModifiedHeaders);\r
+ }\r
+\r
+ private void SendRequestBody()\r
+ {\r
+ _serverStream.Write(_session.Request.Body);\r
}\r
\r
private void ReceiveResponse()\r
.Write(_session.Response.Body);\r
}\r
\r
+ private void HandleConnect()\r
+ {\r
+ var host = "";\r
+ var port = 443;\r
+ if (!ParseAuthority(_session.Request.PathAndQuery, ref host, ref port))\r
+ return;\r
+ _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);\r
+ _server.Connect(host, port);\r
+ _clientStream.WriteLines("HTTP/1.0 200 Connection established\r\n\r\n");\r
+ Task[] tasks =\r
+ {\r
+ Task.Run(() => { TunnnelSockets(_client, _server); }),\r
+ Task.Run(() => { TunnnelSockets(_server, _client); })\r
+ };\r
+ Task.WaitAll(tasks);\r
+ }\r
+\r
+ private void TunnnelSockets(Socket from, Socket to)\r
+ {\r
+ try\r
+ {\r
+ var buf = new byte[8192];\r
+ while (true)\r
+ {\r
+ var n = from.Receive(buf);\r
+ if (n == 0)\r
+ break;\r
+ var sent = to.Send(buf, n, SocketFlags.None);\r
+ if (sent < n)\r
+ break;\r
+ }\r
+ to.Shutdown(SocketShutdown.Send);\r
+ }\r
+ catch (SocketException)\r
+ {\r
+ }\r
+ }\r
+\r
private static readonly Regex HostAndPortRegex =\r
- new Regex("http://([^:/]+)(?::(\\d)+)?/", RegexOptions.Compiled);\r
+ new Regex("http://([^:/]+)(?::(\\d+))?/", RegexOptions.Compiled);\r
\r
private Socket ConnectServer()\r
{\r
port = int.Parse(m.Groups[2].Value);\r
_session.Request.RequestLine = _session.Request.RequestLine.Remove(m.Index, m.Length - 1);\r
}\r
- if (host == null)\r
- host = _session.Request.Host;\r
- if (host == null)\r
- throw new HttpProxyAbort("Can't find destinatio host");\r
+ if (host == null && !ParseAuthority(_session.Request.Host, ref host, ref port))\r
+ throw new HttpProxyAbort("Can't find destination host");\r
connect:\r
var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);\r
socket.Connect(host, port);\r
return socket;\r
}\r
\r
+ private static readonly Regex AuthorityRegex = new Regex("([^:]+)(?::(\\d+))?");\r
+\r
+ private bool ParseAuthority(string authority, ref string host, ref int port)\r
+ {\r
+ if (string.IsNullOrEmpty(authority))\r
+ return false;\r
+ var m = AuthorityRegex.Match(authority);\r
+ if (!m.Success)\r
+ return false;\r
+ host = m.Groups[1].Value;\r
+ if (m.Groups[2].Success)\r
+ port = int.Parse(m.Groups[2].Value);\r
+ return true;\r
+ }\r
+\r
private void Close()\r
{\r
_serverStream?.Close();\r
}\r
}\r
\r
- public string ModifiedHeaders =>\r
- RemoveTransferEncoding(SetConnectionClose(Headers));\r
+ public virtual string ModifiedHeaders => SetConnectionClose(Headers);\r
\r
private string SetConnectionClose(string headers)\r
{\r
- foreach (var name in new[] {"connection", "keep-alive", "proxy-connection"})\r
+ return InsertHeader(RemoveHeaders(headers,\r
+ new[] {"connection", "keep-alive", "proxy-connection"}), "Connection: close\r\n");\r
+ }\r
+\r
+ protected string RemoveHeaders(string headers, string[] fields)\r
+ {\r
+ foreach (var f in fields)\r
{\r
- var m = MatchField(name, headers);\r
+ var m = MatchField(f, headers);\r
if (!m.Success)\r
continue;\r
headers = headers.Remove(m.Index, m.Length);\r
}\r
- return headers.Insert(headers.Length - 2, "Connection: close\r\n");\r
+ return headers;\r
}\r
\r
- private string RemoveTransferEncoding(string headers)\r
+ protected string InsertHeader(string headers, string header)\r
{\r
- var m = MatchField("transfer-encoding", headers);\r
- if (!m.Success)\r
- return headers;\r
- return headers.Remove(m.Index, m.Length).\r
- Insert(headers.Length - m.Length - 2, $"Content-Length: {Body.Length}\r\n");\r
+ return headers.Insert(headers.Length - 2, header);\r
}\r
\r
protected virtual void SetHeaders(string headers)\r
Host = GetField("host");\r
}\r
\r
- private Match MatchField(string name, string headers)\r
+ protected Match MatchField(string name, string headers)\r
{\r
var regex = new Regex("^" + name + ":\\s*([^\r]+)\r\n",\r
RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Multiline);\r
if (Body == null)\r
return "";\r
var m = CharsetRegx.Match(ContentType ?? "");\r
- var encoding = m.Success ? Encoding.GetEncoding(m.Groups[1].Value) : Encoding.ASCII;\r
+ var encoding = Encoding.ASCII;\r
+ if (m.Success)\r
+ {\r
+ var name = m.Groups[1].Value;\r
+ if (name == "utf8")\r
+ name = "UTF-8";\r
+ encoding = Encoding.GetEncoding(name);\r
+ }\r
return encoding.GetString(Body);\r
}\r
}\r
{\r
Body = stream.ReadToEnd();\r
}\r
+ if (ContentEncoding == null)\r
+ return;\r
+ var dc = new MemoryStream();\r
+ try\r
+ {\r
+ if (ContentEncoding == "gzip")\r
+ new GZipStream(new MemoryStream(Body), CompressionMode.Decompress).CopyTo(dc);\r
+ else if (ContentEncoding == "deflate")\r
+ new DeflateStream(new MemoryStream(Body), CompressionMode.Decompress).CopyTo(dc);\r
+ }\r
+ catch (Exception ex)\r
+ {\r
+ throw new HttpProxyAbort($"Fail to decode {ContentEncoding}: " + ex.Message);\r
+ }\r
+ Body = dc.ToArray();\r
}\r
}\r
\r
{\r
private string _statusLine;\r
\r
+ public override string ModifiedHeaders =>\r
+ InsertContentLength(RemoveHeaders(base.ModifiedHeaders,\r
+ new[] {"transfer-encoding", "content-encoding", "content-length"}));\r
+\r
+ private string InsertContentLength(string headers)\r
+ {\r
+ return Body == null ? headers : InsertHeader(headers, $"Content-Length: {Body.Length}\r\n");\r
+ }\r
+\r
public string StatusLine\r
{\r
get { return _statusLine; }\r
var size = ReadLine();\r
if (size.Length < 3)\r
break;\r
+ var ext = size.IndexOf(';');\r
+ size = ext == -1 ? size.Substring(0, size.Length - 2) : size.Substring(0, ext);\r
int val;\r
- if (!int.TryParse(size.Substring(0, size.Length - 2),\r
- NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out val))\r
- break;\r
+ if (!int.TryParse(size, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out val))\r
+ throw new HttpProxyAbort("Can't parse chunk size: " + size);\r
if (val == 0)\r
{\r
ReadLine();\r
buf.Write(chunk, 0, chunk.Length);\r
ReadLine();\r
}\r
+ string line;\r
+ do\r
+ {\r
+ line = ReadLine();\r
+ } while (line != "" && line != "\r\n");\r
return buf.ToArray();\r
}\r
\r
\r
public int Read(byte[] buf, int offset, int count)\r
{\r
- try\r
+ var total = 0;\r
+ do\r
{\r
- var total = 0;\r
- do\r
+ int n;\r
+ if (_position < _available)\r
{\r
- int n;\r
- if (_position < _available)\r
- {\r
- n = Math.Min(count, _available - _position);\r
- Buffer.BlockCopy(_buffer, _position, buf, 0, n);\r
- _position += n;\r
- }\r
- else\r
- {\r
- n = _socket.Receive(buf, offset, count, SocketFlags.None);\r
- if (n == 0)\r
- return total == 0 ? n : total;\r
- }\r
- count -= n;\r
- offset += n;\r
- total += n;\r
- } while (count > 0);\r
- return total;\r
- }\r
- catch (IOException)\r
- {\r
- return -1;\r
- }\r
+ n = Math.Min(count, _available - _position);\r
+ Buffer.BlockCopy(_buffer, _position, buf, 0, n);\r
+ _position += n;\r
+ }\r
+ else\r
+ {\r
+ n = _socket.Receive(buf, offset, count, SocketFlags.None);\r
+ if (n == 0)\r
+ return total == 0 ? n : total;\r
+ }\r
+ count -= n;\r
+ offset += n;\r
+ total += n;\r
+ } while (count > 0);\r
+ return total;\r
}\r
\r
public void Write(byte[] buf, int offset, int count)\r
{\r
- try\r
- {\r
- do\r
- {\r
- var n = _socket.Send(buf, offset, count, SocketFlags.None);\r
- if (n == 0)\r
- return;\r
- count -= n;\r
- offset += n;\r
- } while (count > 0);\r
- }\r
- catch (IOException)\r
+ do\r
{\r
- }\r
+ var n = _socket.Send(buf, offset, count, SocketFlags.None);\r
+ if (n == 0)\r
+ return;\r
+ count -= n;\r
+ offset += n;\r
+ } while (count > 0);\r
}\r
\r
public HttpStream Close()\r