From 6e9ac248dd5c2f7a4cd4e37ff277a86c18085be6 Mon Sep 17 00:00:00 2001 From: Francis Lavoie Date: Fri, 4 Dec 2020 19:12:13 -0500 Subject: fastcgi: Set PATH_INFO to file matcher remainder as fallback (#3739) * fastcgi: Set PATH_INFO to file matcher remainder as fallback * fastcgi: Avoid changing scriptName when not necessary * Stylistic tweaks Co-authored-by: Matthew Holt --- modules/caddyhttp/fileserver/matcher.go | 51 ++++++++++++++--------- modules/caddyhttp/fileserver/matcher_test.go | 8 +++- modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go | 20 ++++++--- 3 files changed, 51 insertions(+), 28 deletions(-) (limited to 'modules') diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index c103b03..44ef9ed 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -44,6 +44,8 @@ func init() { // of the matched file. // - `{http.matchers.file.type}` Set to "directory" if // the matched file is a directory, "file" otherwise. +// - `{http.matchers.file.remainder}` Set to the remainder +// of the path if the path was split by `split_path`. type MatchFile struct { // The root directory, used for creating absolute // file paths, and required when working with @@ -155,11 +157,12 @@ func (m MatchFile) Validate() error { } // Match returns true if r matches m. Returns true -// if a file was matched. If so, three placeholders +// if a file was matched. If so, four placeholders // will be available: // - http.matchers.file.relative // - http.matchers.file.absolute // - http.matchers.file.type +// - http.matchers.file.remainder func (m MatchFile) Match(r *http.Request) bool { return m.selectFile(r) } @@ -179,19 +182,20 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { } // common preparation of the file into parts - prepareFilePath := func(file string) (string, string) { - suffix := m.firstSplit(path.Clean(repl.ReplaceAll(file, ""))) + prepareFilePath := func(file string) (suffix, fullpath, remainder string) { + suffix, remainder = m.firstSplit(path.Clean(repl.ReplaceAll(file, ""))) if strings.HasSuffix(file, "/") { suffix += "/" } - fullpath := sanitizedPathJoin(root, suffix) - return suffix, fullpath + fullpath = sanitizedPathJoin(root, suffix) + return } // sets up the placeholders for the matched file - setPlaceholders := func(info os.FileInfo, rel string, abs string) { + setPlaceholders := func(info os.FileInfo, rel string, abs string, remainder string) { repl.Set("http.matchers.file.relative", rel) repl.Set("http.matchers.file.absolute", abs) + repl.Set("http.matchers.file.remainder", remainder) fileType := "file" if info.IsDir() { @@ -203,9 +207,9 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { switch m.TryPolicy { case "", tryPolicyFirstExist: for _, f := range m.TryFiles { - suffix, fullpath := prepareFilePath(f) + suffix, fullpath, remainder := prepareFilePath(f) if info, exists := strictFileExists(fullpath); exists { - setPlaceholders(info, suffix, fullpath) + setPlaceholders(info, suffix, fullpath, remainder) return true } } @@ -214,52 +218,58 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) { var largestSize int64 var largestFilename string var largestSuffix string + var remainder string var info os.FileInfo for _, f := range m.TryFiles { - suffix, fullpath := prepareFilePath(f) + suffix, fullpath, splitRemainder := prepareFilePath(f) info, err := os.Stat(fullpath) if err == nil && info.Size() > largestSize { largestSize = info.Size() largestFilename = fullpath largestSuffix = suffix + remainder = splitRemainder } } - setPlaceholders(info, largestSuffix, largestFilename) + setPlaceholders(info, largestSuffix, largestFilename, remainder) return true case tryPolicySmallestSize: var smallestSize int64 var smallestFilename string var smallestSuffix string + var remainder string var info os.FileInfo for _, f := range m.TryFiles { - suffix, fullpath := prepareFilePath(f) + suffix, fullpath, splitRemainder := prepareFilePath(f) info, err := os.Stat(fullpath) if err == nil && (smallestSize == 0 || info.Size() < smallestSize) { smallestSize = info.Size() smallestFilename = fullpath smallestSuffix = suffix + remainder = splitRemainder } } - setPlaceholders(info, smallestSuffix, smallestFilename) + setPlaceholders(info, smallestSuffix, smallestFilename, remainder) return true case tryPolicyMostRecentlyMod: var recentDate time.Time var recentFilename string var recentSuffix string + var remainder string var info os.FileInfo for _, f := range m.TryFiles { - suffix, fullpath := prepareFilePath(f) + suffix, fullpath, splitRemainder := prepareFilePath(f) info, err := os.Stat(fullpath) if err == nil && (recentDate.IsZero() || info.ModTime().After(recentDate)) { recentDate = info.ModTime() recentFilename = fullpath recentSuffix = suffix + remainder = splitRemainder } } - setPlaceholders(info, recentSuffix, recentFilename) + setPlaceholders(info, recentSuffix, recentFilename, remainder) return true } @@ -297,10 +307,11 @@ func strictFileExists(file string) (os.FileInfo, bool) { // firstSplit returns the first result where the path // can be split in two by a value in m.SplitPath. The -// result is the first piece of the path that ends with -// in the split value. Returns the path as-is if the -// path cannot be split. -func (m MatchFile) firstSplit(path string) string { +// return values are the first piece of the path that +// ends with the split substring and the remainder. +// If the path cannot be split, the path is returned +// as-is (with no remainder). +func (m MatchFile) firstSplit(path string) (splitPart, remainder string) { for _, split := range m.SplitPath { if idx := indexFold(path, split); idx > -1 { pos := idx + len(split) @@ -308,10 +319,10 @@ func (m MatchFile) firstSplit(path string) string { if pos != len(path) && !strings.HasPrefix(path[pos:], "/") { continue } - return path[:pos] + return path[:pos], path[pos:] } } - return path + return path, "" } // There is no strings.IndexFold() function like there is strings.EqualFold(), diff --git a/modules/caddyhttp/fileserver/matcher_test.go b/modules/caddyhttp/fileserver/matcher_test.go index ddd4f78..e319907 100644 --- a/modules/caddyhttp/fileserver/matcher_test.go +++ b/modules/caddyhttp/fileserver/matcher_test.go @@ -206,9 +206,13 @@ func TestPHPFileMatcher(t *testing.T) { func TestFirstSplit(t *testing.T) { m := MatchFile{SplitPath: []string{".php"}} - actual := m.firstSplit("index.PHP/somewhere") + actual, remainder := m.firstSplit("index.PHP/somewhere") expected := "index.PHP" + expectedRemainder := "/somewhere" if actual != expected { - t.Errorf("Expected %s but got %s", expected, actual) + t.Errorf("Expected split %s but got %s", expected, actual) + } + if remainder != expectedRemainder { + t.Errorf("Expected remainder %s but got %s", expectedRemainder, remainder) } } diff --git a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go index de6d0a4..0976437 100644 --- a/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go +++ b/modules/caddyhttp/reverseproxy/fastcgi/fastcgi.go @@ -195,19 +195,27 @@ func (t Transport) buildEnv(r *http.Request) (map[string]string, error) { } fpath := r.URL.Path + scriptName := fpath + docURI := fpath // split "actual path" from "path info" if configured - var docURI, pathInfo string + var pathInfo string if splitPos := t.splitPos(fpath); splitPos > -1 { docURI = fpath[:splitPos] pathInfo = fpath[splitPos:] - } else { - docURI = fpath + + // Strip PATH_INFO from SCRIPT_NAME + scriptName = strings.TrimSuffix(scriptName, pathInfo) } - scriptName := fpath - // Strip PATH_INFO from SCRIPT_NAME - scriptName = strings.TrimSuffix(scriptName, pathInfo) + // Try to grab the path remainder from a file matcher + // if we didn't get a split result here. + // See https://github.com/caddyserver/caddy/issues/3718 + if pathInfo == "" { + if remainder, ok := repl.GetString("http.matchers.file.remainder"); ok { + pathInfo = remainder + } + } // SCRIPT_FILENAME is the absolute path of SCRIPT_NAME scriptFilename := filepath.Join(root, scriptName) -- cgit v1.2.3