Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/brpc/builtin/flags_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 << '"';
}
Expand Down Expand Up @@ -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;
Expand Down
67 changes: 67 additions & 0 deletions test/brpc_builtin_service_unittest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4619d7f: req/res are now declared inside each { ... } block so every default_method invocation gets fresh, empty request/response objects. Full brpc_builtin_service_unittest suite still passes (20/20).


🤖 This reply was automatically generated by brpc-oncall

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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4619d7f: req/res are now declared inside each { ... } block so every default_method invocation gets fresh, empty request/response objects. Full brpc_builtin_service_unittest suite still passes (20/20).


🤖 This reply was automatically generated by brpc-oncall

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);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 4619d7f: req/res are now declared inside each { ... } block so every default_method invocation gets fresh, empty request/response objects. Full brpc_builtin_service_unittest suite still passes (20/20).


🤖 This reply was automatically generated by brpc-oncall

EXPECT_FALSE(cntl.Failed());
CheckContent(cntl, payload.c_str());
}
}

TEST_F(BuiltinServiceTest, bad_method) {
TestBadMethod(false);
TestBadMethod(true);
Expand Down
Loading