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  }