diff --git a/caddy/caddy.go b/caddy/caddy.go index 0ab9bce29..b8784a9c1 100644 --- a/caddy/caddy.go +++ b/caddy/caddy.go @@ -270,7 +270,7 @@ func (f *FrankenPHPModule) Provision(ctx caddy.Context) error { } if !needReplacement(f.Root) { - root, err := filepath.Abs(f.Root) + root, err := frankenphp.FastAbs(f.Root) if err != nil { return fmt.Errorf("unable to make the root path absolute: %w", err) } diff --git a/filepath_unix.go b/filepath_unix.go new file mode 100644 index 000000000..1940ed3cc --- /dev/null +++ b/filepath_unix.go @@ -0,0 +1,25 @@ +package frankenphp + +import ( + "os" + "path/filepath" +) + +// FastAbs is an optimized version of filepath.Abs for Unix systems, +// since we don't expect the working directory to ever change once +// Caddy is running. Avoid the os.Getwd() syscall overhead. +// +// This function is INTERNAL and must not be used outside of this package. +func FastAbs(path string) (string, error) { + if filepath.IsAbs(path) { + return filepath.Clean(path), nil + } + + if wderr != nil { + return "", wderr + } + + return filepath.Join(wd, path), nil +} + +var wd, wderr = os.Getwd() diff --git a/filepath_windows.go b/filepath_windows.go new file mode 100644 index 000000000..81720d044 --- /dev/null +++ b/filepath_windows.go @@ -0,0 +1,13 @@ +package frankenphp + +import ( + "path/filepath" +) + +// FastAbs can't be optimized on Windows because the +// syscall.FullPath function takes an input. +// +// This function is INTERNAL and must not be used outside of this package. +func FastAbs(path string) (string, error) { + return filepath.Abs(path) +} diff --git a/frankenphp_test.go b/frankenphp_test.go index 370c1b6ca..7d23eaf06 100644 --- a/frankenphp_test.go +++ b/frankenphp_test.go @@ -921,12 +921,13 @@ func testRejectInvalidHeaders(t *testing.T, opts *testOptions) { // To run this fuzzing test use: go test -fuzz FuzzRequest // TODO: Cover more potential cases func FuzzRequest(f *testing.F) { + absPath, _ := filepath.Abs("./testdata/") + f.Add("hello world") f.Add("😀😅🙃ðŸĪĐðŸĨēðŸĪŠðŸ˜˜ðŸ˜‡ðŸ˜‰ðŸ˜ðŸ§Ÿ") f.Add("%00%11%%22%%33%%44%%55%%66%%77%%88%%99%%aa%%bb%%cc%%dd%%ee%%ff") f.Add("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f") f.Fuzz(func(t *testing.T, fuzzedString string) { - absPath, _ := filepath.Abs("./testdata/") runTest(t, func(handler func(http.ResponseWriter, *http.Request), _ *httptest.Server, _ int) { req := httptest.NewRequest("GET", "http://example.com/server-variable", nil) req.URL = &url.URL{RawQuery: "test=" + fuzzedString, Path: "/server-variable.php/" + fuzzedString} diff --git a/internal/watcher/watch_pattern.go b/internal/watcher/watch_pattern.go index 3c75a432e..2d5ab82ca 100644 --- a/internal/watcher/watch_pattern.go +++ b/internal/watcher/watch_pattern.go @@ -3,6 +3,7 @@ package watcher import ( + "github.com/dunglas/frankenphp" "path/filepath" "strings" @@ -34,7 +35,7 @@ func parseFilePattern(filePattern string) (*watchPattern, error) { w := &watchPattern{} // first we clean the pattern - absPattern, err := filepath.Abs(filePattern) + absPattern, err := frankenphp.FastAbs(filePattern) if err != nil { return nil, err } diff --git a/metrics.go b/metrics.go index 29e4514d6..8738c994f 100644 --- a/metrics.go +++ b/metrics.go @@ -1,7 +1,6 @@ package frankenphp import ( - "path/filepath" "regexp" "sync" "time" @@ -126,7 +125,7 @@ func (m *PrometheusMetrics) StopWorker(name string, reason StopReason) { } func (m *PrometheusMetrics) getIdentity(name string) (string, error) { - actualName, err := filepath.Abs(name) + actualName, err := FastAbs(name) if err != nil { return name, err } diff --git a/request_options.go b/request_options.go index 8620d9819..e1c30f34b 100644 --- a/request_options.go +++ b/request_options.go @@ -2,7 +2,6 @@ package frankenphp import ( "path/filepath" - "sync" "go.uber.org/zap" ) @@ -10,8 +9,6 @@ import ( // RequestOption instances allow to configure a FrankenPHP Request. type RequestOption func(h *FrankenPHPContext) error -var documentRootCache sync.Map - // WithRequestDocumentRoot sets the root directory of the PHP application. // if resolveSymlink is true, oath declared as root directory will be resolved // to its absolute value after the evaluation of any symbolic links. @@ -21,25 +18,19 @@ var documentRootCache sync.Map // directive will set $_SERVER['DOCUMENT_ROOT'] to the real directory path. func WithRequestDocumentRoot(documentRoot string, resolveSymlink bool) RequestOption { return func(o *FrankenPHPContext) error { - v, ok := documentRootCache.Load(documentRoot) - if !ok { - var err error - // make sure file root is absolute - v, err = filepath.Abs(documentRoot) - if err != nil { - return err - } + // make sure file root is absolute + root, err := FastAbs(documentRoot) + if err != nil { + return err + } - if resolveSymlink { - if v, err = filepath.EvalSymlinks(v.(string)); err != nil { - return err - } + if resolveSymlink { + if root, err = filepath.EvalSymlinks(root); err != nil { + return err } - - documentRootCache.LoadOrStore(documentRoot, v) } - o.documentRoot = v.(string) + o.documentRoot = root return nil } diff --git a/worker.go b/worker.go index da1dacc16..055a96a8a 100644 --- a/worker.go +++ b/worker.go @@ -63,7 +63,7 @@ func initWorkers(opt []workerOpt) error { } func newWorker(o workerOpt) (*worker, error) { - absFileName, err := filepath.Abs(o.fileName) + absFileName, err := FastAbs(o.fileName) if err != nil { return nil, fmt.Errorf("worker filename is invalid %q: %w", o.fileName, err) }