diff --git a/cgi.go b/cgi.go index b105801891..26312b7d15 100644 --- a/cgi.go +++ b/cgi.go @@ -45,18 +45,7 @@ var cStringHTTPMethods = map[string]*C.char{ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) { request := fc.request // Separate remote IP and port; more lenient than net.SplitHostPort - var ip, port string - if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 { - ip = request.RemoteAddr[:idx] - port = request.RemoteAddr[idx+1:] - } else { - ip = request.RemoteAddr - } - - // Remove [] from IPv6 addresses - if len(ip) > 0 && ip[0] == '[' { - ip = ip[1 : len(ip)-1] - } + ip, port := splitRemoteAddr(request.RemoteAddr) var rs, https, sslProtocol *C.zend_string var sslCipher string @@ -354,6 +343,28 @@ func sanitizedPathJoin(root, reqPath string) string { return path } +// splitRemoteAddr splits "host:port" leniently: a missing port is accepted. +// A malformed value such as "[" must not panic, as that would unwind out of +// the go_register_server_variables cgo callback and crash the whole process. +func splitRemoteAddr(remoteAddr string) (ip, port string) { + if host, p, err := net.SplitHostPort(remoteAddr); err == nil { + return host, p + } + + if idx := strings.LastIndex(remoteAddr, ":"); idx > -1 { + ip = remoteAddr[:idx] + port = remoteAddr[idx+1:] + } else { + ip = remoteAddr + } + + if len(ip) >= 2 && ip[0] == '[' && ip[len(ip)-1] == ']' { + ip = ip[1 : len(ip)-1] + } + + return ip, port +} + const separator = string(filepath.Separator) func ensureLeadingSlash(path string) string { diff --git a/cgi_test.go b/cgi_test.go index f7fdfd856c..181797ccb8 100644 --- a/cgi_test.go +++ b/cgi_test.go @@ -33,6 +33,37 @@ func TestEnsureLeadingSlash(t *testing.T) { } } +func TestSplitRemoteAddr(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + addr string + wantIP string + wantPort string + }{ + {"ipv4 with port", "1.2.3.4:5", "1.2.3.4", "5"}, + {"ipv6 bracketed with port", "[::1]:443", "::1", "443"}, + {"ipv6 zone bracketed with port", "[fe80::1%eth0]:443", "fe80::1%eth0", "443"}, + {"ipv4 without port", "192.168.0.1", "192.168.0.1", ""}, + {"empty", "", "", ""}, + // Must not panic: would crash the process via the cgo callback. + {"lone open bracket", "[", "[", ""}, + {"open bracket with port", "[:9000", "[", "9000"}, + {"empty brackets", "[]", "", ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + ip, port := splitRemoteAddr(tt.addr) + assert.Equal(t, tt.wantIP, ip, "splitRemoteAddr(%q) ip", tt.addr) + assert.Equal(t, tt.wantPort, port, "splitRemoteAddr(%q) port", tt.addr) + }) + } +} + func TestSplitPos(t *testing.T) { tests := []struct { name string