mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-23 22:27:38 -05:00
Support for case insensitive paths using CASE_SENSITIVE_PATH environment variable.
This commit is contained in:
parent
63e4352db7
commit
9f9fbf2e1b
3 changed files with 108 additions and 10 deletions
|
@ -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.
|
// but we also want to be flexible for the script we proxy to.
|
||||||
|
|
||||||
fpath := r.URL.Path
|
fpath := r.URL.Path
|
||||||
|
|
||||||
if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok {
|
if idx, ok := middleware.IndexFile(h.FileSys, fpath, rule.IndexFiles); ok {
|
||||||
fpath = idx
|
fpath = idx
|
||||||
// Index file present.
|
// Index file present.
|
||||||
// If request path cannot be split, return error.
|
// If request path cannot be split, return error.
|
||||||
if !h.canSplit(fpath, rule) {
|
if !rule.canSplit(fpath) {
|
||||||
return http.StatusInternalServerError, ErrIndexMissingSplit
|
return http.StatusInternalServerError, ErrIndexMissingSplit
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No index file present.
|
// No index file present.
|
||||||
// If request path cannot be split, ignore request.
|
// If request path cannot be split, ignore request.
|
||||||
if !h.canSplit(fpath, rule) {
|
if !rule.canSplit(fpath) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,10 +166,6 @@ func (h Handler) exists(path string) bool {
|
||||||
return false
|
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.
|
// 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) {
|
func (h Handler) buildEnv(r *http.Request, rule Rule, fpath string) (map[string]string, error) {
|
||||||
var env map[string]string
|
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.
|
// Split path in preparation for env variables.
|
||||||
// Previous h.canSplit checks ensure this can never be -1.
|
// Previous rule.canSplit checks ensure this can never be -1.
|
||||||
splitPos := strings.Index(fpath, rule.SplitPath)
|
splitPos := rule.splitPos(fpath)
|
||||||
|
|
||||||
// Request has the extension; path was split successfully
|
// Request has the extension; path was split successfully
|
||||||
docURI := fpath[:splitPos+len(rule.SplitPath)]
|
docURI := fpath[:splitPos+len(rule.SplitPath)]
|
||||||
|
@ -292,6 +289,20 @@ type Rule struct {
|
||||||
EnvVars [][2]string
|
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 (
|
var (
|
||||||
headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
|
headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
|
||||||
// ErrIndexMissingSplit describes an index configuration error.
|
// ErrIndexMissingSplit describes an index configuration error.
|
||||||
|
|
|
@ -1,6 +1,32 @@
|
||||||
package middleware
|
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.
|
// Path represents a URI path, maybe with pattern characters.
|
||||||
type Path string
|
type Path string
|
||||||
|
@ -11,5 +37,8 @@ type Path string
|
||||||
// comparison; this method assures that paths can be
|
// comparison; this method assures that paths can be
|
||||||
// easily and consistently matched.
|
// easily and consistently matched.
|
||||||
func (p Path) Matches(other string) bool {
|
func (p Path) Matches(other string) bool {
|
||||||
|
if CaseSensitivePath {
|
||||||
return strings.HasPrefix(string(p), other)
|
return strings.HasPrefix(string(p), other)
|
||||||
}
|
}
|
||||||
|
return strings.HasPrefix(strings.ToLower(string(p)), strings.ToLower(other))
|
||||||
|
}
|
||||||
|
|
58
middleware/path_test.go
Normal file
58
middleware/path_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue