Skip to content

Commit ebe40a7

Browse files
authored
Merge pull request yhirose#50 from sgraham/body-decompress
Support Content-Encoding: gzip on server side
2 parents 4ddd5d9 + 5579d4d commit ebe40a7

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

httplib.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ inline const char* status_message(int status)
706706
case 200: return "OK";
707707
case 400: return "Bad Request";
708708
case 404: return "Not Found";
709+
case 406: return "Not Acceptable";
709710
default:
710711
case 500: return "Internal Server Error";
711712
}
@@ -1198,6 +1199,43 @@ inline void compress(const Request& req, Response& res)
11981199

11991200
deflateEnd(&strm);
12001201
}
1202+
1203+
inline void decompress_request_body(Request& req)
1204+
{
1205+
if (req.get_header_value("Content-Encoding") != "gzip")
1206+
return;
1207+
1208+
z_stream strm;
1209+
strm.zalloc = Z_NULL;
1210+
strm.zfree = Z_NULL;
1211+
strm.opaque = Z_NULL;
1212+
1213+
// 15 is the value of wbits, which should be at the maximum possible value to ensure
1214+
// that any gzip stream can be decoded. The offset of 16 specifies that the stream
1215+
// to decompress will be formatted with a gzip wrapper.
1216+
auto ret = inflateInit2(&strm, 16 + 15);
1217+
if (ret != Z_OK) {
1218+
return;
1219+
}
1220+
1221+
strm.avail_in = req.body.size();
1222+
strm.next_in = (Bytef *)req.body.data();
1223+
1224+
std::string decompressed;
1225+
1226+
const auto bufsiz = 16384;
1227+
char buff[bufsiz];
1228+
do {
1229+
strm.avail_out = bufsiz;
1230+
strm.next_out = (Bytef *)buff;
1231+
inflate(&strm, Z_NO_FLUSH);
1232+
decompressed.append(buff, bufsiz - strm.avail_out);
1233+
} while (strm.avail_out == 0);
1234+
1235+
req.body.swap(decompressed);
1236+
1237+
inflateEnd(&strm);
1238+
}
12011239
#endif
12021240

12031241
#ifdef _WIN32
@@ -1670,6 +1708,16 @@ inline bool Server::process_request(Stream& strm, bool last_connection)
16701708

16711709
const auto& content_type = req.get_header_value("Content-Type");
16721710

