11package frankenphp
22
3- // #cgo nocallback frankenphp_register_bulk
4- // #cgo nocallback frankenphp_register_variables_from_request_info
3+ // #cgo nocallback frankenphp_register_server_vars
54// #cgo nocallback frankenphp_register_variable_safe
6- // #cgo nocallback frankenphp_register_single
7- // #cgo noescape frankenphp_register_bulk
8- // #cgo noescape frankenphp_register_variables_from_request_info
5+ // #cgo nocallback frankenphp_register_known_variable
6+ // #cgo nocallback frankenphp_init_persistent_string
7+ // #cgo noescape frankenphp_register_server_vars
98// #cgo noescape frankenphp_register_variable_safe
10- // #cgo noescape frankenphp_register_single
9+ // #cgo noescape frankenphp_register_known_variable
10+ // #cgo noescape frankenphp_init_persistent_string
1111// #include "frankenphp.h"
1212// #include <php_variables.h>
1313import "C"
@@ -26,47 +26,6 @@ import (
2626 "golang.org/x/text/search"
2727)
2828
29- // Protocol versions, in Apache mod_ssl format: https://httpd.apache.org/docs/current/mod/mod_ssl.html
30- // Note that these are slightly different from SupportedProtocols in caddytls/config.go
31- var tlsProtocolStrings = map [uint16 ]string {
32- tls .VersionTLS10 : "TLSv1" ,
33- tls .VersionTLS11 : "TLSv1.1" ,
34- tls .VersionTLS12 : "TLSv1.2" ,
35- tls .VersionTLS13 : "TLSv1.3" ,
36- }
37-
38- // Known $_SERVER keys
39- var knownServerKeys = []string {
40- "CONTENT_LENGTH" ,
41- "DOCUMENT_ROOT" ,
42- "DOCUMENT_URI" ,
43- "GATEWAY_INTERFACE" ,
44- "HTTP_HOST" ,
45- "HTTPS" ,
46- "PATH_INFO" ,
47- "PHP_SELF" ,
48- "REMOTE_ADDR" ,
49- "REMOTE_HOST" ,
50- "REMOTE_PORT" ,
51- "REQUEST_SCHEME" ,
52- "SCRIPT_FILENAME" ,
53- "SCRIPT_NAME" ,
54- "SERVER_NAME" ,
55- "SERVER_PORT" ,
56- "SERVER_PROTOCOL" ,
57- "SERVER_SOFTWARE" ,
58- "SSL_PROTOCOL" ,
59- "SSL_CIPHER" ,
60- "AUTH_TYPE" ,
61- "REMOTE_IDENT" ,
62- "CONTENT_TYPE" ,
63- "PATH_TRANSLATED" ,
64- "QUERY_STRING" ,
65- "REMOTE_USER" ,
66- "REQUEST_METHOD" ,
67- "REQUEST_URI" ,
68- }
69-
7029// cStringHTTPMethods caches C string versions of common HTTP methods
7130// to avoid allocations in pinCString on every request.
7231var cStringHTTPMethods = map [string ]* C.char {
@@ -87,7 +46,6 @@ var cStringHTTPMethods = map[string]*C.char{
8746// Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
8847func addKnownVariablesToServer (fc * frankenPHPContext , trackVarsArray * C.zval ) {
8948 request := fc .request
90- keys := mainThread .knownServerKeys
9149 // Separate remote IP and port; more lenient than net.SplitHostPort
9250 var ip , port string
9351 if idx := strings .LastIndex (request .RemoteAddr , ":" ); idx > - 1 {
@@ -102,24 +60,21 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
10260 ip = ip [1 : len (ip )- 1 ]
10361 }
10462
105- var https , sslProtocol , sslCipher , rs string
63+ var rs , https , sslProtocol * C.zend_string
64+ var sslCipher string
10665
10766 if request .TLS == nil {
108- rs = "http"
109- https = ""
110- sslProtocol = ""
67+ rs = C . frankenphp_strings . httpLowercase
68+ https = C . frankenphp_strings . empty
69+ sslProtocol = C . frankenphp_strings . empty
11170 sslCipher = ""
11271 } else {
113- rs = "https"
114- https = "on"
72+ rs = C . frankenphp_strings . httpsLowercase
73+ https = C . frankenphp_strings . on
11574
11675 // and pass the protocol details in a manner compatible with Apache's mod_ssl
11776 // (which is why these have an SSL_ prefix and not TLS_).
118- if v , ok := tlsProtocolStrings [request .TLS .Version ]; ok {
119- sslProtocol = v
120- } else {
121- sslProtocol = ""
122- }
77+ sslProtocol = tlsProtocol (request .TLS .Version )
12378
12479 if request .TLS .CipherSuite != 0 {
12580 sslCipher = tls .CipherSuiteName (request .TLS .CipherSuite )
@@ -139,9 +94,9 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
13994 // even if the port is the default port for the scheme and could otherwise be omitted from a URI.
14095 // https://tools.ietf.org/html/rfc3875#section-4.1.15
14196 switch rs {
142- case "https" :
97+ case C . frankenphp_strings . httpsLowercase :
14398 reqPort = "443"
144- case "http" :
99+ case C . frankenphp_strings . httpLowercase :
145100 reqPort = "80"
146101 }
147102 }
@@ -156,59 +111,59 @@ func addKnownVariablesToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
156111 requestURI = fc .requestURI
157112 }
158113
159- C . frankenphp_register_bulk (
160- trackVarsArray ,
161- packCgiVariable ( keys [ "REMOTE_ADDR" ], ip ),
162- packCgiVariable ( keys [ "REMOTE_HOST" ], ip ),
163- packCgiVariable ( keys [ "REMOTE_PORT" ], port ),
164- packCgiVariable ( keys [ "DOCUMENT_ROOT" ], fc .documentRoot ),
165- packCgiVariable ( keys [ "PATH_INFO" ], fc . pathInfo ),
166- packCgiVariable ( keys [ "PHP_SELF" ], ensureLeadingSlash ( request . URL . Path )),
167- packCgiVariable ( keys [ "DOCUMENT_URI" ], fc . docURI ),
168- packCgiVariable ( keys [ "SCRIPT_FILENAME" ], fc . scriptFilename ),
169- packCgiVariable ( keys [ "SCRIPT_NAME" ], fc . scriptName ),
170- packCgiVariable ( keys [ "HTTPS" ], https ),
171- packCgiVariable ( keys [ "SSL_PROTOCOL" ], sslProtocol ),
172- packCgiVariable ( keys [ "REQUEST_SCHEME" ], rs ),
173- packCgiVariable ( keys [ "SERVER_NAME" ], reqHost ),
174- packCgiVariable ( keys [ "SERVER_PORT" ], serverPort ),
175- // Variables defined in CGI 1.1 spec
176- // Some variables are unused but cleared explicitly to prevent
177- // the parent environment from interfering.
178- // These values can not be overridden
179- packCgiVariable ( keys [ "CONTENT_LENGTH" ], contentLength ),
180- packCgiVariable ( keys [ "GATEWAY_INTERFACE" ], "CGI/1.1" ),
181- packCgiVariable ( keys [ "SERVER_PROTOCOL" ], request . Proto ),
182- packCgiVariable ( keys [ "SERVER_SOFTWARE" ], "FrankenPHP" ),
183- packCgiVariable ( keys [ "HTTP_HOST" ], request . Host ),
184- // These values are always empty but must be defined:
185- packCgiVariable ( keys [ "AUTH_TYPE" ], "" ),
186- packCgiVariable ( keys [ "REMOTE_IDENT" ], "" ),
187- // Request uri of the original request
188- packCgiVariable ( keys [ "REQUEST_URI" ], requestURI ),
189- packCgiVariable ( keys [ "SSL_CIPHER" ], sslCipher ),
190- )
191-
192- // These values are already present in the SG(request_info), so we'll register them from there
193- C . frankenphp_register_variables_from_request_info (
194- trackVarsArray ,
195- keys [ "CONTENT_TYPE" ] ,
196- keys [ "PATH_TRANSLATED" ] ,
197- keys [ "QUERY_STRING" ] ,
198- keys [ "REMOTE_USER" ] ,
199- keys [ "REQUEST_METHOD" ],
200- )
201- }
202-
203- func packCgiVariable ( key * C. zend_string , value string ) C. ht_key_value_pair {
204- return C. ht_key_value_pair { key , toUnsafeChar ( value ), C . size_t ( len ( value ))}
114+ requestPath := ensureLeadingSlash ( request . URL . Path )
115+
116+ C . frankenphp_register_server_vars ( trackVarsArray , C. frankenphp_server_vars {
117+ // approximate total length to avoid array re-hashing:
118+ // 28 CGI vars + headers + environment
119+ total_num_vars : C . size_t ( 28 + len ( request . Header ) + len ( fc .env ) + lengthOfEnv ),
120+
121+ // CGI vars with variable values
122+ remote_addr : toUnsafeChar ( ip ),
123+ remote_addr_len : C . size_t ( len ( ip ) ),
124+ remote_host : toUnsafeChar ( ip ),
125+ remote_host_len : C . size_t ( len ( ip ) ),
126+ remote_port : toUnsafeChar ( port ),
127+ remote_port_len : C . size_t ( len ( port ) ),
128+ document_root : toUnsafeChar ( fc . documentRoot ),
129+ document_root_len : C . size_t ( len ( fc . documentRoot ) ),
130+ path_info : toUnsafeChar ( fc . pathInfo ),
131+ path_info_len : C . size_t ( len ( fc . pathInfo )),
132+ php_self : toUnsafeChar ( requestPath ),
133+ php_self_len : C . size_t ( len ( requestPath )),
134+ document_uri : toUnsafeChar ( fc . docURI ),
135+ document_uri_len : C . size_t ( len ( fc . docURI ) ),
136+ script_filename : toUnsafeChar ( fc . scriptFilename ),
137+ script_filename_len : C . size_t ( len ( fc . scriptFilename ) ),
138+ script_name : toUnsafeChar ( fc . scriptName ),
139+ script_name_len : C . size_t ( len ( fc . scriptName )),
140+ server_name : toUnsafeChar ( reqHost ),
141+ server_name_len : C . size_t ( len ( reqHost ) ),
142+ server_port : toUnsafeChar ( serverPort ),
143+ server_port_len : C . size_t ( len ( serverPort ) ),
144+ content_length : toUnsafeChar ( contentLength ),
145+ content_length_len : C . size_t ( len ( contentLength )),
146+ server_protocol : toUnsafeChar ( request . Proto ),
147+ server_protocol_len : C . size_t ( len ( request . Proto )),
148+ http_host : toUnsafeChar ( request . Host ),
149+ http_host_len : C . size_t ( len ( request . Host )) ,
150+ request_uri : toUnsafeChar ( requestURI ) ,
151+ request_uri_len : C . size_t ( len ( requestURI )) ,
152+ ssl_cipher : toUnsafeChar ( sslCipher ) ,
153+ ssl_cipher_len : C . size_t ( len ( sslCipher )) ,
154+
155+ // CGI vars with known values
156+ request_scheme : rs , // "http" or "https"
157+ ssl_protocol : sslProtocol , // values from tlsProtocol
158+ https : https , // "on" or empty
159+ })
205160}
206161
207162func addHeadersToServer (ctx context.Context , request * http.Request , trackVarsArray * C.zval ) {
208163 for field , val := range request .Header {
209- if k := mainThread . commonHeaders [field ]; k != nil {
164+ if k := commonHeaders [field ]; k != nil {
210165 v := strings .Join (val , ", " )
211- C .frankenphp_register_single (k , toUnsafeChar (v ), C .size_t (len (v )), trackVarsArray )
166+ C .frankenphp_register_known_variable (k , toUnsafeChar (v ), C .size_t (len (v )), trackVarsArray )
212167 continue
213168 }
214169
@@ -227,8 +182,8 @@ func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
227182 fc .env = nil
228183}
229184
230- //export go_register_variables
231- func go_register_variables (threadIndex C.uintptr_t , trackVarsArray * C.zval ) {
185+ //export go_register_server_variables
186+ func go_register_server_variables (threadIndex C.uintptr_t , trackVarsArray * C.zval ) {
232187 thread := phpThreads [threadIndex ]
233188 fc := thread .frankenPHPContext ()
234189
@@ -410,8 +365,32 @@ func ensureLeadingSlash(path string) string {
410365 return "/" + path
411366}
412367
368+ // toUnsafeChar returns a *C.char pointing at the backing bytes the Go string.
369+ // If C does not store the string, it may be passed directly in a Cgo call (most efficient).
370+ // If C stores the string, it must be pinned explicitly instead (inefficient).
371+ // C may never modify the string.
413372func toUnsafeChar (s string ) * C.char {
414- sData := unsafe .StringData (s )
373+ return (* C .char )(unsafe .Pointer (unsafe .StringData (s )))
374+ }
375+
376+ // initialize a global zend_string that must never be freed and is ignored by GC
377+ func newPersistentZendString (str string ) * C.zend_string {
378+ return C .frankenphp_init_persistent_string (toUnsafeChar (str ), C .size_t (len (str )))
379+ }
415380
416- return (* C .char )(unsafe .Pointer (sData ))
381+ // Protocol versions, in Apache mod_ssl format: https://httpd.apache.org/docs/current/mod/mod_ssl.html
382+ // Note that these are slightly different from SupportedProtocols in caddytls/config.go
383+ func tlsProtocol (proto uint16 ) * C.zend_string {
384+ switch proto {
385+ case tls .VersionTLS10 :
386+ return C .frankenphp_strings .tls1
387+ case tls .VersionTLS11 :
388+ return C .frankenphp_strings .tls11
389+ case tls .VersionTLS12 :
390+ return C .frankenphp_strings .tls12
391+ case tls .VersionTLS13 :
392+ return C .frankenphp_strings .tls13
393+ default :
394+ return C .frankenphp_strings .empty
395+ }
417396}
0 commit comments