feat: better public API

This commit is contained in:
Kévin Dunglas
2021-11-01 15:38:12 +01:00
parent 290e9e1114
commit f037ba937a
4 changed files with 224 additions and 159 deletions

View File

@@ -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
} }

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)