Skip to content

Commit 3b6597b

Browse files
authored
Fix query parsing when value has = characters (#1822)
* Implement string divider to replace splitter * Divide query string in half * Add a test case for query values containing the '=' character * Add test cases for string divider * Fix warnings
1 parent f10720e commit 3b6597b

File tree

2 files changed

+144
-36
lines changed

2 files changed

+144
-36
lines changed

httplib.h

Lines changed: 54 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2178,6 +2178,16 @@ void read_file(const std::string &path, std::string &out);
21782178

21792179
std::string trim_copy(const std::string &s);
21802180

2181+
void divide(
2182+
const char *data, std::size_t size, char d,
2183+
std::function<void(const char *, std::size_t, const char *, std::size_t)>
2184+
fn);
2185+
2186+
void divide(
2187+
const std::string &str, char d,
2188+
std::function<void(const char *, std::size_t, const char *, std::size_t)>
2189+
fn);
2190+
21812191
void split(const char *b, const char *e, char d,
21822192
std::function<void(const char *, const char *)> fn);
21832193

@@ -2201,6 +2211,8 @@ const char *get_header_value(const Headers &headers, const std::string &key,
22012211

22022212
std::string params_to_query_str(const Params &params);
22032213

2214+
void parse_query_text(const char *data, std::size_t size, Params &params);
2215+
22042216
void parse_query_text(const std::string &s, Params &params);
22052217

22062218
bool parse_multipart_boundary(const std::string &content_type,
@@ -2669,6 +2681,27 @@ inline std::string trim_double_quotes_copy(const std::string &s) {
26692681
return s;
26702682
}
26712683

2684+
inline void
2685+
divide(const char *data, std::size_t size, char d,
2686+
std::function<void(const char *, std::size_t, const char *, std::size_t)>
2687+
fn) {
2688+
const auto it = std::find(data, data + size, d);
2689+
const auto found = static_cast<std::size_t>(it != data + size);
2690+
const auto lhs_data = data;
2691+
const auto lhs_size = static_cast<std::size_t>(it - data);
2692+
const auto rhs_data = it + found;
2693+
const auto rhs_size = size - lhs_size - found;
2694+
2695+
fn(lhs_data, lhs_size, rhs_data, rhs_size);
2696+
}
2697+
2698+
inline void
2699+
divide(const std::string &str, char d,
2700+
std::function<void(const char *, std::size_t, const char *, std::size_t)>
2701+
fn) {
2702+
divide(str.data(), str.size(), d, std::move(fn));
2703+
}
2704+
26722705
inline void split(const char *b, const char *e, char d,
26732706
std::function<void(const char *, const char *)> fn) {
26742707
return split(b, e, d, (std::numeric_limits<size_t>::max)(), std::move(fn));
@@ -4392,29 +4425,33 @@ inline std::string params_to_query_str(const Params &params) {
43924425
return query;
43934426
}
43944427

4395-
inline void parse_query_text(const std::string &s, Params &params) {
4428+
inline void parse_query_text(const char *data, std::size_t size,
4429+
Params &params) {
43964430
std::set<std::string> cache;
4397-
split(s.data(), s.data() + s.size(), '&', [&](const char *b, const char *e) {
4431+
split(data, data + size, '&', [&](const char *b, const char *e) {
43984432
std::string kv(b, e);
43994433
if (cache.find(kv) != cache.end()) { return; }
4400-
cache.insert(kv);
4434+
cache.insert(std::move(kv));
44014435

44024436
std::string key;
44034437
std::string val;
4404-
split(b, e, '=', [&](const char *b2, const char *e2) {
4405-
if (key.empty()) {
4406-
key.assign(b2, e2);
4407-
} else {
4408-
val.assign(b2, e2);
4409-
}
4410-
});
4438+
divide(b, static_cast<std::size_t>(e - b), '=',
4439+
[&](const char *lhs_data, std::size_t lhs_size, const char *rhs_data,
4440+
std::size_t rhs_size) {
4441+
key.assign(lhs_data, lhs_size);
4442+
val.assign(rhs_data, rhs_size);
4443+
});
44114444

44124445
if (!key.empty()) {
44134446
params.emplace(decode_url(key, true), decode_url(val, true));
44144447
}
44154448
});
44164449
}
44174450

4451+
inline void parse_query_text(const std::string &s, Params &params) {
4452+
parse_query_text(s.data(), s.size(), params);
4453+
}
4454+
44184455
inline bool parse_multipart_boundary(const std::string &content_type,
44194456
std::string &boundary) {
44204457
auto boundary_keyword = "boundary=";
@@ -6072,26 +6109,13 @@ inline bool Server::parse_request_line(const char *s, Request &req) const {
60726109
}
60736110
}
60746111

6075-
size_t count = 0;
6076-
6077-
detail::split(req.target.data(), req.target.data() + req.target.size(), '?',
6078-
2, [&](const char *b, const char *e) {
6079-
switch (count) {
6080-
case 0:
6081-
req.path = detail::decode_url(std::string(b, e), false);
6082-
break;
6083-
case 1: {
6084-
if (e - b > 0) {
6085-
detail::parse_query_text(std::string(b, e), req.params);
6086-
}
6087-
break;
6088-
}
6089-
default: break;
6090-
}
6091-
count++;
6092-
});
6093-
6094-
if (count > 2) { return false; }
6112+
detail::divide(req.target, '?',
6113+
[&](const char *lhs_data, std::size_t lhs_size,
6114+
const char *rhs_data, std::size_t rhs_size) {
6115+
req.path = detail::decode_url(
6116+
std::string(lhs_data, lhs_size), false);
6117+
detail::parse_query_text(rhs_data, rhs_size, req.params);
6118+
});
60956119
}
60966120

60976121
return true;

test/test.cc

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,76 @@ TEST(TrimTests, TrimStringTests) {
116116
EXPECT_TRUE(detail::trim_copy("").empty());
117117
}
118118

119+
TEST(DivideTest, DivideStringTests) {
120+
auto divide = [](const std::string &str, char d) {
121+
std::string lhs;
122+
std::string rhs;
123+
124+
detail::divide(str, d,
125+
[&](const char *lhs_data, std::size_t lhs_size,
126+
const char *rhs_data, std::size_t rhs_size) {
127+
lhs.assign(lhs_data, lhs_size);
128+
rhs.assign(rhs_data, rhs_size);
129+
});
130+
131+
return std::make_pair(std::move(lhs), std::move(rhs));
132+
};
133+
134+
{
135+
const auto res = divide("", '=');
136+
EXPECT_EQ(res.first, "");
137+
EXPECT_EQ(res.second, "");
138+
}
139+
140+
{
141+
const auto res = divide("=", '=');
142+
EXPECT_EQ(res.first, "");
143+
EXPECT_EQ(res.second, "");
144+
}
145+
146+
{
147+
const auto res = divide(" ", '=');
148+
EXPECT_EQ(res.first, " ");
149+
EXPECT_EQ(res.second, "");
150+
}
151+
152+
{
153+
const auto res = divide("a", '=');
154+
EXPECT_EQ(res.first, "a");
155+
EXPECT_EQ(res.second, "");
156+
}
157+
158+
{
159+
const auto res = divide("a=", '=');
160+
EXPECT_EQ(res.first, "a");
161+
EXPECT_EQ(res.second, "");
162+
}
163+
164+
{
165+
const auto res = divide("=b", '=');
166+
EXPECT_EQ(res.first, "");
167+
EXPECT_EQ(res.second, "b");
168+
}
169+
170+
{
171+
const auto res = divide("a=b", '=');
172+
EXPECT_EQ(res.first, "a");
173+
EXPECT_EQ(res.second, "b");
174+
}
175+
176+
{
177+
const auto res = divide("a=b=", '=');
178+
EXPECT_EQ(res.first, "a");
179+
EXPECT_EQ(res.second, "b=");
180+
}
181+
182+
{
183+
const auto res = divide("a=b=c", '=');
184+
EXPECT_EQ(res.first, "a");
185+
EXPECT_EQ(res.second, "b=c");
186+
}
187+
}
188+
119189
TEST(SplitTest, ParseQueryString) {
120190
string s = "key1=val1&key2=val2&key3=val3";
121191
Params dic;
@@ -156,14 +226,28 @@ TEST(SplitTest, ParseInvalidQueryTests) {
156226
}
157227

158228
TEST(ParseQueryTest, ParseQueryString) {
159-
string s = "key1=val1&key2=val2&key3=val3";
160-
Params dic;
229+
{
230+
std::string s = "key1=val1&key2=val2&key3=val3";
231+
Params dic;
161232

162-
detail::parse_query_text(s, dic);
233+
detail::parse_query_text(s, dic);
163234

164-
EXPECT_EQ("val1", dic.find("key1")->second);
165-
EXPECT_EQ("val2", dic.find("key2")->second);
166-
EXPECT_EQ("val3", dic.find("key3")->second);
235+
EXPECT_EQ("val1", dic.find("key1")->second);
236+
EXPECT_EQ("val2", dic.find("key2")->second);
237+
EXPECT_EQ("val3", dic.find("key3")->second);
238+
}
239+
240+
{
241+
std::string s = "key1&key2=val1&key3=val1=val2&key4=val1=val2=val3";
242+
Params dic;
243+
244+
detail::parse_query_text(s, dic);
245+
246+
EXPECT_EQ("", dic.find("key1")->second);
247+
EXPECT_EQ("val1", dic.find("key2")->second);
248+
EXPECT_EQ("val1=val2", dic.find("key3")->second);
249+
EXPECT_EQ("val1=val2=val3", dic.find("key4")->second);
250+
}
167251
}
168252

169253
TEST(ParamsToQueryTest, ConvertParamsToQuery) {

0 commit comments

Comments
 (0)