diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 88ce1d02..fde086ef 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -19,6 +19,7 @@ import ( "net/http" "os" "path" + "strings" "time" "github.com/caddyserver/caddy/v2" @@ -149,7 +150,7 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) { for _, f := range m.TryFiles { suffix := path.Clean(repl.ReplaceAll(f, "")) fullpath := sanitizedPathJoin(root, suffix) - if fileExists(fullpath) { + if strictFileExists(fullpath) { return suffix, fullpath, true } } @@ -207,22 +208,33 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) { return } -// fileExists returns true if file exists, -// false if it doesn't, or false if there -// was any other error. -func fileExists(file string) bool { - _, err := os.Stat(file) - if err == nil { - return true - } else if os.IsNotExist(err) { - return false - } else { - // we don't know if it exists, - // so assume it doesn't, since - // there must have been some - // other error anyway +// strictFileExists returns true if file exists +// and matches the convention of the given file +// path. If the path ends in a forward slash, +// the file must also be a directory; if it does +// NOT end in a forward slash, the file must NOT +// be a directory. +func strictFileExists(file string) bool { + stat, err := os.Stat(file) + if err != nil { + // in reality, this can be any error + // such as permission or even obscure + // ones like "is not a directory" (when + // trying to stat a file within a file); + // in those cases we can't be sure if + // the file exists, so we just treat any + // error as if it does not exist; see + // https://stackoverflow.com/a/12518877/1048862 return false } + if strings.HasSuffix(file, "/") { + // by convention, file paths ending + // in a slash must be a directory + return stat.IsDir() + } + // by convention, file paths NOT ending + // in a slash must NOT be a directory + return !stat.IsDir() } const (