diff --git a/middleware/fastcgi/fastcgi.go b/middleware/fastcgi/fastcgi.go index 302bea35..d3600b67 100755 --- a/middleware/fastcgi/fastcgi.go +++ b/middleware/fastcgi/fastcgi.go @@ -45,17 +45,18 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) // but we also want to be flexible for the script we proxy to. fpath := r.URL.Path + if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok { fpath = idx // Index file present. // If request path cannot be split, return error. - if !h.canSplit(fpath, rule) { + if !rule.canSplit(fpath) { return http.StatusInternalServerError, ErrIndexMissingSplit } } else { // No index file present. // If request path cannot be split, ignore request. - if !h.canSplit(fpath, rule) { + if !rule.canSplit(fpath) { continue } } @@ -165,10 +166,6 @@ func (h Handler) exists(path string) bool { return false } -func (h Handler) canSplit(path string, rule Rule) bool { - return strings.Contains(path, rule.SplitPath) -} - // buildEnv returns a set of CGI environment variables for the request. func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]string, error) { var env map[string]string @@ -186,8 +183,8 @@ func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string] } // Split path in preparation for env variables. - // Previous h.canSplit checks ensure this can never be -1. - splitPos := strings.Index(fpath, rule.SplitPath) + // Previous rule.canSplit checks ensure this can never be -1. + splitPos := rule.splitPos(fpath) // Request has the extension; path was split successfully docURI := fpath[:splitPos+len(rule.SplitPath)] @@ -292,6 +289,20 @@ type Rule struct { EnvVars [][2]string } +// canSplit checks if path can split into two based on rule.SplitPath. +func (r Rule) canSplit(path string) bool { + return r.splitPos(path) >= 0 +} + +// splitPos returns the index where path should be split +// based on rule.SplitPath. +func (r Rule) splitPos(path string) int { + if middleware.CaseSensitivePath { + return strings.Index(path, r.SplitPath) + } + return strings.Index(strings.ToLower(path), strings.ToLower(r.SplitPath)) +} + var ( headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_") // ErrIndexMissingSplit describes an index configuration error. diff --git a/middleware/path.go b/middleware/path.go index ecfac747..9c831e77 100644 --- a/middleware/path.go +++ b/middleware/path.go @@ -1,6 +1,32 @@ package middleware -import "strings" +import ( + "os" + "strings" +) + +const caseSensitivePathEnv = "CASE_SENSITIVE_PATH" + +func init() { + initCaseSettings() +} + +// CaseSensitivePath determines if paths should be case sensitive. +// This is configurable via CASE_SENSITIVE_PATH environment variable. +// It defaults to false. +var CaseSensitivePath = true + +// initCaseSettings loads case sensitivity config from environment variable. +// +// This could have been in init, but init cannot be called from tests. +func initCaseSettings() { + switch os.Getenv(caseSensitivePathEnv) { + case "0", "false": + CaseSensitivePath = false + default: + CaseSensitivePath = true + } +} // Path represents a URI path, maybe with pattern characters. type Path string @@ -11,5 +37,8 @@ type Path string // comparison; this method assures that paths can be // easily and consistently matched. func (p Path) Matches(other string) bool { - return strings.HasPrefix(string(p), other) + if CaseSensitivePath { + return strings.HasPrefix(string(p), other) + } + return strings.HasPrefix(strings.ToLower(string(p)), strings.ToLower(other)) } diff --git a/middleware/path_test.go b/middleware/path_test.go new file mode 100644 index 00000000..eb054b1e --- /dev/null +++ b/middleware/path_test.go @@ -0,0 +1,58 @@ +package middleware + +import ( + "os" + "testing" +) + +func TestPathCaseSensitivity(t *testing.T) { + tests := []struct { + basePath string + path string + caseSensitive bool + expected bool + }{ + {"/", "/file", true, true}, + {"/a", "/file", true, false}, + {"/f", "/file", true, true}, + {"/f", "/File", true, false}, + {"/f", "/File", false, true}, + {"/file", "/file", true, true}, + {"/file", "/file", false, true}, + {"/files", "/file", false, false}, + {"/files", "/file", true, false}, + {"/folder", "/folder/file.txt", true, true}, + {"/folders", "/folder/file.txt", true, false}, + {"/folder", "/Folder/file.txt", false, true}, + {"/folders", "/Folder/file.txt", false, false}, + } + + for i, test := range tests { + CaseSensitivePath = test.caseSensitive + valid := Path(test.path).Matches(test.basePath) + if test.expected != valid { + t.Errorf("Test %d: Expected %v, found %v", i, test.expected, valid) + } + } +} + +func TestPathCaseSensitiveEnv(t *testing.T) { + tests := []struct { + envValue string + expected bool + }{ + {"1", true}, + {"0", false}, + {"false", false}, + {"true", true}, + {"", true}, + } + + for i, test := range tests { + os.Setenv(caseSensitivePathEnv, test.envValue) + initCaseSettings() + if test.expected != CaseSensitivePath { + t.Errorf("Test %d: Expected %v, found %v", i, test.expected, CaseSensitivePath) + } + } +}