mirror of
https://github.com/yhirose/cpp-httplib
synced 2024-11-21 14:29:10 -07:00
Support Content-Encoding: gzip on server side
If the client specifies Content-Encoding: gzip for POST requests, decompress the body before attempting to parse it.
This commit is contained in:
parent
0e239a0014
commit
5579d4d101
2 changed files with 117 additions and 0 deletions
48
httplib.h
48
httplib.h
|
@ -701,6 +701,7 @@ inline const char* status_message(int status)
|
|||
case 200: return "OK";
|
||||
case 400: return "Bad Request";
|
||||
case 404: return "Not Found";
|
||||
case 406: return "Not Acceptable";
|
||||
default:
|
||||
case 500: return "Internal Server Error";
|
||||
}
|
||||
|
@ -1187,6 +1188,43 @@ inline void compress(const Request& req, Response& res)
|
|||
|
||||
deflateEnd(&strm);
|
||||
}
|
||||
|
||||
inline void decompress_request_body(Request& req)
|
||||
{
|
||||
if (req.get_header_value("Content-Encoding") != "gzip")
|
||||
return;
|
||||
|
||||
z_stream strm;
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
|
||||
// 15 is the value of wbits, which should be at the maximum possible value to ensure
|
||||
// that any gzip stream can be decoded. The offset of 16 specifies that the stream
|
||||
// to decompress will be formatted with a gzip wrapper.
|
||||
auto ret = inflateInit2(&strm, 16 + 15);
|
||||
if (ret != Z_OK) {
|
||||
return;
|
||||
}
|
||||
|
||||
strm.avail_in = req.body.size();
|
||||
strm.next_in = (Bytef *)req.body.data();
|
||||
|
||||
std::string decompressed;
|
||||
|
||||
const auto bufsiz = 16384;
|
||||
char buff[bufsiz];
|
||||
do {
|
||||
strm.avail_out = bufsiz;
|
||||
strm.next_out = (Bytef *)buff;
|
||||
inflate(&strm, Z_NO_FLUSH);
|
||||
decompressed.append(buff, bufsiz - strm.avail_out);
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
req.body.swap(decompressed);
|
||||
|
||||
inflateEnd(&strm);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -1629,6 +1667,16 @@ inline bool Server::process_request(Stream& strm, bool last_connection)
|
|||
|
||||
const auto& content_type = req.get_header_value("Content-Type");
|
||||
|
||||
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
|
||||
detail::decompress_request_body(req);
|
||||
#else
|
||||
if (req.get_header_value("Content-Encoding") == "gzip") {
|
||||
res.status = 406;
|
||||
write_response(strm, last_connection, req, res);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!content_type.find("application/x-www-form-urlencoded")) {
|
||||
detail::parse_query_text(req.body, req.params);
|
||||
} else if(!content_type.find("multipart/form-data")) {
|
||||
|
|
69
test/test.cc
69
test/test.cc
|
@ -318,6 +318,22 @@ protected:
|
|||
.get("/nogzip", [&](const Request& /*req*/, Response& res) {
|
||||
res.set_content("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "application/octet-stream");
|
||||
})
|
||||
.post("/gzipmultipart", [&](const Request& req, Response& /*res*/) {
|
||||
EXPECT_EQ(2u, req.files.size());
|
||||
ASSERT_TRUE(!req.has_file("???"));
|
||||
|
||||
{
|
||||
const auto& file = req.get_file_value("key1");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_EQ("test", req.body.substr(file.offset, file.length));
|
||||
}
|
||||
|
||||
{
|
||||
const auto& file = req.get_file_value("key2");
|
||||
EXPECT_EQ("", file.filename);
|
||||
EXPECT_EQ("--abcdefg123", req.body.substr(file.offset, file.length));
|
||||
}
|
||||
})
|
||||
#endif
|
||||
;
|
||||
|
||||
|
@ -667,6 +683,59 @@ TEST_F(ServerTest, NoGzip)
|
|||
EXPECT_EQ("100", res->get_header_value("Content-Length"));
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
|
||||
TEST_F(ServerTest, MultipartFormDataGzip)
|
||||
{
|
||||
Request req;
|
||||
req.method = "POST";
|
||||
req.path = "/gzipmultipart";
|
||||
|
||||
std::string host_and_port;
|
||||
host_and_port += HOST;
|
||||
host_and_port += ":";
|
||||
host_and_port += std::to_string(PORT);
|
||||
|
||||
req.headers.emplace("Host", host_and_port.c_str());
|
||||
req.headers.emplace("Accept", "*/*");
|
||||
req.headers.emplace("User-Agent", "cpp-httplib/0.1");
|
||||
req.headers.emplace("Content-Type", "multipart/form-data; boundary=------------------------fcba8368a9f48c0f");
|
||||
req.headers.emplace("Content-Encoding", "gzip");
|
||||
|
||||
// compressed_body generated by creating input.txt to this file:
|
||||
/*
|
||||
--------------------------fcba8368a9f48c0f
|
||||
Content-Disposition: form-data; name="key1"
|
||||
|
||||
test
|
||||
--------------------------fcba8368a9f48c0f
|
||||
Content-Disposition: form-data; name="key2"
|
||||
|
||||
--abcdefg123
|
||||
--------------------------fcba8368a9f48c0f--
|
||||
*/
|
||||
// then running unix2dos input.txt; gzip -9 -c input.txt | xxd -i.
|
||||
uint8_t compressed_body[] = {
|
||||
0x1f, 0x8b, 0x08, 0x08, 0x48, 0xf1, 0xd4, 0x5a, 0x02, 0x03, 0x69, 0x6e,
|
||||
0x70, 0x75, 0x74, 0x2e, 0x74, 0x78, 0x74, 0x00, 0xd3, 0xd5, 0xc5, 0x05,
|
||||
0xd2, 0x92, 0x93, 0x12, 0x2d, 0x8c, 0xcd, 0x2c, 0x12, 0x2d, 0xd3, 0x4c,
|
||||
0x2c, 0x92, 0x0d, 0xd2, 0x78, 0xb9, 0x9c, 0xf3, 0xf3, 0x4a, 0x52, 0xf3,
|
||||
0x4a, 0x74, 0x5d, 0x32, 0x8b, 0x0b, 0xf2, 0x8b, 0x33, 0x4b, 0x32, 0xf3,
|
||||
0xf3, 0xac, 0x14, 0xd2, 0xf2, 0x8b, 0x72, 0x75, 0x53, 0x12, 0x4b, 0x12,
|
||||
0xad, 0x15, 0xf2, 0x12, 0x73, 0x53, 0x6d, 0x95, 0xb2, 0x53, 0x2b, 0x0d,
|
||||
0x95, 0x78, 0xb9, 0x78, 0xb9, 0x4a, 0x52, 0x8b, 0x4b, 0x78, 0xb9, 0x74,
|
||||
0x69, 0x61, 0x81, 0x11, 0xd8, 0x02, 0x5d, 0xdd, 0xc4, 0xa4, 0xe4, 0x94,
|
||||
0xd4, 0xb4, 0x74, 0x43, 0x23, 0x63, 0x52, 0x2c, 0xd2, 0xd5, 0xe5, 0xe5,
|
||||
0x02, 0x00, 0xff, 0x0e, 0x72, 0xdf, 0xf8, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
req.body = std::string((char*)compressed_body, sizeof(compressed_body) / sizeof(compressed_body[0]));
|
||||
|
||||
auto res = std::make_shared<Response>();
|
||||
auto ret = cli_.send(req, *res);
|
||||
|
||||
ASSERT_TRUE(ret);
|
||||
EXPECT_EQ(200, res->status);
|
||||
}
|
||||
#endif
|
||||
|
||||
class ServerTestWithAI_PASSIVE : public ::testing::Test {
|
||||
|
|
Loading…
Reference in a new issue