diff --git a/proxy/https.go b/proxy/https.go
@@ -6,9 +6,23 @@ import (
"log"
"net"
"net/http"
- "strings"
)
+// parseAuthority extracts the server name and a dial-ready "host:port" from
+// a CONNECT request's authority. If the input lacks an explicit port (rare —
+// CONNECT requires one), 443 is assumed. IPv6 literals in brackets are
+// handled (e.g. "[::1]:443" → "::1", "[::1]:443").
+func parseAuthority(authority string) (server, hostPort string) {
+ if h, _, err := net.SplitHostPort(authority); err == nil {
+ return h, authority
+ }
+ server = authority
+ if n := len(server); n >= 2 && server[0] == '[' && server[n-1] == ']' {
+ server = server[1 : n-1]
+ }
+ return server, net.JoinHostPort(server, "443")
+}
+
func HttpsHandler(clientConn net.Conn, p *Proxy) {
defer clientConn.Close()
@@ -24,10 +38,7 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
return
}
- host := req.URL.Host
- if !strings.Contains(host, ":") {
- host = host + ":443"
- }
+ serverName, host := parseAuthority(req.URL.Host)
log.Printf("Connecting to target: %s", host)
targetConn, err := p.dialTarget(host)
@@ -40,11 +51,6 @@ func HttpsHandler(clientConn net.Conn, p *Proxy) {
clientConn.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
if p.LocalCA != nil {
- serverName := host
- if idx := strings.LastIndex(host, ":"); idx != -1 {
- serverName = host[:idx]
- }
-
cert, err := p.LocalCA.issueCert(serverName)
if err != nil {
log.Printf("Failed to issue cert for %s: %v", serverName, err)
diff --git a/proxy/https_test.go b/proxy/https_test.go
@@ -0,0 +1,46 @@
+package proxy
+
+import "testing"
+
+func TestParseAuthority(t *testing.T) {
+ cases := []struct {
+ name string
+ in string
+ wantServer string
+ wantHostPort string
+ }{
+ {"hostname with explicit port", "example.com:443", "example.com", "example.com:443"},
+ {"hostname with non-443 port", "localhost:3000", "localhost", "localhost:3000"},
+ {"hostname with high port", "api.internal:8443", "api.internal", "api.internal:8443"},
+ {"bare hostname defaults to 443", "example.com", "example.com", "example.com:443"},
+ {"bare localhost defaults to 443", "localhost", "localhost", "localhost:443"},
+
+ {"subdomain with port", "www.example.com:443", "www.example.com", "www.example.com:443"},
+ {"subdomain with non-443 port", "api.example.com:8080", "api.example.com", "api.example.com:8080"},
+ {"deep subdomain with port", "a.b.c.example.com:9000", "a.b.c.example.com", "a.b.c.example.com:9000"},
+ {"bare subdomain defaults to 443", "www.example.com", "www.example.com", "www.example.com:443"},
+ {"bare deep subdomain defaults to 443", "a.b.c.example.com", "a.b.c.example.com", "a.b.c.example.com:443"},
+ {"hyphenated hostname with port", "my-host-1.example.com:8443", "my-host-1.example.com", "my-host-1.example.com:8443"},
+
+ {"IPv4 with explicit port", "127.0.0.1:8080", "127.0.0.1", "127.0.0.1:8080"},
+ {"IPv4 with 443", "10.0.0.1:443", "10.0.0.1", "10.0.0.1:443"},
+ {"bare IPv4 defaults to 443", "10.0.0.1", "10.0.0.1", "10.0.0.1:443"},
+
+ {"IPv6 bracketed with port", "[::1]:443", "::1", "[::1]:443"},
+ {"IPv6 bracketed with non-443 port", "[fe80::1]:8080", "fe80::1", "[fe80::1]:8080"},
+ {"IPv6 bracketed without port", "[::1]", "::1", "[::1]:443"},
+ {"IPv6 full address bracketed with port", "[2001:db8::1]:9000", "2001:db8::1", "[2001:db8::1]:9000"},
+ }
+
+ for _, tc := range cases {
+ t.Run(tc.name, func(t *testing.T) {
+ gotServer, gotHostPort := parseAuthority(tc.in)
+ if gotServer != tc.wantServer {
+ t.Errorf("server: got %q, want %q", gotServer, tc.wantServer)
+ }
+ if gotHostPort != tc.wantHostPort {
+ t.Errorf("hostPort: got %q, want %q", gotHostPort, tc.wantHostPort)
+ }
+ })
+ }
+}
diff --git a/proxy/proxy.go b/proxy/proxy.go
@@ -6,7 +6,6 @@ import (
"io"
"log"
"net"
- "strings"
"sync"
utls "github.com/bogdanfinn/utls"
@@ -37,9 +36,10 @@ func (p *Proxy) dialTarget(host string) (*utls.UConn, error) {
helloID = p.Fingerprint.ClientHelloID()
}
- serverName := host
- if idx := strings.LastIndex(host, ":"); idx != -1 {
- serverName = host[:idx]
+ serverName, _, err := net.SplitHostPort(host)
+ if err != nil {
+ tcpConn.Close()
+ return nil, fmt.Errorf("dialTarget: parse host %q: %w", host, err)
}
cfg := &utls.Config{ServerName: serverName}
| | |