1 // Copyright 2017 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "util/net/http_body_gzip.h"
16 
17 #include <string.h>
18 
19 #include <algorithm>
20 #include <memory>
21 #include <string>
22 #include <utility>
23 
24 #include "base/macros.h"
25 #include "base/rand_util.h"
26 #include "base/numerics/safe_conversions.h"
27 #include "gtest/gtest.h"
28 #include "third_party/zlib/zlib_crashpad.h"
29 #include "util/misc/zlib.h"
30 #include "util/net/http_body.h"
31 
32 namespace crashpad {
33 namespace test {
34 namespace {
35 
36 class ScopedZlibInflateStream {
37  public:
ScopedZlibInflateStream(z_stream * zlib)38   explicit ScopedZlibInflateStream(z_stream* zlib) : zlib_(zlib) {}
~ScopedZlibInflateStream()39   ~ScopedZlibInflateStream() {
40     int zr = inflateEnd(zlib_);
41     EXPECT_EQ(zr, Z_OK) << "inflateEnd: " << ZlibErrorString(zr);
42   }
43 
44  private:
45   z_stream* zlib_;  // weak
46   DISALLOW_COPY_AND_ASSIGN(ScopedZlibInflateStream);
47 };
48 
GzipInflate(const std::string & compressed,std::string * decompressed,size_t buf_size)49 void GzipInflate(const std::string& compressed,
50                  std::string* decompressed,
51                  size_t buf_size) {
52   decompressed->clear();
53 
54   // There’s got to be at least a small buffer.
55   buf_size = std::max(buf_size, static_cast<size_t>(1));
56 
57   std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size]);
58   z_stream zlib = {};
59   zlib.zalloc = Z_NULL;
60   zlib.zfree = Z_NULL;
61   zlib.opaque = Z_NULL;
62   zlib.next_in = reinterpret_cast<Bytef*>(const_cast<char*>(&compressed[0]));
63   zlib.avail_in = base::checked_cast<uInt>(compressed.size());
64   zlib.next_out = buf.get();
65   zlib.avail_out = base::checked_cast<uInt>(buf_size);
66 
67   int zr = inflateInit2(&zlib, ZlibWindowBitsWithGzipWrapper(0));
68   ASSERT_EQ(zr, Z_OK) << "inflateInit2: " << ZlibErrorString(zr);
69   ScopedZlibInflateStream zlib_inflate(&zlib);
70 
71   zr = inflate(&zlib, Z_FINISH);
72   ASSERT_EQ(zr, Z_STREAM_END) << "inflate: " << ZlibErrorString(zr);
73 
74   ASSERT_LE(zlib.avail_out, buf_size);
75   decompressed->assign(reinterpret_cast<char*>(buf.get()),
76                        buf_size - zlib.avail_out);
77 }
78 
TestGzipDeflateInflate(const std::string & string)79 void TestGzipDeflateInflate(const std::string& string) {
80   std::unique_ptr<HTTPBodyStream> string_stream(
81       new StringHTTPBodyStream(string));
82   GzipHTTPBodyStream gzip_stream(std::move(string_stream));
83 
84   // The minimum size of a gzip wrapper per RFC 1952: a 10-byte header and an
85   // 8-byte trailer.
86   constexpr size_t kGzipHeaderSize = 18;
87 
88   // Per https://zlib.net/zlib_tech.html, in the worst case, zlib will store
89   // uncompressed data as-is, at an overhead of 5 bytes per 16384-byte block.
90   // Zero-length input will “compress” to a 2-byte zlib stream. Add the overhead
91   // of the gzip wrapper, assuming no optional fields are present.
92   size_t buf_size =
93       string.size() + kGzipHeaderSize +
94       (string.empty() ? 2 : (((string.size() + 16383) / 16384) * 5));
95   std::unique_ptr<uint8_t[]> buf(new uint8_t[buf_size]);
96   FileOperationResult compressed_bytes =
97       gzip_stream.GetBytesBuffer(buf.get(), buf_size);
98   ASSERT_NE(compressed_bytes, -1);
99   ASSERT_LE(static_cast<size_t>(compressed_bytes), buf_size);
100 
101   // Make sure that the stream is really at EOF.
102   uint8_t eof_buf[16];
103   ASSERT_EQ(gzip_stream.GetBytesBuffer(eof_buf, sizeof(eof_buf)), 0);
104 
105   std::string compressed(reinterpret_cast<char*>(buf.get()), compressed_bytes);
106 
107   ASSERT_GE(compressed.size(), kGzipHeaderSize);
108   EXPECT_EQ(compressed[0], '\37');
109   EXPECT_EQ(compressed[1], '\213');
110   EXPECT_EQ(compressed[2], Z_DEFLATED);
111 
112   std::string decompressed;
113   ASSERT_NO_FATAL_FAILURE(
114       GzipInflate(compressed, &decompressed, string.size()));
115 
116   EXPECT_EQ(decompressed, string);
117 
118   // In block mode, compression should be identical.
119   string_stream.reset(new StringHTTPBodyStream(string));
120   GzipHTTPBodyStream block_gzip_stream(std::move(string_stream));
121   uint8_t block_buf[4096];
122   std::string block_compressed;
123   FileOperationResult block_compressed_bytes;
124   while ((block_compressed_bytes = block_gzip_stream.GetBytesBuffer(
125               block_buf, sizeof(block_buf))) > 0) {
126     block_compressed.append(reinterpret_cast<char*>(block_buf),
127                             block_compressed_bytes);
128   }
129   ASSERT_EQ(block_compressed_bytes, 0);
130   EXPECT_EQ(block_compressed, compressed);
131 }
132 
MakeString(size_t size)133 std::string MakeString(size_t size) {
134   std::string string;
135   for (size_t i = 0; i < size; ++i) {
136     string.append(1, (i % 256) ^ ((i >> 8) % 256));
137   }
138   return string;
139 }
140 
141 constexpr size_t kFourKBytes = 4096;
142 constexpr size_t kManyBytes = 375017;
143 
TEST(GzipHTTPBodyStream,Empty)144 TEST(GzipHTTPBodyStream, Empty) {
145   TestGzipDeflateInflate(std::string());
146 }
147 
TEST(GzipHTTPBodyStream,OneByte)148 TEST(GzipHTTPBodyStream, OneByte) {
149   TestGzipDeflateInflate(std::string("Z"));
150 }
151 
TEST(GzipHTTPBodyStream,FourKBytes_NUL)152 TEST(GzipHTTPBodyStream, FourKBytes_NUL) {
153   TestGzipDeflateInflate(std::string(kFourKBytes, '\0'));
154 }
155 
TEST(GzipHTTPBodyStream,ManyBytes_NUL)156 TEST(GzipHTTPBodyStream, ManyBytes_NUL) {
157   TestGzipDeflateInflate(std::string(kManyBytes, '\0'));
158 }
159 
TEST(GzipHTTPBodyStream,FourKBytes_Deterministic)160 TEST(GzipHTTPBodyStream, FourKBytes_Deterministic) {
161   TestGzipDeflateInflate(MakeString(kFourKBytes));
162 }
163 
TEST(GzipHTTPBodyStream,ManyBytes_Deterministic)164 TEST(GzipHTTPBodyStream, ManyBytes_Deterministic) {
165   TestGzipDeflateInflate(MakeString(kManyBytes));
166 }
167 
TEST(GzipHTTPBodyStream,FourKBytes_Random)168 TEST(GzipHTTPBodyStream, FourKBytes_Random) {
169   TestGzipDeflateInflate(base::RandBytesAsString(kFourKBytes));
170 }
171 
TEST(GzipHTTPBodyStream,ManyBytes_Random)172 TEST(GzipHTTPBodyStream, ManyBytes_Random) {
173   TestGzipDeflateInflate(base::RandBytesAsString(kManyBytes));
174 }
175 
176 }  // namespace
177 }  // namespace test
178 }  // namespace crashpad
179