mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-28 12:40:54 +00:00
Compare extensions, header names and media types without case
INDEX.HTML got the default type, a REMOTE_ADDR spelled in lower case got past the guard on prohibited header names, and Multipart/Form-Data was not taken for multipart, though RFC 9110 makes field names and media types case-insensitive and web servers match extensions so.
This commit is contained in:
1 parent
fcf7568230
commit
7c44a7a5e1
2 files changed
+32
-13
No files matched your search
@@ -7668,8 +7668,10 @@ find_content_type(const std::string &path,
|
|||||||
{"wasm", "application/wasm"},
|
{"wasm", "application/wasm"},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Extensions are matched regardless of case, as web servers do
|
||||||
|
auto lower_ext = case_ignore::to_lower(ext);
|
||||||
for (const auto &t : types) {
|
for (const auto &t : types) {
|
||||||
if (ext == t.ext) { return t.type; }
|
if (lower_ext == t.ext) { return t.type; }
|
||||||
}
|
}
|
||||||
return default_content_type;
|
return default_content_type;
|
||||||
}
|
}
|
||||||
@@ -7705,7 +7707,8 @@ extract_media_type(const std::string &content_type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline bool can_compress_content_type(const std::string &content_type) {
|
inline bool can_compress_content_type(const std::string &content_type) {
|
||||||
auto mime_type = extract_media_type(content_type);
|
// Media types are case-insensitive (RFC 9110 §8.3.1)
|
||||||
|
auto mime_type = case_ignore::to_lower(extract_media_type(content_type));
|
||||||
|
|
||||||
static const char *const compressible[] = {
|
static const char *const compressible[] = {
|
||||||
"image/svg+xml",
|
"image/svg+xml",
|
||||||
@@ -8202,18 +8205,23 @@ create_compressor() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// s == lit, comparing the lengths first, which operator== may not do.
|
// Whether s is lit, ignoring case, with the lengths compared first.
|
||||||
template <size_t N>
|
template <size_t N>
|
||||||
inline bool equals_literal(const std::string &s, const char (&lit)[N]) {
|
inline bool equals_literal_ignoring_case(const std::string &s,
|
||||||
return s.size() == N - 1 && !s.compare(0, N - 1, lit);
|
const char (&lit)[N]) {
|
||||||
|
return s.size() == N - 1 &&
|
||||||
|
std::equal(s.begin(), s.end(), lit, [](char a, char b) {
|
||||||
|
return case_ignore::to_lower(a) == case_ignore::to_lower(b);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool is_prohibited_header_name(const std::string &name) {
|
inline bool is_prohibited_header_name(const std::string &name) {
|
||||||
// Every header lookup by name runs this.
|
// Field names are case-insensitive (RFC 9110 §5.1), and every header lookup
|
||||||
return equals_literal(name, "REMOTE_ADDR") ||
|
// by name runs this.
|
||||||
equals_literal(name, "REMOTE_PORT") ||
|
return equals_literal_ignoring_case(name, "REMOTE_ADDR") ||
|
||||||
equals_literal(name, "LOCAL_ADDR") ||
|
equals_literal_ignoring_case(name, "REMOTE_PORT") ||
|
||||||
equals_literal(name, "LOCAL_PORT");
|
equals_literal_ignoring_case(name, "LOCAL_ADDR") ||
|
||||||
|
equals_literal_ignoring_case(name, "LOCAL_PORT");
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool has_header(const Headers &headers, const std::string &key) {
|
inline bool has_header(const Headers &headers, const std::string &key) {
|
||||||
@@ -11885,7 +11893,8 @@ inline size_t Request::get_param_value_count(const std::string &key) const {
|
|||||||
|
|
||||||
inline bool Request::is_multipart_form_data() const {
|
inline bool Request::is_multipart_form_data() const {
|
||||||
const auto &content_type = get_header_value("Content-Type");
|
const auto &content_type = get_header_value("Content-Type");
|
||||||
return detail::extract_media_type(content_type) == "multipart/form-data";
|
return detail::case_ignore::equal(detail::extract_media_type(content_type),
|
||||||
|
"multipart/form-data");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Multipart FormData implementation
|
// Multipart FormData implementation
|
||||||
@@ -13931,8 +13940,8 @@ inline bool Server::read_content(Stream &strm, Request &req, Response &res) {
|
|||||||
return true;
|
return true;
|
||||||
})) {
|
})) {
|
||||||
const auto &content_type = req.get_header_value("Content-Type");
|
const auto &content_type = req.get_header_value("Content-Type");
|
||||||
if (detail::extract_media_type(content_type) ==
|
if (detail::case_ignore::equal(detail::extract_media_type(content_type),
|
||||||
"application/x-www-form-urlencoded") {
|
"application/x-www-form-urlencoded")) {
|
||||||
if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
|
if (req.body.size() > CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH) {
|
||||||
res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
|
res.status = StatusCode::PayloadTooLarge_413; // NOTE: should be 414?
|
||||||
output_error_log(Error::ExceedMaxPayloadSize, &req);
|
output_error_log(Error::ExceedMaxPayloadSize, &req);
|
||||||
|
|||||||
@@ -681,6 +681,7 @@ TEST(FindContentTypeTest, ComparesWholeExtensions) {
|
|||||||
EXPECT_EQ("text/html", detail::find_content_type("/a.html", {}, ""));
|
EXPECT_EQ("text/html", detail::find_content_type("/a.html", {}, ""));
|
||||||
EXPECT_EQ("image/svg+xml", detail::find_content_type("/a.svg", {}, ""));
|
EXPECT_EQ("image/svg+xml", detail::find_content_type("/a.svg", {}, ""));
|
||||||
EXPECT_EQ("text/javascript", detail::find_content_type("/a.js", {}, ""));
|
EXPECT_EQ("text/javascript", detail::find_content_type("/a.js", {}, ""));
|
||||||
|
EXPECT_EQ("text/html", detail::find_content_type("/INDEX.HTML", {}, ""));
|
||||||
|
|
||||||
// Same 26-bit hash as html, svg and js.
|
// Same 26-bit hash as html, svg and js.
|
||||||
EXPECT_EQ("", detail::find_content_type("/a.qo2yh", {}, ""));
|
EXPECT_EQ("", detail::find_content_type("/a.qo2yh", {}, ""));
|
||||||
@@ -693,15 +694,24 @@ TEST(CanCompressContentTypeTest, ComparesWholeTypes) {
|
|||||||
EXPECT_TRUE(detail::can_compress_content_type("text/plain; charset=utf-8"));
|
EXPECT_TRUE(detail::can_compress_content_type("text/plain; charset=utf-8"));
|
||||||
EXPECT_FALSE(detail::can_compress_content_type("text/event-stream"));
|
EXPECT_FALSE(detail::can_compress_content_type("text/event-stream"));
|
||||||
EXPECT_FALSE(detail::can_compress_content_type("image/png"));
|
EXPECT_FALSE(detail::can_compress_content_type("image/png"));
|
||||||
|
EXPECT_TRUE(detail::can_compress_content_type("Application/JSON"));
|
||||||
|
EXPECT_FALSE(detail::can_compress_content_type("Text/Event-Stream"));
|
||||||
|
|
||||||
// Same 26-bit hash as application/json.
|
// Same 26-bit hash as application/json.
|
||||||
EXPECT_FALSE(detail::can_compress_content_type("VY_LQ"));
|
EXPECT_FALSE(detail::can_compress_content_type("VY_LQ"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(MultipartFormDataTest, MediaTypeIgnoresCase) {
|
||||||
|
Request req;
|
||||||
|
req.set_header("Content-Type", "Multipart/Form-Data; boundary=x");
|
||||||
|
EXPECT_TRUE(req.is_multipart_form_data());
|
||||||
|
}
|
||||||
|
|
||||||
TEST(ProhibitedHeaderNameTest, ComparesWholeNames) {
|
TEST(ProhibitedHeaderNameTest, ComparesWholeNames) {
|
||||||
EXPECT_TRUE(detail::is_prohibited_header_name("REMOTE_ADDR"));
|
EXPECT_TRUE(detail::is_prohibited_header_name("REMOTE_ADDR"));
|
||||||
EXPECT_TRUE(detail::is_prohibited_header_name("LOCAL_PORT"));
|
EXPECT_TRUE(detail::is_prohibited_header_name("LOCAL_PORT"));
|
||||||
EXPECT_FALSE(detail::is_prohibited_header_name("Host"));
|
EXPECT_FALSE(detail::is_prohibited_header_name("Host"));
|
||||||
|
EXPECT_TRUE(detail::is_prohibited_header_name("remote_addr"));
|
||||||
|
|
||||||
// Same 26-bit hash as REMOTE_ADDR.
|
// Same 26-bit hash as REMOTE_ADDR.
|
||||||
EXPECT_FALSE(detail::is_prohibited_header_name("IIJRp"));
|
EXPECT_FALSE(detail::is_prohibited_header_name("IIJRp"));
|
||||||
|
|||||||
Reference in new issue
Block a user