From 2e76cb6052aa7adced9cba196b41af3aeeb21672 Mon Sep 17 00:00:00 2001 From: AdamMagued Date: Fri, 9 Oct 2026 16:09:14 +0300 Subject: [PATCH] fix(quote): escape commas in QuoteMeta to prevent splitting in alternative patterns Signed-off-by: AdamMagued --- glob_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++ issues_test.go | 6 +++++ syntax/lexer.go | 5 ++-- syntax/lexer_test.go | 13 ++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/glob_test.go b/glob_test.go index baba220..2bbb835 100644 --- a/glob_test.go +++ b/glob_test.go @@ -752,10 +752,26 @@ func TestQuoteMeta(t *testing.T) { in: `*?\[]{}`, out: `\*\?\\\[\]\{\}`, }, + { + in: `a,b`, + out: `a\,b`, + }, + { + in: `,`, + out: `\,`, + }, + { + in: `*?\[]{},`, + out: `\*\?\\\[\]\{\}\,`, + }, { in: `some text and *?\[]{}`, out: `some text and \*\?\\\[\]\{\}`, }, + { + in: `some, text and *?\[]{}`, + out: `some\, text and \*\?\\\[\]\{\}`, + }, } { act := QuoteMeta(test.in) if act != test.out { @@ -767,6 +783,50 @@ func TestQuoteMeta(t *testing.T) { } } +func TestQuoteMetaAlternatives(t *testing.T) { + for id, test := range []struct { + pattern string + match []string + mismatch []string + }{ + { + pattern: "{" + QuoteMeta("a,b") + ",zz}", + match: []string{"a,b", "zz"}, + mismatch: []string{"a", "b", "z", "a,b,zz"}, + }, + { + pattern: "{" + QuoteMeta("foo,bar") + "," + QuoteMeta("baz,qux") + "}", + match: []string{"foo,bar", "baz,qux"}, + mismatch: []string{"foo", "bar", "baz", "qux"}, + }, + { + pattern: "{" + QuoteMeta(",") + ",x}", + match: []string{",", "x"}, + mismatch: []string{"", ",x"}, + }, + { + pattern: QuoteMeta("a,b"), + match: []string{"a,b"}, + mismatch: []string{"a", "b", "a,b,c"}, + }, + } { + g, err := Compile(test.pattern) + if err != nil { + t.Fatalf("#%d Compile(%q) failed: %v", id, test.pattern, err) + } + for _, s := range test.match { + if !g.Match(s) { + t.Errorf("#%d pattern %q failed to match %q", id, test.pattern, s) + } + } + for _, s := range test.mismatch { + if g.Match(s) { + t.Errorf("#%d pattern %q unexpectedly matched %q", id, test.pattern, s) + } + } + } +} + func BenchmarkPattern(b *testing.B) { for _, test := range []struct { name string diff --git a/issues_test.go b/issues_test.go index 1282aea..08d6673 100644 --- a/issues_test.go +++ b/issues_test.go @@ -47,6 +47,12 @@ func TestOpenIssues(t *testing.T) { {66, `{**/daxing,daxing,x}/**/*dev*.yaml`, "playground/daxing/generated/dev.yaml", nil, true}, {66, `{**/daxing,daxing}/**/*dev*.yaml`, "playground/daxing/generated/dev.yaml", nil, true}, {66, `{**/daxing}/**/*dev*.yaml`, "playground/daxing/generated/dev.yaml", nil, true}, + {78, "{" + QuoteMeta("a,b") + ",zz}", "a,b", nil, true}, + {78, "{" + QuoteMeta("a,b") + ",zz}", "a", nil, false}, + {78, "{" + QuoteMeta("a,b") + ",zz}", "b", nil, false}, + {78, "{" + QuoteMeta("a,b") + ",zz}", "zz", nil, true}, + {78, QuoteMeta("a,b"), "a,b", nil, true}, + {78, QuoteMeta("a,b"), "a", nil, false}, } { got := MustCompile(c.pat, c.sep...).Match(c.str) if got != c.exp { diff --git a/syntax/lexer.go b/syntax/lexer.go index b3d1ff9..97b3a50 100644 --- a/syntax/lexer.go +++ b/syntax/lexer.go @@ -112,6 +112,7 @@ const ( var specials = []byte{ char_any, + char_comma, char_single, char_escape, char_range_open, @@ -121,8 +122,8 @@ var specials = []byte{ } // IsSpecial reports whether c is a glob meta character, that is, one that -// [glob.QuoteMeta] escapes. Note that `,`, `!` and `-` are not among them: -// they are special only inside `{...}` and `[...]` respectively, which are. +// [glob.QuoteMeta] escapes. Note that `!` and `-` are not among them: +// they are special only inside `[...]`. func IsSpecial(c byte) bool { return bytes.IndexByte(specials, c) != -1 } diff --git a/syntax/lexer_test.go b/syntax/lexer_test.go index 9f2f208..dab81a0 100644 --- a/syntax/lexer_test.go +++ b/syntax/lexer_test.go @@ -190,3 +190,16 @@ func TestLexGood(t *testing.T) { } } } + +func TestIsSpecial(t *testing.T) { + for _, c := range []byte{'*', ',', '?', '\\', '[', ']', '{', '}'} { + if !IsSpecial(c) { + t.Errorf("IsSpecial(%q) = false; want true", c) + } + } + for _, c := range []byte{'a', 'z', '0', '9', '!', '-', '_', '.', ' ', '/', 0} { + if IsSpecial(c) { + t.Errorf("IsSpecial(%q) = true; want false", c) + } + } +}