transocks/http_tunnel.go
Алексей Бадяев a59ad95f63
All checks were successful
build / build (push) Successful in 1m34s
build / build_windows (push) Successful in 1m24s
Добавлен build workflow.
2024-11-01 22:43:08 +07:00

133 lines
2.4 KiB
Go

// 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
}