`EscapeAssertion` rewrites dots inside quoted string literals
还没有人认领这个 Issue。
评估
调研方向
从 casbin/util/escape_assertion.cpp 中的 EscapeAssertion 开始,将其与 include/casbin/util/util.h 中的声明进行比较。将带引号的字面量用例添加到 tests/util_test.cpp 中的 TestEscapeAssertion,然后运行该测试二进制文件以查看当前失败。完成意味着单引号和双引号字面量保留其点号,而断言的 r/p 前缀仍会被重写。
由索引模型根据 Issue 内容生成。
描述
Location (apache/casbin-cpp @ default branch 0ad64a7):
casbin/util/escape_assertion.cpp:34-51—EscapeAssertioninclude/casbin/util/util.h:37-43— public declaration
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.
- 主要语言
- C++
- 星标
- 257
- 派生
- 67
- 平均合并
- 10 小时 23 分钟
- 30 天内合并 PR
- 1
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
apache/casbin-cpp 的其他 Issue
-
ci: automated PR performance benchmark comparison可能重新可做 @knyk-dev 于 297 天前认领,目前没有进行中的 PR。 未关闭enhancement
apache/casbin-cpp#266 · 已指派 1 人 ·
-
Porting Casbin-CPP to Real-Time Operating Systems (RTOS)可能重新可做 @hsluoyz 于 1276 天前认领,目前没有进行中的 PR。 未关闭enhancement
apache/casbin-cpp#229 · 1 条评论 · 已指派 1 人 ·
-
Use the same performance benchmark tests for all languages可能重新可做 @hsluoyz 于 1575 天前认领,目前没有进行中的 PR。 未关闭enhancement
apache/casbin-cpp#207 · 3 条评论 · 已指派 1 人 ·
-
Benchmarking pycasbin's performance to compare it with Python's casbin implementation可能重新可做 @hsluoyz 于 1747 天前认领,目前没有进行中的 PR。 未关闭enhancement
apache/casbin-cpp#181 · 1 条评论 · 已指派 1 人 ·
-
Make an authorization middleware for Envoy based on Casbin可能重新可做 @hsluoyz 于 1950 天前认领,目前没有进行中的 PR。 未关闭enhancement
apache/casbin-cpp#115 · 1 条评论 · 已指派 1 人 ·
查看 apache/casbin-cpp 的全部 Issue
相似的 Issue
-
Bug
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 1 天内回复
-
难度 1/5 1 小时以内 新手友好度 82/100
-
v3: Lua m.setvar() returns -1 on errors, so the script gets stack garbage可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭3.x bug
难度 2/5 1-3 小时 新手友好度 72/100
owasp-modsecurity/ModSecurity#3731 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 72/100
Sendspin/sendspin-cpp-cli#111 ·
维护者通常 1 天内回复
-
[Core/Config] The server fails to start due to an invalid config path when debugging可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭
难度 2/5 1-3 小时 新手友好度 62/100
azerothcore/azerothcore-wotlk#28350 ·
维护者通常 1 天内回复