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  }