This is an automated email from the ASF dual-hosted git repository.
wasphin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new bb0861e1 Escape HTML output in builtin /flags service (#3529)
bb0861e1 is described below
commit bb0861e1830c03031ab333e123c8caffa5e71b57
Author: Weibing Wang <[email protected]>
AuthorDate: Mon Sep 14 13:16:36 2026 +0800
Escape HTML output in builtin /flags service (#3529)
* Escape HTML output in builtin /flags service
The ?setvalue= confirmation message and the ?setvalue&withform page
wrote the gflag name/value into the html page without escaping, unlike
the flag list page which already escapes values with HtmlReplace.
Escape them with WebEscape like rpcz_service does. Plain text output
is unchanged.
* Use FlagSaver in flags_escaping unittest
Replace the manual save/restore of the modified gflag with
GFLAGS_NAMESPACE::FlagSaver so the flag value is restored on every
exit of the test, following the established pattern in
brpc_http_rpc_protocol_unittest.cpp.
* Scope req/res per sub-case in flags_escaping unittest
Declare FlagsRequest/FlagsResponse inside each { ... } block so no
state can leak between the default_method invocations.
---
src/brpc/builtin/flags_service.cpp | 9 +++--
test/brpc_builtin_service_unittest.cpp | 67 ++++++++++++++++++++++++++++++++++
2 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/src/brpc/builtin/flags_service.cpp
b/src/brpc/builtin/flags_service.cpp
index 18152baa..569da313 100644
--- a/src/brpc/builtin/flags_service.cpp
+++ b/src/brpc/builtin/flags_service.cpp
@@ -117,11 +117,11 @@ void FlagsService::set_value_page(Controller* cntl,
const bool is_string = (info.type == "string");
os << "<!DOCTYPE html><html><body>"
"<form action='' method='get'>"
- " Set `" << name << "' from ";
+ " Set `" << WebEscape(name) << "' from ";
if (is_string) {
os << '"';
}
- os << info.current_value;
+ os << WebEscape(info.current_value);
if (is_string) {
os << '"';
}
@@ -177,9 +177,12 @@ void
FlagsService::default_method(::google::protobuf::RpcController* cntl_base,
return;
}
butil::IOBufBuilder os;
- os << "Set `" << constraint << "' to " << *value_str;
if (use_html) {
+ os << "Set `" << WebEscape(constraint) << "' to "
+ << WebEscape(*value_str);
os << "<br><a href='/flags'>[back to flags]</a>";
+ } else {
+ os << "Set `" << constraint << "' to " << *value_str;
}
os.move_to(cntl->response_attachment());
return;
diff --git a/test/brpc_builtin_service_unittest.cpp
b/test/brpc_builtin_service_unittest.cpp
index e496c05f..0d1ca248 100644
--- a/test/brpc_builtin_service_unittest.cpp
+++ b/test/brpc_builtin_service_unittest.cpp
@@ -60,6 +60,17 @@
DEFINE_bool(foo, false, "Flags for UT");
BRPC_VALIDATE_GFLAG(foo, brpc::PassValidate);
+// A reloadable string gflag so that FlagsService is able to modify its
+// value via ?setvalue=. String flags must register the validator manually,
+// see comments in butil/reloadable_flags.h.
+DEFINE_string(reloadable_string_flag_for_ut, "", "Flags for UT");
+static bool PassValidateStringFlag(const char*, const std::string&) {
+ return true;
+}
+const bool ALLOW_UNUSED dummy_validate_reloadable_string_flag_for_ut =
+ GFLAGS_NAMESPACE::RegisterFlagValidator(
+ &FLAGS_reloadable_string_flag_for_ut, PassValidateStringFlag);
+
namespace brpc {
DECLARE_bool(enable_rpcz);
DECLARE_bool(rpcz_hex_log_id);
@@ -686,6 +697,62 @@ TEST_F(BuiltinServiceTest, flags) {
TestFlags(true);
}
+TEST_F(BuiltinServiceTest, flags_escaping) {
+ // Save all flags and restore them on any exit of this test, since the
+ // /flags service below modifies `reloadable_string_flag_for_ut'.
+ GFLAGS_NAMESPACE::FlagSaver flag_saver;
+ brpc::FlagsService service;
+ const std::string payload = "<svg onload=alert(1)>&\"'";
+ const std::string escaped = brpc::WebEscape(payload);
+
+ // Reflected: the ?setvalue= value is echoed into the html page.
+ {
+ ClosureChecker done;
+ brpc::Controller cntl;
+ brpc::FlagsRequest req;
+ brpc::FlagsResponse res;
+ SetUpController(&cntl, true);
+ cntl.http_request()._unresolved_path = "reloadable_string_flag_for_ut";
+ cntl.http_request().uri().SetQuery(brpc::SETVALUE_STR, payload);
+ service.default_method(&cntl, &req, &res, &done);
+ EXPECT_FALSE(cntl.Failed());
+ const std::string& body = cntl.response_attachment().to_string();
+ EXPECT_EQ(std::string::npos, body.find(payload))
+ << "unescaped payload in html: " << body;
+ CheckContent(cntl, escaped.c_str());
+ }
+ // Stored: ?setvalue&withform renders the flag value stored above.
+ {
+ ClosureChecker done;
+ brpc::Controller cntl;
+ brpc::FlagsRequest req;
+ brpc::FlagsResponse res;
+ SetUpController(&cntl, true);
+ cntl.http_request()._unresolved_path = "reloadable_string_flag_for_ut";
+ cntl.http_request().uri().SetQuery(brpc::SETVALUE_STR, "");
+ cntl.http_request().uri().SetQuery("withform", "");
+ service.default_method(&cntl, &req, &res, &done);
+ EXPECT_FALSE(cntl.Failed());
+ const std::string& body = cntl.response_attachment().to_string();
+ EXPECT_EQ(std::string::npos, body.find(payload))
+ << "unescaped payload in html: " << body;
+ CheckContent(cntl, escaped.c_str());
+ }
+ // Plain text output is not html-escaped.
+ {
+ ClosureChecker done;
+ brpc::Controller cntl;
+ brpc::FlagsRequest req;
+ brpc::FlagsResponse res;
+ SetUpController(&cntl, false);
+ cntl.http_request()._unresolved_path = "reloadable_string_flag_for_ut";
+ cntl.http_request().uri().SetQuery(brpc::SETVALUE_STR, payload);
+ service.default_method(&cntl, &req, &res, &done);
+ EXPECT_FALSE(cntl.Failed());
+ CheckContent(cntl, payload.c_str());
+ }
+}
+
TEST_F(BuiltinServiceTest, bad_method) {
TestBadMethod(false);
TestBadMethod(true);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]