diff --git a/httplib.h b/httplib.h index 7cd44cd..3c902f6 100644 --- a/httplib.h +++ b/httplib.h @@ -706,6 +706,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"; } @@ -1198,6 +1199,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 @@ -1670,6 +1708,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")) { diff --git a/test/test.cc b/test/test.cc index 8b027d4..de2ebf2 100644 --- a/test/test.cc +++ b/test/test.cc @@ -325,6 +325,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 ; @@ -674,6 +690,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(); + auto ret = cli_.send(req, *res); + + ASSERT_TRUE(ret); + EXPECT_EQ(200, res->status); +} #endif class ServerTestWithAI_PASSIVE : public ::testing::Test {