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 }