From 3952168a899bee16a8f5fb06f4ab7f799b8b2b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Wed, 4 Nov 2026 18:40:47 +0100 Subject: [PATCH] feat: populate SERVER_ADDR Set $_SERVER['SERVER_ADDR'] to the IP address that accepted the connection, as Caddy's FastCGI transport, nginx and Apache do. Unix socket listeners have no IP address, so SERVER_ADDR stays unset there. --- cgi.go | 16 ++++++++++++++-- frankenphp.c | 3 +++ frankenphp.h | 3 +++ frankenphp_test.go | 3 +++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/cgi.go b/cgi.go index 427fcdab71..a8e227598b 100644 --- a/cgi.go +++ b/cgi.go @@ -78,6 +78,16 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) { } serverPort := reqPort + + // IP of the interface that accepted the connection, absent for Unix sockets + var serverAddr string + switch addr := request.Context().Value(http.LocalAddrContextKey).(type) { + case *net.TCPAddr: + serverAddr = addr.IP.String() + case *net.UDPAddr: + serverAddr = addr.IP.String() + } + contentLength := request.Header.Get("Content-Length") phpSelf := fc.scriptName + fc.pathInfo @@ -90,8 +100,8 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) { C.frankenphp_register_server_vars(trackVarsArray, C.frankenphp_server_vars{ // approximate total length to avoid array re-hashing: - // 28 CGI vars + headers + environment - total_num_vars: C.size_t(28 + len(request.Header) + len(fc.env) + lengthOfEnv), + // 29 CGI vars + headers + environment + total_num_vars: C.size_t(29 + len(request.Header) + len(fc.env) + lengthOfEnv), // CGI vars with variable values remote_addr: toUnsafeChar(ip), @@ -118,6 +128,8 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) { server_name_len: C.size_t(len(reqHost)), server_port: toUnsafeChar(serverPort), server_port_len: C.size_t(len(serverPort)), + server_addr: toUnsafeChar(serverAddr), + server_addr_len: C.size_t(len(serverAddr)), content_length: toUnsafeChar(contentLength), content_length_len: C.size_t(len(contentLength)), server_protocol: toUnsafeChar(request.Proto), diff --git a/frankenphp.c b/frankenphp.c index c1c20b1151..425eb0d99c 100644 --- a/frankenphp.c +++ b/frankenphp.c @@ -1516,6 +1516,9 @@ void frankenphp_register_server_vars(zval *track_vars_array, FRANKENPHP_REGISTER_VAR(ssl_cipher); FRANKENPHP_REGISTER_VAR(server_name); FRANKENPHP_REGISTER_VAR(server_port); + if (vars.server_addr_len != 0) { + FRANKENPHP_REGISTER_VAR(server_addr); + } FRANKENPHP_REGISTER_VAR(content_length); FRANKENPHP_REGISTER_VAR(server_protocol); FRANKENPHP_REGISTER_VAR(http_host); diff --git a/frankenphp.h b/frankenphp.h index e700deb925..2ecd5fc3a1 100644 --- a/frankenphp.h +++ b/frankenphp.h @@ -85,6 +85,8 @@ typedef struct frankenphp_server_vars { size_t server_name_len; char *server_port; size_t server_port_len; + char *server_addr; + size_t server_addr_len; char *content_length; size_t content_length_len; char *server_protocol; @@ -121,6 +123,7 @@ typedef struct frankenphp_server_vars { X(request_scheme, "REQUEST_SCHEME") \ X(server_name, "SERVER_NAME") \ X(server_port, "SERVER_PORT") \ + X(server_addr, "SERVER_ADDR") \ X(content_length, "CONTENT_LENGTH") \ X(server_protocol, "SERVER_PROTOCOL") \ X(http_host, "HTTP_HOST") \ diff --git a/frankenphp_test.go b/frankenphp_test.go index cedcf88007..75e8130c35 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -16,6 +16,7 @@ import ( "log" "log/slog" "mime/multipart" + "net" "net/http" "net/http/cookiejar" "net/http/httptest" @@ -222,6 +223,7 @@ func testServerVariable(t *testing.T, opts *testOptions) { req := httptest.NewRequest("POST", fmt.Sprintf("http://example.com/server-variable.php/baz/bat?foo=a&bar=b&i=%d#hash", i), strings.NewReader("foo")) req.SetBasicAuth(strings.Clone("kevin"), strings.Clone("password")) req.Header.Add(strings.Clone("Content-Type"), strings.Clone("text/plain")) + req = req.WithContext(context.WithValue(req.Context(), http.LocalAddrContextKey, &net.TCPAddr{IP: net.ParseIP("192.0.2.1"), Port: 80})) body, _ := testRequest(req, handler, t) assert.Contains(t, body, "[REMOTE_HOST]") @@ -243,6 +245,7 @@ func testServerVariable(t *testing.T, opts *testOptions) { assert.Contains(t, body, "[REMOTE_IDENT]") assert.Contains(t, body, "[REQUEST_METHOD] => POST") assert.Contains(t, body, "[SERVER_NAME] => example.com") + assert.Contains(t, body, "[SERVER_ADDR] => 192.0.2.1") assert.Contains(t, body, "[SERVER_PROTOCOL] => HTTP/1.1") assert.Contains(t, body, "[SCRIPT_FILENAME]") assert.Contains(t, body, "[SERVER_SOFTWARE] => FrankenPHP")