Skip to content

Commit feccb44

Browse files
committed
Fix #1908
1 parent c5ee208 commit feccb44

File tree

2 files changed

+63
-4
lines changed

2 files changed

+63
-4
lines changed

httplib.h

+29-4
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,11 @@ using ssize_t = long;
149149
#endif // _MSC_VER
150150

151151
#ifndef S_ISREG
152-
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
152+
#define S_ISREG(m) (((m)&S_IFREG) == S_IFREG)
153153
#endif // S_ISREG
154154

155155
#ifndef S_ISDIR
156-
#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
156+
#define S_ISDIR(m) (((m)&S_IFDIR) == S_IFDIR)
157157
#endif // S_ISDIR
158158

159159
#ifndef NOMINMAX
@@ -2790,6 +2790,10 @@ inline bool stream_line_reader::getline() {
27902790
fixed_buffer_used_size_ = 0;
27912791
glowable_buffer_.clear();
27922792

2793+
#ifndef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
2794+
char prev_byte = 0;
2795+
#endif
2796+
27932797
for (size_t i = 0;; i++) {
27942798
char byte;
27952799
auto n = strm_.read(&byte, 1);
@@ -2806,7 +2810,12 @@ inline bool stream_line_reader::getline() {
28062810

28072811
append(byte);
28082812

2813+
#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR
28092814
if (byte == '\n') { break; }
2815+
#else
2816+
if (prev_byte == '\r' && byte == '\n') { break; }
2817+
prev_byte = byte;
2818+
#endif
28102819
}
28112820

28122821
return true;
@@ -2862,7 +2871,8 @@ inline bool mmap::open(const char *path) {
28622871
// If the following line doesn't compile due to QuadPart, update Windows SDK.
28632872
// See:
28642873
// https://github.com/yhirose/cpp-httplib/issues/1903#issuecomment-2316520721
2865-
if (static_cast<ULONGLONG>(size.QuadPart) > std::numeric_limits<decltype(size_)>::max()) {
2874+
if (static_cast<ULONGLONG>(size.QuadPart) >
2875+
std::numeric_limits<decltype(size_)>::max()) {
28662876
// `size_t` might be 32-bits, on 32-bits Windows.
28672877
return false;
28682878
}
@@ -4049,7 +4059,22 @@ inline bool read_headers(Stream &strm, Headers &headers) {
40494059
auto end = line_reader.ptr() + line_reader.size() - line_terminator_len;
40504060

40514061
parse_header(line_reader.ptr(), end,
4052-
[&](const std::string &key, const std::string &val) {
4062+
[&](const std::string &key, std::string &val) {
4063+
// NOTE: From RFC 9110:
4064+
// Field values containing CR, LF, or NUL characters are
4065+
// invalid and dangerous, due to the varying ways that
4066+
// implementations might parse and interpret those
4067+
// characters; a recipient of CR, LF, or NUL within a field
4068+
// value MUST either reject the message or replace each of
4069+
// those characters with SP before further processing or
4070+
// forwarding of that message.
4071+
for (auto &c : val) {
4072+
switch (c) {
4073+
case '\0':
4074+
case '\n':
4075+
case '\r': c = ' '; break;
4076+
}
4077+
}
40534078
headers.emplace(key, val);
40544079
});
40554080
}

test/test.cc

+34
Original file line numberDiff line numberDiff line change
@@ -4718,6 +4718,11 @@ static void test_raw_request(const std::string &req,
47184718
svr.Put("/put_hi", [&](const Request & /*req*/, Response &res) {
47194719
res.set_content("ok", "text/plain");
47204720
});
4721+
svr.Get("/header_field_value_check", [&](const Request &req, Response &res) {
4722+
auto val = req.get_header_value("Test");
4723+
EXPECT_EQ("[ ]", val);
4724+
res.set_content("ok", "text/plain");
4725+
});
47214726

47224727
// Server read timeout must be longer than the client read timeout for the
47234728
// bug to reproduce, probably to force the server to process a request
@@ -4851,6 +4856,12 @@ TEST(ServerRequestParsingTest, InvalidSpaceInURL) {
48514856
EXPECT_EQ("HTTP/1.1 400 Bad Request", out.substr(0, 24));
48524857
}
48534858

4859+
TEST(ServerRequestParsingTest, InvalidFieldValueContains_CR_LF_NUL) {
4860+
std::string request(
4861+
"GET /header_field_value_check HTTP/1.1\r\nTest: [\r\x00\n]\r\n\r\n", 55);
4862+
test_raw_request(request);
4863+
}
4864+
48544865
TEST(ServerStopTest, StopServerWithChunkedTransmission) {
48554866
Server svr;
48564867

@@ -7572,3 +7583,26 @@ TEST(FileSystemTest, FileAndDirExistenceCheck) {
75727583
EXPECT_FALSE(detail::is_file(dir_path));
75737584
EXPECT_TRUE(detail::is_dir(dir_path));
75747585
}
7586+
7587+
TEST(DirtyDataRequestTest, HeadFieldValueContains_CR_LF_NUL) {
7588+
Server svr;
7589+
7590+
svr.Get("/test", [&](const Request &req, Response &) {
7591+
auto val = req.get_header_value("Test");
7592+
EXPECT_EQ(val.size(), 7u);
7593+
EXPECT_EQ(val, "_ _ _");
7594+
});
7595+
7596+
auto thread = std::thread([&]() { svr.listen(HOST, PORT); });
7597+
7598+
auto se = detail::scope_exit([&] {
7599+
svr.stop();
7600+
thread.join();
7601+
ASSERT_FALSE(svr.is_running());
7602+
});
7603+
7604+
svr.wait_until_ready();
7605+
7606+
Client cli(HOST, PORT);
7607+
cli.Get("/test", {{"Test", "_\n\r_\n\r_"}});
7608+
}

0 commit comments

Comments
 (0)