Browse Source

Merge pull request #50 from sgraham/body-decompress

Support Content-Encoding: gzip on server side
yhirose 7 years ago
parent
commit
ebe40a7d94
2 changed files with 117 additions and 0 deletions
  1. 48 0
      httplib.h
  2. 69 0
      test/test.cc

+ 48 - 0
httplib.h

@@ -706,6 +706,7 @@ inline const char* status_message(int status)
     case 200: return "OK";
     case 200: return "OK";
     case 400: return "Bad Request";
     case 400: return "Bad Request";
     case 404: return "Not Found";
     case 404: return "Not Found";
+    case 406: return "Not Acceptable";
     default:
     default:
         case 500: return "Internal Server Error";
         case 500: return "Internal Server Error";
     }
     }
@@ -1198,6 +1199,43 @@ inline void compress(const Request& req, Response& res)
 
 
     deflateEnd(&strm);
     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
 #endif
 
 
 #ifdef _WIN32
 #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");
         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")) {
         if (!content_type.find("application/x-www-form-urlencoded")) {
             detail::parse_query_text(req.body, req.params);
             detail::parse_query_text(req.body, req.params);
         } else if(!content_type.find("multipart/form-data")) {
         } else if(!content_type.find("multipart/form-data")) {

+ 69 - 0
test/test.cc

@@ -325,6 +325,22 @@ protected:
             .get("/nogzip", [&](const Request& /*req*/, Response& res) {
             .get("/nogzip", [&](const Request& /*req*/, Response& res) {
                 res.set_content("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", "application/octet-stream");
                 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
 #endif
             ;
             ;
 
 
@@ -674,6 +690,59 @@ TEST_F(ServerTest, NoGzip)
     EXPECT_EQ("100", res->get_header_value("Content-Length"));
     EXPECT_EQ("100", res->get_header_value("Content-Length"));
     EXPECT_EQ(200, res->status);
     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
 #endif
 
 
 class ServerTestWithAI_PASSIVE : public ::testing::Test {
 class ServerTestWithAI_PASSIVE : public ::testing::Test {