github.com/sagernet/sing-box@v1.9.0-rc.20/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  	"time"
    21  
    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 = 15 * time.Second
    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 err != nil {
   155  		return
   156  	}
   157  	if r.request.URL.User != nil {
   158  		user := r.request.URL.User.Username()
   159  		password, _ := r.request.URL.User.Password()
   160  		r.request.SetBasicAuth(user, password)
   161  	}
   162  	return
   163  }
   164  
   165  func (r *httpRequest) SetMethod(method string) {
   166  	r.request.Method = method
   167  }
   168  
   169  func (r *httpRequest) SetHeader(key string, value string) {
   170  	r.request.Header.Set(key, value)
   171  }
   172  
   173  func (r *httpRequest) RandomUserAgent() {
   174  	r.request.Header.Set("User-Agent", fmt.Sprintf("curl/7.%d.%d", rand.Int()%54, rand.Int()%2))
   175  }
   176  
   177  func (r *httpRequest) SetUserAgent(userAgent string) {
   178  	r.request.Header.Set("User-Agent", userAgent)
   179  }
   180  
   181  func (r *httpRequest) SetContent(content []byte) {
   182  	buffer := bytes.Buffer{}
   183  	buffer.Write(content)
   184  	r.request.Body = io.NopCloser(bytes.NewReader(buffer.Bytes()))
   185  	r.request.ContentLength = int64(len(content))
   186  }
   187  
   188  func (r *httpRequest) SetContentString(content string) {
   189  	r.SetContent([]byte(content))
   190  }
   191  
   192  func (r *httpRequest) Execute() (HTTPResponse, error) {
   193  	response, err := r.client.Do(&r.request)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  	httpResp := &httpResponse{Response: response}
   198  	if response.StatusCode != http.StatusOK {
   199  		return nil, errors.New(httpResp.errorString())
   200  	}
   201  	return httpResp, nil
   202  }
   203  
   204  type httpResponse struct {
   205  	*http.Response
   206  
   207  	getContentOnce sync.Once
   208  	content        []byte
   209  	contentError   error
   210  }
   211  
   212  func (h *httpResponse) errorString() string {
   213  	content, err := h.GetContentString()
   214  	if err != nil {
   215  		return fmt.Sprint("HTTP ", h.Status)
   216  	}
   217  	return fmt.Sprint("HTTP ", h.Status, ": ", content)
   218  }
   219  
   220  func (h *httpResponse) GetContent() ([]byte, error) {
   221  	h.getContentOnce.Do(func() {
   222  		defer h.Body.Close()
   223  		h.content, h.contentError = io.ReadAll(h.Body)
   224  	})
   225  	return h.content, h.contentError
   226  }
   227  
   228  func (h *httpResponse) GetContentString() (string, error) {
   229  	content, err := h.GetContent()
   230  	if err != nil {
   231  		return "", err
   232  	}
   233  	return string(content), nil
   234  }
   235  
   236  func (h *httpResponse) WriteTo(path string) error {
   237  	defer h.Body.Close()
   238  	file, err := os.Create(path)
   239  	if err != nil {
   240  		return err
   241  	}
   242  	defer file.Close()
   243  	return common.Error(bufio.Copy(file, h.Body))
   244  }