// This file provides a dialer type of "http://" scheme for // golang.org/x/net/proxy package. // // The dialer type will be automatically registered by init(). // // The dialer requests an upstream HTTP proxy to create a TCP tunnel // by CONNECT method. package transocks import ( "bufio" "bytes" "encoding/base64" "fmt" "net" "net/http" "net/url" "time" "golang.org/x/net/proxy" ) func init() { proxy.RegisterDialerType("http", httpDialType) } type httpDialer struct { addr string header http.Header forward proxy.Dialer } func httpDialType(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { var header http.Header if uu := u.User; uu != nil { passwd, _ := uu.Password() up := uu.Username() + ":" + passwd authz := "Basic " + base64.StdEncoding.EncodeToString([]byte(up)) header = map[string][]string{ "Proxy-Authorization": {authz}, } } return &httpDialer{ addr: u.Host, header: header, forward: forward, }, nil } func (d *httpDialer) Dial(network, addr string) (c net.Conn, err error) { req := &http.Request{ Method: http.MethodConnect, URL: &url.URL{Opaque: addr}, Host: addr, Header: d.header, } c, err = d.forward.Dial("tcp", d.addr) if err != nil { return } if err = req.Write(c); err != nil { return } // Read response until "\r\n\r\n". // bufio cannot be used as the connected server may not be // a HTTP(S) server. if err = c.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil { return } buf := make([]byte, 0, 4096) b := make([]byte, 1) state := 0 for { if _, err = c.Read(b); err != nil { _ = c.Close() return nil, fmt.Errorf("reset proxy connection: %w", err) } buf = append(buf, b[0]) switch state { case 0: if b[0] == byte('\r') { state++ } continue case 1: if b[0] == byte('\n') { state++ } else { state = 0 } continue case 2: if b[0] == byte('\r') { state++ } else { state = 0 } continue case 3: if b[0] == byte('\n') { goto PARSE } else { state = 0 } } } PARSE: var zero time.Time if err = c.SetReadDeadline(zero); err != nil { return nil, err } resp, err := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(buf)), req) if err != nil { _ = c.Close() return nil, err } _ = resp.Body.Close() if resp.StatusCode != http.StatusOK { _ = c.Close() return nil, fmt.Errorf("proxy returns %s", resp.Status) } return c, nil }