github.com/sagernet/sing@v0.4.0-beta.19.0.20240518125136-f67a0988a636/protocol/http/client.go (about) 1 package http 2 3 import ( 4 std_bufio "bufio" 5 "context" 6 "encoding/base64" 7 "net" 8 "net/http" 9 "net/url" 10 "os" 11 12 "github.com/sagernet/sing/common/buf" 13 "github.com/sagernet/sing/common/bufio" 14 E "github.com/sagernet/sing/common/exceptions" 15 M "github.com/sagernet/sing/common/metadata" 16 N "github.com/sagernet/sing/common/network" 17 ) 18 19 var _ N.Dialer = (*Client)(nil) 20 21 type Client struct { 22 dialer N.Dialer 23 serverAddr M.Socksaddr 24 username string 25 password string 26 host string 27 path string 28 headers http.Header 29 } 30 31 type Options struct { 32 Dialer N.Dialer 33 Server M.Socksaddr 34 Username string 35 Password string 36 Path string 37 Headers http.Header 38 } 39 40 func NewClient(options Options) *Client { 41 client := &Client{ 42 dialer: options.Dialer, 43 serverAddr: options.Server, 44 username: options.Username, 45 password: options.Password, 46 path: options.Path, 47 headers: options.Headers, 48 } 49 if options.Dialer == nil { 50 client.dialer = N.SystemDialer 51 } 52 var host string 53 if client.headers != nil { 54 host = client.headers.Get("Host") 55 client.headers.Del("Host") 56 client.host = host 57 } 58 return client 59 } 60 61 func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 62 network = N.NetworkName(network) 63 switch network { 64 case N.NetworkTCP: 65 case N.NetworkUDP: 66 return nil, os.ErrInvalid 67 default: 68 return nil, E.Extend(N.ErrUnknownNetwork, network) 69 } 70 var conn net.Conn 71 conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr) 72 if err != nil { 73 return nil, err 74 } 75 request := &http.Request{ 76 Method: http.MethodConnect, 77 Header: http.Header{ 78 "Proxy-Connection": []string{"Keep-Alive"}, 79 }, 80 } 81 if c.host != "" && c.host != destination.Fqdn { 82 if c.path != "" { 83 return nil, E.New("Host header and path are not allowed at the same time") 84 } 85 request.Host = c.host 86 request.URL = &url.URL{Opaque: destination.String()} 87 } else { 88 request.URL = &url.URL{Host: destination.String()} 89 } 90 if c.path != "" { 91 err = URLSetPath(request.URL, c.path) 92 if err != nil { 93 return nil, err 94 } 95 } 96 for key, valueList := range c.headers { 97 request.Header.Set(key, valueList[0]) 98 for _, value := range valueList[1:] { 99 request.Header.Add(key, value) 100 } 101 } 102 if c.username != "" { 103 auth := c.username + ":" + c.password 104 request.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 105 } 106 err = request.Write(conn) 107 if err != nil { 108 conn.Close() 109 return nil, err 110 } 111 reader := std_bufio.NewReader(conn) 112 response, err := http.ReadResponse(reader, request) 113 if err != nil { 114 conn.Close() 115 return nil, err 116 } 117 if response.StatusCode == http.StatusOK { 118 if reader.Buffered() > 0 { 119 buffer := buf.NewSize(reader.Buffered()) 120 _, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) 121 if err != nil { 122 conn.Close() 123 return nil, err 124 } 125 conn = bufio.NewCachedConn(conn, buffer) 126 } 127 return conn, nil 128 } else { 129 conn.Close() 130 switch response.StatusCode { 131 case http.StatusProxyAuthRequired: 132 return nil, E.New("authentication required") 133 case http.StatusMethodNotAllowed: 134 return nil, E.New("method not allowed") 135 default: 136 return nil, E.New("unexpected status: ", response.Status) 137 } 138 } 139 } 140 141 func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 142 return nil, os.ErrInvalid 143 }