| Index: net/filter/gzip_stream_source_unittest.cc | 
| diff --git a/net/filter/gzip_stream_source_unittest.cc b/net/filter/gzip_stream_source_unittest.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..2a2c7761bd6a263a8779ca2c6a551f14bdc3cf0c | 
| --- /dev/null | 
| +++ b/net/filter/gzip_stream_source_unittest.cc | 
| @@ -0,0 +1,313 @@ | 
| +// Copyright 2016 The Chromium Authors. All rights reserved. | 
| +// Use of this source code is governed by a BSD-style license that can be | 
| +// found in the LICENSE file. | 
| + | 
| +#include "base/bind.h" | 
| +#include "base/bit_cast.h" | 
| +#include "base/callback.h" | 
| +#include "net/base/completion_callback.h" | 
| +#include "net/base/io_buffer.h" | 
| +#include "net/base/test_completion_callback.h" | 
| +#include "net/filter/gzip_stream_source.h" | 
| +#include "net/filter/mock_stream_source.h" | 
| +#include "testing/gtest/include/gtest/gtest.h" | 
| +#include "third_party/zlib/zlib.h" | 
| + | 
| +namespace net { | 
| + | 
| +namespace { | 
| + | 
| +const size_t kBufferSize = 4096; | 
| + | 
| +// How many bytes to leave unused at the end of |source_data_|. This margin is | 
| +// present so that tests that need to append data after the zlib EOF do not run | 
| +// out of room in the output buffer. | 
| +const size_t kEOFMargin = 64; | 
| + | 
| +}  // namespace | 
| + | 
| +class GzipStreamSourceTest : public ::testing::Test { | 
| + protected: | 
| +  void Init() { Init(false); } | 
| + | 
| +  // If |allow_gzip_fallback| is true, will use | 
| +  // GZIP_STREAM_SOURCE_GZIP_WITH_FALLBACK when constructing |gzip_stream_|. | 
| +  void Init(bool allow_gzip_fallback) { | 
| +    source_data_len_ = kBufferSize - kEOFMargin; | 
| + | 
| +    for (size_t i = 0; i < source_data_len_; i++) | 
| +      source_data_[i] = i % 256; | 
| + | 
| +    deflated_data_len_ = kBufferSize; | 
| + | 
| +    Compress(source_data_, source_data_len_, deflated_data_, | 
| +             &deflated_data_len_, false); | 
| + | 
| +    gzipped_data_ = new char[kBufferSize]; | 
| +    gzipped_data_len_ = kBufferSize; | 
| + | 
| +    Compress(source_data_, source_data_len_, gzipped_data_, &gzipped_data_len_, | 
| +             true); | 
| + | 
| +    output_buffer_ = new IOBuffer(kBufferSize); | 
| + | 
| +    std::unique_ptr<MockStreamSource> deflate_source(new MockStreamSource); | 
| +    deflate_source_ = deflate_source.get(); | 
| +    deflate_stream_ = | 
| +        GzipStreamSource::Create(std::move(deflate_source), | 
| +                                 GzipStreamSource::GZIP_STREAM_SOURCE_DEFLATE); | 
| + | 
| +    std::unique_ptr<MockStreamSource> source(new MockStreamSource); | 
| +    source_ = source.get(); | 
| +    if (allow_gzip_fallback) { | 
| +      gzip_stream_ = GzipStreamSource::Create( | 
| +          std::move(source), | 
| +          GzipStreamSource::GZIP_STREAM_SOURCE_GZIP_WITH_FALLBACK); | 
| +    } else { | 
| +      gzip_stream_ = GzipStreamSource::Create( | 
| +          std::move(source), GzipStreamSource::GZIP_STREAM_SOURCE_GZIP); | 
| +    } | 
| +  } | 
| + | 
| +  void Compress(char* source, | 
| +                size_t source_len, | 
| +                char* dest, | 
| +                size_t* dest_len, | 
| +                bool gzip_framing) { | 
| +    size_t dest_left = *dest_len; | 
| +    z_stream zlib_stream; | 
| +    memset(&zlib_stream, 0, sizeof(zlib_stream)); | 
| +    int code; | 
| +    if (gzip_framing) { | 
| +      const int kMemLevel = 8;  // the default, see deflateInit2(3) | 
| +      code = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, | 
| +                          -MAX_WBITS, kMemLevel, Z_DEFAULT_STRATEGY); | 
| +    } else { | 
| +      code = deflateInit(&zlib_stream, Z_DEFAULT_COMPRESSION); | 
| +    } | 
| +    DCHECK_EQ(Z_OK, code); | 
| + | 
| +    // If compressing with gzip framing, prepend a gzip header. See RFC 1952 2.2 | 
| +    // and 2.3 for more information. | 
| +    if (gzip_framing) { | 
| +      const unsigned char gzip_header[] = { | 
| +          0x1f, | 
| +          0x8b,  // magic number | 
| +          0x08,  // CM 0x08 == "deflate" | 
| +          0x00,  // FLG 0x00 == nothing | 
| +          0x00, 0x00, 0x00, | 
| +          0x00,  // MTIME 0x00000000 == no mtime | 
| +          0x00,  // XFL 0x00 == nothing | 
| +          0xff,  // OS 0xff == unknown | 
| +      }; | 
| +      DCHECK_GE(dest_left, sizeof(gzip_header)); | 
| +      memcpy(dest, gzip_header, sizeof(gzip_header)); | 
| +      dest += sizeof(gzip_header); | 
| +      dest_left -= sizeof(gzip_header); | 
| +    } | 
| + | 
| +    zlib_stream.next_in = bit_cast<Bytef*>(source); | 
| +    zlib_stream.avail_in = source_len; | 
| +    zlib_stream.next_out = bit_cast<Bytef*>(dest); | 
| +    zlib_stream.avail_out = dest_left; | 
| + | 
| +    code = deflate(&zlib_stream, Z_FINISH); | 
| +    DCHECK_EQ(Z_STREAM_END, code); | 
| +    dest_left = zlib_stream.avail_out; | 
| + | 
| +    deflateEnd(&zlib_stream); | 
| +    *dest_len -= dest_left; | 
| +  } | 
| + | 
| +  char* source_data() { return source_data_; } | 
| +  size_t source_data_len() { return source_data_len_; } | 
| + | 
| +  char* deflated_data() { return deflated_data_; } | 
| +  size_t deflated_data_len() { return deflated_data_len_; } | 
| + | 
| +  char* gzipped_data() { return gzipped_data_; } | 
| +  size_t gzipped_data_len() { return gzipped_data_len_; } | 
| + | 
| +  IOBuffer* output_buffer() { return output_buffer_.get(); } | 
| +  char* output_data() { return output_buffer_->data(); } | 
| +  size_t output_buffer_size() { return kBufferSize; } | 
| + | 
| +  MockStreamSource* deflate_source() { return deflate_source_; } | 
| +  MockStreamSource* source() { return source_; } | 
| +  GzipStreamSource* gzip_stream() { return gzip_stream_.get(); } | 
| + | 
| +  void AddTrailingDeflatedData(const char* data, size_t data_len) { | 
| +    DCHECK_LE(data_len, kBufferSize - deflated_data_len_); | 
| +    memcpy(deflated_data_ + deflated_data_len_, data, data_len); | 
| +    deflated_data_len_ += data_len; | 
| +  } | 
| + | 
| +  int ReadStream(GzipStreamSource* stream, const CompletionCallback& callback) { | 
| +    return stream->Read(output_buffer(), output_buffer_size(), callback); | 
| +  } | 
| + | 
| +  int ReadDeflateStream(const CompletionCallback& callback) { | 
| +    return ReadStream(deflate_stream_.get(), callback); | 
| +  } | 
| + | 
| +  int ReadGzipStream(const CompletionCallback& callback) { | 
| +    return ReadStream(gzip_stream_.get(), callback); | 
| +  } | 
| + | 
| + private: | 
| +  char source_data_[kBufferSize]; | 
| +  size_t source_data_len_; | 
| + | 
| +  char deflated_data_[kBufferSize]; | 
| +  size_t deflated_data_len_; | 
| + | 
| +  char* gzipped_data_; | 
| +  size_t gzipped_data_len_; | 
| + | 
| +  scoped_refptr<IOBuffer> output_buffer_; | 
| + | 
| +  MockStreamSource* deflate_source_; | 
| +  std::unique_ptr<GzipStreamSource> deflate_stream_; | 
| +  MockStreamSource* source_; | 
| +  std::unique_ptr<GzipStreamSource> gzip_stream_; | 
| +}; | 
| + | 
| +TEST_F(GzipStreamSourceTest, EmptyStream) { | 
| +  Init(); | 
| +  deflate_source()->AddReadResult("", 0, OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int result = ReadDeflateStream(callback.callback()); | 
| +  EXPECT_EQ(OK, result); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, DeflateOneBlockSync) { | 
| +  Init(); | 
| +  deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), OK, | 
| +                                  true); | 
| +  TestCompletionCallback callback; | 
| +  int result = ReadDeflateStream(callback.callback()); | 
| +  EXPECT_EQ(static_cast<int>(source_data_len()), result); | 
| +  EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, GzipOneBlockSync) { | 
| +  Init(); | 
| +  source()->AddReadResult(gzipped_data(), gzipped_data_len(), OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int result = ReadGzipStream(callback.callback()); | 
| +  EXPECT_EQ(static_cast<int>(source_data_len()), result); | 
| +  EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, DeflateTwoBlocksSync) { | 
| +  Init(); | 
| +  deflate_source()->AddReadResult(deflated_data(), 10, OK, true); | 
| +  deflate_source()->AddReadResult(deflated_data() + 10, | 
| +                                  deflated_data_len() - 10, OK, true); | 
| +  TestCompletionCallback callback; | 
| +  char buffer[kBufferSize]; | 
| +  int first_bytes_read = ReadDeflateStream(callback.callback()); | 
| +  EXPECT_LE(OK, first_bytes_read); | 
| +  memcpy(buffer, output_data(), first_bytes_read); | 
| +  int second_bytes_read = ReadDeflateStream(callback.callback()); | 
| +  EXPECT_LE(OK, second_bytes_read); | 
| +  EXPECT_EQ(static_cast<int>(source_data_len()), | 
| +            first_bytes_read + second_bytes_read); | 
| +  memcpy(buffer + first_bytes_read, output_data(), second_bytes_read); | 
| +  memcpy(output_data(), buffer, first_bytes_read + second_bytes_read); | 
| +  EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, DeflateOneBlockAsync) { | 
| +  Init(); | 
| +  deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), OK, | 
| +                                  false); | 
| +  TestCompletionCallback callback; | 
| +  EXPECT_EQ(ERR_IO_PENDING, ReadDeflateStream(callback.callback())); | 
| +  deflate_source()->CompleteNextRead(); | 
| +  EXPECT_EQ(static_cast<int>(source_data_len()), callback.WaitForResult()); | 
| +  EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, PassThroughAfterEOF) { | 
| +  Init(); | 
| +  char test_data[] = "Hello, World!"; | 
| +  AddTrailingDeflatedData(test_data, sizeof(test_data)); | 
| +  deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), OK, | 
| +                                  true); | 
| +  // Compressed and uncompressed data get returned as separate Read() results, | 
| +  // so this test has to call Read twice. | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadDeflateStream(callback.callback()); | 
| +  EXPECT_EQ(static_cast<int>(source_data_len() + sizeof(test_data)), | 
| +            bytes_read); | 
| +  EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); | 
| +  EXPECT_EQ(0, memcmp(output_data() + source_data_len(), test_data, | 
| +                      sizeof(test_data))); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, MissingZlibHeader) { | 
| +  Init(); | 
| +  const size_t kZlibHeaderLen = 2; | 
| +  deflate_source()->AddReadResult(deflated_data() + kZlibHeaderLen, | 
| +                                  deflated_data_len() - kZlibHeaderLen, OK, | 
| +                                  true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadDeflateStream(callback.callback()); | 
| +  EXPECT_EQ(static_cast<int>(source_data_len()), bytes_read); | 
| +  EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +// A slight wrinkle: since the test GZip stream runs with gzip_fallback set to | 
| +// true, corrupting the first byte instead of the second byte will cause the | 
| +// gzip stream to falsely accept the corrupt data and pass it through untouched. | 
| +TEST_F(GzipStreamSourceTest, CorruptGzipHeader) { | 
| +  Init(); | 
| +  gzipped_data()[1] = 0; | 
| +  source()->AddReadResult(gzipped_data(), gzipped_data_len(), OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadGzipStream(callback.callback()); | 
| +  EXPECT_EQ(ERR_CONTENT_DECODING_FAILED, bytes_read); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, GzipFallback) { | 
| +  Init(/*allow_gzip_fallback=*/true); | 
| +  source()->AddReadResult(source_data(), source_data_len(), OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadGzipStream(callback.callback()); | 
| +  EXPECT_EQ(static_cast<int>(source_data_len()), bytes_read); | 
| +  EXPECT_EQ(0, memcmp(output_data(), source_data(), source_data_len())); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +// This test checks that the gzip stream source works correctly on 'golden' data | 
| +// as produced by gzip(1). | 
| +TEST_F(GzipStreamSourceTest, GzipCorrectness) { | 
| +  Init(); | 
| +  char plain_data[] = "Hello, World!"; | 
| +  unsigned char gzip_data[] = { | 
| +      // From: | 
| +      //   echo -n 'Hello, World!' | gzip | xxd -i | sed -e 's/^/  /' | 
| +      0x1f, 0x8b, 0x08, 0x00, 0x2b, 0x02, 0x84, 0x55, 0x00, 0x03, 0xf3, | 
| +      0x48, 0xcd, 0xc9, 0xc9, 0xd7, 0x51, 0x08, 0xcf, 0x2f, 0xca, 0x49, | 
| +      0x51, 0x04, 0x00, 0xd0, 0xc3, 0x4a, 0xec, 0x0d, 0x00, 0x00, 0x00}; | 
| +  source()->AddReadResult(reinterpret_cast<char*>(gzip_data), sizeof(gzip_data), | 
| +                          OK, true); | 
| +  TestCompletionCallback callback; | 
| +  int bytes_read = ReadGzipStream(callback.callback()); | 
| +  // Using strlen(3) here because echo doesn't leave the null terminator on the | 
| +  // string. | 
| +  EXPECT_EQ(static_cast<int>(strlen(plain_data)), bytes_read); | 
| +  EXPECT_EQ(0, memcmp(output_data(), plain_data, strlen(plain_data))); | 
| +  EXPECT_EQ("GZIP", gzip_stream()->OrderedTypeStringList()); | 
| +} | 
| + | 
| +}  // namespace | 
|  |