mirror of
https://github.com/dunglas/frankenphp.git
synced 2025-10-20 14:15:14 +08:00
feat: better public API
This commit is contained in:
@@ -26,7 +26,6 @@ type FrankenPHPModule struct {
|
|||||||
SplitPath []string `json:"split_path,omitempty"`
|
SplitPath []string `json:"split_path,omitempty"`
|
||||||
ResolveRootSymlink bool `json:"resolve_root_symlink,omitempty"`
|
ResolveRootSymlink bool `json:"resolve_root_symlink,omitempty"`
|
||||||
Env map[string]string `json:"env,omitempty"`
|
Env map[string]string `json:"env,omitempty"`
|
||||||
frankenphp *frankenphp.FrankenPHP
|
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,11 +63,6 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error {
|
|||||||
f.SplitPath = []string{".php"}
|
f.SplitPath = []string{".php"}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.frankenphp = &frankenphp.FrankenPHP{}
|
|
||||||
f.frankenphp.SplitPath = f.SplitPath
|
|
||||||
f.frankenphp.ResolveRootSymlink = f.ResolveRootSymlink
|
|
||||||
f.frankenphp.EnvVars = f.Env
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +79,17 @@ func (f FrankenPHPModule) ServeHTTP(w http.ResponseWriter, r *http.Request, next
|
|||||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||||
|
|
||||||
documentRoot := repl.ReplaceKnown(f.Root, "")
|
documentRoot := repl.ReplaceKnown(f.Root, "")
|
||||||
if err := f.frankenphp.ExecuteScript(documentRoot, w, r, &origReq); err != nil {
|
fr := frankenphp.NewRequestWithContext(r, documentRoot)
|
||||||
|
fc := fr.Context().Value(frankenphp.FrankenPHPContextKey).(*frankenphp.FrankenPHPContext)
|
||||||
|
fc.ResolveRootSymlink = f.ResolveRootSymlink
|
||||||
|
fc.SplitPath = f.SplitPath
|
||||||
|
|
||||||
|
fc.Env["REQUEST_URI"] = origReq.URL.RequestURI()
|
||||||
|
for k, v := range f.Env {
|
||||||
|
fc.Env[k] = repl.ReplaceKnown(v, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := frankenphp.ExecuteScript(w, fr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -180,8 +180,10 @@ int frankenphp_request_startup(
|
|||||||
SG(request_info).path_translated = path_translated;
|
SG(request_info).path_translated = path_translated;
|
||||||
SG(request_info).request_uri = request_uri;
|
SG(request_info).request_uri = request_uri;
|
||||||
SG(request_info).content_type = content_type;
|
SG(request_info).content_type = content_type;
|
||||||
SG(request_info).auth_user = auth_user;
|
if (auth_user != NULL)
|
||||||
SG(request_info).auth_password = auth_password;
|
SG(request_info).auth_user = estrdup(auth_user);
|
||||||
|
if (auth_password != NULL)
|
||||||
|
SG(request_info).auth_password = estrdup(auth_password);
|
||||||
SG(request_info).proto_num = proto_num;
|
SG(request_info).proto_num = proto_num;
|
||||||
|
|
||||||
if (php_request_startup() == FAILURE) {
|
if (php_request_startup() == FAILURE) {
|
||||||
|
312
frankenphp.go
312
frankenphp.go
@@ -26,12 +26,15 @@ import (
|
|||||||
|
|
||||||
var started int32
|
var started int32
|
||||||
|
|
||||||
type CtxKey string
|
type ContextKey string
|
||||||
|
|
||||||
const CGICtxKey CtxKey = "cgi"
|
const FrankenPHPContextKey ContextKey = "frankenphp"
|
||||||
|
|
||||||
// FrankenPHP executes PHP scripts.
|
// FrankenPHP executes PHP scripts.
|
||||||
type FrankenPHP struct {
|
type FrankenPHPContext struct {
|
||||||
|
// The root directory of the PHP application.
|
||||||
|
DocumentRoot string
|
||||||
|
|
||||||
// The path in the URL will be split into two, with the first piece ending
|
// The path in the URL will be split into two, with the first piece ending
|
||||||
// with the value of SplitPath. The first piece will be assumed as the
|
// with the value of SplitPath. The first piece will be assumed as the
|
||||||
// actual resource (CGI script) name, and the second piece will be set to
|
// actual resource (CGI script) name, and the second piece will be set to
|
||||||
@@ -50,12 +53,19 @@ type FrankenPHP struct {
|
|||||||
// directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path.
|
// directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path.
|
||||||
ResolveRootSymlink bool
|
ResolveRootSymlink bool
|
||||||
|
|
||||||
// Extra environment variables.
|
// CGI-like environment variables that will be available in $_SERVER.
|
||||||
EnvVars map[string]string
|
// This map is populated automatically, exisiting key are never replaced.
|
||||||
|
Env map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFrankenPHP() *FrankenPHP {
|
func NewRequestWithContext(r *http.Request, documentRoot string) *http.Request {
|
||||||
return &FrankenPHP{SplitPath: []string{".php"}}
|
ctx := context.WithValue(r.Context(), FrankenPHPContextKey, &FrankenPHPContext{
|
||||||
|
DocumentRoot: documentRoot,
|
||||||
|
SplitPath: []string{".php"},
|
||||||
|
Env: make(map[string]string),
|
||||||
|
})
|
||||||
|
|
||||||
|
return r.WithContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Startup starts the PHP engine.
|
// Startup starts the PHP engine.
|
||||||
@@ -82,32 +92,30 @@ func Shutdown() {
|
|||||||
atomic.StoreInt32(&started, 0)
|
atomic.StoreInt32(&started, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrankenPHP) ExecuteScript(documentRoot string, responseWriter http.ResponseWriter, request *http.Request, originalRequest *http.Request) error {
|
func ExecuteScript(responseWriter http.ResponseWriter, request *http.Request) error {
|
||||||
if atomic.LoadInt32(&started) < 1 {
|
if atomic.LoadInt32(&started) < 1 {
|
||||||
if err := Startup(); err != nil {
|
if err := Startup(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cgiEnv, err := f.buildCGIEnv(documentRoot, request, originalRequest)
|
authPassword, err := populateEnv(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fc := request.Context().Value(FrankenPHPContextKey).(*FrankenPHPContext)
|
||||||
|
|
||||||
var cAuthUser, cAuthPassword *C.char
|
var cAuthUser, cAuthPassword *C.char
|
||||||
authUser, authPassword, ok := request.BasicAuth()
|
if authPassword != "" {
|
||||||
if ok {
|
|
||||||
cgiEnv["REMOTE_USER"] = authUser
|
|
||||||
|
|
||||||
cAuthUser = C.CString(authUser)
|
|
||||||
defer C.free(unsafe.Pointer(cAuthUser))
|
|
||||||
|
|
||||||
cAuthPassword = C.CString(authPassword)
|
cAuthPassword = C.CString(authPassword)
|
||||||
defer C.free(unsafe.Pointer(cAuthPassword))
|
//defer C.free(unsafe.Pointer(cAuthPassword))
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.WithValue(request.Context(), CGICtxKey, cgiEnv)
|
if authUser := fc.Env["REMOTE_USER"]; authUser != "" {
|
||||||
request = request.WithContext(ctx)
|
cAuthUser = C.CString(authUser)
|
||||||
|
defer C.free(unsafe.Pointer(cAuthUser))
|
||||||
|
}
|
||||||
|
|
||||||
wh := cgo.NewHandle(responseWriter)
|
wh := cgo.NewHandle(responseWriter)
|
||||||
defer wh.Delete()
|
defer wh.Delete()
|
||||||
@@ -135,8 +143,8 @@ func (f *FrankenPHP) ExecuteScript(documentRoot string, responseWriter http.Resp
|
|||||||
}
|
}
|
||||||
|
|
||||||
var cPathTranslated *C.char
|
var cPathTranslated *C.char
|
||||||
if cgiEnv["PATH_TRANSLATED"] == "" {
|
if pathTranslated := fc.Env["PATH_TRANSLATED"]; pathTranslated != "" {
|
||||||
cPathTranslated = C.CString(cgiEnv["PATH_TRANSLATED"])
|
cPathTranslated = C.CString(pathTranslated)
|
||||||
defer C.free(unsafe.Pointer(cPathTranslated))
|
defer C.free(unsafe.Pointer(cPathTranslated))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,10 +168,10 @@ func (f *FrankenPHP) ExecuteScript(documentRoot string, responseWriter http.Resp
|
|||||||
return fmt.Errorf("error during PHP request startup")
|
return fmt.Errorf("error during PHP request startup")
|
||||||
}
|
}
|
||||||
|
|
||||||
cFileName := C.CString(cgiEnv["SCRIPT_FILENAME"])
|
cFileName := C.CString(fc.Env["SCRIPT_FILENAME"])
|
||||||
defer C.free(unsafe.Pointer(cFileName))
|
defer C.free(unsafe.Pointer(cFileName))
|
||||||
C.frankenphp_execute_script(cFileName)
|
|
||||||
|
|
||||||
|
C.frankenphp_execute_script(cFileName)
|
||||||
C.frankenphp_request_shutdown()
|
C.frankenphp_request_shutdown()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -172,39 +180,51 @@ func (f *FrankenPHP) ExecuteScript(documentRoot string, responseWriter http.Resp
|
|||||||
// buildEnv returns a set of CGI environment variables for the request.
|
// buildEnv returns a set of CGI environment variables for the request.
|
||||||
//
|
//
|
||||||
// TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
|
// TODO: handle this case https://github.com/caddyserver/caddy/issues/3718
|
||||||
// TODO: add Apache mod_ssl's like TLS versions
|
// Inspired by https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
||||||
// Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
func populateEnv(request *http.Request) (authPassword string, err error) {
|
||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
fc := request.Context().Value(FrankenPHPContextKey).(*FrankenPHPContext)
|
||||||
func (f FrankenPHP) buildCGIEnv(documentRoot string, request *http.Request, originalRequest *http.Request) (map[string]string, error) {
|
|
||||||
if originalRequest == nil {
|
|
||||||
originalRequest = request
|
|
||||||
}
|
|
||||||
|
|
||||||
var env map[string]string
|
_, addrOk := fc.Env["REMOTE_ADDR"]
|
||||||
|
_, portOk := fc.Env["REMOTE_PORT"]
|
||||||
// Separate remote IP and port; more lenient than net.SplitHostPort
|
if !addrOk || !portOk {
|
||||||
var ip, port string
|
// Separate remote IP and port; more lenient than net.SplitHostPort
|
||||||
if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 {
|
var ip, port string
|
||||||
ip = request.RemoteAddr[:idx]
|
if idx := strings.LastIndex(request.RemoteAddr, ":"); idx > -1 {
|
||||||
port = request.RemoteAddr[idx+1:]
|
ip = request.RemoteAddr[:idx]
|
||||||
} else {
|
port = request.RemoteAddr[idx+1:]
|
||||||
ip = request.RemoteAddr
|
} else {
|
||||||
}
|
ip = request.RemoteAddr
|
||||||
|
|
||||||
// Remove [] from IPv6 addresses
|
|
||||||
ip = strings.Replace(ip, "[", "", 1)
|
|
||||||
ip = strings.Replace(ip, "]", "", 1)
|
|
||||||
|
|
||||||
// make sure file root is absolute
|
|
||||||
root, err := filepath.Abs(documentRoot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.ResolveRootSymlink {
|
|
||||||
if root, err = filepath.EvalSymlinks(root); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove [] from IPv6 addresses
|
||||||
|
ip = strings.Replace(ip, "[", "", 1)
|
||||||
|
ip = strings.Replace(ip, "]", "", 1)
|
||||||
|
|
||||||
|
if _, ok := fc.Env["REMOTE_ADDR"]; !ok {
|
||||||
|
fc.Env["REMOTE_ADDR"] = ip
|
||||||
|
}
|
||||||
|
if _, ok := fc.Env["REMOTE_HOST"]; !ok {
|
||||||
|
fc.Env["REMOTE_HOST"] = ip // For speed, remote host lookups disabled
|
||||||
|
}
|
||||||
|
if _, ok := fc.Env["REMOTE_PORT"]; !ok {
|
||||||
|
fc.Env["REMOTE_PORT"] = port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := fc.Env["DOCUMENT_ROOT"]; !ok {
|
||||||
|
// make sure file root is absolute
|
||||||
|
root, err := filepath.Abs(fc.DocumentRoot)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fc.ResolveRootSymlink {
|
||||||
|
if root, err = filepath.EvalSymlinks(root); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fc.Env["DOCUMENT_ROOT"] = root
|
||||||
}
|
}
|
||||||
|
|
||||||
fpath := request.URL.Path
|
fpath := request.URL.Path
|
||||||
@@ -212,17 +232,16 @@ func (f FrankenPHP) buildCGIEnv(documentRoot string, request *http.Request, orig
|
|||||||
|
|
||||||
docURI := fpath
|
docURI := fpath
|
||||||
// split "actual path" from "path info" if configured
|
// split "actual path" from "path info" if configured
|
||||||
var pathInfo string
|
if splitPos := splitPos(fc, fpath); splitPos > -1 {
|
||||||
if splitPos := f.splitPos(fpath); splitPos > -1 {
|
|
||||||
docURI = fpath[:splitPos]
|
docURI = fpath[:splitPos]
|
||||||
pathInfo = fpath[splitPos:]
|
fc.Env["PATH_INFO"] = fpath[splitPos:]
|
||||||
|
|
||||||
// Strip PATH_INFO from SCRIPT_NAME
|
// Strip PATH_INFO from SCRIPT_NAME
|
||||||
scriptName = strings.TrimSuffix(scriptName, pathInfo)
|
scriptName = strings.TrimSuffix(scriptName, fc.Env["PATH_INFO"])
|
||||||
}
|
}
|
||||||
|
|
||||||
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
|
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
|
||||||
scriptFilename := sanitizedPathJoin(root, scriptName)
|
scriptFilename := sanitizedPathJoin(fc.Env["DOCUMENT_ROOT"], scriptName)
|
||||||
|
|
||||||
// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
|
// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
|
||||||
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
|
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
|
||||||
@@ -230,98 +249,141 @@ func (f FrankenPHP) buildCGIEnv(documentRoot string, request *http.Request, orig
|
|||||||
scriptName = "/" + scriptName
|
scriptName = "/" + scriptName
|
||||||
}
|
}
|
||||||
|
|
||||||
requestScheme := "http"
|
if _, ok := fc.Env["DOCUMENT_URI"]; !ok {
|
||||||
|
fc.Env["DOCUMENT_URI"] = docURI
|
||||||
|
}
|
||||||
|
if _, ok := fc.Env["SCRIPT_FILENAME"]; !ok {
|
||||||
|
fc.Env["SCRIPT_FILENAME"] = scriptFilename
|
||||||
|
}
|
||||||
|
if _, ok := fc.Env["SCRIPT_NAME"]; !ok {
|
||||||
|
fc.Env["SCRIPT_NAME"] = scriptName
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := fc.Env["REQUEST_SCHEME"]; !ok {
|
||||||
|
if request.TLS == nil {
|
||||||
|
fc.Env["REQUEST_SCHEME"] = "http"
|
||||||
|
} else {
|
||||||
|
fc.Env["REQUEST_SCHEME"] = "https"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if request.TLS != nil {
|
if request.TLS != nil {
|
||||||
requestScheme = "https"
|
if _, ok := fc.Env["HTTPS"]; !ok {
|
||||||
|
fc.Env["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_).
|
||||||
|
_, sslProtocolOk := fc.Env["SSL_PROTOCOL"]
|
||||||
|
v, versionOk := tlsProtocolStrings[request.TLS.Version]
|
||||||
|
if !sslProtocolOk && versionOk {
|
||||||
|
fc.Env["SSL_PROTOCOL"] = v
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reqHost, reqPort, err := net.SplitHostPort(request.Host)
|
_, serverNameOk := fc.Env["SERVER_NAME"]
|
||||||
if err != nil {
|
_, serverPortOk := fc.Env["SERVER_PORT"]
|
||||||
// whatever, just assume there was no port
|
if !serverNameOk || !serverPortOk {
|
||||||
reqHost = request.Host
|
reqHost, reqPort, err := net.SplitHostPort(request.Host)
|
||||||
|
if err == nil {
|
||||||
|
if !serverNameOk {
|
||||||
|
fc.Env["SERVER_NAME"] = reqHost
|
||||||
|
}
|
||||||
|
|
||||||
|
// compliance with the CGI specification requires that
|
||||||
|
// SERVER_PORT should only exist if it's a valid numeric value.
|
||||||
|
// Info: https://www.ietf.org/rfc/rfc3875 Page 18
|
||||||
|
if !serverPortOk && reqPort != "" {
|
||||||
|
fc.Env["SERVER_PORT"] = reqPort
|
||||||
|
}
|
||||||
|
} else if !serverNameOk {
|
||||||
|
// whatever, just assume there was no port
|
||||||
|
fc.Env["SERVER_NAME"] = request.Host
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Variables defined in CGI 1.1 spec
|
||||||
// Some variables are unused but cleared explicitly to prevent
|
// Some variables are unused but cleared explicitly to prevent
|
||||||
// the parent environment from interfering.
|
// the parent environment from interfering.
|
||||||
env = map[string]string{
|
// We never override an entry previously set
|
||||||
// Variables defined in CGI 1.1 spec
|
if _, ok := fc.Env["REMOTE_IDENT"]; !ok {
|
||||||
"AUTH_TYPE": "", // Not used
|
fc.Env["REMOTE_IDENT"] = "" // Not used
|
||||||
"CONTENT_LENGTH": request.Header.Get("Content-Length"),
|
}
|
||||||
"CONTENT_TYPE": request.Header.Get("Content-Type"),
|
if _, ok := fc.Env["AUTH_TYPE"]; !ok {
|
||||||
"GATEWAY_INTERFACE": "CGI/1.1",
|
fc.Env["AUTH_TYPE"] = "" // Not used
|
||||||
"PATH_INFO": pathInfo,
|
}
|
||||||
"QUERY_STRING": request.URL.RawQuery,
|
if _, ok := fc.Env["CONTENT_LENGTH"]; !ok {
|
||||||
"REMOTE_ADDR": ip,
|
fc.Env["CONTENT_LENGTH"] = request.Header.Get("Content-Length")
|
||||||
"REMOTE_HOST": ip, // For speed, remote host lookups disabled
|
}
|
||||||
"REMOTE_PORT": port,
|
if _, ok := fc.Env["CONTENT_TYPE"]; !ok {
|
||||||
"REMOTE_IDENT": "", // Not used
|
fc.Env["CONTENT_TYPE"] = request.Header.Get("Content-Type")
|
||||||
"REMOTE_USER": "", // Will be set later
|
}
|
||||||
"REQUEST_METHOD": request.Method,
|
if _, ok := fc.Env["GATEWAY_INTERFACE"]; !ok {
|
||||||
"REQUEST_SCHEME": requestScheme,
|
fc.Env["GATEWAY_INTERFACE"] = "CGI/1.1"
|
||||||
"SERVER_NAME": reqHost,
|
}
|
||||||
"SERVER_PROTOCOL": request.Proto,
|
if _, ok := fc.Env["QUERY_STRING"]; !ok {
|
||||||
"SERVER_SOFTWARE": "FrankenPHP",
|
fc.Env["QUERY_STRING"] = request.URL.RawQuery
|
||||||
|
}
|
||||||
// Other variables
|
if _, ok := fc.Env["QUERY_STRING"]; !ok {
|
||||||
"DOCUMENT_ROOT": root,
|
fc.Env["QUERY_STRING"] = request.URL.RawQuery
|
||||||
"DOCUMENT_URI": docURI,
|
}
|
||||||
"HTTP_HOST": request.Host, // added here, since not always part of headers
|
if _, ok := fc.Env["REQUEST_METHOD"]; !ok {
|
||||||
"REQUEST_URI": originalRequest.URL.RequestURI(),
|
fc.Env["REQUEST_METHOD"] = request.Method
|
||||||
"SCRIPT_FILENAME": scriptFilename,
|
}
|
||||||
"SCRIPT_NAME": scriptName,
|
if _, ok := fc.Env["SERVER_PROTOCOL"]; !ok {
|
||||||
|
fc.Env["SERVER_PROTOCOL"] = request.Proto
|
||||||
|
}
|
||||||
|
if _, ok := fc.Env["SERVER_SOFTWARE"]; !ok {
|
||||||
|
fc.Env["SERVER_SOFTWARE"] = "FrankenPHP"
|
||||||
|
}
|
||||||
|
if _, ok := fc.Env["HTTP_HOST"]; !ok {
|
||||||
|
fc.Env["HTTP_HOST"] = request.Host // added here, since not always part of headers
|
||||||
|
}
|
||||||
|
if _, ok := fc.Env["REQUEST_URI"]; !ok {
|
||||||
|
fc.Env["REQUEST_URI"] = request.URL.RequestURI()
|
||||||
}
|
}
|
||||||
|
|
||||||
// compliance with the CGI specification requires that
|
// compliance with the CGI specification requires that
|
||||||
// PATH_TRANSLATED should only exist if PATH_INFO is defined.
|
// PATH_TRANSLATED should only exist if PATH_INFO is defined.
|
||||||
// Info: https://www.ietf.org/rfc/rfc3875 Page 14
|
// Info: https://www.ietf.org/rfc/rfc3875 Page 14
|
||||||
if env["PATH_INFO"] != "" {
|
if fc.Env["PATH_INFO"] != "" {
|
||||||
env["PATH_TRANSLATED"] = sanitizedPathJoin(root, pathInfo) // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html
|
fc.Env["PATH_TRANSLATED"] = sanitizedPathJoin(fc.Env["DOCUMENT_ROOT"], fc.Env["PATH_INFO"]) // Info: http://www.oreilly.com/openbook/cgi/ch02_04.html
|
||||||
}
|
|
||||||
|
|
||||||
// compliance with the CGI specification requires that
|
|
||||||
// SERVER_PORT should only exist if it's a valid numeric value.
|
|
||||||
// Info: https://www.ietf.org/rfc/rfc3875 Page 18
|
|
||||||
if reqPort != "" {
|
|
||||||
env["SERVER_PORT"] = reqPort
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some web apps rely on knowing HTTPS or not
|
|
||||||
if request.TLS != nil {
|
|
||||||
env["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_).
|
|
||||||
v, ok := tlsProtocolStrings[request.TLS.Version]
|
|
||||||
if ok {
|
|
||||||
env["SSL_PROTOCOL"] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add env variables from config
|
|
||||||
for key, value := range f.EnvVars {
|
|
||||||
env[key] = value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all HTTP headers to env variables
|
// Add all HTTP headers to env variables
|
||||||
for field, val := range request.Header {
|
for field, val := range request.Header {
|
||||||
header := strings.ToUpper(field)
|
k := "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(field))
|
||||||
header = headerNameReplacer.Replace(header)
|
if _, ok := fc.Env[k]; !ok {
|
||||||
env["HTTP_"+header] = strings.Join(val, ", ")
|
fc.Env[k] = strings.Join(val, ", ")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return env, nil
|
|
||||||
|
if _, ok := fc.Env["REMOTE_USER"]; !ok {
|
||||||
|
var (
|
||||||
|
authUser string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
authUser, authPassword, ok = request.BasicAuth()
|
||||||
|
if ok {
|
||||||
|
fc.Env["REMOTE_USER"] = authUser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authPassword, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// splitPos returns the index where path should
|
// splitPos returns the index where path should
|
||||||
// be split based on t.SplitPath.
|
// be split based on SplitPath.
|
||||||
//
|
//
|
||||||
// Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
// Adapted from https://github.com/caddyserver/caddy/blob/master/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go
|
||||||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||||
func (f FrankenPHP) splitPos(path string) int {
|
func splitPos(fc *FrankenPHPContext, path string) int {
|
||||||
if len(f.SplitPath) == 0 {
|
if len(fc.SplitPath) == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
lowerPath := strings.ToLower(path)
|
lowerPath := strings.ToLower(path)
|
||||||
for _, split := range f.SplitPath {
|
for _, split := range fc.SplitPath {
|
||||||
if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
|
if idx := strings.Index(lowerPath, strings.ToLower(split)); idx > -1 {
|
||||||
return idx + len(split)
|
return idx + len(split)
|
||||||
}
|
}
|
||||||
@@ -381,7 +443,7 @@ func go_ub_write(wh C.uintptr_t, cString *C.char, length C.int) C.size_t {
|
|||||||
//export go_register_variables
|
//export go_register_variables
|
||||||
func go_register_variables(rh C.uintptr_t, trackVarsArray *C.zval) {
|
func go_register_variables(rh C.uintptr_t, trackVarsArray *C.zval) {
|
||||||
r := cgo.Handle(rh).Value().(*http.Request)
|
r := cgo.Handle(rh).Value().(*http.Request)
|
||||||
for k, v := range r.Context().Value(CGICtxKey).(map[string]string) {
|
for k, v := range r.Context().Value(FrankenPHPContextKey).(*FrankenPHPContext).Env {
|
||||||
ck := C.CString(k)
|
ck := C.CString(k)
|
||||||
cv := C.CString(v)
|
cv := C.CString(v)
|
||||||
C.php_register_variable_safe(ck, cv, C.size_t(len(v)), trackVarsArray)
|
C.php_register_variable_safe(ck, cv, C.size_t(len(v)), trackVarsArray)
|
||||||
|
@@ -19,13 +19,18 @@ func TestStartup(t *testing.T) {
|
|||||||
assert.Nil(t, frankenphp.Startup())
|
assert.Nil(t, frankenphp.Startup())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setRequestContext(t *testing.T, r *http.Request) *http.Request {
|
||||||
|
t.Helper()
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
|
||||||
|
return frankenphp.NewRequestWithContext(r, cwd+"/testdata/")
|
||||||
|
}
|
||||||
|
|
||||||
func TestHelloWorld(t *testing.T) {
|
func TestHelloWorld(t *testing.T) {
|
||||||
defer frankenphp.Shutdown()
|
defer frankenphp.Shutdown()
|
||||||
|
|
||||||
f := frankenphp.NewFrankenPHP()
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
cwd, _ := os.Getwd()
|
assert.Nil(t, frankenphp.ExecuteScript(w, setRequestContext(t, r)))
|
||||||
assert.Nil(t, f.ExecuteScript(cwd+"/testdata/", w, r, nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "http://example.com/index.php", nil)
|
req := httptest.NewRequest("GET", "http://example.com/index.php", nil)
|
||||||
@@ -41,13 +46,12 @@ func TestHelloWorld(t *testing.T) {
|
|||||||
func TestServerVariable(t *testing.T) {
|
func TestServerVariable(t *testing.T) {
|
||||||
defer frankenphp.Shutdown()
|
defer frankenphp.Shutdown()
|
||||||
|
|
||||||
f := frankenphp.NewFrankenPHP()
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
cwd, _ := os.Getwd()
|
assert.Nil(t, frankenphp.ExecuteScript(w, setRequestContext(t, r)))
|
||||||
assert.Nil(t, f.ExecuteScript(cwd+"/testdata/", w, r, nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "http://kevin:password@example.com/server-variable.php?foo=a&bar=b#hash", nil)
|
req := httptest.NewRequest("GET", "http://example.com/server-variable.php?foo=a&bar=b#hash", nil)
|
||||||
|
req.SetBasicAuth("kevin", "password")
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
handler(w, req)
|
handler(w, req)
|
||||||
|
|
||||||
@@ -57,7 +61,10 @@ func TestServerVariable(t *testing.T) {
|
|||||||
strBody := string(body)
|
strBody := string(body)
|
||||||
|
|
||||||
assert.Contains(t, strBody, "[REMOTE_HOST]")
|
assert.Contains(t, strBody, "[REMOTE_HOST]")
|
||||||
assert.Contains(t, strBody, "[REMOTE_USER]")
|
assert.Contains(t, strBody, "[REMOTE_USER] => kevin")
|
||||||
|
assert.Contains(t, strBody, "[PHP_AUTH_USER] => kevin")
|
||||||
|
assert.Contains(t, strBody, "[PHP_AUTH_PW] => password")
|
||||||
|
assert.Contains(t, strBody, "[HTTP_AUTHORIZATION] => Basic a2V2aW46cGFzc3dvcmQ=")
|
||||||
assert.Contains(t, strBody, "[DOCUMENT_ROOT]")
|
assert.Contains(t, strBody, "[DOCUMENT_ROOT]")
|
||||||
assert.Contains(t, strBody, "[CONTENT_TYPE]")
|
assert.Contains(t, strBody, "[CONTENT_TYPE]")
|
||||||
assert.Contains(t, strBody, "[QUERY_STRING] => foo=a&bar=b#hash")
|
assert.Contains(t, strBody, "[QUERY_STRING] => foo=a&bar=b#hash")
|
||||||
@@ -83,14 +90,12 @@ func TestServerVariable(t *testing.T) {
|
|||||||
func TestPathInfo(t *testing.T) {
|
func TestPathInfo(t *testing.T) {
|
||||||
defer frankenphp.Shutdown()
|
defer frankenphp.Shutdown()
|
||||||
|
|
||||||
f := frankenphp.NewFrankenPHP()
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
cwd, _ := os.Getwd()
|
rewriteRequest := setRequestContext(t, r.Clone(context.TODO()))
|
||||||
|
|
||||||
rewriteRequest := r.Clone(context.TODO())
|
|
||||||
rewriteRequest.URL.Path = "/server-variable.php/pathinfo"
|
rewriteRequest.URL.Path = "/server-variable.php/pathinfo"
|
||||||
|
rewriteRequest.Context().Value(frankenphp.FrankenPHPContextKey).(*frankenphp.FrankenPHPContext).Env["REQUEST_URI"] = r.URL.RequestURI()
|
||||||
|
|
||||||
assert.Nil(t, f.ExecuteScript(cwd+"/testdata/", w, rewriteRequest, r))
|
assert.Nil(t, frankenphp.ExecuteScript(w, rewriteRequest))
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "http://example.com/pathinfo", nil)
|
req := httptest.NewRequest("GET", "http://example.com/pathinfo", nil)
|
||||||
@@ -111,10 +116,8 @@ func TestPathInfo(t *testing.T) {
|
|||||||
func TestHeaders(t *testing.T) {
|
func TestHeaders(t *testing.T) {
|
||||||
defer frankenphp.Shutdown()
|
defer frankenphp.Shutdown()
|
||||||
|
|
||||||
f := frankenphp.NewFrankenPHP()
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
cwd, _ := os.Getwd()
|
assert.Nil(t, frankenphp.ExecuteScript(w, setRequestContext(t, r)))
|
||||||
assert.Nil(t, f.ExecuteScript(cwd+"/testdata/", w, r, nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "http://example.com/headers.php", nil)
|
req := httptest.NewRequest("GET", "http://example.com/headers.php", nil)
|
||||||
@@ -133,10 +136,8 @@ func TestHeaders(t *testing.T) {
|
|||||||
func TestInput(t *testing.T) {
|
func TestInput(t *testing.T) {
|
||||||
defer frankenphp.Shutdown()
|
defer frankenphp.Shutdown()
|
||||||
|
|
||||||
f := frankenphp.NewFrankenPHP()
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
cwd, _ := os.Getwd()
|
assert.Nil(t, frankenphp.ExecuteScript(w, setRequestContext(t, r)))
|
||||||
assert.Nil(t, f.ExecuteScript(cwd+"/testdata/", w, r, nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest("POST", "http://example.com/input.php", strings.NewReader("post data"))
|
req := httptest.NewRequest("POST", "http://example.com/input.php", strings.NewReader("post data"))
|
||||||
@@ -153,10 +154,8 @@ func TestInput(t *testing.T) {
|
|||||||
func TestPostSuperGlobals(t *testing.T) {
|
func TestPostSuperGlobals(t *testing.T) {
|
||||||
defer frankenphp.Shutdown()
|
defer frankenphp.Shutdown()
|
||||||
|
|
||||||
f := frankenphp.NewFrankenPHP()
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
cwd, _ := os.Getwd()
|
assert.Nil(t, frankenphp.ExecuteScript(w, setRequestContext(t, r)))
|
||||||
assert.Nil(t, f.ExecuteScript(cwd+"/testdata/", w, r, nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
formData := url.Values{"baz": {"bat"}}
|
formData := url.Values{"baz": {"bat"}}
|
||||||
@@ -175,10 +174,8 @@ func TestPostSuperGlobals(t *testing.T) {
|
|||||||
func TestCookies(t *testing.T) {
|
func TestCookies(t *testing.T) {
|
||||||
defer frankenphp.Shutdown()
|
defer frankenphp.Shutdown()
|
||||||
|
|
||||||
f := frankenphp.NewFrankenPHP()
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||||
cwd, _ := os.Getwd()
|
assert.Nil(t, frankenphp.ExecuteScript(w, setRequestContext(t, r)))
|
||||||
assert.Nil(t, f.ExecuteScript(cwd+"/testdata/", w, r, nil))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
req := httptest.NewRequest("GET", "http://example.com/cookies.php", nil)
|
req := httptest.NewRequest("GET", "http://example.com/cookies.php", nil)
|
||||||
|
Reference in New Issue
Block a user