1711+
#ifdef CPPHTTPLIB_ZLIB_SUPPORT
1712+
detail::decompress_request_body(req);
1713+
#else
1714+
if (req.get_header_value("Content-Encoding") == "gzip") {
1715+
res.status = 406;
1716+
write_response(strm, last_connection, req, res);
1717+
return ret;
1718+
}
1719+
#endif
1720+
16731721
if (!content_type.find("application/x-www-form-urlencoded")) {
16741722
detail::parse_query_text(req.body, req.params);
16751723
} else if(!content_type.find("multipart/form-data")) {

test/test.cc

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,22 @@ class ServerTest : public ::testing::Test {
325325
.get("/nogzip", [&](const Request& /*req*/, Response& res) {
326326
res.set_content("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "application/octet-stream");
327327
})
328+
.post("/gzipmultipart", [&](const Request& req, Response& /*res*/) {
329+
EXPECT_EQ(2u, req.files.size());
330+
ASSERT_TRUE(!req.has_file("???"));
331+
332+
{
333+
const auto& file = req.get_file_value("key1");
334+
EXPECT_EQ("", file.filename);
335+
EXPECT_EQ("test", req.body.substr(file.offset, file.length));
336+
}
337+
338+
{
339+
const auto& file = req.get_file_value("key2");
340+
EXPECT_EQ("", file.filename);
341+
EXPECT_EQ("--abcdefg123", req.body.substr(file.offset, file.length));
342+
}
343+
})
328344
#endif
329345
;
330346

@@ -674,6 +690,59 @@ TEST_F(ServerTest, NoGzip)
674690
EXPECT_EQ("100", res->get_header_value("Content-Length"));
675691
EXPECT_EQ(200, res->status);
676692
}
693+
694+
TEST_F(ServerTest, MultipartFormDataGzip)
695+
{
696+
Request req;
697+
req.method = "POST";
698+
req.path = "/gzipmultipart";
699+
700+
std::string host_and_port;
701+
host_and_port += HOST;
702+
host_and_port += ":";
703+
host_and_port += std::to_string(PORT);
704+
705+
req.headers.emplace("Host", host_and_port.c_str());
706+
req.headers.emplace("Accept", "*/*");
707+
req.headers.emplace("User-Agent", "cpp-httplib/0.1");
708+
req.headers.emplace("Content-Type", "multipart/form-data; boundary=------------------------fcba8368a9f48c0f");
709+
req.headers.emplace("Content-Encoding", "gzip");
710+
711+
// compressed_body generated by creating input.txt to this file:
712+
/*
713+
--------------------------fcba8368a9f48c0f
714+
Content-Disposition: form-data; name="key1"
715+
716+
test
717+
--------------------------fcba8368a9f48c0f
718+
Content-Disposition: form-data; name="key2"
719+
720+
--abcdefg123
721+
--------------------------fcba8368a9f48c0f--
722+
*/
723+
// then running unix2dos input.txt; gzip -9 -c input.txt | xxd -i.
724+
uint8_t compressed_body[] = {
725+
0x1f, 0x8b, 0x08, 0x08, 0x48, 0xf1, 0xd4, 0x5a, 0x02, 0x03, 0x69, 0x6e,
726+
0x70, 0x75, 0x74, 0x2e, 0x74, 0x78, 0x74, 0x00, 0xd3, 0xd5, 0xc5, 0x05,
727+
0xd2, 0x92, 0x93, 0x12, 0x2d, 0x8c, 0xcd, 0x2c, 0x12, 0x2d, 0xd3, 0x4c,
728+
0x2c, 0x92, 0x0d, 0xd2, 0x78, 0xb9, 0x9c, 0xf3, 0xf3, 0x4a, 0x52, 0xf3,
729+
0x4a, 0x74, 0x5d, 0x32, 0x8b, 0x0b, 0xf2, 0x8b, 0x33, 0x4b, 0x32, 0xf3,
730+
0xf3, 0xac, 0x14, 0xd2, 0xf2, 0x8b, 0x72, 0x75, 0x53, 0x12, 0x4b, 0x12,
731+
0xad, 0x15, 0xf2, 0x12, 0x73, 0x53, 0x6d, 0x95, 0xb2, 0x53, 0x2b, 0x0d,
732+
0x95, 0x78, 0xb9, 0x78, 0xb9, 0x4a, 0x52, 0x8b, 0x4b, 0x78, 0xb9, 0x74,
733+
0x69, 0x61, 0x81, 0x11, 0xd8, 0x02, 0x5d, 0xdd, 0xc4, 0xa4, 0xe4, 0x94,
734+
0xd4, 0xb4, 0x74, 0x43, 0x23, 0x63, 0x52, 0x2c, 0xd2, 0xd5, 0xe5, 0xe5,
735+
0x02, 0x00, 0xff, 0x0e, 0x72, 0xdf, 0xf8, 0x00, 0x00, 0x00
736+
};
737+
738+
req.body = std::string((char*)compressed_body, sizeof(compressed_body) / sizeof(compressed_body[0]));
739+
740+
auto res = std::make_shared<Response>();
741+
auto ret = cli_.send(req, *res);
742+
743+
ASSERT_TRUE(ret);
744+
EXPECT_EQ(200, res->status);
745+
}
677746
#endif
678747

679748
class ServerTestWithAI_PASSIVE : public ::testing::Test {

0 commit comments

Comments
 (0)