Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions cgi.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import "C"
import (
"crypto/tls"
"net"
"net/http"
"path/filepath"
"strings"
"unsafe"
Expand Down Expand Up @@ -56,7 +55,8 @@ var knownServerKeys = []string{
//
// 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 addKnownVariablesToServer(thread *phpThread, request *http.Request, fc *FrankenPHPContext, trackVarsArray *C.zval) {
func addKnownVariablesToServer(thread *phpThread, fc *frankenPHPContext, trackVarsArray *C.zval) {
request := fc.request
keys := mainThread.knownServerKeys
// Separate remote IP and port; more lenient than net.SplitHostPort
var ip, port string
Expand Down Expand Up @@ -168,8 +168,8 @@ func packCgiVariable(key *C.zend_string, value string) C.ht_key_value_pair {
return C.ht_key_value_pair{key, toUnsafeChar(value), C.size_t(len(value))}
}

func addHeadersToServer(request *http.Request, thread *phpThread, fc *FrankenPHPContext, trackVarsArray *C.zval) {
for field, val := range request.Header {
func addHeadersToServer(thread *phpThread, fc *frankenPHPContext, trackVarsArray *C.zval) {
for field, val := range fc.request.Header {
if k := mainThread.commonHeaders[field]; k != nil {
v := strings.Join(val, ", ")
C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
Expand All @@ -184,7 +184,7 @@ func addHeadersToServer(request *http.Request, thread *phpThread, fc *FrankenPHP
}
}

func addPreparedEnvToServer(fc *FrankenPHPContext, trackVarsArray *C.zval) {
func addPreparedEnvToServer(fc *frankenPHPContext, trackVarsArray *C.zval) {
for k, v := range fc.env {
C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
}
Expand All @@ -194,11 +194,10 @@ func addPreparedEnvToServer(fc *FrankenPHPContext, trackVarsArray *C.zval) {
//export go_register_variables
func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
thread := phpThreads[threadIndex]
r := thread.getActiveRequest()
fc := r.Context().Value(contextKey).(*FrankenPHPContext)
fc := thread.getRequestContext()

addKnownVariablesToServer(thread, r, fc, trackVarsArray)
addHeadersToServer(r, thread, fc, trackVarsArray)
addKnownVariablesToServer(thread, fc, trackVarsArray)
addHeadersToServer(thread, fc, trackVarsArray)

// The Prepared Environment is registered last and can overwrite any previous values
addPreparedEnvToServer(fc, trackVarsArray)
Expand All @@ -209,7 +208,7 @@ func go_register_variables(threadIndex C.uintptr_t, trackVarsArray *C.zval) {
//
// 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 {
func splitPos(fc *frankenPHPContext, path string) int {
if len(fc.splitPath) == 0 {
return 0
}
Expand Down
164 changes: 164 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package frankenphp

import (
"context"
"net/http"
"os"
"strings"
"time"

"go.uber.org/zap"
)

// frankenPHPContext provides contextual information about the Request to handle.
type frankenPHPContext struct {
documentRoot string
splitPath []string
env PreparedEnv
logger *zap.Logger
request *http.Request
originalRequest *http.Request

docURI string
pathInfo string
scriptName string
scriptFilename string

// Whether the request is already closed by us
isDone bool

responseWriter http.ResponseWriter

done chan interface{}
startedAt time.Time
}

// fromContext extracts the frankenPHPContext from a context.
func fromContext(ctx context.Context) (fctx *frankenPHPContext, ok bool) {
fctx, ok = ctx.Value(contextKey).(*frankenPHPContext)
return
}

// NewRequestWithContext creates a new FrankenPHP request context.
func NewRequestWithContext(r *http.Request, opts ...RequestOption) (*http.Request, error) {
fc := &frankenPHPContext{
done: make(chan interface{}),
startedAt: time.Now(),
request: r,
}
for _, o := range opts {
if err := o(fc); err != nil {
return nil, err
}
}

if fc.logger == nil {
fc.logger = logger
}

if fc.documentRoot == "" {
if EmbeddedAppPath != "" {
fc.documentRoot = EmbeddedAppPath
} else {
var err error
if fc.documentRoot, err = os.Getwd(); err != nil {
return nil, err
}
}
}

if fc.splitPath == nil {
fc.splitPath = []string{".php"}
}

if fc.env == nil {
fc.env = make(map[string]string)
}

if splitPos := splitPos(fc, r.URL.Path); splitPos > -1 {
fc.docURI = r.URL.Path[:splitPos]
fc.pathInfo = r.URL.Path[splitPos:]

// Strip PATH_INFO from SCRIPT_NAME
fc.scriptName = strings.TrimSuffix(r.URL.Path, fc.pathInfo)

// Ensure the SCRIPT_NAME has a leading slash for compliance with RFC3875
// Info: https://tools.ietf.org/html/rfc3875#section-4.1.13
if fc.scriptName != "" && !strings.HasPrefix(fc.scriptName, "/") {
fc.scriptName = "/" + fc.scriptName
}
}

// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
fc.scriptFilename = sanitizedPathJoin(fc.documentRoot, fc.scriptName)

c := context.WithValue(r.Context(), contextKey, fc)

return r.WithContext(c), nil
}

func newDummyContext(requestPath string, opts ...RequestOption) (*frankenPHPContext, error) {
r, err := http.NewRequest(http.MethodGet, requestPath, nil)
if err != nil {
return nil, err
}

fr, err := NewRequestWithContext(r, opts...)
if err != nil {
return nil, err
}

fc, _ := fromContext(fr.Context())

return fc, nil
}

// closeContext sends the response to the client
func (fc *frankenPHPContext) closeContext() {
if fc.isDone {
return
}

close(fc.done)
fc.isDone = true
}

// validate checks if the request should be outright rejected
func (fc *frankenPHPContext) validate() bool {
if !strings.Contains(fc.request.URL.Path, "\x00") {
return true
}

fc.rejectBadRequest("Invalid request path")

return false
}

func (fc *frankenPHPContext) clientHasClosed() bool {
select {
case <-fc.request.Context().Done():
return true
default:
return false
}
}

// reject sends a response with the given status code and message
func (fc *frankenPHPContext) reject(statusCode int, message string) {
if fc.isDone {
return
}

rw := fc.responseWriter
if rw != nil {
rw.WriteHeader(statusCode)
_, _ = rw.Write([]byte(message))
rw.(http.Flusher).Flush()
}

fc.closeContext()
}

func (fc *frankenPHPContext) rejectBadRequest(message string) {
fc.reject(http.StatusBadRequest, message)
}
Loading
Loading