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 }