From 35e572406b51b3e5c5263286e2444d79f38f6850 Mon Sep 17 00:00:00 2001
From: Dreamacro <305009791@qq.com>
Date: Sun, 12 Aug 2018 12:14:59 +0800
Subject: [PATCH] Fix: firefox one socket to process multiple domains

---
 adapters/local/http.go | 99 +++++++++++++++++++++++++++++++++++++++++-
 proxy/http/server.go   |  4 +-
 proxy/http/util.go     | 77 --------------------------------
 3 files changed, 100 insertions(+), 80 deletions(-)
 delete mode 100644 proxy/http/util.go

diff --git a/adapters/local/http.go b/adapters/local/http.go
index cd335f92..574f02bc 100644
--- a/adapters/local/http.go
+++ b/adapters/local/http.go
@@ -1,7 +1,12 @@
 package adapters
 
 import (
+	"bufio"
+	"bytes"
+	"io"
 	"net"
+	"net/http"
+	"strings"
 
 	C "github.com/Dreamacro/clash/constant"
 )
@@ -9,6 +14,8 @@ import (
 type PeekedConn struct {
 	net.Conn
 	Peeked []byte
+	host   string
+	isHTTP bool
 }
 
 func (c *PeekedConn) Read(p []byte) (n int, err error) {
@@ -20,6 +27,23 @@ func (c *PeekedConn) Read(p []byte) (n int, err error) {
 		}
 		return n, nil
 	}
+
+	// Sometimes firefox just open a socket to process multiple domains in HTTP
+	// The temporary solution is to return io.EOF when encountering different HOST
+	if c.isHTTP {
+		br := bufio.NewReader(bytes.NewReader(p))
+		_, hostName := ParserHTTPHostHeader(br)
+		if hostName != "" {
+			if !strings.Contains(hostName, ":") {
+				hostName += ":80"
+			}
+
+			if hostName != c.host {
+				return 0, io.EOF
+			}
+		}
+	}
+
 	return c.Conn.Read(p)
 }
 
@@ -40,12 +64,85 @@ func (h *HttpAdapter) Conn() net.Conn {
 	return h.conn
 }
 
