cgi.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package frankenphp
  2. // #include "frankenphp.h"
  3. import "C"
  4. import (
  5. "crypto/tls"
  6. "net"
  7. "net/http"
  8. "path/filepath"
  9. "runtime"
  10. "strings"
  11. "unsafe"
  12. )
  13. type serverKey int
  14. const (
  15. contentLength serverKey = iota
  16. documentRoot
  17. documentUri
  18. gatewayInterface
  19. httpHost
  20. https
  21. pathInfo
  22. phpSelf
  23. remoteAddr
  24. remoteHost
  25. remotePort
  26. requestScheme
  27. scriptFilename
  28. scriptName
  29. serverName
  30. serverPort
  31. serverProtocol
  32. serverSoftware
  33. sslProtocol
  34. )
  35. var knownServerKeys = map[string]struct{}{
  36. "CONTENT_LENGTH\x00": {},
  37. "DOCUMENT_ROOT\x00": {},
  38. "DOCUMENT_URI\x00": {},
  39. "GATEWAY_INTERFACE\x00": {},
  40. "HTTP_HOST\x00": {},
  41. "HTTPS\x00": {},
  42. "PATH_INFO\x00": {},
  43. "PHP_SELF\x00": {},
  44. "REMOTE_ADDR\x00": {},
  45. "REMOTE_HOST\x00": {},
  46. "REMOTE_PORT\x00": {},
  47. "REQUEST_SCHEME\x00": {},
  48. "SCRIPT_FILENAME\x00": {},
  49. "SCRIPT_NAME\x00": {},
  50. "SERVER_NAME\x00": {},
  51. "SERVER_PORT\x00": {},
  52. "SERVER_PROTOCOL\x00": {},
  53. "SERVER_SOFTWARE\x00": {},
  54. "SSL_PROTOCOL\x00": {},
  55. }
  56. func setKnownServerVariable(p *runtime.Pinner, cArr *[27]C.go_string, serverKey serverKey, val string) {
  57. if val == "" {
  58. return
  59. }
  60. valData := unsafe.StringData(val)
  61. p.Pin(valData)
  62. cArr[serverKey].len = C.size_t(len(val))
  63. cArr[serverKey].data = (*C.char)(unsafe.Pointer(valData))
  64. }
  65. // computeKnownVariables returns a set of CGI environment variables for the request.
  66. //
  67. // TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
  68. // Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
  69. func computeKnownVariables(request *http.Request, p *runtime.Pinner) (cArr [27]C.go_string) {
  70. fc, fcOK := FromContext(request.Context())
  71. if !fcOK {
  72. panic("not a FrankenPHP request")
  73. }
  74. // Separate remote IP and port; more lenient than net.SplitHostPort
  75. var ip, port string
  76. if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 {
  77. ip = request.RemoteAddr[:idx]
  78. port = request.RemoteAddr[idx+1:]
  79. } else {
  80. ip = request.RemoteAddr
  81. }
  82. // Remove [] from IPv6 addresses
  83. ip = strings.Replace(ip, "[", "", 1)
  84. ip = strings.Replace(ip, "]", "", 1)
  85. ra, raOK := fc.env["REMOTE_ADDR\x00"]
  86. if raOK {
  87. setKnownServerVariable(p, &cArr, remoteAddr, ra)
  88. } else {
  89. setKnownServerVariable(p, &cArr, remoteAddr, ip)
  90. }
  91. if rh, ok := fc.env["REMOTE_HOST\x00"]; ok {
  92. setKnownServerVariable(p, &cArr, remoteHost, rh) // For speed, remote host lookups disabled
  93. } else {
  94. if raOK {
  95. setKnownServerVariable(p, &cArr, remoteHost, ip)
  96. } else {
  97. cArr[remoteHost] = cArr[remoteAddr]
  98. }
  99. }
  100. setKnownServerVariable(p, &cArr, remotePort, port)
  101. setKnownServerVariable(p, &cArr, documentRoot, fc.documentRoot)
  102. setKnownServerVariable(p, &cArr, pathInfo, fc.pathInfo)
  103. setKnownServerVariable(p, &cArr, phpSelf, request.URL.Path)
  104. setKnownServerVariable(p, &cArr, documentUri, fc.docURI)
  105. setKnownServerVariable(p, &cArr, scriptFilename, fc.scriptFilename)
  106. setKnownServerVariable(p, &cArr, scriptName, fc.scriptName)
  107. var rs string
  108. if request.TLS == nil {
  109. rs = "http"
  110. } else {
  111. rs = "https"
  112. if h, ok := fc.env["HTTPS\x00"]; ok {
  113. setKnownServerVariable(p, &cArr, https, h)
  114. } else {
  115. setKnownServerVariable(p, &cArr, https, "on")
  116. }
  117. // and pass the protocol details in a manner compatible with apache's mod_ssl
  118. // (which is why these have a SSL_ prefix and not TLS_).
  119. if pr, ok := fc.env["SSL_PROTOCOL\x00"]; ok {
  120. setKnownServerVariable(p, &cArr, sslProtocol, pr)
  121. } else {
  122. if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
  123. setKnownServerVariable(p, &cArr, sslProtocol, v)
  124. }
  125. }
  126. }
  127. setKnownServerVariable(p, &cArr, requestScheme, rs)
  128. reqHost, reqPort, _ := net.SplitHostPort(request.Host)
  129. if reqHost == "" {
  130. // whatever, just assume there was no port
  131. reqHost = request.Host
  132. }
  133. if reqPort == "" {
  134. // compliance with the CGI specification requires that
  135. // the SERVER_PORT variable MUST be set to the TCP/IP port number on which this request is received from the client
  136. // even if the port is the default port for the scheme and could otherwise be omitted from a URI.
  137. // https://tools.ietf.org/html/rfc3875#section-4.1.15
  138. switch rs {
  139. case "https":
  140. reqPort = "443"
  141. case "http":
  142. reqPort = "80"
  143. }
  144. }
  145. setKnownServerVariable(p, &cArr, serverName, reqHost)
  146. if reqPort != "" {
  147. setKnownServerVariable(p, &cArr, serverPort, reqPort)
  148. }
  149. // Variables defined in CGI 1.1 spec
  150. // Some variables are unused but cleared explicitly to prevent
  151. // the parent environment from interfering.
  152. // These values can not be override
  153. setKnownServerVariable(p, &cArr, contentLength, request.Header.Get("Content-Length"))
  154. setKnownServerVariable(p, &cArr, gatewayInterface, "CGI/1.1")
  155. setKnownServerVariable(p, &cArr, serverProtocol, request.Proto)
  156. setKnownServerVariable(p, &cArr, serverSoftware, "FrankenPHP")
  157. setKnownServerVariable(p, &cArr, httpHost, request.Host) // added here, since not always part of headers
  158. return
  159. }
  160. // splitPos returns the index where path should
  161. // be split based on SplitPath.
  162. //
  163. // Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
  164. // Copyright 2015 Matthew Holt and The Caddy Authors
  165. func splitPos(fc *FrankenPHPContext, path string) int {
  166. if len(fc.splitPath) == 0 {
  167. return 0
  168. }
  169. lowerPath := strings.ToLower(path)
  170. for _, split := range fc.splitPath {
  171. if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
  172. return idx + len(split)
  173. }
  174. }
  175. return -1
  176. }
  177. // Map of supported protocols to Apache ssl_mod format
  178. // Note that these are slightly different from SupportedProtocols in caddytls/config.go
  179. var tlsProtocolStrings = map[uint16]string{
  180. tls.VersionTLS10: "TLSv1",
  181. tls.VersionTLS11: "TLSv1.1",
  182. tls.VersionTLS12: "TLSv1.2",
  183. tls.VersionTLS13: "TLSv1.3",
  184. }
  185. var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
  186. // SanitizedPathJoin performs filepath.Join(root, reqPath) that
  187. // is safe against directory traversal attacks. It uses logic
  188. // similar to that in the Go standard library, specifically
  189. // in the implementation of http.Dir. The root is assumed to
  190. // be a trusted path, but reqPath is not; and the output will
  191. // never be outside of root. The resulting path can be used
  192. // with the local file system.
  193. //
  194. // Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
  195. // Copyright 2015 Matthew Holt and The Caddy Authors
  196. func sanitizedPathJoin(root, reqPath string) string {
  197. if root == "" {
  198. root = "."
  199. }
  200. path := filepath.Join(root, filepath.Clean("/"+reqPath))
  201. // filepath.Join also cleans the path, and cleaning strips
  202. // the trailing slash, so we need to re-add it afterwards.
  203. // if the length is 1, then it's a path to the root,
  204. // and that should return ".", so we don't append the separator.
  205. if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
  206. path += separator
  207. }
  208. return path
  209. }
  210. const separator = string(filepath.Separator)