transocks/http_tunnel.go

133 lines
2.4 KiB
Go
Raw Normal View History

2016-03-04 07:54:59 +06:00
// 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) {
2016-03-09 20:23:35 +06:00
var header http.Header
2016-03-04 07:54:59 +06:00
if uu := u.User; uu != nil {
passwd, _ := uu.Password()
up := uu.Username() + ":" + passwd
2016-03-09 20:23:35 +06:00
authz := "Basic " + base64.StdEncoding.EncodeToString([]byte(up))
header = map[string][]string{
2016-09-01 20:51:27 +07:00
"Proxy-Authorization": {authz},
2016-03-09 20:23:35 +06:00
}
2016-03-04 07:54:59 +06:00
}
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{
2024-11-01 22:43:08 +07:00
Method: http.MethodConnect,
2016-03-04 07:54:59 +06:00
URL: &url.URL{Opaque: addr},
Host: addr,
Header: d.header,
}
c, err = d.forward.Dial("tcp", d.addr)
if err != nil {
return
}
2024-11-01 22:43:08 +07:00
if err = req.Write(c); err != nil {
return
}
2016-03-04 07:54:59 +06:00
// Read response until "\r\n\r\n".
// bufio cannot be used as the connected server may not be
// a HTTP(S) server.
2024-11-01 22:43:08 +07:00
if err = c.SetReadDeadline(time.Now().Add(10 * time.Second)); err != nil {
return
}
2016-03-04 07:54:59 +06:00
buf := make([]byte, 0, 4096)
b := make([]byte, 1)
state := 0
for {
2024-11-01 22:43:08 +07:00
if _, err = c.Read(b); err != nil {
_ = c.Close()
return nil, fmt.Errorf("reset proxy connection: %w", err)
2016-03-04 07:54:59 +06:00
}
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
2024-11-01 22:43:08 +07:00
if err = c.SetReadDeadline(zero); err != nil {
return nil, err
2016-03-04 07:54:59 +06:00
}
2024-11-01 22:43:08 +07:00
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()
2016-03-04 07:54:59 +06:00
return nil, fmt.Errorf("proxy returns %s", resp.Status)
}
return c, nil
}