Files
timmy-config/go/pkg/mod/github.com/valyala/fasthttp@v1.59.0/fasthttpproxy/dialer.go
2026-03-31 20:02:01 +00:00

264 lines
7.8 KiB
Go

package fasthttpproxy
import (
"bufio"
"encoding/base64"
"errors"
"fmt"
"net"
"net/url"
"strings"
"sync"
"time"
"github.com/valyala/fasthttp"
"golang.org/x/net/http/httpproxy"
"golang.org/x/net/proxy"
)
var (
// Used for caching authentication information when using an HTTP proxy,
// it helps avoid re-encoding the authentication details when the ProxyURL
// changes along with the request URL.
authCache = sync.Map{}
colonTLSPort = ":443"
tmpURL = &url.URL{Scheme: httpsScheme, Host: "example.com"}
)
// Dialer embeds both fasthttp.TCPDialer and httpproxy.Config, allowing it
// to take advantage of the optimizations provided by fasthttp for dialing while also
// utilizing the finer-grained configuration options offered by httpproxy.
type Dialer struct {
fasthttp.TCPDialer
// Support HTTPProxy, HTTPSProxy and NoProxy configuration.
//
// HTTPProxy represents the value of the HTTP_PROXY or
// http_proxy environment variable. It will be used as the proxy
// URL for HTTP requests unless overridden by NoProxy.
//
// HTTPSProxy represents the HTTPS_PROXY or https_proxy
// environment variable. It will be used as the proxy URL for
// HTTPS requests unless overridden by NoProxy.
//
// NoProxy represents the NO_PROXY or no_proxy environment
// variable. It specifies a string that contains comma-separated values
// specifying hosts that should be excluded from proxying. Each value is
// represented by an IP address prefix (1.2.3.4), an IP address prefix in
// CIDR notation (1.2.3.4/8), a domain name, or a special DNS label (*).
// An IP address prefix and domain name can also include a literal port
// number (1.2.3.4:80).
// A domain name matches that name and all subdomains. A domain name with
// a leading "." matches subdomains only. For example "foo.com" matches
// "foo.com" and "bar.foo.com"; ".y.com" matches "x.y.com" but not "y.com".
// A single asterisk (*) indicates that no proxying should be done.
// A best effort is made to parse the string and errors are
// ignored.
httpproxy.Config
// Attempt to connect to both ipv4 and ipv6 addresses if set to true.
// By default, dial only to ipv4 addresses,
// since unfortunately ipv6 remains broken in many networks worldwide :)
//
// This field from the fasthttp client is provided redundantly here because
// when we customize the Dial function for the client, its DialDualStack field
// configuration becomes ineffective.
DialDualStack bool
// Dial timeout.
//
// This field from the fasthttp client is provided redundantly here because
// when we customize the Dial function for the client, its DialTimeout field
// configuration becomes ineffective.
Timeout time.Duration
// The timeout for sending a CONNECT request when using an HTTP proxy.
ConnectTimeout time.Duration
}
// GetDialFunc method returns a fasthttp-style dial function. The useEnv parameter
// determines whether the proxy address comes from Dialer.Config or from environment variables.
func (d *Dialer) GetDialFunc(useEnv bool) (dialFunc fasthttp.DialFunc, err error) {
config := &d.Config
if useEnv {
config = httpproxy.FromEnvironment()
}
proxyURLIsSame := false
if config.HTTPSProxy == config.HTTPProxy && config.NoProxy == "" {
proxyURLIsSame = true
}
network := "tcp4"
if d.DialDualStack {
network = "tcp"
}
proxyFunc := config.ProxyFunc()
if proxyURLIsSame {
var proxyURL *url.URL
var proxyDialer proxy.Dialer
proxyURL, err = proxyFunc(tmpURL)
if err != nil {
return nil, err
}
if proxyURL == nil {
// dial directly
return func(addr string) (net.Conn, error) {
return d.Dial(network, addr)
}, nil
}
switch proxyURL.Scheme {
case "socks5", "socks5h":
proxyDialer, err = proxy.FromURL(proxyURL, d)
if err != nil {
return
}
case "http":
proxyAddr, auth := addrAndAuth(proxyURL)
proxyDialer = DialerFunc(func(network, addr string) (conn net.Conn, err error) {
return httpProxyDial(d, network, addr, proxyAddr, auth)
})
default:
return nil, errors.New("proxy: unknown scheme: " + proxyURL.Scheme)
}
return func(addr string) (net.Conn, error) {
return proxyDialer.Dial(network, addr)
}, nil
}
// slow path when the proxyURL changes along with the request URL.
return func(addr string) (conn net.Conn, err error) {
var proxyDialer proxy.Dialer
var proxyURL *url.URL
scheme := httpsScheme
if !strings.HasSuffix(addr, colonTLSPort) {
scheme = httpScheme
}
reqURL := &url.URL{Host: addr, Scheme: scheme}
proxyURL, err = proxyFunc(reqURL)
if err != nil {
return
}
if proxyURL == nil {
// dial directly
return d.Dial(network, addr)
}
switch proxyURL.Scheme {
case "socks5", "socks5h":
proxyDialer, err = proxy.FromURL(proxyURL, d)
if err != nil {
return
}
case "http":
proxyAddr, auth := addrAndAuth(proxyURL)
proxyDialer = DialerFunc(func(network, addr string) (conn net.Conn, err error) {
return httpProxyDial(d, network, addr, proxyAddr, auth)
})
default:
return nil, errors.New("proxy: unknown scheme: " + proxyURL.Scheme)
}
return proxyDialer.Dial(network, addr)
}, nil
}
// Dial is solely for implementing the proxy.Dialer interface.
func (d *Dialer) Dial(network, addr string) (conn net.Conn, err error) {
if network == "tcp4" {
if d.Timeout > 0 {
return d.TCPDialer.DialTimeout(addr, d.Timeout)
}
return d.TCPDialer.Dial(addr)
}
if network == "tcp" {
if d.Timeout > 0 {
return d.TCPDialer.DialDualStackTimeout(addr, d.Timeout)
}
return d.TCPDialer.DialDualStack(addr)
}
err = errors.New("dont support the network: " + network)
return
}
func (d *Dialer) connectTimeout() time.Duration {
return d.ConnectTimeout
}
// In the httpProxyDial function, the proxy.Dialer that implements
// this interface can retrieve timeout information when sending the CONNECT
// method to the HTTP proxy.
type httpProxyDialer interface {
connectTimeout() time.Duration
}
// DialerFunc Make a function of type func(network, addr string) (net.Conn, error)
// implement the proxy.Dialer interface.
type DialerFunc func(network, addr string) (net.Conn, error)
func (d DialerFunc) Dial(network, addr string) (net.Conn, error) {
return d(network, addr)
}
// Establish a connection through an HTTP proxy.
func httpProxyDial(dialer proxy.Dialer, network, addr, proxyAddr, auth string) (conn net.Conn, err error) {
conn, err = dialer.Dial(network, proxyAddr)
if err != nil {
return
}
var connectTimeout time.Duration
hp, ok := dialer.(httpProxyDialer)
if ok {
connectTimeout = hp.connectTimeout()
}
if connectTimeout > 0 {
if err = conn.SetDeadline(time.Now().Add(connectTimeout)); err != nil {
_ = conn.Close()
return nil, err
}
defer func() {
_ = conn.SetDeadline(time.Time{})
}()
}
req := "CONNECT " + addr + " HTTP/1.1\r\nHost: " + addr + "\r\n"
if auth != "" {
req += "Proxy-Authorization: Basic " + auth + "\r\n"
}
req += "\r\n"
_, err = conn.Write([]byte(req))
if err != nil {
_ = conn.Close()
return
}
res := fasthttp.AcquireResponse()
defer fasthttp.ReleaseResponse(res)
res.SkipBody = true
if err = res.Read(bufio.NewReaderSize(conn, 1024)); err != nil {
_ = conn.Close()
return
}
if res.Header.StatusCode() != 200 {
_ = conn.Close()
err = fmt.Errorf("could not connect to proxyAddr: %s status code: %d", proxyAddr, res.Header.StatusCode())
return
}
return
}
// Cache authentication information for HTTP proxies.
type proxyInfo struct {
auth string
addr string
}
func addrAndAuth(pu *url.URL) (proxyAddr, auth string) {
if pu.User == nil {
proxyAddr = pu.Host + pu.Path
return
}
var info *proxyInfo
v, ok := authCache.Load(pu)
if ok {
info = v.(*proxyInfo)
return info.addr, info.auth
}
info = &proxyInfo{
auth: base64.StdEncoding.EncodeToString([]byte(pu.User.String())),
addr: pu.Host + pu.Path,
}
authCache.Store(pu, info)
return info.addr, info.auth
}