| /* |
| * Copyright 2010 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // Author: bmcquade@google.com (Bryan McQuade) |
| |
| #include "pagespeed/kernel/util/gzip_inflater.h" |
| |
| #include <cstddef> |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/null_mutex.h" |
| #include "pagespeed/kernel/base/stack_buffer.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/string_writer.h" |
| #include "pagespeed/kernel/util/simple_random.h" |
| |
| namespace net_instaweb { |
| |
| class GzipInflaterTestPeer { |
| public: |
| static void SetInputBypassFirstByteCheck( |
| GzipInflater* inflater, const unsigned char* in, size_t in_size) { |
| inflater->SetInputInternal(in, in_size); |
| } |
| |
| static bool FormatIsZlibStream(const GzipInflater& inflater) { |
| return inflater.format_ == GzipInflater::FORMAT_ZLIB_STREAM; |
| } |
| }; |
| |
| namespace { |
| |
| class GzipInflaterTest : public testing::Test { |
| protected: |
| void TestInflateDeflate(StringPiece payload) { |
| GoogleString deflated, inflated; |
| StringWriter deflate_writer(&deflated); |
| EXPECT_TRUE(GzipInflater::Deflate(payload, GzipInflater::kDeflate, |
| &deflate_writer)); |
| StringWriter inflate_writer(&inflated); |
| EXPECT_TRUE(GzipInflater::Inflate(deflated, GzipInflater::kDeflate, |
| &inflate_writer)); |
| EXPECT_STREQ(payload, inflated); |
| } |
| }; |
| |
| const char kBasic[] = "Hello\n"; |
| |
| // The above string "Hello\n", gzip compressed. |
| const unsigned char kCompressed[] = { |
| 0x1f, 0x8b, 0x08, 0x08, 0x38, 0x18, 0x2e, 0x4c, 0x00, 0x03, 0x63, |
| 0x6f, 0x6d, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2e, 0x68, |
| 0x74, 0x6d, 0x6c, 0x00, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0xe7, 0x02, |
| 0x00, 0x16, 0x35, 0x96, 0x31, 0x06, 0x00, 0x00, 0x00 |
| }; |
| |
| // The above string "Hello\n", zlib stream compressed. |
| const unsigned char kCompressedZlibStream[] = { |
| 0x78, 0x9c, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0xe7, 0x02, 0x00, 0x07, |
| 0x8b, 0x01, 0xff |
| }; |
| |
| // The above string "Hello\n", raw deflate compressed. |
| const unsigned char kCompressedRawDeflate[] = { |
| 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0xe7, 0x02, 0x00, |
| }; |
| |
| const size_t kBufSize = 256; |
| |
| void AssertInflate(GzipInflater::InflateType type, |
| const unsigned char* in, |
| size_t in_size) { |
| std::string buf; |
| buf.resize(kBufSize); |
| GzipInflater inflater(type); |
| inflater.Init(); |
| EXPECT_FALSE(inflater.HasUnconsumedInput()); |
| EXPECT_TRUE(inflater.SetInput(in, in_size)); |
| EXPECT_TRUE(inflater.HasUnconsumedInput()); |
| int num_inflated_bytes = inflater.InflateBytes(&buf[0], kBufSize); |
| ASSERT_EQ(strlen(kBasic), static_cast<size_t>(num_inflated_bytes)); |
| // null-terminate the buffer |
| buf[num_inflated_bytes] = '\0'; |
| EXPECT_FALSE(inflater.HasUnconsumedInput()); |
| EXPECT_TRUE(inflater.finished()); |
| EXPECT_FALSE(inflater.error()); |
| inflater.ShutDown(); |
| EXPECT_STREQ(kBasic, buf.c_str()); |
| } |
| |
| void AssertInflateOneByteAtATime(GzipInflater::InflateType type, |
| const unsigned char* in, |
| size_t in_size) { |
| std::string buf; |
| buf.resize(kBufSize); |
| GzipInflater inflater(type); |
| inflater.Init(); |
| int num_inflated_bytes = 0; |
| EXPECT_FALSE(inflater.HasUnconsumedInput()); |
| for (size_t input_offset = 0; |
| input_offset < in_size; |
| ++input_offset) { |
| EXPECT_TRUE(inflater.SetInput(in + input_offset, 1)); |
| EXPECT_TRUE(inflater.HasUnconsumedInput()); |
| num_inflated_bytes += |
| inflater.InflateBytes(&buf[num_inflated_bytes], |
| kBufSize - num_inflated_bytes); |
| EXPECT_FALSE(inflater.error()); |
| } |
| ASSERT_EQ(strlen(kBasic), static_cast<size_t>(num_inflated_bytes)); |
| // null-terminate the buffer |
| buf[num_inflated_bytes] = '\0'; |
| EXPECT_FALSE(inflater.HasUnconsumedInput()); |
| EXPECT_TRUE(inflater.finished()); |
| EXPECT_FALSE(inflater.error()); |
| inflater.ShutDown(); |
| EXPECT_STREQ(kBasic, buf.c_str()); |
| } |
| |
| TEST_F(GzipInflaterTest, Gzip) { |
| AssertInflate(GzipInflater::kGzip, |
| kCompressed, |
| sizeof(kCompressed)); |
| } |
| |
| TEST_F(GzipInflaterTest, GzipOneByteAtATime) { |
| AssertInflateOneByteAtATime(GzipInflater::kGzip, |
| kCompressed, |
| sizeof(kCompressed)); |
| } |
| |
| TEST_F(GzipInflaterTest, ZlibStream) { |
| AssertInflate(GzipInflater::kDeflate, |
| kCompressedZlibStream, |
| sizeof(kCompressedZlibStream)); |
| } |
| |
| TEST_F(GzipInflaterTest, ZlibStreamOneByteAtATime) { |
| AssertInflateOneByteAtATime(GzipInflater::kDeflate, |
| kCompressedZlibStream, |
| sizeof(kCompressedZlibStream)); |
| } |
| |
| TEST_F(GzipInflaterTest, RawDeflate) { |
| AssertInflate(GzipInflater::kDeflate, |
| kCompressedRawDeflate, |
| sizeof(kCompressedRawDeflate)); |
| } |
| |
| TEST_F(GzipInflaterTest, RawDeflateOneByteAtATime) { |
| AssertInflateOneByteAtATime(GzipInflater::kDeflate, |
| kCompressedRawDeflate, |
| sizeof(kCompressedRawDeflate)); |
| } |
| |
| // We want to exercise the code path that detects a decompression |
| // failure inside InflateBytes and attempts to decode as zlib |
| // stream. However there is also a code path in SetInput() that |
| // inspects the input to see if the input is a valid zlib stream, |
| // which we must bypass in order to exercise this code path. It is |
| // possible for there to exist valid deflate streams that do have a |
| // valid zlib header byte, so we do need this code path as |
| // well. Unfortunately I am not able to produce such a deflate stream, |
| // which is why we need this special case flow with the |
| // GzipInflaterTest here. |
| TEST_F(GzipInflaterTest, RawDeflateBypassFirstByteCheck) { |
| std::string buf; |
| buf.resize(kBufSize); |
| GzipInflater inflater(GzipInflater::kDeflate); |
| inflater.Init(); |
| EXPECT_FALSE(inflater.HasUnconsumedInput()); |
| // Normally, calling SetInput() will attempt to do stream type |
| // detection on the first byte of input. We want to bypass that so |
| // that we can exercise the failure path in InflateBytes that |
| // attempts to fall back to raw deflate format. |
| GzipInflaterTestPeer::SetInputBypassFirstByteCheck( |
| &inflater, kCompressedRawDeflate, sizeof(kCompressedRawDeflate)); |
| EXPECT_TRUE(inflater.HasUnconsumedInput()); |
| // We expect the inflater to be in zlib stream format going into the |
| // invocation of InflateBytes. |
| EXPECT_TRUE(GzipInflaterTestPeer::FormatIsZlibStream(inflater)); |
| // InflateBytes should have detected that this was not a valid zlib |
| // stream and switched the format to raw deflate. |
| int num_inflated_bytes = inflater.InflateBytes(&buf[0], kBufSize); |
| EXPECT_FALSE(GzipInflaterTestPeer::FormatIsZlibStream(inflater)); |
| ASSERT_EQ(strlen(kBasic), static_cast<size_t>(num_inflated_bytes)); |
| // null-terminate the buffer |
| buf[num_inflated_bytes] = '\0'; |
| EXPECT_FALSE(inflater.HasUnconsumedInput()); |
| EXPECT_TRUE(inflater.finished()); |
| EXPECT_FALSE(inflater.error()); |
| inflater.ShutDown(); |
| EXPECT_STREQ(kBasic, buf.c_str()); // buf.size() is 0 here, so we use c_str |
| } |
| |
| TEST_F(GzipInflaterTest, InflateDeflate) { |
| TestInflateDeflate("The quick brown fox jumps over the lazy dog"); |
| } |
| |
| TEST_F(GzipInflaterTest, InflateDeflateLargeDataHighEntropy) { |
| SimpleRandom random(new NullMutex); |
| GoogleString value = random.GenerateHighEntropyString(5 * kStackBufferSize); |
| TestInflateDeflate(value); |
| } |
| |
| TEST_F(GzipInflaterTest, IncrementalInflateOfOneShotDeflate) { |
| const char kPayload[] = "The quick brown fox jumps over the lazy dog"; |
| GoogleString deflated, inflated; |
| StringWriter deflate_writer(&deflated); |
| EXPECT_TRUE( |
| GzipInflater::Deflate(kPayload, GzipInflater::kDeflate, &deflate_writer)); |
| |
| char buf[STATIC_STRLEN(kPayload) + 1]; |
| const char kDontTouchMarker = 0xf; |
| buf[STATIC_STRLEN(kPayload)] = kDontTouchMarker; |
| GzipInflater inflater(GzipInflater::kDeflate); |
| inflater.Init(); |
| EXPECT_FALSE(inflater.HasUnconsumedInput()); |
| EXPECT_TRUE(inflater.SetInput(deflated.data(), deflated.size())); |
| EXPECT_TRUE(inflater.HasUnconsumedInput()); |
| int num_inflated_bytes = inflater.InflateBytes(buf, sizeof(buf)); |
| EXPECT_EQ(STATIC_STRLEN(kPayload), static_cast<size_t>(num_inflated_bytes)); |
| EXPECT_EQ(kDontTouchMarker, buf[STATIC_STRLEN(kPayload)]); |
| EXPECT_FALSE(inflater.HasUnconsumedInput()); |
| EXPECT_TRUE(inflater.finished()); |
| EXPECT_FALSE(inflater.error()); |
| inflater.ShutDown(); |
| EXPECT_STREQ(kPayload, StringPiece(buf, num_inflated_bytes)); |
| } |
| |
| TEST_F(GzipInflaterTest, TestUngzip) { |
| const char kHello[] = "hello"; |
| // Generated with command line tool "gzip". |
| const char kHelloGzip[] = |
| "\x1f\x8b\x08\x08\x64\x7d\x33\x56\x00\x03\x68\x65\x6c\x6c\x6f\x00\xcb\x48" |
| "\xcd\xc9\xc9\x07\x00\x86\xa6\x10\x36\x05\x00\x00\x00"; |
| |
| GoogleString inflated; |
| StringWriter inflate_writer(&inflated); |
| StringPiece deflated(kHelloGzip, sizeof(kHelloGzip)); |
| EXPECT_TRUE( |
| GzipInflater::Inflate(deflated, GzipInflater::kGzip, &inflate_writer)); |
| EXPECT_STREQ(kHello, inflated); |
| } |
| |
| TEST_F(GzipInflaterTest, TestGzipUnGzip) { |
| const int compression_level = 9; |
| StringPiece payload("hello"); |
| GoogleString deflated, inflated; |
| StringWriter deflate_writer(&deflated); |
| EXPECT_TRUE(GzipInflater::Deflate(payload, GzipInflater::kGzip, |
| compression_level, &deflate_writer)); |
| StringWriter inflate_writer(&inflated); |
| EXPECT_TRUE( |
| GzipInflater::Inflate(deflated, GzipInflater::kGzip, &inflate_writer)); |
| EXPECT_STREQ(payload, inflated); |
| } |
| |
| } // namespace |
| |
| } // namespace net_instaweb |