This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin.git


The following commit(s) were added to refs/heads/master by this push:
     new 6a018f2  fix: don't treat # and ; inside quoted model strings as 
comments
6a018f2 is described below

commit 6a018f27246f5f270f6cfa6ca3e8c82f45812bec
Author: Yang Luo <[email protected]>
AuthorDate: Mon Nov 2 20:55:20 2026 +0800

    fix: don't treat # and ; inside quoted model strings as comments
---
 config/config.go            | 11 +++++++++--
 config/config_test.go       |  3 +++
 config/testdata/testini.ini |  1 +
 enforcer_test.go            | 29 +++++++++++++++++++++++++++++
 util/util.go                | 36 ++++++++++++++++++++----------------
 util/util_test.go           |  6 +++++-
 6 files changed, 67 insertions(+), 19 deletions(-)

diff --git a/config/config.go b/config/config.go
index 6b6f2d4..22f4a85 100644
--- a/config/config.go
+++ b/config/config.go
@@ -153,9 +153,16 @@ func (c *Config) parseBuffer(buf *bufio.Reader) error {
                                canWrite = true
                        }
 
+                       // a comment marker inside a quoted string is part of 
the value
                        end := len(p)
-                       for i, value := range p {
-                               if value == DEFAULT_COMMENT[0] || value == 
DEFAULT_COMMENT_SEM[0] {
+                       for i := 0; i < len(p); i++ {
+                               if p[i] == '"' || p[i] == '\'' {
+                                       j := bytes.IndexByte(p[i+1:], p[i])
+                                       if j < 0 {
+                                               break
+                                       }
+                                       i += j + 1
+                               } else if p[i] == DEFAULT_COMMENT[0] || p[i] == 
DEFAULT_COMMENT_SEM[0] {
                                        end = i
                                        break
                                }
diff --git a/config/config_test.go b/config/config_test.go
index 66b7454..c3e0591 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -50,6 +50,9 @@ func TestGet(t *testing.T) {
        if v := config.String("mysql::mysql.master.user"); v != "root" {
                t.Errorf("Get failure: expected different value for 
mysql::mysql.master.user (expected: [%#v] got: [%#v])", "root", v)
        }
+       if v := config.String("mysql::mysql.master.note"); v != `"a#b;c" 'd#e'` 
{
+               t.Errorf("Get failure: expected different value for 
mysql::mysql.master.note (expected: [%#v] got: [%#v])", `"a#b;c" 'd#e'`, v)
+       }
        if v := config.String("mysql::mysql.master.pass"); v != "89dds)2$" {
                t.Errorf("Get failure: expected different value for 
mysql::mysql.master.pass (expected: [%#v] got: [%#v])", "89dds)2$", v)
        }
diff --git a/config/testdata/testini.ini b/config/testdata/testini.ini
index cbb9bab..86bcb39 100644
--- a/config/testdata/testini.ini
+++ b/config/testdata/testini.ini
@@ -17,6 +17,7 @@ mysql.master.host = 10.0.0.1 # host # 10.0.0.1
 mysql.master.user = root ; user name
 mysql.master.pass = 89dds)2$#d
 mysql.master.db = act
+mysql.master.note = "a#b;c" 'd#e' # note
 
 ; math config
 [math]
diff --git a/enforcer_test.go b/enforcer_test.go
index 918f38e..6e3ba90 100644
--- a/enforcer_test.go
+++ b/enforcer_test.go
@@ -187,6 +187,35 @@ m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
        testEnforce(t, e, "bob", "data2", "write", true)
 }
 
+func TestCommentMarkerInMatcherString(t *testing.T) {
+       text := `
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = sub, obj, act
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = r.sub == p.sub && (r.act == "#write" || r.obj == 'a;b') # comment
+`
+       m, err := model.NewModelFromString(text)
+       if err != nil {
+               t.Fatal(err)
+       }
+       e, err := NewEnforcer(m)
+       if err != nil {
+               t.Fatal(err)
+       }
+       _, _ = e.AddPolicy("alice", "data1", "read")
+
+       testEnforce(t, e, "alice", "data1", "#write", true)
+       testEnforce(t, e, "alice", "a;b", "read", true)
+       testEnforce(t, e, "alice", "data1", "read", false)
+}
+
 func TestNotUsedRBACModelInMemory(t *testing.T) {
        m := model.NewModel()
        m.AddDef("r", "r", "sub, obj, act")
diff --git a/util/util.go b/util/util.go
index d508bfd..0808992 100644
--- a/util/util.go
+++ b/util/util.go
@@ -48,18 +48,7 @@ func EscapeAssertion(s string) string {
                if quote != '"' && quote != '\'' {
                        continue
                }
-               end := i + 1
-               for end < len(s) && s[end] != quote {
-                       if s[end] == '\\' {
-                               end++
-                       }
-                       end++
-               }
-               if end < len(s) {
-                       end++
-               } else {
-                       end = len(s)
-               }
+               end := stringLiteralEnd(s, i)
                b.WriteString(escapeAssertionUnquoted(s[start:i]))
                b.WriteString(s[i:end])
                start = end
@@ -69,6 +58,16 @@ func EscapeAssertion(s string) string {
        return b.String()
 }
 
+// stringLiteralEnd returns the index just past the string literal that opens 
at s[i].
+// A backslash is not an escape character in matchers (see 
EscapeStringLiterals), so the
+// literal ends at the next matching quote, or at the end of s when it is not 
closed.
+func stringLiteralEnd(s string, i int) int {
+       if j := strings.IndexByte(s[i+1:], s[i]); j >= 0 {
+               return i + j + 2
+       }
+       return len(s)
+}
+
 func escapeAssertionUnquoted(s string) string {
        return escapeAssertionRegex.ReplaceAllStringFunc(s, func(m string) 
string {
                // Replace only the last dot with underscore (preserve the 
prefix character)
@@ -81,12 +80,17 @@ func escapeAssertionUnquoted(s string) string {
 }
 
 // RemoveComments removes the comments starting with # in the text.
+// A # inside a quoted string literal is part of the literal.
 func RemoveComments(s string) string {
-       pos := strings.Index(s, "#")
-       if pos == -1 {
-               return s
+       for i := 0; i < len(s); i++ {
+               switch s[i] {
+               case '#':
+                       return strings.TrimSpace(s[:i])
+               case '"', '\'':
+                       i = stringLiteralEnd(s, i) - 1
+               }
        }
-       return strings.TrimSpace(s[0:pos])
+       return s
 }
 
 // ArrayEquals determines whether two string arrays are identical.
diff --git a/util/util_test.go b/util/util_test.go
index 4f81133..4580b2f 100644
--- a/util/util_test.go
+++ b/util/util_test.go
@@ -55,7 +55,7 @@ func TestEscapeAssertion(t *testing.T) {
        testEscapeAssertion(t, `r.sub == "test.p.value"`, `r_sub == 
"test.p.value"`)
        testEscapeAssertion(t, `r.obj == "https://p.example.com"`, `r_obj == 
"https://p.example.com"`)
        testEscapeAssertion(t, `r.obj == 'https://p.example.com' && p.sub == 
r.sub`, `r_obj == 'https://p.example.com' && p_sub == r_sub`)
-       testEscapeAssertion(t, `keyMatch(r.obj, "/p.*") || r.obj == "a\"/r.b"`, 
`keyMatch(r_obj, "/p.*") || r_obj == "a\"/r.b"`)
+       testEscapeAssertion(t, `keyMatch(r.obj, "/p.*") || r.obj == "a\" && 
r.sub == p.sub`, `keyMatch(r_obj, "/p.*") || r_obj == "a\" && r_sub == p_sub`)
 }
 
 func testRemoveComments(t *testing.T, s string, res string) {
@@ -74,6 +74,10 @@ func TestRemoveComments(t *testing.T) {
        testRemoveComments(t, "r.act == p.act###", "r.act == p.act")
        testRemoveComments(t, "### comments", "")
        testRemoveComments(t, "r.act == p.act", "r.act == p.act")
+       testRemoveComments(t, `r.act == "#write" && r.sub == p.sub`, `r.act == 
"#write" && r.sub == p.sub`)
+       testRemoveComments(t, `r.act == '#write' # it's a comment`, `r.act == 
'#write'`)
+       testRemoveComments(t, `r.act == "a\" # comments`, `r.act == "a\"`)
+       testRemoveComments(t, `r.act == "#write`, `r.act == "#write`)
 }
 
 func testArrayEquals(t *testing.T, a []string, b []string, res bool) {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to