github.com/chwjbn/xclash@v0.2.0/listener/http/proxy.go (about)

     1  package http
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/chwjbn/xclash/component/auth"
     6  	"net"
     7  	"net/http"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/chwjbn/xclash/adapter/inbound"
    12  	"github.com/chwjbn/xclash/common/cache"
    13  	N "github.com/chwjbn/xclash/common/net"
    14  	C "github.com/chwjbn/xclash/constant"
    15  	authStore "github.com/chwjbn/xclash/listener/auth"
    16  	"github.com/chwjbn/xclash/log"
    17  )
    18  
    19  func HandleConn(c net.Conn, in chan<- C.ConnContext, cache *cache.Cache) {
    20  
    21  	//http客户端,http时有效
    22  	var xHttpClient *http.Client=nil
    23  	defer func() {
    24  		if xHttpClient!=nil{
    25  			xHttpClient.CloseIdleConnections()
    26  		}
    27  	}()
    28  
    29  	conn := N.NewBufferedConn(c)
    30  
    31  	keepAlive := true
    32  
    33  	trusted:=false
    34  	if cache==nil{
    35  		trusted=true
    36  	}
    37  
    38  	for keepAlive {
    39  
    40  		request, err := ReadRequest(conn.Reader())
    41  		if err != nil {
    42  			break
    43  		}
    44  
    45  		keepAlive = strings.TrimSpace(strings.ToLower(request.Header.Get("Proxy-Connection"))) == "keep-alive"
    46  
    47  		request.RemoteAddr = conn.RemoteAddr().String()
    48  
    49  		var authUser *auth.AuthUser
    50  		var resp *http.Response
    51  
    52  		if !trusted{
    53  			resp,authUser = authenticate(request, cache)
    54  			if resp==nil{
    55  				trusted=true
    56  			}
    57  		}
    58  
    59  		if trusted{
    60  
    61  			//HTTPS连接
    62  			if strings.EqualFold(request.Method,http.MethodConnect){
    63  
    64  				// Manual writing to support CONNECT for http 1.0 (workaround for uplay client)
    65  				if _, err = fmt.Fprintf(conn, "HTTP/%d.%d %03d %s\r\n\r\n", request.ProtoMajor, request.ProtoMinor, http.StatusOK, "Connection established"); err != nil {
    66  					break // close connection
    67  				}
    68  
    69  				connCtx:=inbound.NewHTTPS(request, conn)
    70  				connCtx.SetAuthUser(authUser)
    71  
    72  				in <- connCtx
    73  
    74  				return // hijack connection
    75  
    76  			}
    77  
    78  
    79  			//HTTP连接
    80  			if xHttpClient==nil{
    81  				xHttpClient=newHttpClient(c.RemoteAddr(), in,authUser)
    82  			}
    83  
    84  			host := request.Header.Get("Host")
    85  			if host != "" {
    86  				request.Host = host
    87  			}
    88  
    89  			request.RequestURI = ""
    90  			removeHopByHopHeaders(request.Header)
    91  			removeExtraHTTPHostPort(request)
    92  
    93  			if request.URL.Scheme == "" || request.URL.Host == "" {
    94  				resp = responseWith(request, http.StatusBadRequest)
    95  			} else {
    96  				resp, err = xHttpClient.Do(request)
    97  				if err != nil {
    98  					resp = responseWith(request, http.StatusBadGateway)
    99  				}
   100  			}
   101  
   102  			removeHopByHopHeaders(resp.Header)
   103  		}
   104  
   105  
   106  		if keepAlive {
   107  			resp.Header.Set("Proxy-Connection", "keep-alive")
   108  			resp.Header.Set("Connection", "keep-alive")
   109  			resp.Header.Set("Keep-Alive", "timeout=4")
   110  		}
   111  
   112  		resp.Close = !keepAlive
   113  
   114  		err = resp.Write(conn)
   115  		if err != nil {
   116  			break // close connection
   117  		}
   118  
   119  	}
   120  
   121  	conn.Close()
   122  }
   123  
   124  func authenticate(request *http.Request, cache *cache.Cache) (*http.Response,*auth.AuthUser) {
   125  
   126  	var authUser *auth.AuthUser=nil
   127  
   128  	authenticator := authStore.Authenticator()
   129  	if authenticator != nil {
   130  		credential := parseBasicProxyAuthorization(request)
   131  		if credential == "" {
   132  			resp := responseWith(request, http.StatusProxyAuthRequired)
   133  			resp.Header.Set("Proxy-Authenticate", "Basic")
   134  			return resp,authUser
   135  		}
   136  
   137  		var authed interface{}
   138  		if authed = cache.Get(credential); authed == nil {
   139  			user, pass, err := decodeBasicProxyAuthorization(credential)
   140  			authed = err == nil && authenticator.Verify(user, pass)
   141  			cache.Put(credential, authed, time.Minute)
   142  
   143  			authUser=&auth.AuthUser{User: user,Pass: pass}
   144  		}
   145  		if !authed.(bool) {
   146  			log.Infoln("Auth failed from %s", request.RemoteAddr)
   147  
   148  			return responseWith(request, http.StatusForbidden),authUser
   149  		}
   150  	}
   151  
   152  	return nil,authUser
   153  }
   154  
   155  func responseWith(request *http.Request, statusCode int) *http.Response {
   156  	return &http.Response{
   157  		StatusCode: statusCode,
   158  		Status:     http.StatusText(statusCode),
   159  		Proto:      request.Proto,
   160  		ProtoMajor: request.ProtoMajor,
   161  		ProtoMinor: request.ProtoMinor,
   162  		Header:     http.Header{},
   163  	}
   164  }