From 1a390aba1c7c591e2c0c75be43eb830091b7d6f7 Mon Sep 17 00:00:00 2001 From: Carl Jackson Date: Sat, 1 Nov 2014 14:59:43 -0700 Subject: [PATCH] Allow ".,;" as string pattern "break chars" This allows you to match "/a/cat.gif" with patterns like "/a/:b.:c". Thanks to @Minecrell for an early patch implementing this functionality. Fixes #75. Fixes #48. --- web/pattern_test.go | 18 ++++++++++++++++++ web/string_pattern.go | 20 ++++++++++++++++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/web/pattern_test.go b/web/pattern_test.go index 6b2575f..3794270 100644 --- a/web/pattern_test.go +++ b/web/pattern_test.go @@ -93,6 +93,9 @@ var patternTests = []struct { pt("/hello/world", true, map[string]string{ "name": "world", }), + pt("/hello/my.world;wow", true, map[string]string{ + "name": "my.world;wow", + }), pt("/hell", false, nil), pt("/hello/", false, nil), pt("/hello/my/love", false, nil), @@ -107,6 +110,21 @@ var patternTests = []struct { pt("/a//b/", false, nil), pt("/a/1/b/2/3", false, nil), }}, + {parseStringPattern("/a/:b.:c"), + "/a/", []patternTest{ + pt("/a/cat.gif", true, map[string]string{ + "b": "cat", + "c": "gif", + }), + pt("/a/cat.tar.gz", true, map[string]string{ + "b": "cat", + "c": "tar.gz", + }), + pt("/a", false, nil), + pt("/a/cat", false, nil), + pt("/a/cat/gif", false, nil), + pt("/a/cat.", false, nil), + }}, // String prefix tests {parseStringPattern("/user/:user*"), diff --git a/web/string_pattern.go b/web/string_pattern.go index c6bfc3e..1dcb3c7 100644 --- a/web/string_pattern.go +++ b/web/string_pattern.go @@ -7,9 +7,11 @@ import ( "strings" ) +// stringPattern is a struct describing type stringPattern struct { raw string pats []string + breaks []byte literals []string isPrefix bool } @@ -37,8 +39,9 @@ func (s stringPattern) match(r *http.Request, c *C, dryrun bool) bool { path = path[len(sli):] m := 0 + bc := s.breaks[i] for ; m < len(path); m++ { - if path[m] == '/' { + if path[m] == bc { break } } @@ -81,7 +84,13 @@ func (s stringPattern) String() string { return fmt.Sprintf("stringPattern(%q, %v)", s.raw, s.isPrefix) } -var patternRe = regexp.MustCompile(`/:([^/]+)`) +// "Break characters" are characters that can end patterns. They are not allowed +// to appear in pattern names. "/" was chosen because it is the standard path +// separator, and "." was chosen because it often delimits file extensions. ";" +// and "," were chosen because Section 3.3 of RFC 3986 suggests their use. +const bc = "/.;," + +var patternRe = regexp.MustCompile(`[` + bc + `]:([^` + bc + `]+)`) func parseStringPattern(s string) stringPattern { var isPrefix bool @@ -93,18 +102,25 @@ func parseStringPattern(s string) stringPattern { matches := patternRe.FindAllStringSubmatchIndex(s, -1) pats := make([]string, len(matches)) + breaks := make([]byte, len(matches)) literals := make([]string, len(matches)+1) n := 0 for i, match := range matches { a, b := match[2], match[3] literals[i] = s[n : a-1] // Need to leave off the colon pats[i] = s[a:b] + if b == len(s) { + breaks[i] = '/' + } else { + breaks[i] = s[b] + } n = b } literals[len(matches)] = s[n:] return stringPattern{ raw: s, pats: pats, + breaks: breaks, literals: literals, isPrefix: isPrefix, }