github.com/inazumav/sing-box@v0.0.0-20230926072359-ab51429a14f1/experimental/libbox/http.go (about)

     1  package libbox
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/sha256"
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"encoding/hex"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"math/rand"
    14  	"net"
    15  	"net/http"
    16  	"net/url"
    17  	"os"
    18  	"strconv"
    19  	"sync"
    20  
    21  	C "github.com/inazumav/sing-box/constant"
    22  	"github.com/sagernet/sing/common"
    23  	"github.com/sagernet/sing/common/bufio"
    24  	E "github.com/sagernet/sing/common/exceptions"
    25  	M "github.com/sagernet/sing/common/metadata"
    26  	"github.com/sagernet/sing/protocol/socks"
    27  	"github.com/sagernet/sing/protocol/socks/socks5"
    28  )
    29  
    30  type HTTPClient interface {
    31  	RestrictedTLS()
    32  	ModernTLS()
    33  	PinnedTLS12()
    34  	PinnedSHA256(sumHex string)
    35  	TrySocks5(port int32)
    36  	KeepAlive()
    37  	NewRequest() HTTPRequest
    38  	Close()
    39  }
    40  
    41  type HTTPRequest interface {
    42  	SetURL(link string) error
    43  	SetMethod(method string)
    44  	SetHeader(key string, value string)
    45  	SetContent(content []byte)
    46  	SetContentString(content string)
    47  	RandomUserAgent()
    48  	SetUserAgent(userAgent string)
    49  	Execute() (HTTPResponse, error)
    50  }
    51  
    52  type HTTPResponse interface {
    53  	GetContent() ([]byte, error)
    54  	GetContentString() (string, error)
    55  	WriteTo(path string) error
    56  }
    57  
    58  var (
    59  	_ HTTPClient   = (*httpClient)(nil)
    60  	_ HTTPRequest  = (*httpRequest)(nil)
    61  	_ HTTPResponse = (*httpResponse)(nil)
    62  )
    63  
    64  type httpClient struct {
    65  	tls       tls.Config
    66  	client    http.Client
    67  	transport http.Transport
    68  }
    69  
    70  func NewHTTPClient() HTTPClient {
    71  	client := new(httpClient)
    72  	client.client.Timeout = C.TCPTimeout
    73  	client.client.Transport = &client.transport
    74  	client.transport.TLSClientConfig = &client.tls
    75  	client.transport.DisableKeepAlives = true
    76  	return client
    77  }
    78  
    79  func (c *httpClient) ModernTLS() {
    80  	c.tls.MinVersion = tls.VersionTLS12
    81  	c.tls.CipherSuites = common.Map(tls.CipherSuites(), func(it *tls.CipherSuite) uint16 { return it.ID })
    82  }
    83  
    84  func (c *httpClient) RestrictedTLS() {
    85  	c.tls.MinVersion = tls.VersionTLS13
    86  	c.tls.CipherSuites = common.Map(common.Filter(tls.CipherSuites(), func(it *tls.CipherSuite) bool {
    87  		return common.Contains(it.SupportedVersions, uint16(tls.VersionTLS13))
    88  	}), func(it *tls.CipherSuite) uint16 {
    89  		return it.ID
    90  	})
    91  }
    92  
    93  func (c *httpClient) PinnedTLS12() {
    94  	c.tls.MinVersion = tls.VersionTLS12
    95  	c.tls.MaxVersion = tls.VersionTLS12
    96  }
    97  
    98  func (c *httpClient) PinnedSHA256(sumHex string) {
    99  	c.tls.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
   100  		for _, rawCert := range rawCerts {
   101  			certSum := sha256.Sum256(rawCert)
   102  			if sumHex == hex.EncodeToString(certSum[:]) {
   103  				return nil
   104  			}
   105  		}
   106  		return E.New("pinned sha256 sum mismatch")
   107  	}
   108  }
   109  
   110  func (c *httpClient) TrySocks5(port int32) {
   111  	dialer := new(net.Dialer)
   112  	c.transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
   113  		for {
   114  			socksConn, err := dialer.DialContext(ctx, "tcp", "127.0.0.1:"+strconv.Itoa(int(port)))
   115  			if err != nil {
   116  				break
   117  			}
   118  			_, err = socks.ClientHandshake5(socksConn, socks5.CommandConnect, M.ParseSocksaddr(addr), "", "")
   119  			if err != nil {
   120  				break
   121  			}
   122  			//nolint:staticcheck
   123  			return socksConn, err
   124  		}
   125  		return dialer.DialContext(ctx, network, addr)
   126  	}
   127  }
   128  
   129  func (c *httpClient) KeepAlive() {
   130  	c.transport.ForceAttemptHTTP2 = true
   131  	c.transport.DisableKeepAlives = false
   132  }
   133  
   134  func (c *httpClient) NewRequest() HTTPRequest {
   135  	req := &httpRequest{httpClient: c}
   136  	req.request = http.Request{
   137  		Method: "GET",
   138  		Header: http.Header{},
   139  	}
   140  	return req
   141  }
   142  
   143  func (c *httpClient) Close() {
   144  	c.transport.CloseIdleConnections()
   145  }
   146  
   147  type httpRequest struct {
   148  	*httpClient
   149  	request http.Request
   150  }
   151  
   152  func (r *httpRequest) SetURL(link string) (err error) {
   153  	r.request.URL, err = url.Parse(link)
   154  	if r.request.URL.User != nil {
   155  		user := r.request.URL.User.Username()
   156  		password, _ := r.request.URL.User.Password()
   157  		r.request.SetBasicAuth(user, password)
   158  	}
   159  	return
   160  }
   161  
   162  func (r *httpRequest) SetMethod(method string) {
   163  	r.request.Method = method
   164  }
   165  
   166  func (r *httpRequest) SetHeader(key string, value string) {
   167  	r.request.Header.Set(key, value)
   168  }
   169  
   170  func (r *httpRequest) RandomUserAgent() {
   171  	r.request.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
   172  }
   173  
   174  func (r *httpRequest) SetUserAgent(userAgent string) {
   175  	r.request.Header.Set("User-Agent", userAgent)
   176  }
   177  
   178  func (r *httpRequest) SetContent(content []byte) {
   179  	buffer := bytes.Buffer{}
   180  	buffer.Write(content)
   181  	r.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes()))
   182  	r.request.ContentLength = int64(len(content))
   183  }
   184  
   185  func (r *httpRequest) SetContentString(content string) {
   186  	r.SetContent([]byte(content))
   187  }
   188  
   189  func (r *httpRequest) Execute() (HTTPResponse, error) {
   190  	response, err := r.client.Do(&r.request)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	httpResp := &httpResponse{Response: response}
   195  	if response.StatusCode != http.StatusOK {
   196  		return nil, errors.New(httpResp.errorString())
   197  	}
   198  	return httpResp, nil
   199  }
   200  
   201  type httpResponse struct {
   202  	*http.Response
   203  
   204  	getContentOnce sync.Once
   205  	content        []byte
   206  	contentError   error
   207  }
   208  
   209  func (h *httpResponse) errorString() string {
   210  	content, err := h.GetContentString()
   211  	if err != nil {
   212  		return fmt.Sprint("HTTP ", h.Status)
   213  	}
   214  	return fmt.Sprint("HTTP ", h.Status, ": ", content)
   215  }
   216  
   217  func (h *httpResponse) GetContent() ([]byte, error) {
   218  	h.getContentOnce.Do(func() {
   219  		defer h.Body.Close()
   220  		h.content, h.contentError = io.ReadAll(h.Body)
   221  	})
   222  	return h.content, h.contentError
   223  }
   224  
   225  func (h *httpResponse) GetContentString() (string, error) {
   226  	content, err := h.GetContent()
   227  	if err != nil {
   228  		return "", err
   229  	}
   230  	return string(content), nil
   231  }
   232  
   233  func (h *httpResponse) WriteTo(path string) error {
   234  	defer h.Body.Close()
   235  	file, err := os.Create(path)
   236  	if err != nil {
   237  		return err
   238  	}
   239  	defer file.Close()
   240  	return common.Error(bufio.Copy(file, h.Body))
   241  }