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  }