OSDN Git Service

ビルド後にEnemySlot.csvをTargetDirにコピーする
[kancollesniffer/KancolleSniffer.git] / KancolleSniffer / HttpProxy.cs
index 8ac6910..e97cf56 100644 (file)
@@ -15,6 +15,7 @@
 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
@@ -102,20 +103,22 @@ namespace KancolleSniffer
                 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
@@ -128,6 +131,10 @@ namespace KancolleSniffer
                 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
@@ -136,8 +143,12 @@ namespace KancolleSniffer
             {\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
@@ -164,8 +175,46 @@ namespace KancolleSniffer
                     .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
@@ -185,16 +234,29 @@ namespace KancolleSniffer
                         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
@@ -234,28 +296,29 @@ namespace KancolleSniffer
                 }\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
@@ -272,7 +335,7 @@ namespace KancolleSniffer
                 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
@@ -292,7 +355,14 @@ namespace KancolleSniffer
                     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
@@ -316,6 +386,21 @@ namespace KancolleSniffer
                 {\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
@@ -345,6 +430,15 @@ namespace KancolleSniffer
         {\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
@@ -430,10 +524,11 @@ namespace KancolleSniffer
                     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
@@ -444,6 +539,11 @@ namespace KancolleSniffer
                     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
@@ -466,52 +566,39 @@ namespace KancolleSniffer
 \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