-func NewHttp(host string, peeked []byte, conn net.Conn) *HttpAdapter {
+func NewHttp(host string, peeked []byte, isHTTP bool, conn net.Conn) *HttpAdapter {
 	return &HttpAdapter{
 		addr: parseHttpAddr(host),
 		conn: &PeekedConn{
 			Peeked: peeked,
 			Conn:   conn,
+			host:   host,
+			isHTTP: isHTTP,
 		},
 	}
 }
+
+// ParserHTTPHostHeader returns the HTTP Host header from br without
+// consuming any of its bytes. It returns "" if it can't find one.
+func ParserHTTPHostHeader(br *bufio.Reader) (method, host string) {
+	// br := bufio.NewReader(bytes.NewReader(data))
+	const maxPeek = 4 << 10
+	peekSize := 0
+	for {
+		peekSize++
+		if peekSize > maxPeek {
+			b, _ := br.Peek(br.Buffered())
+			return method, httpHostHeaderFromBytes(b)
+		}
+		b, err := br.Peek(peekSize)
+		if n := br.Buffered(); n > peekSize {
+			b, _ = br.Peek(n)
+			peekSize = n
+		}
+		if len(b) > 0 {
+			if b[0] < 'A' || b[0] > 'Z' {
+				// Doesn't look like an HTTP verb
+				// (GET, POST, etc).
+				return
+			}
+			if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 {
+				req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b)))
+				if err != nil {
+					return
+				}
+				if len(req.Header["Host"]) > 1 {
+					// TODO(bradfitz): what does
+					// ReadRequest do if there are
+					// multiple Host headers?
+					return
+				}
+				return req.Method, req.Host
+			}
+		}
+		if err != nil {
+			return method, httpHostHeaderFromBytes(b)
+		}
+	}
+}
+
+var (
+	lfHostColon = []byte("\nHost:")
+	lfhostColon = []byte("\nhost:")
+	crlf        = []byte("\r\n")
+	lf          = []byte("\n")
+	crlfcrlf    = []byte("\r\n\r\n")
+	lflf        = []byte("\n\n")
+)
+
+func httpHostHeaderFromBytes(b []byte) string {
+	if i := bytes.Index(b, lfHostColon); i != -1 {
+		return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):])))
+	}
+	if i := bytes.Index(b, lfhostColon); i != -1 {
+		return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):])))
+	}
+	return ""
+}
+
+// untilEOL returns v, truncated before the first '\n' byte, if any.
+// The returned slice may include a '\r' at the end.
+func untilEOL(v []byte) []byte {
+	if i := bytes.IndexByte(v, '\n'); i != -1 {
+		return v[:i]
+	}
+	return v
+}
diff --git a/proxy/http/server.go b/proxy/http/server.go
index 31c0856f..d015a085 100644
--- a/proxy/http/server.go
+++ b/proxy/http/server.go
@@ -56,7 +56,7 @@ func NewHttpProxy(addr string) (*C.ProxySignal, error) {
 
 func handleConn(conn net.Conn) {
 	br := bufio.NewReader(conn)
-	method, hostName := httpHostHeader(br)
+	method, hostName := adapters.ParserHTTPHostHeader(br)
 	if hostName == "" {
 		return
 	}
@@ -75,5 +75,5 @@ func handleConn(conn net.Conn) {
 		peeked, _ = br.Peek(br.Buffered())
 	}
 
-	tun.Add(adapters.NewHttp(hostName, peeked, conn))
+	tun.Add(adapters.NewHttp(hostName, peeked, method != http.MethodConnect, conn))
 }
diff --git a/proxy/http/util.go b/proxy/http/util.go
deleted file mode 100644
index 090ff7e6..00000000
--- a/proxy/http/util.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package http
-
-import (
-	"bufio"
-	"bytes"
-	"net/http"
-)
-
-// httpHostHeader returns the HTTP Host header from br without
-// consuming any of its bytes. It returns ""if it can't find one.
-func httpHostHeader(br *bufio.Reader) (method, host string) {
-	const maxPeek = 4 << 10
-	peekSize := 0
-	for {
-		peekSize++
-		if peekSize > maxPeek {
-			b, _ := br.Peek(br.Buffered())
-			return method, httpHostHeaderFromBytes(b)
-		}
-		b, err := br.Peek(peekSize)
-		if n := br.Buffered(); n > peekSize {
-			b, _ = br.Peek(n)
-			peekSize = n
-		}
-		if len(b) > 0 {
-			if b[0] < 'A' || b[0] > 'Z' {
-				// Doesn't look like an HTTP verb
-				// (GET, POST, etc).
-				return
-			}
-			if bytes.Index(b, crlfcrlf) != -1 || bytes.Index(b, lflf) != -1 {
-				req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(b)))
-				if err != nil {
-					return
-				}
-				if len(req.Header["Host"]) > 1 {
-					// TODO(bradfitz): what does
-					// ReadRequest do if there are
-					// multiple Host headers?
-					return
-				}
-				return req.Method, req.Host
-			}
-		}
-		if err != nil {
-			return method, httpHostHeaderFromBytes(b)
-		}
-	}
-}
-
-var (
-	lfHostColon = []byte("\nHost:")
-	lfhostColon = []byte("\nhost:")
-	crlf        = []byte("\r\n")
-	lf          = []byte("\n")
-	crlfcrlf    = []byte("\r\n\r\n")
-	lflf        = []byte("\n\n")
-)
-
-func httpHostHeaderFromBytes(b []byte) string {
-	if i := bytes.Index(b, lfHostColon); i != -1 {
-		return string(bytes.TrimSpace(untilEOL(b[i+len(lfHostColon):])))
-	}
-	if i := bytes.Index(b, lfhostColon); i != -1 {
-		return string(bytes.TrimSpace(untilEOL(b[i+len(lfhostColon):])))
-	}
-	return ""
-}
-
-// untilEOL returns v, truncated before the first '\n' byte, if any.
-// The returned slice may include a '\r' at the end.
-func untilEOL(v []byte) []byte {
-	if i := bytes.IndexByte(v, '\n'); i != -1 {
-		return v[:i]
-	}
-	return v
-}