This commit is contained in:
yhirose committed 2026-02-27 22:08:27 -05:00
1 parent c99d7472b5
commit 0853ce7753
2 files changed
+69 -15

No files matched your search

+47 -15
View File
@@ -6380,10 +6380,46 @@ find_content_type(const std::string &path,
} }
} }
inline std::string
extract_media_type(const std::string &content_type,
std::map<std::string, std::string> *params = nullptr) {
// Extract type/subtype from Content-Type value (RFC 2045)
// e.g. "application/json; charset=utf-8" -> "application/json"
auto media_type = content_type;
auto semicolon_pos = media_type.find(';');
if (semicolon_pos != std::string::npos) {
auto param_str = media_type.substr(semicolon_pos + 1);
media_type = media_type.substr(0, semicolon_pos);
if (params) {
// Parse parameters: key=value pairs separated by ';'
split(param_str.data(), param_str.data() + param_str.size(), ';',
[&](const char *b, const char *e) {
std::string key;
std::string val;
split(b, e, '=', [&](const char *b2, const char *e2) {
if (key.empty()) {
key.assign(b2, e2);
} else {
val.assign(b2, e2);
}
});
if (!key.empty()) {
params->emplace(trim_copy(key), trim_double_quotes_copy(val));
}
});
}
}
// Trim whitespace from media type
return trim_copy(media_type);
}
inline bool can_compress_content_type(const std::string &content_type) { inline bool can_compress_content_type(const std::string &content_type) {
using udl::operator""_t; using udl::operator""_t;
auto tag = str2tag(content_type); auto mime_type = extract_media_type(content_type);
auto tag = str2tag(mime_type);
switch (tag) { switch (tag) {
case "image/svg+xml"_t: case "image/svg+xml"_t:
@@ -6395,7 +6431,7 @@ inline bool can_compress_content_type(const std::string &content_type) {
case "text/event-stream"_t: return false; case "text/event-stream"_t: return false;
default: return !content_type.rfind("text/", 0); default: return !mime_type.rfind("text/", 0);
} }
} }
@@ -7426,12 +7462,11 @@ inline std::string normalize_query_string(const std::string &query) {
inline bool parse_multipart_boundary(const std::string &content_type, inline bool parse_multipart_boundary(const std::string &content_type,
std::string &boundary) { std::string &boundary) {
auto boundary_keyword = "boundary="; std::map<std::string, std::string> params;
auto pos = content_type.find(boundary_keyword); extract_media_type(content_type, &params);
if (pos == std::string::npos) { return false; } auto it = params.find("boundary");
auto end = content_type.find(';', pos); if (it == params.end()) { return false; }
auto beg = pos + strlen(boundary_keyword); boundary = it->second;
boundary = trim_double_quotes_copy(content_type.substr(beg, end - beg));
return !boundary.empty(); return !boundary.empty();
} }
@@ -7599,11 +7634,7 @@ inline bool parse_accept_header(const std::string &s,
} }
// Remove additional parameters from media type // Remove additional parameters from media type
auto param_pos = accept_entry.media_type.find(';'); accept_entry.media_type = extract_media_type(accept_entry.media_type);
if (param_pos != std::string::npos) {
accept_entry.media_type =
trim_copy(accept_entry.media_type.substr(0, param_pos));
}
// Basic validation of media type format // Basic validation of media type format
if (accept_entry.media_type.empty()) { if (accept_entry.media_type.empty()) {
@@ -9433,7 +9464,7 @@ 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 !content_type.rfind("multipart/form-data", 0); return detail::extract_media_type(content_type) == "multipart/form-data";
} }
// Multipart FormData implementation // Multipart FormData implementation
@@ -10915,7 +10946,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 (!content_type.find("application/x-www-form-urlencoded")) { if (detail::extract_media_type(content_type) ==
"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);
+22
View File
@@ -3887,6 +3887,13 @@ protected:
"01234567890123456789012345678901234567890", "01234567890123456789012345678901234567890",
"text/plain"); "text/plain");
}) })
.Get("/compress-with-charset",
[&](const Request & /*req*/, Response &res) {
res.set_content(
"12345678901234567890123456789012345678901234567890123456789"
"01234567890123456789012345678901234567890",
"application/json; charset=utf-8");
})
.Get("/nocompress", .Get("/nocompress",
[&](const Request & /*req*/, Response &res) { [&](const Request & /*req*/, Response &res) {
res.set_content( res.set_content(
@@ -6247,6 +6254,21 @@ TEST_F(ServerTest, Gzip) {
EXPECT_EQ(StatusCode::OK_200, res->status); EXPECT_EQ(StatusCode::OK_200, res->status);
} }
TEST_F(ServerTest, GzipWithContentTypeParameters) {
Headers headers;
headers.emplace("Accept-Encoding", "gzip, deflate");
auto res = cli_.Get("/compress-with-charset", headers);
ASSERT_TRUE(res);
EXPECT_EQ("gzip", res->get_header_value("Content-Encoding"));
EXPECT_EQ("application/json; charset=utf-8",
res->get_header_value("Content-Type"));
EXPECT_EQ("123456789012345678901234567890123456789012345678901234567890123456"
"7890123456789012345678901234567890",
res->body);
EXPECT_EQ(StatusCode::OK_200, res->status);
}
TEST_F(ServerTest, GzipWithoutAcceptEncoding) { TEST_F(ServerTest, GzipWithoutAcceptEncoding) {
Headers headers; Headers headers;
headers.emplace("Accept-Encoding", ""); headers.emplace("Accept-Encoding", "");