| 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..5fd60dc6b2b189981c5377d87fb247169e17eff8 | 
| --- /dev/null | 
| +++ b/net/filter/gzip_stream_source_unittest.cc | 
| @@ -0,0 +1,330 @@ | 
| +// 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/io_buffer.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 { | 
| + | 
| +using net::Error; | 
| +using net::GzipStreamSource; | 
| +using net::MockStreamSource; | 
| + | 
| +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; | 
| + | 
| +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_ = new char[kBufferSize]; | 
| +    source_data_len_ = kBufferSize - kEOFMargin; | 
| + | 
| +    for (size_t i = 0; i < source_data_len_; i++) | 
| +      source_data_[i] = i % 256; | 
| + | 
| +    deflated_data_ = new char[kBufferSize]; | 
| +    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); | 
| + | 
| +    out_buffer_ = new net::IOBuffer(kBufferSize); | 
| + | 
| +    last_error_ = net::OK; | 
| +    last_bytes_read_ = 0; | 
| + | 
| +    std::unique_ptr<MockStreamSource> deflate_source(new MockStreamSource); | 
| +    deflate_source_ = deflate_source.get(); | 
| +    deflate_stream_.reset( | 
| +        new GzipStreamSource(std::move(deflate_source), | 
| +                             GzipStreamSource::GZIP_STREAM_SOURCE_DEFLATE)); | 
| +    deflate_stream_->Init(); | 
| + | 
| +    std::unique_ptr<MockStreamSource> gzip_source(new MockStreamSource); | 
| +    gzip_source_ = gzip_source.get(); | 
| +    if (allow_gzip_fallback) { | 
| +      gzip_stream_.reset(new GzipStreamSource( | 
| +          std::move(gzip_source), | 
| +          GzipStreamSource::GZIP_STREAM_SOURCE_GZIP_WITH_FALLBACK)); | 
| +    } else { | 
| +      gzip_stream_.reset(new GzipStreamSource( | 
| +          std::move(gzip_source), GzipStreamSource::GZIP_STREAM_SOURCE_GZIP)); | 
| +    } | 
| +    gzip_stream_->Init(); | 
| +  } | 
| + | 
| +  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) { | 
| +      static const 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; | 
| +  } | 
| + | 
| +  void OnReadComplete(Error error, size_t bytes_read) { | 
| +    last_error_ = error; | 
| +    last_bytes_read_ = bytes_read; | 
| +  } | 
| + | 
| +  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_; } | 
| + | 
| +  net::IOBuffer* out_buffer() { return out_buffer_.get(); } | 
| +  char* out_data() { return out_buffer_->data(); } | 
| +  size_t out_buffer_size() { return kBufferSize; } | 
| + | 
| +  Error last_error() { return last_error_; } | 
| +  size_t last_bytes_read() { return last_bytes_read_; } | 
| + | 
| +  MockStreamSource* deflate_source() { return deflate_source_; } | 
| +  MockStreamSource* gzip_source() { return gzip_source_; } | 
| + | 
| +  net::StreamSource::OnReadCompleteCallback callback() { | 
| +    return base::Bind(&GzipStreamSourceTest::OnReadComplete, | 
| +                      base::Unretained(this)); | 
| +  } | 
| + | 
| +  void AddTrailingDeflatedData(const char* data, size_t sz) { | 
| +    DCHECK_LE(sz, kBufferSize - deflated_data_len_); | 
| +    memcpy(deflated_data_ + deflated_data_len_, data, sz); | 
| +    deflated_data_len_ += sz; | 
| +  } | 
| + | 
| +  void ReadStream(GzipStreamSource* stream) { | 
| +    last_error_ = stream->Read(out_buffer(), out_buffer_size(), | 
| +                               &last_bytes_read_, callback()); | 
| +  } | 
| + | 
| +  void ReadDeflateStream() { return ReadStream(deflate_stream_.get()); } | 
| + | 
| +  void ReadGzipStream() { return ReadStream(gzip_stream_.get()); } | 
| + | 
| +  void ReadStreamAll(GzipStreamSource* stream) { | 
| +    char buffer[kBufferSize]; | 
| +    size_t index = 0; | 
| +    do { | 
| +      ReadStream(stream); | 
| +      ASSERT_LE(last_bytes_read_, kBufferSize - index); | 
| +      memcpy(buffer + index, out_data(), last_bytes_read_); | 
| +      index += last_bytes_read_; | 
| +    } while (last_error_ == net::OK && last_bytes_read_ > 0); | 
| +    memcpy(out_data(), buffer, index); | 
| +    if (last_error_ == net::OK) | 
| +      last_bytes_read_ = index; | 
| +  } | 
| + | 
| +  void ReadDeflateStreamAll() { ReadStreamAll(deflate_stream_.get()); } | 
| + | 
| +  void ReadGzipStreamAll() { ReadStreamAll(gzip_stream_.get()); } | 
| + | 
| + private: | 
| +  char* source_data_; | 
| +  size_t source_data_len_; | 
| + | 
| +  char* deflated_data_; | 
| +  size_t deflated_data_len_; | 
| + | 
| +  char* gzipped_data_; | 
| +  size_t gzipped_data_len_; | 
| + | 
| +  scoped_refptr<net::IOBuffer> out_buffer_; | 
| + | 
| +  MockStreamSource* deflate_source_; | 
| +  std::unique_ptr<GzipStreamSource> deflate_stream_; | 
| +  MockStreamSource* gzip_source_; | 
| +  std::unique_ptr<GzipStreamSource> gzip_stream_; | 
| + | 
| +  Error last_error_; | 
| +  size_t last_bytes_read_; | 
| +}; | 
| + | 
| +TEST_F(GzipStreamSourceTest, EmptyStream) { | 
| +  Init(); | 
| +  deflate_source()->AddReadResult("", 0, net::OK, true); | 
| +  ReadDeflateStream(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  EXPECT_EQ(0U, last_bytes_read()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, DeflateOneBlockSync) { | 
| +  Init(); | 
| +  deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), net::OK, | 
| +                                  true); | 
| +  ReadDeflateStream(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  EXPECT_EQ(last_bytes_read(), source_data_len()); | 
| +  EXPECT_EQ(0, memcmp(out_data(), source_data(), source_data_len())); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, GzipOneBlockSync) { | 
| +  Init(); | 
| +  gzip_source()->AddReadResult(gzipped_data(), gzipped_data_len(), net::OK, | 
| +                               true); | 
| +  ReadGzipStream(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  EXPECT_EQ(last_bytes_read(), source_data_len()); | 
| +  EXPECT_EQ(0, memcmp(out_data(), source_data(), source_data_len())); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, DeflateTwoBlocksSync) { | 
| +  Init(); | 
| +  deflate_source()->AddReadResult(deflated_data(), 10, net::OK, true); | 
| +  deflate_source()->AddReadResult(deflated_data() + 10, | 
| +                                  deflated_data_len() - 10, net::OK, true); | 
| +  ReadDeflateStreamAll(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  EXPECT_EQ(last_bytes_read(), source_data_len()); | 
| +  EXPECT_EQ(0, memcmp(out_data(), source_data(), source_data_len())); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, DeflateOneBlockAsync) { | 
| +  Init(); | 
| +  deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), net::OK, | 
| +                                  false); | 
| +  ReadDeflateStream(); | 
| +  EXPECT_EQ(net::ERR_IO_PENDING, last_error()); | 
| +  deflate_source()->CompleteNextRead(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  EXPECT_EQ(last_bytes_read(), source_data_len()); | 
| +  EXPECT_EQ(0, memcmp(out_data(), source_data(), source_data_len())); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, PassThroughAfterEOF) { | 
| +  Init(); | 
| +  char test_data[] = "Hello, World!"; | 
| +  AddTrailingDeflatedData(test_data, sizeof(test_data)); | 
| +  deflate_source()->AddReadResult(deflated_data(), deflated_data_len(), net::OK, | 
| +                                  true); | 
| +  // Compressed and uncompressed data get returned as separate Read() results, | 
| +  // so this test has to use ReadDeflateStreamAll to get both. | 
| +  ReadDeflateStreamAll(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  EXPECT_EQ(last_bytes_read(), source_data_len() + sizeof(test_data)); | 
| +  EXPECT_EQ(0, memcmp(out_data(), source_data(), source_data_len())); | 
| +  EXPECT_EQ( | 
| +      0, memcmp(out_data() + source_data_len(), test_data, sizeof(test_data))); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, MissingZlibHeader) { | 
| +  Init(); | 
| +  const size_t kZlibHeaderLen = 2; | 
| +  deflate_source()->AddReadResult(deflated_data() + kZlibHeaderLen, | 
| +                                  deflated_data_len() - kZlibHeaderLen, net::OK, | 
| +                                  true); | 
| +  ReadDeflateStream(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  EXPECT_EQ(last_bytes_read(), source_data_len()); | 
| +  EXPECT_EQ(0, memcmp(out_data(), source_data(), source_data_len())); | 
| +} | 
| + | 
| +// 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; | 
| +  gzip_source()->AddReadResult(gzipped_data(), gzipped_data_len(), net::OK, | 
| +                               true); | 
| +  ReadGzipStream(); | 
| +  EXPECT_EQ(net::ERR_CONTENT_DECODING_FAILED, last_error()); | 
| +} | 
| + | 
| +TEST_F(GzipStreamSourceTest, GzipFallback) { | 
| +  Init(true); | 
| +  gzip_source()->AddReadResult(source_data(), source_data_len(), net::OK, true); | 
| +  ReadGzipStream(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  EXPECT_EQ(last_bytes_read(), source_data_len()); | 
| +  EXPECT_EQ(0, memcmp(out_data(), source_data(), source_data_len())); | 
| +} | 
| + | 
| +// 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!"; | 
| +  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}; | 
| +  gzip_source()->AddReadResult(gzip_data, sizeof(gzip_data), net::OK, true); | 
| +  ReadGzipStream(); | 
| +  EXPECT_EQ(net::OK, last_error()); | 
| +  // Using strlen(3) here because echo doesn't leave the null terminator on the | 
| +  // string. | 
| +  EXPECT_EQ(strlen(plain_data), last_bytes_read()); | 
| +  EXPECT_EQ(0, memcmp(out_data(), plain_data, strlen(plain_data))); | 
| +} | 
| + | 
| +}  // namespace | 
|  |