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