Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(752)

Side by Side Diff: net/filter/gzip_source_stream.cc

Issue 2334773002: Add net::GzipSourceStream (Closed)
Patch Set: Address Randy's comments Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « net/filter/gzip_source_stream.h ('k') | net/filter/gzip_source_stream_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/filter/gzip_source_stream.h"
6
7 #include "base/bind.h"
8 #include "base/bit_cast.h"
9 #include "base/logging.h"
10 #include "net/base/io_buffer.h"
11 #include "third_party/zlib/zlib.h"
12
13 namespace net {
14
15 namespace {
16
17 const char kDeflate[] = "DEFLATE";
18 const char kGzip[] = "GZIP";
19 const char kGzipFallback[] = "GZIP_FALLBACK";
20
21 } // namespace
22
23 GzipSourceStream::~GzipSourceStream() {
24 if (zlib_stream_)
25 inflateEnd(zlib_stream_.get());
26 }
27
28 std::unique_ptr<GzipSourceStream> GzipSourceStream::Create(
29 std::unique_ptr<SourceStream> upstream,
30 SourceStream::SourceType type) {
31 std::unique_ptr<GzipSourceStream> source(
32 new GzipSourceStream(std::move(upstream), type));
33
34 if (!source->Init())
35 return nullptr;
36 return source;
37 }
38
39 GzipSourceStream::GzipSourceStream(std::unique_ptr<SourceStream> upstream,
40 SourceStream::SourceType type)
41 : FilterSourceStream(type, std::move(upstream)),
42 zlib_header_added_(false),
43 gzip_footer_bytes_left_(0),
44 input_state_(STATE_START) {}
45
46 bool GzipSourceStream::Init() {
47 zlib_stream_.reset(new z_stream);
48 if (!zlib_stream_)
49 return false;
50 memset(zlib_stream_.get(), 0, sizeof(z_stream));
51
52 int ret;
53 if (type() == TYPE_GZIP || type() == TYPE_GZIP_FALLBACK) {
54 ret = inflateInit2(zlib_stream_.get(), -MAX_WBITS);
55 } else {
56 ret = inflateInit(zlib_stream_.get());
57 }
58 DCHECK_NE(Z_VERSION_ERROR, ret);
59 return ret == Z_OK;
60 }
61
62 std::string GzipSourceStream::GetTypeAsString() const {
63 switch (type()) {
64 case TYPE_GZIP:
65 return kGzip;
66 case TYPE_GZIP_FALLBACK:
67 return kGzipFallback;
68 case TYPE_DEFLATE:
69 return kDeflate;
70 default:
71 NOTREACHED();
72 return "";
73 }
74 }
75
76 int GzipSourceStream::FilterData(IOBuffer* output_buffer,
77 int output_buffer_size,
78 IOBuffer* input_buffer,
79 int input_buffer_size,
80 int* consumed_bytes,
81 bool /*upstream_end_reached*/) {
82 *consumed_bytes = 0;
83 char* input_data = input_buffer->data();
84 int input_data_size = input_buffer_size;
85 int bytes_out = 0;
86 bool state_compressed_entered = false;
87 while (input_data_size > 0) {
88 InputState state = input_state_;
89 switch (state) {
90 case STATE_START: {
91 if (type() == TYPE_DEFLATE) {
92 input_state_ = STATE_COMPRESSED_BODY;
93 break;
94 }
95 // If this stream is not really gzipped as detected by
96 // ShouldFallbackToPlain, pretend that the zlib stream has ended.
97 DCHECK_LT(0, input_data_size);
98 if (ShouldFallbackToPlain(input_data[0])) {
99 input_state_ = STATE_UNCOMPRESSED_BODY;
100 } else {
101 input_state_ = STATE_GZIP_HEADER;
102 }
103 break;
104 }
105 case STATE_GZIP_HEADER: {
106 const size_t kGzipFooterBytes = 8;
107 const char* end = nullptr;
108 GZipHeader::Status status =
109 gzip_header_.ReadMore(input_data, input_data_size, &end);
110 if (status == GZipHeader::INCOMPLETE_HEADER) {
111 input_data += input_data_size;
112 input_data_size = 0;
113 } else if (status == GZipHeader::COMPLETE_HEADER) {
114 // If there is a valid header, there should also be a valid footer.
115 gzip_footer_bytes_left_ = kGzipFooterBytes;
116 int bytes_consumed = end - input_data;
117 input_data += bytes_consumed;
118 input_data_size -= bytes_consumed;
119 input_state_ = STATE_COMPRESSED_BODY;
120 } else if (status == GZipHeader::INVALID_HEADER) {
121 return ERR_CONTENT_DECODING_FAILED;
122 }
123 break;
124 }
125 case STATE_COMPRESSED_BODY: {
126 DCHECK(!state_compressed_entered);
127 DCHECK_LE(0, input_data_size);
128
129 state_compressed_entered = true;
130 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_data);
131 zlib_stream_.get()->avail_in = input_data_size;
132 zlib_stream_.get()->next_out = bit_cast<Bytef*>(output_buffer->data());
133 zlib_stream_.get()->avail_out = output_buffer_size;
134
135 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
136
137 // Sometimes misconfigured servers omit the zlib header, relying on
138 // clients to splice it back in.
139 if (ret < 0 && !zlib_header_added_) {
140 zlib_header_added_ = true;
141 if (!InsertZlibHeader())
142 return ERR_CONTENT_DECODING_FAILED;
143
144 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_data);
145 zlib_stream_.get()->avail_in = input_data_size;
146 zlib_stream_.get()->next_out =
147 bit_cast<Bytef*>(output_buffer->data());
148 zlib_stream_.get()->avail_out = output_buffer_size;
149
150 ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
151 // TODO(xunjieli): add a histogram to see how often this happens. The
152 // original bug for this behavior was ancient and maybe it doesn't
153 // happen in the wild any more? crbug.com/649339
154 }
155 if (ret != Z_STREAM_END && ret != Z_OK)
156 return ERR_CONTENT_DECODING_FAILED;
157
158 int bytes_used = input_data_size - zlib_stream_.get()->avail_in;
159 bytes_out = output_buffer_size - zlib_stream_.get()->avail_out;
160 input_data_size -= bytes_used;
161 input_data += bytes_used;
162 if (ret == Z_STREAM_END) {
163 input_state_ = STATE_GZIP_FOOTER;
164 break;
165 }
166 // Return early here since zlib has written as much data to
167 // |output_buffer| as it could. There might still be some unconsumed
168 // data in |input_buffer| if there is no space in |output_buffer|.
169 DCHECK_EQ(Z_OK, ret);
170 *consumed_bytes = input_buffer_size - input_data_size;
171 return bytes_out;
172 }
173 case STATE_GZIP_FOOTER: {
174 size_t to_read = std::min(gzip_footer_bytes_left_,
175 base::checked_cast<size_t>(input_data_size));
176 gzip_footer_bytes_left_ -= to_read;
177 input_data_size -= to_read;
178 input_data += to_read;
179 if (gzip_footer_bytes_left_ == 0)
180 input_state_ = STATE_UNCOMPRESSED_BODY;
181 break;
182 }
183 case STATE_UNCOMPRESSED_BODY: {
184 int to_copy = std::min(input_data_size, output_buffer_size - bytes_out);
185 memcpy(output_buffer->data() + bytes_out, input_data, to_copy);
186 input_data_size -= to_copy;
187 input_data += to_copy;
188 bytes_out += to_copy;
189 break;
190 }
191 }
192 }
193 *consumed_bytes = input_buffer_size - input_data_size;
194 return bytes_out;
195 }
196
197 bool GzipSourceStream::InsertZlibHeader() {
198 char dummy_header[] = {0x78, 0x01};
199 char dummy_output[4];
200
201 inflateReset(zlib_stream_.get());
202 zlib_stream_.get()->next_in = bit_cast<Bytef*>(&dummy_header[0]);
203 zlib_stream_.get()->avail_in = sizeof(dummy_header);
204 zlib_stream_.get()->next_out = bit_cast<Bytef*>(&dummy_output[0]);
205 zlib_stream_.get()->avail_out = sizeof(dummy_output);
206
207 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
208 return ret == Z_OK;
209 }
210
211 // Dumb heuristic. Gzip files always start with a two-byte magic value per RFC
212 // 1952 2.3.1, so if the first byte isn't the first byte of the gzip magic, and
213 // this filter is checking whether it should fallback, then fallback.
214 bool GzipSourceStream::ShouldFallbackToPlain(char first_byte) {
215 if (type() != TYPE_GZIP_FALLBACK)
216 return false;
217 static const char kGzipFirstByte = 0x1f;
218 return first_byte != kGzipFirstByte;
219 }
220
221 } // namespace net
OLDNEW
« no previous file with comments | « net/filter/gzip_source_stream.h ('k') | net/filter/gzip_source_stream_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698