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