From a479943acd70068c4b80d3a8f4b8dd7ab93ca2ba Mon Sep 17 00:00:00 2001 From: Matt Holt Date: Tue, 16 Aug 2022 08:48:57 -0600 Subject: caddyhttp: Smarter path matching and rewriting (#4948) Co-authored-by: RussellLuo --- modules/caddyhttp/caddyhttp.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'modules/caddyhttp/caddyhttp.go') diff --git a/modules/caddyhttp/caddyhttp.go b/modules/caddyhttp/caddyhttp.go index 784b2b9..c9cc9e6 100644 --- a/modules/caddyhttp/caddyhttp.go +++ b/modules/caddyhttp/caddyhttp.go @@ -20,6 +20,7 @@ import ( "io" "net" "net/http" + "path" "path/filepath" "strconv" "strings" @@ -244,6 +245,40 @@ func SanitizedPathJoin(root, reqPath string) string { return path } +// CleanPath cleans path p according to path.Clean(), but only +// merges repeated slashes if collapseSlashes is true, and always +// preserves trailing slashes. +func CleanPath(p string, collapseSlashes bool) string { + if collapseSlashes { + return cleanPath(p) + } + + // insert an invalid/impossible URI character into each two consecutive + // slashes to expand empty path segments; then clean the path as usual, + // and then remove the remaining temporary characters. + const tmpCh = 0xff + var sb strings.Builder + for i, ch := range p { + if ch == '/' && i > 0 && p[i-1] == '/' { + sb.WriteByte(tmpCh) + } + sb.WriteRune(ch) + } + halfCleaned := cleanPath(sb.String()) + halfCleaned = strings.ReplaceAll(halfCleaned, string([]byte{tmpCh}), "") + + return halfCleaned +} + +// cleanPath does path.Clean(p) but preserves any trailing slash. +func cleanPath(p string) string { + cleaned := path.Clean(p) + if cleaned != "/" && strings.HasSuffix(p, "/") { + cleaned = cleaned + "/" + } + return cleaned +} + // tlsPlaceholderWrapper is a no-op listener wrapper that marks // where the TLS listener should be in a chain of listener wrappers. // It should only be used if another listener wrapper must be placed -- cgit v1.2.3