diff options
| author | Francis Lavoie <lavofr@gmail.com> | 2020-09-16 20:09:28 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-09-16 18:09:28 -0600 | 
| commit | b95b87381a282e1fe57295d145b71645d7801f07 (patch) | |
| tree | 853f7368ac81f6ede2de30503201cf943841b34e /modules/caddyhttp/fileserver/matcher.go | |
| parent | b01bb275b395643542ceca4fbc82bedea8e43937 (diff) | |
fileserver: Fix try_files for directories; windows fix (#3684)
* fileserver: Fix try_files for directories, windows fix
* fileserver: Add new file type placeholder, refactoring, tests
* fileserver: Review cleanup
* fileserver: Flip the return args order
Diffstat (limited to 'modules/caddyhttp/fileserver/matcher.go')
| -rw-r--r-- | modules/caddyhttp/fileserver/matcher.go | 85 | 
1 files changed, 53 insertions, 32 deletions
diff --git a/modules/caddyhttp/fileserver/matcher.go b/modules/caddyhttp/fileserver/matcher.go index 1844421..475a9ee 100644 --- a/modules/caddyhttp/fileserver/matcher.go +++ b/modules/caddyhttp/fileserver/matcher.go @@ -19,6 +19,7 @@ import (  	"net/http"  	"os"  	"path" +	"path/filepath"  	"strings"  	"time" @@ -34,7 +35,7 @@ func init() {  // MatchFile is an HTTP request matcher that can match  // requests based upon file existence.  // -// Upon matching, two new placeholders will be made +// Upon matching, three new placeholders will be made  // available:  //  // - `{http.matchers.file.relative}` The root-relative @@ -42,6 +43,8 @@ func init() {  // requests.  // - `{http.matchers.file.absolute}` The absolute path  // of the matched file. +// - `{http.matchers.file.type}` Set to "directory" if +// the matched file is a directory, "file" otherwise.  type MatchFile struct {  	// The root directory, used for creating absolute  	// file paths, and required when working with @@ -153,25 +156,18 @@ func (m MatchFile) Validate() error {  }  // Match returns true if r matches m. Returns true -// if a file was matched. If so, two placeholders +// if a file was matched. If so, three placeholders  // will be available:  //    - http.matchers.file.relative  //    - http.matchers.file.absolute +//    - http.matchers.file.type  func (m MatchFile) Match(r *http.Request) bool { -	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer) -	rel, abs, matched := m.selectFile(r) -	if matched { -		repl.Set("http.matchers.file.relative", rel) -		repl.Set("http.matchers.file.absolute", abs) -	} -	return matched +	return m.selectFile(r)  }  // selectFile chooses a file according to m.TryPolicy by appending  // the paths in m.TryFiles to m.Root, with placeholder replacements. -// It returns the root-relative path to the matched file, the full -// or absolute path, and whether a match was made. -func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) { +func (m MatchFile) selectFile(r *http.Request) (matched bool) {  	repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)  	root := repl.ReplaceAll(m.Root, ".") @@ -183,13 +179,35 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {  		m.TryFiles = []string{r.URL.Path}  	} +	// common preparation of the file into parts +	prepareFilePath := func(file string) (string, string) { +		suffix := m.firstSplit(path.Clean(repl.ReplaceAll(file, ""))) +		if strings.HasSuffix(file, "/") { +			suffix += "/" +		} +		fullpath := sanitizedPathJoin(root, suffix) +		return suffix, fullpath +	} + +	// sets up the placeholders for the matched file +	setPlaceholders := func(info os.FileInfo, rel string, abs string) { +		repl.Set("http.matchers.file.relative", rel) +		repl.Set("http.matchers.file.absolute", abs) + +		fileType := "file" +		if info.IsDir() { +			fileType = "directory" +		} +		repl.Set("http.matchers.file.type", fileType) +	} +  	switch m.TryPolicy {  	case "", tryPolicyFirstExist:  		for _, f := range m.TryFiles { -			suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, ""))) -			fullpath := sanitizedPathJoin(root, suffix) -			if strictFileExists(fullpath) { -				return suffix, fullpath, true +			suffix, fullpath := prepareFilePath(f) +			if info, exists := strictFileExists(fullpath); exists { +				setPlaceholders(info, suffix, fullpath) +				return true  			}  		} @@ -197,9 +215,9 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {  		var largestSize int64  		var largestFilename string  		var largestSuffix string +		var info os.FileInfo  		for _, f := range m.TryFiles { -			suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, ""))) -			fullpath := sanitizedPathJoin(root, suffix) +			suffix, fullpath := prepareFilePath(f)  			info, err := os.Stat(fullpath)  			if err == nil && info.Size() > largestSize {  				largestSize = info.Size() @@ -207,15 +225,16 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {  				largestSuffix = suffix  			}  		} -		return largestSuffix, largestFilename, true +		setPlaceholders(info, largestSuffix, largestFilename) +		return true  	case tryPolicySmallestSize:  		var smallestSize int64  		var smallestFilename string  		var smallestSuffix string +		var info os.FileInfo  		for _, f := range m.TryFiles { -			suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, ""))) -			fullpath := sanitizedPathJoin(root, suffix) +			suffix, fullpath := prepareFilePath(f)  			info, err := os.Stat(fullpath)  			if err == nil && (smallestSize == 0 || info.Size() < smallestSize) {  				smallestSize = info.Size() @@ -223,15 +242,16 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {  				smallestSuffix = suffix  			}  		} -		return smallestSuffix, smallestFilename, true +		setPlaceholders(info, smallestSuffix, smallestFilename) +		return true  	case tryPolicyMostRecentlyMod:  		var recentDate time.Time  		var recentFilename string  		var recentSuffix string +		var info os.FileInfo  		for _, f := range m.TryFiles { -			suffix := m.firstSplit(path.Clean(repl.ReplaceAll(f, ""))) -			fullpath := sanitizedPathJoin(root, suffix) +			suffix, fullpath := prepareFilePath(f)  			info, err := os.Stat(fullpath)  			if err == nil &&  				(recentDate.IsZero() || info.ModTime().After(recentDate)) { @@ -240,7 +260,8 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {  				recentSuffix = suffix  			}  		} -		return recentSuffix, recentFilename, true +		setPlaceholders(info, recentSuffix, recentFilename) +		return true  	}  	return @@ -252,7 +273,7 @@ func (m MatchFile) selectFile(r *http.Request) (rel, abs string, matched bool) {  // 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 { +func strictFileExists(file string) (os.FileInfo, bool) {  	stat, err := os.Stat(file)  	if err != nil {  		// in reality, this can be any error @@ -263,16 +284,16 @@ func strictFileExists(file string) bool {  		// the file exists, so we just treat any  		// error as if it does not exist; see  		// https://stackoverflow.com/a/12518877/1048862 -		return false +		return nil, false  	} -	if strings.HasSuffix(file, "/") { +	if strings.HasSuffix(file, string(filepath.Separator)) {  		// by convention, file paths ending -		// in a slash must be a directory -		return stat.IsDir() +		// in a path separator must be a directory +		return stat, stat.IsDir()  	}  	// by convention, file paths NOT ending -	// in a slash must NOT be a directory -	return !stat.IsDir() +	// in a path separator must NOT be a directory +	return stat, !stat.IsDir()  }  // firstSplit returns the first result where the path  | 
