github.com/igoogolx/clash@v1.19.8/adapter/outbound/http.go (about)

     1  package outbound
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"crypto/tls"
     7  	"encoding/base64"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	"net/http"
    13  	"net/url"
    14  	"strconv"
    15  
    16  	"github.com/igoogolx/clash/component/dialer"
    17  	C "github.com/igoogolx/clash/constant"
    18  )
    19  
    20  type Http struct {
    21  	*Base
    22  	user      string
    23  	pass      string
    24  	tlsConfig *tls.Config
    25  	Headers   http.Header
    26  }
    27  
    28  type HttpOption struct {
    29  	BasicOption
    30  	Name           string            `proxy:"name"`
    31  	Server         string            `proxy:"server"`
    32  	Port           int               `proxy:"port"`
    33  	UserName       string            `proxy:"username,omitempty"`
    34  	Password       string            `proxy:"password,omitempty"`
    35  	TLS            bool              `proxy:"tls,omitempty"`
    36  	SNI            string            `proxy:"sni,omitempty"`
    37  	SkipCertVerify bool              `proxy:"skip-cert-verify,omitempty"`
    38  	Headers        map[string]string `proxy:"headers,omitempty"`
    39  }
    40  
    41  // StreamConn implements C.ProxyAdapter
    42  func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
    43  	if h.tlsConfig != nil {
    44  		cc := tls.Client(c, h.tlsConfig)
    45  		ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
    46  		defer cancel()
    47  		err := cc.HandshakeContext(ctx)
    48  		c = cc
    49  		if err != nil {
    50  			return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
    51  		}
    52  	}
    53  
    54  	if err := h.shakeHand(metadata, c); err != nil {
    55  		return nil, err
    56  	}
    57  	return c, nil
    58  }
    59  
    60  // DialContext implements C.ProxyAdapter
    61  func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
    62  	c, err := dialer.DialContext(ctx, "tcp", h.addr, h.Base.DialOptions(opts...)...)
    63  	if err != nil {
    64  		return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
    65  	}
    66  	tcpKeepAlive(c)
    67  
    68  	defer func(c net.Conn) {
    69  		safeConnClose(c, err)
    70  	}(c)
    71  
    72  	c, err = h.StreamConn(c, metadata)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	return NewConn(c, h), nil
    78  }
    79  
    80  func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
    81  	addr := metadata.RemoteAddress()
    82  	req := &http.Request{
    83  		Method: http.MethodConnect,
    84  		URL: &url.URL{
    85  			Host: addr,
    86  		},
    87  		Host:   addr,
    88  		Header: h.Headers.Clone(),
    89  	}
    90  
    91  	req.Header.Add("Proxy-Connection", "Keep-Alive")
    92  
    93  	if h.user != "" && h.pass != "" {
    94  		auth := h.user + ":" + h.pass
    95  		req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
    96  	}
    97  
    98  	if err := req.Write(rw); err != nil {
    99  		return err
   100  	}
   101  
   102  	resp, err := http.ReadResponse(bufio.NewReader(rw), req)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	if resp.StatusCode == http.StatusOK {
   108  		return nil
   109  	}
   110  
   111  	if resp.StatusCode == http.StatusProxyAuthRequired {
   112  		return errors.New("HTTP need auth")
   113  	}
   114  
   115  	if resp.StatusCode == http.StatusMethodNotAllowed {
   116  		return errors.New("CONNECT method not allowed by proxy")
   117  	}
   118  
   119  	if resp.StatusCode >= http.StatusInternalServerError {
   120  		return errors.New(resp.Status)
   121  	}
   122  
   123  	return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
   124  }
   125  
   126  func NewHttp(option HttpOption) *Http {
   127  	var tlsConfig *tls.Config
   128  	if option.TLS {
   129  		sni := option.Server
   130  		if option.SNI != "" {
   131  			sni = option.SNI
   132  		}
   133  		tlsConfig = &tls.Config{
   134  			InsecureSkipVerify: option.SkipCertVerify,
   135  			ServerName:         sni,
   136  		}
   137  	}
   138  
   139  	headers := http.Header{}
   140  	for name, value := range option.Headers {
   141  		headers.Add(name, value)
   142  	}
   143  
   144  	return &Http{
   145  		Base: &Base{
   146  			name:  option.Name,
   147  			addr:  net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
   148  			tp:    C.Http,
   149  			iface: option.Interface,
   150  			rmark: option.RoutingMark,
   151  		},
   152  		user:      option.UserName,
   153  		pass:      option.Password,
   154  		tlsConfig: tlsConfig,
   155  		Headers:   headers,
   156  	}
   157  }