mirror of
https://github.com/caddyserver/caddy.git
synced 2025-02-25 01:09:04 +01:00
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 <mholt@users.noreply.github.com>
This commit is contained in:
parent
5643dc3fb9
commit
6e9ac248dd
3 changed files with 52 additions and 29 deletions
|
@ -44,6 +44,8 @@ func init() {
|
||||||
// of the matched file.
|
// of the matched file.
|
||||||
// - `{http.matchers.file.type}` Set to "directory" if
|
// - `{http.matchers.file.type}` Set to "directory" if
|
||||||
// the matched file is a directory, "file" otherwise.
|
// 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 {
|
type MatchFile struct {
|
||||||
// The root directory, used for creating absolute
|
// The root directory, used for creating absolute
|
||||||
// file paths, and required when working with
|
// 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
|
// 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:
|
// will be available:
|
||||||
// - http.matchers.file.relative
|
// - http.matchers.file.relative
|
||||||
// - http.matchers.file.absolute
|
// - http.matchers.file.absolute
|
||||||
// - http.matchers.file.type
|
// - http.matchers.file.type
|
||||||
|
// - http.matchers.file.remainder
|
||||||
func (m MatchFile) Match(r *http.Request) bool {
|
func (m MatchFile) Match(r *http.Request) bool {
|
||||||
return m.selectFile(r)
|
return m.selectFile(r)
|
||||||
}
|
}
|
||||||
|
@ -179,19 +182,20 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// common preparation of the file into parts
|
// common preparation of the file into parts
|
||||||
prepareFilePath := func(file string) (string, string) {
|
prepareFilePath := func(file string) (suffix, fullpath, remainder string) {
|
||||||
suffix := m.firstSplit(path.Clean(repl.ReplaceAll(file, "")))
|
suffix, remainder = m.firstSplit(path.Clean(repl.ReplaceAll(file, "")))
|
||||||
if strings.HasSuffix(file, "/") {
|
if strings.HasSuffix(file, "/") {
|
||||||
suffix += "/"
|
suffix += "/"
|
||||||
}
|
}
|
||||||
fullpath := sanitizedPathJoin(root, suffix)
|
fullpath = sanitizedPathJoin(root, suffix)
|
||||||
return suffix, fullpath
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// sets up the placeholders for the matched file
|
// 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.relative", rel)
|
||||||
repl.Set("http.matchers.file.absolute", abs)
|
repl.Set("http.matchers.file.absolute", abs)
|
||||||
|
repl.Set("http.matchers.file.remainder", remainder)
|
||||||
|
|
||||||
fileType := "file"
|
fileType := "file"
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
|
@ -203,9 +207,9 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||||
switch m.TryPolicy {
|
switch m.TryPolicy {
|
||||||
case "", tryPolicyFirstExist:
|
case "", tryPolicyFirstExist:
|
||||||
for _, f := range m.TryFiles {
|
for _, f := range m.TryFiles {
|
||||||
suffix, fullpath := prepareFilePath(f)
|
suffix, fullpath, remainder := prepareFilePath(f)
|
||||||
if info, exists := strictFileExists(fullpath); exists {
|
if info, exists := strictFileExists(fullpath); exists {
|
||||||
setPlaceholders(info, suffix, fullpath)
|
setPlaceholders(info, suffix, fullpath, remainder)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,52 +218,58 @@ func (m MatchFile) selectFile(r *http.Request) (matched bool) {
|
||||||
var largestSize int64
|
var largestSize int64
|
||||||
var largestFilename string
|
var largestFilename string
|
||||||
var largestSuffix string
|
var largestSuffix string
|
||||||
|
var remainder string
|
||||||
var info os.FileInfo
|
var info os.FileInfo
|
||||||
for _, f := range m.TryFiles {
|
for _, f := range m.TryFiles {
|
||||||
suffix, fullpath := prepareFilePath(f)
|
suffix, fullpath, splitRemainder := prepareFilePath(f)
|
||||||
info, err := os.Stat(fullpath)
|
info, err := os.Stat(fullpath)
|
||||||
if err == nil && info.Size() > largestSize {
|
if err == nil && info.Size() > largestSize {
|
||||||
largestSize = info.Size()
|
largestSize = info.Size()
|
||||||
largestFilename = fullpath
|
largestFilename = fullpath
|
||||||
largestSuffix = suffix
|
largestSuffix = suffix
|
||||||
|
remainder = splitRemainder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setPlaceholders(info, largestSuffix, largestFilename)
|
setPlaceholders(info, largestSuffix, largestFilename, remainder)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case tryPolicySmallestSize:
|
case tryPolicySmallestSize:
|
||||||
var smallestSize int64
|
var smallestSize int64
|
||||||
var smallestFilename string
|
var smallestFilename string
|
||||||
var smallestSuffix string
|
var smallestSuffix string
|
||||||
|
var remainder string
|
||||||
var info os.FileInfo
|
var info os.FileInfo
|
||||||
for _, f := range m.TryFiles {
|
for _, f := range m.TryFiles {
|
||||||
suffix, fullpath := prepareFilePath(f)
|
suffix, fullpath, splitRemainder := prepareFilePath(f)
|
||||||
info, err := os.Stat(fullpath)
|
info, err := os.Stat(fullpath)
|
||||||
if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
|
if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {
|
||||||
smallestSize = info.Size()
|
smallestSize = info.Size()
|
||||||
smallestFilename = fullpath
|
smallestFilename = fullpath
|
||||||
smallestSuffix = suffix
|
smallestSuffix = suffix
|
||||||
|
remainder = splitRemainder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setPlaceholders(info, smallestSuffix, smallestFilename)
|
setPlaceholders(info, smallestSuffix, smallestFilename, remainder)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
case tryPolicyMostRecentlyMod:
|
case tryPolicyMostRecentlyMod:
|
||||||
var recentDate time.Time
|
var recentDate time.Time
|
||||||
var recentFilename string
|
var recentFilename string
|
||||||
var recentSuffix string
|
var recentSuffix string
|
||||||
|
var remainder string
|
||||||
var info os.FileInfo
|
var info os.FileInfo
|
||||||
for _, f := range m.TryFiles {
|
for _, f := range m.TryFiles {
|
||||||
suffix, fullpath := prepareFilePath(f)
|
suffix, fullpath, splitRemainder := prepareFilePath(f)
|
||||||
info, err := os.Stat(fullpath)
|
info, err := os.Stat(fullpath)
|
||||||
if err == nil &&
|
if err == nil &&
|
||||||
(recentDate.IsZero() || info.ModTime().After(recentDate)) {
|
(recentDate.IsZero() || info.ModTime().After(recentDate)) {
|
||||||
recentDate = info.ModTime()
|
recentDate = info.ModTime()
|
||||||
recentFilename = fullpath
|
recentFilename = fullpath
|
||||||
recentSuffix = suffix
|
recentSuffix = suffix
|
||||||
|
remainder = splitRemainder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setPlaceholders(info, recentSuffix, recentFilename)
|
setPlaceholders(info, recentSuffix, recentFilename, remainder)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,10 +307,11 @@ func strictFileExists(file string) (os.FileInfo, bool) {
|
||||||
|
|
||||||
// firstSplit returns the first result where the path
|
// firstSplit returns the first result where the path
|
||||||
// can be split in two by a value in m.SplitPath. The
|
// can be split in two by a value in m.SplitPath. The
|
||||||
// result is the first piece of the path that ends with
|
// return values are the first piece of the path that
|
||||||
// in the split value. Returns the path as-is if the
|
// ends with the split substring and the remainder.
|
||||||
// path cannot be split.
|
// If the path cannot be split, the path is returned
|
||||||
func (m MatchFile) firstSplit(path string) string {
|
// as-is (with no remainder).
|
||||||
|
func (m MatchFile) firstSplit(path string) (splitPart, remainder string) {
|
||||||
for _, split := range m.SplitPath {
|
for _, split := range m.SplitPath {
|
||||||
if idx := indexFold(path, split); idx > -1 {
|
if idx := indexFold(path, split); idx > -1 {
|
||||||
pos := idx + len(split)
|
pos := idx + len(split)
|
||||||
|
@ -308,10 +319,10 @@ func (m MatchFile) firstSplit(path string) string {
|
||||||
if pos != len(path) && !strings.HasPrefix(path[pos:], "/") {
|
if pos != len(path) && !strings.HasPrefix(path[pos:], "/") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return path[:pos]
|
return path[:pos], path[pos:]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return path
|
return path, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// There is no strings.IndexFold() function like there is strings.EqualFold(),
|
// There is no strings.IndexFold() function like there is strings.EqualFold(),
|
||||||
|
|
|
@ -206,9 +206,13 @@ func TestPHPFileMatcher(t *testing.T) {
|
||||||
|
|
||||||
func TestFirstSplit(t *testing.T) {
|
func TestFirstSplit(t *testing.T) {
|
||||||
m := MatchFile{SplitPath: []string{".php"}}
|
m := MatchFile{SplitPath: []string{".php"}}
|
||||||
actual := m.firstSplit("index.PHP/somewhere")
|
actual, remainder := m.firstSplit("index.PHP/somewhere")
|
||||||
expected := "index.PHP"
|
expected := "index.PHP"
|
||||||
|
expectedRemainder := "/somewhere"
|
||||||
if actual != expected {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,19 +195,27 @@ func (t Transport) buildEnv(r *http.Request) (map[string]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fpath := r.URL.Path
|
fpath := r.URL.Path
|
||||||
|
scriptName := fpath
|
||||||
|
|
||||||
|
docURI := fpath
|
||||||
// split "actual path" from "path info" if configured
|
// split "actual path" from "path info" if configured
|
||||||
var docURI, pathInfo string
|
var pathInfo string
|
||||||
if splitPos := t.splitPos(fpath); splitPos > -1 {
|
if splitPos := t.splitPos(fpath); splitPos > -1 {
|
||||||
docURI = fpath[:splitPos]
|
docURI = fpath[:splitPos]
|
||||||
pathInfo = fpath[splitPos:]
|
pathInfo = fpath[splitPos:]
|
||||||
} else {
|
|
||||||
docURI = fpath
|
|
||||||
}
|
|
||||||
scriptName := fpath
|
|
||||||
|
|
||||||
// Strip PATH_INFO from SCRIPT_NAME
|
// Strip PATH_INFO from SCRIPT_NAME
|
||||||
scriptName = strings.TrimSuffix(scriptName, pathInfo)
|
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
|
// SCRIPT_FILENAME is the absolute path of SCRIPT_NAME
|
||||||
scriptFilename := filepath.Join(root, scriptName)
|
scriptFilename := filepath.Join(root, scriptName)
|
||||||
|
|
Loading…
Reference in a new issue