cgi.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. package frankenphp
  2. // #include <php_variables.h>
  3. // #include "frankenphp.h"
  4. import "C"
  5. import (
  6. "crypto/tls"
  7. "net"
  8. "net/http"
  9. "path/filepath"
  10. "strings"
  11. "unsafe"
  12. "github.com/dunglas/frankenphp/internal/phpheaders"
  13. )
  14. var knownServerKeys = []string{
  15. "CONTENT_LENGTH",
  16. "DOCUMENT_ROOT",
  17. "DOCUMENT_URI",
  18. "GATEWAY_INTERFACE",
  19. "HTTP_HOST",
  20. "HTTPS",
  21. "PATH_INFO",
  22. "PHP_SELF",
  23. "REMOTE_ADDR",
  24. "REMOTE_HOST",
  25. "REMOTE_PORT",
  26. "REQUEST_SCHEME",
  27. "SCRIPT_FILENAME",
  28. "SCRIPT_NAME",
  29. "SERVER_NAME",
  30. "SERVER_PORT",
  31. "SERVER_PROTOCOL",
  32. "SERVER_SOFTWARE",
  33. "SSL_PROTOCOL",
  34. "AUTH_TYPE",
  35. "REMOTE_IDENT",
  36. "CONTENT_TYPE",
  37. "PATH_TRANSLATED",
  38. "QUERY_STRING",
  39. "REMOTE_USER",
  40. "REQUEST_METHOD",
  41. "REQUEST_URI",
  42. }
  43. // computeKnownVariables returns a set of CGI environment variables for the request.
  44. //
  45. // TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
  46. // Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
  47. func addKnownVariablesToServer(thread *phpThread, request *http.Request, fc *FrankenPHPContext, trackVarsArray *C.zval) {
  48. keys := getKnownVariableKeys(thread)
  49. // Separate remote IP and port; more lenient than net.SplitHostPort
  50. var ip, port string
  51. if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 {
  52. ip = request.RemoteAddr[:idx]
  53. port = request.RemoteAddr[idx+1:]
  54. } else {
  55. ip = request.RemoteAddr
  56. }
  57. // Remove [] from IPv6 addresses
  58. ip = strings.Replace(ip, "[", "", 1)
  59. ip = strings.Replace(ip, "]", "", 1)
  60. var https string
  61. var sslProtocol string
  62. var rs string
  63. if request.TLS == nil {
  64. rs = "http"
  65. https = ""
  66. sslProtocol = ""
  67. } else {
  68. rs = "https"
  69. https = "on"
  70. // and pass the protocol details in a manner compatible with apache's mod_ssl
  71. // (which is why these have an SSL_ prefix and not TLS_).
  72. if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
  73. sslProtocol = v
  74. } else {
  75. sslProtocol = ""
  76. }
  77. }
  78. reqHost, reqPort, _ := net.SplitHostPort(request.Host)
  79. if reqHost == "" {
  80. // whatever, just assume there was no port
  81. reqHost = request.Host
  82. }
  83. if reqPort == "" {
  84. // compliance with the CGI specification requires that
  85. // the SERVER_PORT variable MUST be set to the TCP/IP port number on which this request is received from the client
  86. // even if the port is the default port for the scheme and could otherwise be omitted from a URI.
  87. // https://tools.ietf.org/html/rfc3875#section-4.1.15
  88. switch rs {
  89. case "https":
  90. reqPort = "443"
  91. case "http":
  92. reqPort = "80"
  93. }
  94. }
  95. serverPort := reqPort
  96. contentLength := request.Header.Get("Content-Length")
  97. var requestURI string
  98. if fc.originalRequest != nil {
  99. requestURI = fc.originalRequest.URL.RequestURI()
  100. } else {
  101. requestURI = request.URL.RequestURI()
  102. }
  103. C.frankenphp_register_bulk(
  104. trackVarsArray,
  105. packCgiVariable(keys["REMOTE_ADDR"], ip),
  106. packCgiVariable(keys["REMOTE_HOST"], ip),
  107. packCgiVariable(keys["REMOTE_PORT"], port),
  108. packCgiVariable(keys["DOCUMENT_ROOT"], fc.documentRoot),
  109. packCgiVariable(keys["PATH_INFO"], fc.pathInfo),
  110. packCgiVariable(keys["PHP_SELF"], request.URL.Path),
  111. packCgiVariable(keys["DOCUMENT_URI"], fc.docURI),
  112. packCgiVariable(keys["SCRIPT_FILENAME"], fc.scriptFilename),
  113. packCgiVariable(keys["SCRIPT_NAME"], fc.scriptName),
  114. packCgiVariable(keys["HTTPS"], https),
  115. packCgiVariable(keys["SSL_PROTOCOL"], sslProtocol),
  116. packCgiVariable(keys["REQUEST_SCHEME"], rs),
  117. packCgiVariable(keys["SERVER_NAME"], reqHost),
  118. packCgiVariable(keys["SERVER_PORT"], serverPort),
  119. // Variables defined in CGI 1.1 spec
  120. // Some variables are unused but cleared explicitly to prevent
  121. // the parent environment from interfering.
  122. // These values can not be overridden
  123. packCgiVariable(keys["CONTENT_LENGTH"], contentLength),
  124. packCgiVariable(keys["GATEWAY_INTERFACE"], "CGI/1.1"),
  125. packCgiVariable(keys["SERVER_PROTOCOL"], request.Proto),
  126. packCgiVariable(keys["SERVER_SOFTWARE"], "FrankenPHP"),
  127. packCgiVariable(keys["HTTP_HOST"], request.Host),
  128. // These values are always empty but must be defined:
  129. packCgiVariable(keys["AUTH_TYPE"], ""),
  130. packCgiVariable(keys["REMOTE_IDENT"], ""),
  131. // Request uri of the original request
  132. packCgiVariable(keys["REQUEST_URI"], requestURI),
  133. )
  134. // These values are already present in the SG(request_info), so we'll register them from there
  135. C.frankenphp_register_variables_from_request_info(
  136. trackVarsArray,
  137. keys["CONTENT_TYPE"],
  138. keys["PATH_TRANSLATED"],
  139. keys["QUERY_STRING"],
  140. keys["REMOTE_USER"],
  141. keys["REQUEST_METHOD"],
  142. )
  143. }
  144. func packCgiVariable(key *C.zend_string, value string) C.ht_key_value_pair {
  145. return C.ht_key_value_pair{key, toUnsafeChar(value), C.size_t(len(value))}
  146. }
  147. func addHeadersToServer(request *http.Request, thread *phpThread, fc *FrankenPHPContext, trackVarsArray *C.zval) {
  148. for field, val := range request.Header {
  149. if k := getCachedHeaderKey(thread, field); k != nil {
  150. v := strings.Join(val, ", ")
  151. C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
  152. continue
  153. }
  154. // if the header name could not be cached, register it inefficiently
  155. k := phpheaders.GetUnCommonHeader(field)
  156. v := strings.Join(val, ", ")
  157. C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
  158. }
  159. }
  160. func addPreparedEnvToServer(fc *FrankenPHPContext, trackVarsArray *C.zval) {
  161. for k, v := range fc.env {
  162. C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
  163. }
  164. fc.env = nil
  165. }
  166. func getKnownVariableKeys(thread *phpThread) map[string]*C.zend_string {
  167. if thread.knownVariableKeys != nil {
  168. return thread.knownVariableKeys
  169. }
  170. threadServerKeys := make(map[string]*C.zend_string)
  171. for _, k := range knownServerKeys {
  172. threadServerKeys[k] = C.frankenphp_init_persistent_string(toUnsafeChar(k), C.size_t(len(k)))
  173. }
  174. thread.knownVariableKeys = threadServerKeys
  175. return threadServerKeys
  176. }
  177. func getCachedHeaderKey(thread *phpThread, key string) *C.zend_string {
  178. if thread.knownHeaderKeys == nil {
  179. thread.knownHeaderKeys = make(map[string]*C.zend_string)
  180. }
  181. // check if the header exists as cached zend_string
  182. if h, ok := thread.knownHeaderKeys[key]; ok {
  183. return h
  184. }
  185. // try to cache cache the header as zend_string
  186. commonKey := phpheaders.GetCommonHeader(key)
  187. if commonKey == "" {
  188. return nil
  189. }
  190. zendHeader := C.frankenphp_init_persistent_string(toUnsafeChar(commonKey), C.size_t(len(commonKey)))
  191. thread.knownHeaderKeys[key] = zendHeader
  192. return zendHeader
  193. }
  194. //export go_register_variables
  195. func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
  196. thread := phpThreads[threadIndex]
  197. r := thread.getActiveRequest()
  198. fc := r.Context().Value(contextKey).(*FrankenPHPContext)
  199. addKnownVariablesToServer(thread, r, fc, trackVarsArray)
  200. addHeadersToServer(r, thread, fc, trackVarsArray)
  201. // The Prepared Environment is registered last and can overwrite any previous values
  202. addPreparedEnvToServer(fc, trackVarsArray)
  203. }
  204. // splitPos returns the index where path should
  205. // be split based on SplitPath.
  206. //
  207. // Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
  208. // Copyright 2015 Matthew Holt and The Caddy Authors
  209. func splitPos(fc *FrankenPHPContext, path string) int {
  210. if len(fc.splitPath) == 0 {
  211. return 0
  212. }
  213. lowerPath := strings.ToLower(path)
  214. for _, split := range fc.splitPath {
  215. if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
  216. return idx + len(split)
  217. }
  218. }
  219. return -1
  220. }
  221. // Map of supported protocols to Apache ssl_mod format
  222. // Note that these are slightly different from SupportedProtocols in caddytls/config.go
  223. var tlsProtocolStrings = map[uint16]string{
  224. tls.VersionTLS10: "TLSv1",
  225. tls.VersionTLS11: "TLSv1.1",
  226. tls.VersionTLS12: "TLSv1.2",
  227. tls.VersionTLS13: "TLSv1.3",
  228. }
  229. // SanitizedPathJoin performs filepath.Join(root, reqPath) that
  230. // is safe against directory traversal attacks. It uses logic
  231. // similar to that in the Go standard library, specifically
  232. // in the implementation of http.Dir. The root is assumed to
  233. // be a trusted path, but reqPath is not; and the output will
  234. // never be outside of root. The resulting path can be used
  235. // with the local file system.
  236. //
  237. // Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
  238. // Copyright 2015 Matthew Holt and The Caddy Authors
  239. func sanitizedPathJoin(root, reqPath string) string {
  240. if root == "" {
  241. root = "."
  242. }
  243. path := filepath.Join(root, filepath.Clean("/"+reqPath))
  244. // filepath.Join also cleans the path, and cleaning strips
  245. // the trailing slash, so we need to re-add it afterward.
  246. // if the length is 1, then it's a path to the root,
  247. // and that should return ".", so we don't append the separator.
  248. if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
  249. path += separator
  250. }
  251. return path
  252. }
  253. const separator = string(filepath.Separator)
  254. func toUnsafeChar(s string) *C.char {
  255. sData := unsafe.StringData(s)
  256. return (*C.char)(unsafe.Pointer(sData))
  257. }