package http import ( "fmt" "net" "net/http" "strings" "github.com/Dreamacro/clash/adapter/inbound" "github.com/Dreamacro/clash/common/cache" N "github.com/Dreamacro/clash/common/net" C "github.com/Dreamacro/clash/constant" authStore "github.com/Dreamacro/clash/listener/auth" "github.com/Dreamacro/clash/log" ) func HandleConn(c net.Conn, tunnel C.Tunnel, cache *cache.LruCache[string, bool], additions ...inbound.Addition) { client := newClient(c, tunnel, additions...) defer client.CloseIdleConnections() conn := N.NewBufferedConn(c) keepAlive := true trusted := cache == nil // disable authenticate if cache is nil for keepAlive { request, err := ReadRequest(conn.Reader()) if err != nil { break } request.RemoteAddr = conn.RemoteAddr().String() keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive" var resp *http.Response if !trusted { resp = authenticate(request, cache) trusted = resp == nil } if trusted { if request.Method == http.MethodConnect { // Manual writing to support CONNECT for http 1.0 (workaround for uplay client) if _, err = fmt.Fprintf(conn, "HTTP/%d.%d %03d %s\r\n\r\n", request.ProtoMajor, request.ProtoMinor, http.StatusOK, "Connection established"); err != nil { break // close connection } tunnel.HandleTCPConn(inbound.NewHTTPS(request, conn, additions...)) return // hijack connection } host := request.Header.Get("Host") if host != "" { request.Host = host } request.RequestURI = "" if isUpgradeRequest(request) { handleUpgrade(conn, request, tunnel, additions...) return // hijack connection } removeHopByHopHeaders(request.Header) removeExtraHTTPHostPort(request) if request.URL.Scheme == "" || request.URL.Host == "" { resp = responseWith(request, http.StatusBadRequest) } else { resp, err = client.Do(request) if err != nil { resp = responseWith(request, http.StatusBadGateway) } } removeHopByHopHeaders(resp.Header) } if keepAlive { resp.Header.Set("Proxy-Connection", "keep-alive") resp.Header.Set("Connection", "keep-alive") resp.Header.Set("Keep-Alive", "timeout=4") } resp.Close = !keepAlive err = resp.Write(conn) if err != nil { break // close connection } } _ = conn.Close() } func authenticate(request *http.Request, cache *cache.LruCache[string, bool]) *http.Response { authenticator := authStore.Authenticator() if inbound.SkipAuthRemoteAddress(request.RemoteAddr) { authenticator = nil } if authenticator != nil { credential := parseBasicProxyAuthorization(request) if credential == "" { resp := responseWith(request, http.StatusProxyAuthRequired) resp.Header.Set("Proxy-Authenticate", "Basic") return resp } authed, exist := cache.Get(credential) if !exist { user, pass, err := decodeBasicProxyAuthorization(credential) authed = err == nil && authenticator.Verify(user, pass) cache.Set(credential, authed) } if !authed { log.Infoln("Auth failed from %s", request.RemoteAddr) return responseWith(request, http.StatusForbidden) } } return nil } func responseWith(request *http.Request, statusCode int) *http.Response { return &http.Response{ StatusCode: statusCode, Status: http.StatusText(statusCode), Proto: request.Proto, ProtoMajor: request.ProtoMajor, ProtoMinor: request.ProtoMinor, Header: http.Header{}, } }