diff --git a/api/go1.tailscale.txt b/api/go1.tailscale.txt new file mode 100644 index 0000000000000..5393b8427d441 --- /dev/null +++ b/api/go1.tailscale.txt @@ -0,0 +1 @@ +pkg net/http, type Transport struct, OnProxyConnectResponse func(context.Context, *url.URL, *Request, *Response) error diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 5fe3e6ebb49ac..507eb7c8b3fe5 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -252,6 +252,11 @@ type Transport struct { // ignored. GetProxyConnectHeader func(ctx context.Context, proxyURL *url.URL, target string) (Header, error) + // OnProxyConnectResponse is called when the Transport gets an HTTP response from + // a proxy for a CONNECT request. It's called before the check for a 200 OK response. + // If it returns an error, the request fails with that error. + OnProxyConnectResponse func(ctx context.Context, proxyURL *url.URL, connectReq *Request, connectRes *Response) error + // MaxResponseHeaderBytes specifies a limit on how many // response bytes are allowed in the server's response // header. @@ -324,6 +329,7 @@ func (t *Transport) Clone() *Transport { ExpectContinueTimeout: t.ExpectContinueTimeout, ProxyConnectHeader: t.ProxyConnectHeader.Clone(), GetProxyConnectHeader: t.GetProxyConnectHeader, + OnProxyConnectResponse: t.OnProxyConnectResponse, MaxResponseHeaderBytes: t.MaxResponseHeaderBytes, ForceAttemptHTTP2: t.ForceAttemptHTTP2, WriteBufferSize: t.WriteBufferSize, @@ -1714,6 +1720,11 @@ func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *pers conn.Close() return nil, err } + if f := t.OnProxyConnectResponse; f != nil { + if err := f(ctx, cm.proxyURL, connectReq, resp); err != nil { + return nil, err + } + } if resp.StatusCode != 200 { _, text, ok := strings.Cut(resp.Status, " ") conn.Close() diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index e5d60afb1bb51..1e46d7eab30d3 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -5878,23 +5878,26 @@ func TestTransportRequestWriteRoundTrip(t *testing.T) { func TestTransportClone(t *testing.T) { tr := &Transport{ - Proxy: func(*Request) (*url.URL, error) { panic("") }, - DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { panic("") }, - Dial: func(network, addr string) (net.Conn, error) { panic("") }, - DialTLS: func(network, addr string) (net.Conn, error) { panic("") }, - DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { panic("") }, - TLSClientConfig: new(tls.Config), - TLSHandshakeTimeout: time.Second, - DisableKeepAlives: true, - DisableCompression: true, - MaxIdleConns: 1, - MaxIdleConnsPerHost: 1, - MaxConnsPerHost: 1, - IdleConnTimeout: time.Second, - ResponseHeaderTimeout: time.Second, - ExpectContinueTimeout: time.Second, - ProxyConnectHeader: Header{}, - GetProxyConnectHeader: func(context.Context, *url.URL, string) (Header, error) { return nil, nil }, + Proxy: func(*Request) (*url.URL, error) { panic("") }, + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { panic("") }, + Dial: func(network, addr string) (net.Conn, error) { panic("") }, + DialTLS: func(network, addr string) (net.Conn, error) { panic("") }, + DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) { panic("") }, + TLSClientConfig: new(tls.Config), + TLSHandshakeTimeout: time.Second, + DisableKeepAlives: true, + DisableCompression: true, + MaxIdleConns: 1, + MaxIdleConnsPerHost: 1, + MaxConnsPerHost: 1, + IdleConnTimeout: time.Second, + ResponseHeaderTimeout: time.Second, + ExpectContinueTimeout: time.Second, + ProxyConnectHeader: Header{}, + GetProxyConnectHeader: func(context.Context, *url.URL, string) (Header, error) { return nil, nil }, + OnProxyConnectResponse: func(ctx context.Context, proxyURL *url.URL, connectReq *Request, connectRes *Response) error { + return nil + }, MaxResponseHeaderBytes: 1, ForceAttemptHTTP2: true, TLSNextProto: map[string]func(authority string, c *tls.Conn) RoundTripper{