github.com/sagernet/sing@v0.2.6/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 path string 27 headers http.Header 28 } 29 30 type Options struct { 31 Dialer N.Dialer 32 Server M.Socksaddr 33 Username string 34 Password string 35 Path string 36 Headers http.Header 37 } 38 39 func NewClient(options Options) *Client { 40 client := &Client{ 41 dialer: options.Dialer, 42 serverAddr: options.Server, 43 username: options.Username, 44 password: options.Password, 45 path: options.Path, 46 headers: options.Headers, 47 } 48 if options.Dialer == nil { 49 client.dialer = N.SystemDialer 50 } 51 return client 52 } 53 54 func (c *Client) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { 55 network = N.NetworkName(network) 56 switch network { 57 case N.NetworkTCP: 58 case N.NetworkUDP: 59 return nil, os.ErrInvalid 60 default: 61 return nil, E.Extend(N.ErrUnknownNetwork, network) 62 } 63 var conn net.Conn 64 conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.serverAddr) 65 if err != nil { 66 return nil, err 67 } 68 request := &http.Request{ 69 Method: http.MethodConnect, 70 URL: &url.URL{ 71 Host: destination.String(), 72 }, 73 Header: http.Header{ 74 "Proxy-Connection": []string{"Keep-Alive"}, 75 }, 76 } 77 if c.path != "" { 78 err = URLSetPath(request.URL, c.path) 79 if err != nil { 80 return nil, err 81 } 82 } 83 for key, valueList := range c.headers { 84 request.Header.Set(key, valueList[0]) 85 for _, value := range valueList[1:] { 86 request.Header.Add(key, value) 87 } 88 } 89 if c.username != "" { 90 auth := c.username + ":" + c.password 91 request.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 92 } 93 err = request.Write(conn) 94 if err != nil { 95 conn.Close() 96 return nil, err 97 } 98 reader := std_bufio.NewReader(conn) 99 response, err := http.ReadResponse(reader, request) 100 if err != nil { 101 conn.Close() 102 return nil, err 103 } 104 if response.StatusCode == http.StatusOK { 105 if reader.Buffered() > 0 { 106 buffer := buf.NewSize(reader.Buffered()) 107 _, err = buffer.ReadFullFrom(reader, buffer.FreeLen()) 108 if err != nil { 109 conn.Close() 110 return nil, err 111 } 112 conn = bufio.NewCachedConn(conn, buffer) 113 } 114 return conn, nil 115 } else { 116 conn.Close() 117 switch response.StatusCode { 118 case http.StatusProxyAuthRequired: 119 return nil, E.New("authentication required") 120 case http.StatusMethodNotAllowed: 121 return nil, E.New("method not allowed") 122 default: 123 return nil, E.New("unexpected status: ", response.Status) 124 } 125 } 126 } 127 128 func (c *Client) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { 129 return nil, os.ErrInvalid 130 }