123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- package frankenphp
- // #include "frankenphp.h"
- import "C"
- import (
- "crypto/tls"
- "net"
- "net/http"
- "path/filepath"
- "runtime"
- "strings"
- "unsafe"
- )
- type serverKey int
- const (
- contentLength serverKey = iota
- documentRoot
- documentUri
- gatewayInterface
- httpHost
- https
- pathInfo
- phpSelf
- remoteAddr
- remoteHost
- remotePort
- requestScheme
- scriptFilename
- scriptName
- serverName
- serverPort
- serverProtocol
- serverSoftware
- sslProtocol
- )
- var knownServerKeys = map[string]struct{}{
- "CONTENT_LENGTH\x00": {},
- "DOCUMENT_ROOT\x00": {},
- "DOCUMENT_URI\x00": {},
- "GATEWAY_INTERFACE\x00": {},
- "HTTP_HOST\x00": {},
- "HTTPS\x00": {},
- "PATH_INFO\x00": {},
- "PHP_SELF\x00": {},
- "REMOTE_ADDR\x00": {},
- "REMOTE_HOST\x00": {},
- "REMOTE_PORT\x00": {},
- "REQUEST_SCHEME\x00": {},
- "SCRIPT_FILENAME\x00": {},
- "SCRIPT_NAME\x00": {},
- "SERVER_NAME\x00": {},
- "SERVER_PORT\x00": {},
- "SERVER_PROTOCOL\x00": {},
- "SERVER_SOFTWARE\x00": {},
- "SSL_PROTOCOL\x00": {},
- }
- func setKnownServerVariable(p *runtime.Pinner, cArr *[27]C.go_string, serverKey serverKey, val string) {
- if val == "" {
- return
- }
- valData := unsafe.StringData(val)
- p.Pin(valData)
- cArr[serverKey].len = C.size_t(len(val))
- cArr[serverKey].data = (*C.char)(unsafe.Pointer(valData))
- }
- // computeKnownVariables returns a set of CGI environment variables for the request.
- //
- // TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
- // Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
- func computeKnownVariables(request *http.Request, p *runtime.Pinner) (cArr [27]C.go_string) {
- fc, fcOK := FromContext(request.Context())
- if !fcOK {
- panic("not a FrankenPHP 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
- ip = strings.Replace(ip, "[", "", 1)
- ip = strings.Replace(ip, "]", "", 1)
- ra, raOK := fc.env["REMOTE_ADDR\x00"]
- if raOK {
- setKnownServerVariable(p, &cArr, remoteAddr, ra)
- } else {
- setKnownServerVariable(p, &cArr, remoteAddr, ip)
- }
- if rh, ok := fc.env["REMOTE_HOST\x00"]; ok {
- setKnownServerVariable(p, &cArr, remoteHost, rh) // For speed, remote host lookups disabled
- } else {
- if raOK {
- setKnownServerVariable(p, &cArr, remoteHost, ip)
- } else {
- cArr[remoteHost] = cArr[remoteAddr]
- }
- }
- setKnownServerVariable(p, &cArr, remotePort, port)
- setKnownServerVariable(p, &cArr, documentRoot, fc.documentRoot)
- setKnownServerVariable(p, &cArr, pathInfo, fc.pathInfo)
- setKnownServerVariable(p, &cArr, phpSelf, request.URL.Path)
- setKnownServerVariable(p, &cArr, documentUri, fc.docURI)
- setKnownServerVariable(p, &cArr, scriptFilename, fc.scriptFilename)
- setKnownServerVariable(p, &cArr, scriptName, fc.scriptName)
- var rs string
- if request.TLS == nil {
- rs = "http"
- } else {
- rs = "https"
- if h, ok := fc.env["HTTPS\x00"]; ok {
- setKnownServerVariable(p, &cArr, https, h)
- } else {
- setKnownServerVariable(p, &cArr, https, "on")
- }
- // and pass the protocol details in a manner compatible with apache's mod_ssl
- // (which is why these have a SSL_ prefix and not TLS_).
- if pr, ok := fc.env["SSL_PROTOCOL\x00"]; ok {
- setKnownServerVariable(p, &cArr, sslProtocol, pr)
- } else {
- if v, ok := tlsProtocolStrings[request.TLS.Version]; ok {
- setKnownServerVariable(p, &cArr, sslProtocol, v)
- }
- }
- }
- setKnownServerVariable(p, &cArr, requestScheme, rs)
- reqHost, reqPort, _ := net.SplitHostPort(request.Host)
- if reqHost == "" {
- // whatever, just assume there was no port
- reqHost = request.Host
- }
- if reqPort == "" {
- // compliance with the CGI specification requires that
- // the SERVER_PORT variable MUST be set to the TCP/IP port number on which this request is received from the client
- // even if the port is the default port for the scheme and could otherwise be omitted from a URI.
- // https://tools.ietf.org/html/rfc3875#section-4.1.15
- switch rs {
- case "https":
- reqPort = "443"
- case "http":
- reqPort = "80"
- }
- }
- setKnownServerVariable(p, &cArr, serverName, reqHost)
- if reqPort != "" {
- setKnownServerVariable(p, &cArr, serverPort, reqPort)
- }
- // Variables defined in CGI 1.1 spec
- // Some variables are unused but cleared explicitly to prevent
- // the parent environment from interfering.
- // These values can not be override
- setKnownServerVariable(p, &cArr, contentLength, request.Header.Get("Content-Length"))
- setKnownServerVariable(p, &cArr, gatewayInterface, "CGI/1.1")
- setKnownServerVariable(p, &cArr, serverProtocol, request.Proto)
- setKnownServerVariable(p, &cArr, serverSoftware, "FrankenPHP")
- setKnownServerVariable(p, &cArr, httpHost, request.Host) // added here, since not always part of headers
- return
- }
- // splitPos returns the index where path should
- // be split based on SplitPath.
- //
- // Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
- // Copyright 2015 Matthew Holt and The Caddy Authors
- func splitPos(fc *FrankenPHPContext, path string) int {
- if len(fc.splitPath) == 0 {
- return 0
- }
- lowerPath := strings.ToLower(path)
- for _, split := range fc.splitPath {
- if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
- return idx + len(split)
- }
- }
- return -1
- }
- // Map of supported protocols to Apache ssl_mod format
- // Note that these are slightly different from SupportedProtocols in caddytls/config.go
- var tlsProtocolStrings = map[uint16]string{
- tls.VersionTLS10: "TLSv1",
- tls.VersionTLS11: "TLSv1.1",
- tls.VersionTLS12: "TLSv1.2",
- tls.VersionTLS13: "TLSv1.3",
- }
- var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
- // SanitizedPathJoin performs filepath.Join(root, reqPath) that
- // is safe against directory traversal attacks. It uses logic
- // similar to that in the Go standard library, specifically
- // in the implementation of http.Dir. The root is assumed to
- // be a trusted path, but reqPath is not; and the output will
- // never be outside of root. The resulting path can be used
- // with the local file system.
- //
- // Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
- // Copyright 2015 Matthew Holt and The Caddy Authors
- func sanitizedPathJoin(root, reqPath string) string {
- if root == "" {
- root = "."
- }
- path := filepath.Join(root, filepath.Clean("/"+reqPath))
- // filepath.Join also cleans the path, and cleaning strips
- // the trailing slash, so we need to re-add it afterwards.
- // if the length is 1, then it's a path to the root,
- // and that should return ".", so we don't append the separator.
- if strings.HasSuffix(reqPath, "/") && len(reqPath) > 1 {
- path += separator
- }
- return path
- }
- const separator = string(filepath.Separator)
|