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:
Scott Graham 2018-04-16 11:01:17 -07:00
parent 0e239a0014
commit 5579d4d101
2 changed files with 117 additions and 0 deletions

View file

@ -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")) {

View file

@ -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 {