github.com/yaling888/clash@v1.53.0/listener/http/proxy.go (about) 1 package http 2 3 import ( 4 "fmt" 5 "net" 6 "net/http" 7 "strings" 8 9 "github.com/phuslu/log" 10 11 "github.com/yaling888/clash/adapter/inbound" 12 "github.com/yaling888/clash/common/cache" 13 N "github.com/yaling888/clash/common/net" 14 "github.com/yaling888/clash/component/auth" 15 C "github.com/yaling888/clash/constant" 16 authStore "github.com/yaling888/clash/listener/auth" 17 ) 18 19 func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.LruCache[string, bool], auth auth.Authenticator) { 20 client := newClient(c.RemoteAddr(), c.LocalAddr(), in) 21 defer client.CloseIdleConnections() 22 23 conn := N.NewBufferedConn(c) 24 25 keepAlive := true 26 trusted := cache == nil // disable authenticate if cache is nil 27 28 for keepAlive { 29 request, err := ReadRequest(conn.Reader()) 30 if err != nil { 31 break 32 } 33 34 request.RemoteAddr = conn.RemoteAddr().String() 35 36 keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive" 37 38 var resp *http.Response 39 40 if !trusted { 41 resp = Authenticate(request, cache, auth) 42 43 trusted = resp == nil 44 } 45 46 if trusted { 47 if request.Method == http.MethodConnect { 48 // Manual writing to support CONNECT for http 1.0 (workaround for uplay client) 49 if _, err = fmt.Fprintf(conn, "HTTP/%d.%d %03d %s\r\n\r\n", request.ProtoMajor, request.ProtoMinor, http.StatusOK, "Connection established"); err != nil { 50 break // close connection 51 } 52 53 in <- inbound.NewHTTPS(request, conn) 54 55 return // hijack connection 56 } 57 58 host := request.Header.Get("Host") 59 if host != "" { 60 request.Host = host 61 } 62 63 request.RequestURI = "" 64 65 if isUpgradeRequest(request) { 66 if resp = HandleUpgrade(conn, nil, request, in); resp == nil { 67 return // hijack connection 68 } 69 } 70 71 if resp == nil { 72 RemoveHopByHopHeaders(request.Header) 73 RemoveExtraHTTPHostPort(request) 74 75 if request.URL.Scheme == "" || request.URL.Host == "" { 76 resp = responseWith(request, http.StatusBadRequest) 77 } else { 78 resp, err = client.Do(request) 79 if err != nil { 80 resp = responseWith(request, http.StatusBadGateway) 81 } 82 } 83 } 84 85 RemoveHopByHopHeaders(resp.Header) 86 } 87 88 if keepAlive { 89 resp.Header.Set("Proxy-Connection", "keep-alive") 90 resp.Header.Set("Connection", "keep-alive") 91 resp.Header.Set("Keep-Alive", "timeout=4") 92 } 93 94 resp.Close = !keepAlive 95 96 err = resp.Write(conn) 97 if err != nil { 98 break // close connection 99 } 100 } 101 102 _ = conn.Close() 103 } 104 105 func Authenticate(request *http.Request, cache *cache.LruCache[string, bool], auth auth.Authenticator) *http.Response { 106 authenticator := auth 107 if authenticator == nil { 108 authenticator = authStore.Authenticator() 109 } 110 if authenticator != nil { 111 credential := parseBasicProxyAuthorization(request) 112 if credential == "" { 113 resp := responseWith(request, http.StatusProxyAuthRequired) 114 resp.Header.Set("Proxy-Authenticate", "Basic") 115 return resp 116 } 117 118 authed, exist := cache.Get(credential) 119 if !exist { 120 user, pass, err := decodeBasicProxyAuthorization(credential) 121 authed = err == nil && authenticator.Verify(user, pass) 122 cache.Set(credential, authed) 123 } 124 if !authed { 125 log.Info().Str("client", request.RemoteAddr).Msg("[Inbound] server auth failed") 126 127 return responseWith(request, http.StatusForbidden) 128 } 129 } 130 131 return nil 132 } 133 134 func responseWith(request *http.Request, statusCode int) *http.Response { 135 return &http.Response{ 136 StatusCode: statusCode, 137 Status: http.StatusText(statusCode), 138 Proto: request.Proto, 139 ProtoMajor: request.ProtoMajor, 140 ProtoMinor: request.ProtoMinor, 141 Header: http.Header{}, 142 } 143 }