Browse Source

Fix successful decompress reported as Error::Read (#1612)

* Fix successful decompress reported as Error::Read

Streams less than 4096 bytes are sometimes reported as failed reads because stream_.avail_in is not reduced to 0. The next iteration of the loop finds `prev_avail_in == strm_.avail_in` and return false. `ret = inflate(...)` returns Z_STREAM_END on the first iteration of the loop indicating that inflate is finished. This fix prevents the second iteration of the loop from failing.

* Fix successful decompress reported as Error::Read

- Add unit tests for raw deflate that illustrates the decompression failure when there are extra trailing bytes
bdenhollander 2 years ago
parent
commit
ee625232a4
2 changed files with 54 additions and 5 deletions
  1. 1 5
      httplib.h
  2. 53 0
      test/test.cc

+ 1 - 5
httplib.h

@@ -3384,16 +3384,12 @@ inline bool gzip_decompressor::decompress(const char *data, size_t data_length,
     data += strm_.avail_in;
     data += strm_.avail_in;
 
 
     std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
     std::array<char, CPPHTTPLIB_COMPRESSION_BUFSIZ> buff{};
-    while (strm_.avail_in > 0) {
+    while (strm_.avail_in > 0 && ret == Z_OK) {
       strm_.avail_out = static_cast<uInt>(buff.size());
       strm_.avail_out = static_cast<uInt>(buff.size());
       strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
       strm_.next_out = reinterpret_cast<Bytef *>(buff.data());
 
 
-      auto prev_avail_in = strm_.avail_in;
-
       ret = inflate(&strm_, Z_NO_FLUSH);
       ret = inflate(&strm_, Z_NO_FLUSH);
 
 
-      if (prev_avail_in - strm_.avail_in == 0) { return false; }
-
       assert(ret != Z_STREAM_ERROR);
       assert(ret != Z_STREAM_ERROR);
       switch (ret) {
       switch (ret) {
       case Z_NEED_DICT:
       case Z_NEED_DICT:

+ 53 - 0
test/test.cc

@@ -3330,6 +3330,59 @@ TEST(GzipDecompressor, ChunkedDecompression) {
   ASSERT_EQ(data, decompressed_data);
   ASSERT_EQ(data, decompressed_data);
 }
 }
 
 
+TEST(GzipDecompressor, DeflateDecompression) {
+  std::string original_text = "Raw deflate without gzip";
+  unsigned char data[32] = {0x78, 0x9C, 0x0B, 0x4A, 0x2C, 0x57, 0x48, 0x49,
+                            0x4D, 0xCB, 0x49, 0x2C, 0x49, 0x55, 0x28, 0xCF,
+                            0x2C, 0xC9, 0xC8, 0x2F, 0x2D, 0x51, 0x48, 0xAF,
+                            0xCA, 0x2C, 0x00, 0x00, 0x6F, 0x98, 0x09, 0x2E};
+  std::string compressed_data(data, data + sizeof(data) / sizeof(data[0]));
+
+  std::string decompressed_data;
+  {
+    httplib::detail::gzip_decompressor decompressor;
+
+    bool result = decompressor.decompress(
+        compressed_data.data(), compressed_data.size(),
+        [&](const char *decompressed_data_chunk,
+            size_t decompressed_data_chunk_size) {
+          decompressed_data.insert(decompressed_data.size(),
+                                   decompressed_data_chunk,
+                                   decompressed_data_chunk_size);
+          return true;
+        });
+    ASSERT_TRUE(result);
+  }
+  ASSERT_EQ(original_text, decompressed_data);
+}
+
+TEST(GzipDecompressor, DeflateDecompressionTrailingBytes) {
+  std::string original_text = "Raw deflate without gzip";
+  unsigned char data[40] = {0x78, 0x9C, 0x0B, 0x4A, 0x2C, 0x57, 0x48, 0x49,
+                            0x4D, 0xCB, 0x49, 0x2C, 0x49, 0x55, 0x28, 0xCF,
+                            0x2C, 0xC9, 0xC8, 0x2F, 0x2D, 0x51, 0x48, 0xAF,
+                            0xCA, 0x2C, 0x00, 0x00, 0x6F, 0x98, 0x09, 0x2E,
+                            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+  std::string compressed_data(data, data + sizeof(data) / sizeof(data[0]));
+
+  std::string decompressed_data;
+  {
+    httplib::detail::gzip_decompressor decompressor;
+
+    bool result = decompressor.decompress(
+        compressed_data.data(), compressed_data.size(),
+        [&](const char *decompressed_data_chunk,
+            size_t decompressed_data_chunk_size) {
+          decompressed_data.insert(decompressed_data.size(),
+                                   decompressed_data_chunk,
+                                   decompressed_data_chunk_size);
+          return true;
+        });
+    ASSERT_TRUE(result);
+  }
+  ASSERT_EQ(original_text, decompressed_data);
+}
+
 #ifdef _WIN32
 #ifdef _WIN32
 TEST(GzipDecompressor, LargeRandomData) {
 TEST(GzipDecompressor, LargeRandomData) {