Skip to content

Commit 82de268

Browse files
authored
feature: Add support for HTTP_PROXY, HTTPS_PROXY, and NO_PROXY (#1260)
1 parent 284f08b commit 82de268

2 files changed

Lines changed: 416 additions & 87 deletions

File tree

internal/api/proxy.go

Lines changed: 9 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package api
22

33
import (
4-
"bufio"
54
"context"
65
"crypto/tls"
7-
"encoding/base64"
8-
"fmt"
96
"net"
107
"net/http"
118
"net/url"
@@ -15,22 +12,22 @@ import (
1512
//
1613
// Note: baseTransport is considered to be a clone created with transport.Clone()
1714
//
18-
// - If a the proxyPath is not empty, a unix socket proxy is created.
19-
// - Otherwise, the proxyURL is used to determine if we should proxy socks5 / http connections
15+
// - If proxyPath is not empty, a unix socket proxy is created.
16+
// - Otherwise, proxyURL is used to determine if we should proxy socks5 / http connections
2017
func withProxyTransport(baseTransport *http.Transport, proxyURL *url.URL, proxyPath string) *http.Transport {
2118
handshakeTLS := func(ctx context.Context, conn net.Conn, addr string) (net.Conn, error) {
2219
// Extract the hostname (without the port) for TLS SNI
2320
host, _, err := net.SplitHostPort(addr)
2421
if err != nil {
2522
return nil, err
2623
}
27-
tlsConn := tls.Client(conn, &tls.Config{
28-
ServerName: host,
29-
// Pull InsecureSkipVerify from the target host transport
30-
// so that insecure-skip-verify flag settings are honored for the proxy server
31-
InsecureSkipVerify: baseTransport.TLSClientConfig.InsecureSkipVerify,
32-
})
24+
cfg := baseTransport.TLSClientConfig.Clone()
25+
if cfg.ServerName == "" {
26+
cfg.ServerName = host
27+
}
28+
tlsConn := tls.Client(conn, cfg)
3329
if err := tlsConn.HandshakeContext(ctx); err != nil {
30+
tlsConn.Close()
3431
return nil, err
3532
}
3633
return tlsConn, nil
@@ -53,82 +50,7 @@ func withProxyTransport(baseTransport *http.Transport, proxyURL *url.URL, proxyP
5350
// clear out any system proxy settings
5451
baseTransport.Proxy = nil
5552
} else if proxyURL != nil {
56-
switch proxyURL.Scheme {
57-
case "socks5", "socks5h":
58-
// SOCKS proxies work out of the box - no need to manually dial
59-
baseTransport.Proxy = http.ProxyURL(proxyURL)
60-
case "http", "https":
61-
dial := func(ctx context.Context, network, addr string) (net.Conn, error) {
62-
// Dial the proxy
63-
d := net.Dialer{}
64-
conn, err := d.DialContext(ctx, "tcp", proxyURL.Host)
65-
if err != nil {
66-
return nil, err
67-
}
68-
69-
// this is the whole point of manually dialing the HTTP(S) proxy:
70-
// being able to force HTTP/1.
71-
// When relying on Transport.Proxy, the protocol is always HTTP/2,
72-
// but many proxy servers don't support HTTP/2.
73-
// We don't want to disable HTTP/2 in general because we want to use it when
74-
// connecting to the Sourcegraph API, using HTTP/1 for the proxy connection only.
75-
protocol := "HTTP/1.1"
76-
77-
// CONNECT is the HTTP method used to set up a tunneling connection with a proxy
78-
method := "CONNECT"
79-
80-
// Manually writing out the HTTP commands because it's not complicated,
81-
// and http.Request has some janky behavior:
82-
// - ignores the Proto field and hard-codes the protocol to HTTP/1.1
83-
// - ignores the Host Header (Header.Set("Host", host)) and uses URL.Host instead.
84-
// - When the Host field is set, overrides the URL field
85-
connectReq := fmt.Sprintf("%s %s %s\r\n", method, addr, protocol)
86-
87-
// A Host header is required per RFC 2616, section 14.23
88-
connectReq += fmt.Sprintf("Host: %s\r\n", addr)
89-
90-
// use authentication if proxy credentials are present
91-
if proxyURL.User != nil {
92-
password, _ := proxyURL.User.Password()
93-
auth := base64.StdEncoding.EncodeToString([]byte(proxyURL.User.Username() + ":" + password))
94-
connectReq += fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", auth)
95-
}
96-
97-
// finish up with an extra carriage return + newline, as per RFC 7230, section 3
98-
connectReq += "\r\n"
99-
100-
// Send the CONNECT request to the proxy to establish the tunnel
101-
if _, err := conn.Write([]byte(connectReq)); err != nil {
102-
conn.Close()
103-
return nil, err
104-
}
105-
106-
// Read and check the response from the proxy
107-
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
108-
if err != nil {
109-
conn.Close()
110-
return nil, err
111-
}
112-
if resp.StatusCode != http.StatusOK {
113-
conn.Close()
114-
return nil, fmt.Errorf("failed to connect to proxy %v: %v", proxyURL, resp.Status)
115-
}
116-
resp.Body.Close()
117-
return conn, nil
118-
}
119-
dialTLS := func(ctx context.Context, network, addr string) (net.Conn, error) {
120-
// Dial the underlying connection through the proxy
121-
conn, err := dial(ctx, network, addr)
122-
if err != nil {
123-
return nil, err
124-
}
125-
return handshakeTLS(ctx, conn, addr)
126-
}
127-
baseTransport.DialContext = dial
128-
baseTransport.DialTLSContext = dialTLS
129-
// clear out any system proxy settings
130-
baseTransport.Proxy = nil
131-
}
53+
baseTransport.Proxy = http.ProxyURL(proxyURL)
13254
}
13355

13456
return baseTransport

0 commit comments

Comments
 (0)