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

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

Issue 1662763002: [ON HOLD] Implement pull-based design for content decoding (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: rebased Created 4 years, 4 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 "third_party/zlib/zlib.h"
11
12 namespace net {
13
14 namespace {
15
16 const char kDeflate[] = "DEFLATE";
17 const char kGzip[] = "GZIP";
18 const char kGzipFallback[] = "GZIP_FALLBACK";
19
20 } // namespace
21
22 GzipSourceStream::~GzipSourceStream() {
23 if (zlib_stream_)
24 inflateEnd(zlib_stream_.get());
25 }
26
27 std::unique_ptr<GzipSourceStream> GzipSourceStream::Create(
28 std::unique_ptr<SourceStream> previous,
29 GzipSourceStreamMode mode) {
30 std::unique_ptr<GzipSourceStream> source(
31 new GzipSourceStream(std::move(previous), mode));
32
33 if (!source->Init())
34 return nullptr;
35 return source;
36 }
37
38 GzipSourceStream::GzipSourceStream(std::unique_ptr<SourceStream> previous,
39 GzipSourceStreamMode mode)
40 : FilterSourceStream(SourceStream::TYPE_GZIP, std::move(previous)),
41 mode_(mode),
42 zlib_eof_(false),
43 zlib_header_added_(false),
44 should_check_gzip_header_(true),
45 gzip_footer_bytes_left_(0) {}
46
47 bool GzipSourceStream::Init() {
48 zlib_stream_.reset(new z_stream);
49 if (!zlib_stream_)
50 return false;
51 memset(zlib_stream_.get(), 0, sizeof(z_stream));
52
53 if (mode_ == GZIP_SOURCE_STREAM_GZIP ||
54 mode_ == GZIP_SOURCE_STREAM_GZIP_WITH_FALLBACK) {
55 if (inflateInit2(zlib_stream_.get(), -MAX_WBITS) != Z_OK)
56 return false;
57 } else {
58 should_check_gzip_header_ = false;
59 if (inflateInit(zlib_stream_.get()) != Z_OK)
60 return false;
61 }
62 return true;
63 }
64
65 std::string GzipSourceStream::GetTypeAsString() const {
66 switch (type()) {
67 case TYPE_GZIP:
68 return kGzip;
69 case TYPE_GZIP_FALLBACK:
70 return kGzipFallback;
71 case TYPE_DEFLATE:
72 return kDeflate;
73 default:
74 NOTREACHED();
75 return "";
76 }
77 }
78
79 int GzipSourceStream::FilterData(IOBuffer* output_buffer,
80 size_t output_buffer_size,
81 DrainableIOBuffer* input_buffer) {
82 // If this stream is not really gzipped as detected by
83 // ShouldFallbackToPlain, pretend the zlib stream already ended.
84 if (ShouldFallbackToPlain(input_buffer)) {
85 zlib_eof_ = true;
86 should_check_gzip_header_ = false;
87 }
88
89 // Require a valid gzip header when decompressing a gzip stream.
90 if (should_check_gzip_header_ && IsGzipHeaderInvalid(input_buffer))
91 return ERR_CONTENT_DECODING_FAILED;
92
93 size_t bytes_read =
94 Decompress(output_buffer, output_buffer_size, input_buffer);
95
96 // If there was already some data buffered internally in |buffer_|,
97 // or some output buffered internally in zlib, |Decompress| can succeed
98 // synchronously. If this happens, return right here.
99 if (bytes_read > 0)
100 return bytes_read;
101
102 // Since Decompress needs more input, it has consumed all existing input.
103 DCHECK_EQ(0, input_buffer->BytesRemaining());
104
105 return bytes_read;
106 }
107
108 int GzipSourceStream::Decompress(IOBuffer* output_buffer,
109 size_t output_buffer_size,
110 DrainableIOBuffer* input_buffer) {
111 DCHECK(output_buffer);
112 DCHECK_NE(0u, output_buffer_size);
113
114 if (input_buffer->BytesRemaining() == 0)
115 return 0;
116
117 // If the zlib stream has already ended, pass any further data through.
118 if (zlib_eof_)
119 return Passthrough(output_buffer->data(), output_buffer_size, input_buffer);
120 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_buffer->data());
121 zlib_stream_.get()->avail_in = input_buffer->BytesRemaining();
122 zlib_stream_.get()->next_out = bit_cast<Bytef*>(output_buffer->data());
123 zlib_stream_.get()->avail_out = output_buffer_size;
124
125 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
126
127 // Sometime misconfigured servers omit the zlib header, relying on clients
128 // to splice it back in.
129 if (ret < 0 && !zlib_header_added_) {
130 zlib_header_added_ = true;
131 if (!InsertZlibHeader())
132 return ERR_CONTENT_DECODING_FAILED;
133
134 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_buffer->data());
135 zlib_stream_.get()->avail_in = input_buffer->BytesRemaining();
136 zlib_stream_.get()->next_out = bit_cast<Bytef*>(output_buffer->data());
137 zlib_stream_.get()->avail_out = output_buffer_size;
138
139 ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
140 // TODO(xunjieli): add a histogram to see how often this happens. The
141 // original bug for this behavior was ancient and maybe it doesn't happen
142 // in the wild any more?
143 }
144
145 size_t bytes_used =
146 input_buffer->BytesRemaining() - zlib_stream_.get()->avail_in;
147 size_t bytes_out = output_buffer_size - zlib_stream_.get()->avail_out;
148
149 input_buffer->DidConsume(bytes_used);
150
151 if (ret != Z_STREAM_END && ret != Z_OK)
152 return ERR_CONTENT_DECODING_FAILED;
153
154 // The zlib stream can end before the input stream ends. If this happens,
155 // |Decompress| will pass any further data on untouched.
156 if (ret == Z_STREAM_END) {
157 zlib_eof_ = true;
158 return bytes_out + Passthrough(output_buffer->data() + bytes_out,
159 output_buffer_size - bytes_out,
160 input_buffer);
161 }
162 return bytes_out;
163 }
164
165 size_t GzipSourceStream::Passthrough(char* output_buffer,
166 size_t output_buffer_size,
167 DrainableIOBuffer* input_buffer) {
168 SkipGzipFooterIfNeeded(input_buffer);
169 size_t to_copy = input_buffer->BytesRemaining();
170 if (to_copy > output_buffer_size)
171 to_copy = output_buffer_size;
172 memcpy(output_buffer, input_buffer->data(), to_copy);
173 input_buffer->DidConsume(to_copy);
174 return to_copy;
175 }
176
177 bool GzipSourceStream::InsertZlibHeader() {
178 char dummy_header[] = {0x78, 0x01};
179 char dummy_output[4];
180
181 inflateReset(zlib_stream_.get());
182 zlib_stream_.get()->next_in = bit_cast<Bytef*>(&dummy_header[0]);
183 zlib_stream_.get()->avail_in = sizeof(dummy_header);
184 zlib_stream_.get()->next_out = bit_cast<Bytef*>(&dummy_output[0]);
185 zlib_stream_.get()->avail_out = sizeof(dummy_output);
186
187 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
188 return ret == Z_OK;
189 }
190
191 bool GzipSourceStream::IsGzipHeaderInvalid(DrainableIOBuffer* input_buffer) {
192 const size_t kGzipFooterBytes = 8;
193 const char* end = nullptr;
194 GZipHeader::Status status = gzip_header_.ReadMore(
195 input_buffer->data(), input_buffer->BytesRemaining(), &end);
196 if (status == GZipHeader::INCOMPLETE_HEADER) {
197 input_buffer->DidConsume(input_buffer->BytesRemaining());
198 return false;
199 }
200
201 should_check_gzip_header_ = false;
202 if (status == GZipHeader::COMPLETE_HEADER) {
203 // If there is a valid header, there should also be a valid footer.
204 gzip_footer_bytes_left_ = kGzipFooterBytes;
205 input_buffer->DidConsume(end - input_buffer->data());
206 }
207
208 return status == GZipHeader::INVALID_HEADER;
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(DrainableIOBuffer* input_buffer) {
215 static const char kGzipFirstByte = 0x1f;
216 if (mode_ != GZIP_SOURCE_STREAM_GZIP_WITH_FALLBACK)
217 return false;
218 if (!should_check_gzip_header_)
219 return false;
220 if (input_buffer->BytesRemaining() == 0)
221 return false;
222 return input_buffer->data()[0] != kGzipFirstByte;
223 }
224
225 void GzipSourceStream::SkipGzipFooterIfNeeded(DrainableIOBuffer* input_buffer) {
226 if (gzip_footer_bytes_left_ == 0)
227 return;
228 size_t to_read = gzip_footer_bytes_left_;
229 if (to_read > base::checked_cast<size_t>(input_buffer->BytesRemaining()))
230 to_read = input_buffer->BytesRemaining();
231 input_buffer->DidConsume(to_read);
232 gzip_footer_bytes_left_ -= to_read;
233 }
234
235 } // 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