diff --git a/interfaces/prompting/patterns/patterns_test.go b/interfaces/prompting/patterns/patterns_test.go index 94d8cc15708..3cf6d5bd21e 100644 --- a/interfaces/prompting/patterns/patterns_test.go +++ b/interfaces/prompting/patterns/patterns_test.go @@ -133,6 +133,12 @@ func (s *patternsSuite) TestParsePathPatternHappy(c *C) { "/foo/{a,b}{c,d}{e,f}{g,h,i,j,k}{l,m,n,o,p}{q,r,s,t,u},1,2,3", // expands to 1000, with commas outside groups "/" + strings.Repeat("{a,", 999) + "a" + strings.Repeat("}", 999), "/" + strings.Repeat("{", 999) + "a" + strings.Repeat(",a}", 999), + "/foo/.../bar", + "/foo/...", + "/foo/.{bar,baz}", + "/foo/..{bar,baz}", + "/foo/{bar,baz}.", + "/foo/{bar,baz}..", } { _, err := patterns.ParsePathPattern(pattern) c.Check(err, IsNil, Commentf("valid path pattern %q was incorrectly not allowed", pattern)) @@ -152,6 +158,22 @@ func (s *patternsSuite) TestParsePathPatternUnhappy(c *C) { `file.txt`, `invalid path pattern: pattern must start with '/': "file.txt"`, }, + { + `/foo/./bar`, + `invalid path pattern: pattern cannot contain '/./' or '/../': .*`, + }, + { + `/foo/../bar`, + `invalid path pattern: pattern cannot contain '/./' or '/../': .*`, + }, + { + `/foo/.`, + `invalid path pattern: pattern cannot contain '/./' or '/../': .*`, + }, + { + `/foo/..`, + `invalid path pattern: pattern cannot contain '/./' or '/../': .*`, + }, { `{/,/foo}`, `invalid path pattern: pattern must start with '/': .*`, diff --git a/interfaces/prompting/patterns/scan.go b/interfaces/prompting/patterns/scan.go index 00483d3b64c..f734399f4bc 100644 --- a/interfaces/prompting/patterns/scan.go +++ b/interfaces/prompting/patterns/scan.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" "io" + "regexp" "strings" ) @@ -59,6 +60,9 @@ type token struct { text string } +// relpathFinder matches `/./` and `/../` along with their trailing variants `/.` and `/..` in path patterns. +var relpathFinder = regexp.MustCompile(`/\.(\.)?(/|$)`) + func scan(text string) (tokens []token, err error) { if len(text) == 0 { return nil, errors.New("pattern has length 0") @@ -66,6 +70,9 @@ func scan(text string) (tokens []token, err error) { if text[0] != '/' { return nil, errors.New("pattern must start with '/'") } + if relpathFinder.MatchString(text) { + return nil, errors.New("pattern cannot contain '/./' or '/../'") + } var runes []rune diff --git a/interfaces/prompting/patterns/scan_internal_test.go b/interfaces/prompting/patterns/scan_internal_test.go index 6282ff17d6b..26039203671 100644 --- a/interfaces/prompting/patterns/scan_internal_test.go +++ b/interfaces/prompting/patterns/scan_internal_test.go @@ -123,6 +123,22 @@ func (s *scanSuite) TestScanUnhappy(c *C) { `foo`, `pattern must start with '/'`, }, + { + `/foo/./bar`, + `pattern cannot contain '/./' or '/../'`, + }, + { + `/foo/../bar`, + `pattern cannot contain '/./' or '/../'`, + }, + { + `/foo/.`, + `pattern cannot contain '/./' or '/../'`, + }, + { + `/foo/..`, + `pattern cannot contain '/./' or '/../'`, + }, { `/foo\`, `trailing unescaped '\\' character`,