Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

`EscapeAssertion` rewrites dots inside quoted string literals

Cerrado Apto para principiantes
#276 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
1/5
Tiempo estimado
1-3 horas
Aptitud para principiantes
88/100
Tipo de issue
Error
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
cpp
Área
tooling

Línea de trabajo

Comienza en casbin/util/escape_assertion.cpp, en EscapeAssertion, y compáralo con la declaración en include/casbin/util/util.h. Añade los casos de literales entrecomillados a TestEscapeAssertion en tests/util_test.cpp y luego ejecuta ese binario de prueba para ver el fallo actual. Hecho significa que los literales con comillas simples y dobles conservan sus puntos, mientras que los prefijos r/p de las aserciones siguen reescribiéndose.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

Location (apache/casbin-cpp @ default branch 0ad64a7):

std::string EscapeAssertion(std::string s) {
    std::regex regex_s("[a-zA-Z0-9. ]+");

    std::sregex_iterator words_begin = std::sregex_iterator(s.begin(), s.end(), regex_s);
    auto words_end = std::sregex_iterator();

    for (std::sregex_iterator k = words_begin; k != words_end; ++k) {
        std::smatch match = *k;
        std::string match_str = match.str();
        int pos = static_cast<int>(match_str.find("."));
        if (pos != -1) {
            std::string new_str = match_str.replace(pos, 1, "_");
            s = s.replace(match.position(), match.str().length(), new_str);
        }
    }

    return s;
}

Problem
casbin::EscapeAssertion replaces the first . in every [a-zA-Z0-9. ]+ run, including runs inside quoted string literals. The regex doesn't know about quotes: because " and ' are not in the class, the text between the quotes becomes its own match, and its first dot gets replaced.

input result expected
r.sub == "r.my_resource" r_sub == "r_my_resource" r_sub == "r.my_resource"
p.obj == 'p.resource' p_obj == 'p_resource' p_obj == 'p.resource'
p.sub == p.obj && p.obj == "r.something" p_sub == p_obj && p_obj == "r_something" p_sub == p_obj && p_obj == "r.something"
p.obj == "r.my_resource" p_obj == "r_my_resource" p_obj == "r.my_resource"

Scope: EscapeAssertion is declared in the public header include/casbin/util/util.h (included by casbin/casbin.h), documented as taking "the value of the matcher and effect assertions". In the current Enforce path it is only applied to the argument names of eval(...) (casbin/enforcer.cpp:120-124), so this is not an enforcement failure: Enforce results are not affected. This is about the result of the public utility for code that calls it directly (model tooling, converters).

Reproduction

#include <iostream>
#include <casbin/casbin.h>

int main() {
    std::cout << casbin::EscapeAssertion("r.sub == \"r.my_resource\"") << "\n";
    std::cout << casbin::EscapeAssertion("p.obj == 'p.resource'") << "\n";
}

The "result" column above is verbatim gtest output, e.g.:

Expected equality of these values:
  result
    Which is: "r_sub == \"r_my_resource\""
  "r_sub == \"r.my_resource\""

Environment:

  • Commit: 0ad64a7 (master)
  • Toolchain: GCC 11.5.0

Already fixed in apache/casbin-node-casbin
PR: https://github.com/apache/casbin-node-casbin/pull/514 — "feat: fix escapeAssertion to preserve string literals" (merged 2025-11-11T17:50:46Z, merge commit 9abab0fb8b5). node-casbin's escapeAssertion also rewrote r./p. inside string literals ("r.my_resource" became "r_my_resource"). It now only rewrites an r/p (optionally numbered) prefix that is preceded by start-of-string, whitespace, a parenthesis or an operator character; since quotes are not in that set, text inside literals is left alone. In node-casbin the helper is on the enforcement path, so the bug there also changed Enforce results; in casbin-cpp only the public helper's output is affected.

How node-casbin fixed it:

--- a/src/util/util.ts
+++ b/src/util/util.ts
@@ -17,11 +17,16 @@
 
 import { mustGetDefaultFileSystem } from '../persist';
 
