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  }