-const escapeAssertionReg = new RegExp(/(^|[^A-Za-z0-9_])([rp])[0-9]*\./g);
+const escapeAssertionReg = new RegExp(/([()\s|&,=!><+\-*/]|^)((r|p)[0-9]*)\./g);
 
 function escapeAssertion(s: string): string {
-  s = s.replace(escapeAssertionReg, (match, p1, p2) => {
-    return p1 + p2 + match.substring(p1.length + p2.length).replace('.', '_');
+  s = s.replace(escapeAssertionReg, (match) => {
+    // Replace only the last dot with underscore (preserve the prefix character)
+    const lastDotIdx = match.lastIndexOf('.');
+    if (lastDotIdx > 0) {
+      return match.substring(0, lastDotIdx) + '_';
+    }
+    return match;
   });
   return s;
 }
--- a/test/util.test.ts
+++ b/test/util.test.ts
@@ -228,3 +228,27 @@ test('bracketCompatible', () => {
...
+test('test escapeAssertion', () => {
+  expect(util.escapeAssertion('r_sub == r_obj.value')).toEqual('r_sub == r_obj.value');
+  expect(util.escapeAssertion('p_sub == r_sub.value')).toEqual('p_sub == r_sub.value');
+  expect(util.escapeAssertion('r.attr.value == p.attr')).toEqual('r_attr.value == p_attr');
...
+  // Test that patterns inside strings are not escaped
+  expect(util.escapeAssertion('r.sub == "a.p.p.l.e"')).toEqual('r_sub == "a.p.p.l.e"');
+  expect(util.escapeAssertion('r.sub == "test.p.value"')).toEqual('r_sub == "test.p.value"');
+});

Suggested fix
Only rewrite an r/p token (optionally numbered) that follows start-of-string, whitespace, a parenthesis, a comma or an operator, as node-casbin now does, so dots inside single- or double-quoted literals are left alone:

diff --git a/casbin/util/escape_assertion.cpp b/casbin/util/escape_assertion.cpp
--- a/casbin/util/escape_assertion.cpp
+++ b/casbin/util/escape_assertion.cpp
@@ -32,22 +32,10 @@ namespace casbin {
  * @return the escaped value.
  */
 std::string EscapeAssertion(std::string s) {
-    std::regex regex_s("[a-zA-Z0-9. ]+");
-
-    std::sregex_iterator words_begin = std::sregex_iterator(s.begin(), s.end(), regex_s);
-    auto words_end = std::sregex_iterator();
-
-    for (std::sregex_iterator k = words_begin; k != words_end; ++k) {
-        std::smatch match = *k;
-        std::string match_str = match.str();
-        int pos = static_cast<int>(match_str.find("."));
-        if (pos != -1) {
-            std::string new_str = match_str.replace(pos, 1, "_");
-            s = s.replace(match.position(), match.str().length(), new_str);
-        }
-    }
-
-    return s;
+    // Only an r/p token (e.g. r., p., r2.) that follows start-of-string, whitespace, a parenthesis, a comma or an
+    // operator is escaped, so dots inside quoted string literals are left alone.
+    static const std::regex regex_s("([()\\s|&,=!><+\\-*/]|^)([rp][0-9]*)\\.");
+    return std::regex_replace(s, regex_s, "$1$2_");
 }
 
 } // namespace casbin

Verified on 0ad64a7 (GCC 11.4.0, GoogleTest 1.14.0): with the first three rows of the table above added to TestEscapeAssertion in tests/util_test.cpp, the test fails without this patch and passes with it; no other test results change, including the existing TestEscapeAssertion cases.

I can submit a PR for this.

Related: searched issues/PRs (all states) for EscapeAssertion, escape + string literal, eval rule + dot: no hits. The underscore-in-identifier case has a different cause and is reported separately.

Lenguaje dominante
C++
Estrellas
257
Forks
67
Merge medio
10 h 23 min
PR fusionados (30 d)
1

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de apache/casbin-cpp

Todos los issues de apache/casbin-cpp

Issues similares

Más issues de C++

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.