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

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

Issue 2334773002: Add net::GzipSourceStream (Closed)
Patch Set: self review sync-ed to r417929 Created 4 years, 3 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
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> previous,
30 GzipSourceStreamMode mode) {
31 std::unique_ptr<GzipSourceStream> source(
32 new GzipSourceStream(std::move(previous), mode));
33
34 if (!source->Init())
35 return nullptr;
36 return source;
37 }
38
39 GzipSourceStream::GzipSourceStream(std::unique_ptr<SourceStream> previous,
40 GzipSourceStreamMode mode)
41 : FilterSourceStream(SourceStream::TYPE_GZIP, std::move(previous)),
42 mode_(mode),
43 zlib_eof_(false),
44 zlib_header_added_(false),
45 should_check_gzip_header_(true),
46 gzip_footer_bytes_left_(0) {}
47
48 bool GzipSourceStream::Init() {
49 zlib_stream_.reset(new z_stream);
50 if (!zlib_stream_)
51 return false;
52 memset(zlib_stream_.get(), 0, sizeof(z_stream));
53
54 if (mode_ == GZIP_SOURCE_STREAM_GZIP ||
55 mode_ == GZIP_SOURCE_STREAM_GZIP_WITH_FALLBACK) {
56 if (inflateInit2(zlib_stream_.get(), -MAX_WBITS) != Z_OK)
57 return false;
58 } else {
59 should_check_gzip_header_ = false;
60 if (inflateInit(zlib_stream_.get()) != Z_OK)
61 return false;
62 }
63 return true;
64 }
65
66 std::string GzipSourceStream::GetTypeAsString() const {
67 switch (type()) {
68 case TYPE_GZIP:
69 return kGzip;
70 case TYPE_GZIP_FALLBACK:
71 return kGzipFallback;
72 case TYPE_DEFLATE:
73 return kDeflate;
74 default:
75 NOTREACHED();
76 return "";
77 }
78 }
79
80 int GzipSourceStream::FilterData(IOBuffer* output_buffer,
81 int output_buffer_size,
82 DrainableIOBuffer* input_buffer) {
83 // If this stream is not really gzipped as detected by
84 // ShouldFallbackToPlain, pretend the zlib stream already ended.
85 if (ShouldFallbackToPlain(input_buffer)) {
86 zlib_eof_ = true;
87 should_check_gzip_header_ = false;
88 }
89
90 // Require a valid gzip header when decompressing a gzip stream.
91 if (should_check_gzip_header_ && IsGzipHeaderInvalid(input_buffer))
92 return ERR_CONTENT_DECODING_FAILED;
93
94 size_t bytes_read =
95 Decompress(output_buffer, output_buffer_size, input_buffer);
96
97 // If there was already some data buffered internally in |buffer_|,
98 // or some output buffered internally in zlib, |Decompress| can succeed
99 // synchronously. If this happens, return right here.
Randy Smith (Not in Mondays) 2016/09/12 20:54:00 Couldn't everything after line 97 here be replaced
xunjieli 2016/09/14 16:44:01 Done.
100 if (bytes_read > 0)
101 return bytes_read;
102
103 // Since Decompress needs more input, it has consumed all existing input.
104 DCHECK_EQ(0, input_buffer->BytesRemaining());
Randy Smith (Not in Mondays) 2016/09/12 20:54:00 This won't necessarily be true if Decompress() ret
xunjieli 2016/09/14 16:44:01 Done. Yes, we might not drain all bytes if there i
105
106 return bytes_read;
107 }
108
109 int GzipSourceStream::Decompress(IOBuffer* output_buffer,
110 size_t output_buffer_size,
111 DrainableIOBuffer* input_buffer) {
112 DCHECK(output_buffer);
113 DCHECK_NE(0u, output_buffer_size);
114
115 if (input_buffer->BytesRemaining() == 0)
116 return 0;
117
118 // If the zlib stream has already ended, pass any further data through.
119 if (zlib_eof_)
120 return Passthrough(output_buffer->data(), output_buffer_size, input_buffer);
121 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_buffer->data());
122 zlib_stream_.get()->avail_in = input_buffer->BytesRemaining();
123 zlib_stream_.get()->next_out = bit_cast<Bytef*>(output_buffer->data());
124 zlib_stream_.get()->avail_out = output_buffer_size;
125
126 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
127
128 // Sometime misconfigured servers omit the zlib header, relying on clients
129 // to splice it back in.
130 if (ret < 0 && !zlib_header_added_) {
131 zlib_header_added_ = true;
132 if (!InsertZlibHeader())
133 return ERR_CONTENT_DECODING_FAILED;
134
135 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_buffer->data());
136 zlib_stream_.get()->avail_in = input_buffer->BytesRemaining();
137 zlib_stream_.get()->next_out = bit_cast<Bytef*>(output_buffer->data());
138 zlib_stream_.get()->avail_out = output_buffer_size;
139
140 ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
141 // TODO(xunjieli): add a histogram to see how often this happens. The
142 // original bug for this behavior was ancient and maybe it doesn't happen
143 // in the wild any more?
144 }
145
146 size_t bytes_used =
147 input_buffer->BytesRemaining() - zlib_stream_.get()->avail_in;
148 size_t bytes_out = output_buffer_size - zlib_stream_.get()->avail_out;
149
150 input_buffer->DidConsume(bytes_used);
151
152 if (ret != Z_STREAM_END && ret != Z_OK)
153 return ERR_CONTENT_DECODING_FAILED;
154
155 // The zlib stream can end before the input stream ends. If this happens,
156 // |Decompress| will pass any further data on untouched.
157 if (ret == Z_STREAM_END) {
158 zlib_eof_ = true;
159 return bytes_out + Passthrough(output_buffer->data() + bytes_out,
160 output_buffer_size - bytes_out,
161 input_buffer);
162 }
163 return bytes_out;
164 }
165
166 size_t GzipSourceStream::Passthrough(char* output_buffer,
167 size_t output_buffer_size,
168 DrainableIOBuffer* input_buffer) {
169 SkipGzipFooterIfNeeded(input_buffer);
170 size_t to_copy = input_buffer->BytesRemaining();
171 if (to_copy > output_buffer_size)
172 to_copy = output_buffer_size;
173 memcpy(output_buffer, input_buffer->data(), to_copy);
174 input_buffer->DidConsume(to_copy);
175 return to_copy;
176 }
177
178 bool GzipSourceStream::InsertZlibHeader() {
179 char dummy_header[] = {0x78, 0x01};
180 char dummy_output[4];
181
182 inflateReset(zlib_stream_.get());
183 zlib_stream_.get()->next_in = bit_cast<Bytef*>(&dummy_header[0]);
184 zlib_stream_.get()->avail_in = sizeof(dummy_header);
185 zlib_stream_.get()->next_out = bit_cast<Bytef*>(&dummy_output[0]);
186 zlib_stream_.get()->avail_out = sizeof(dummy_output);
187
188 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
189 return ret == Z_OK;
190 }
191
192 bool GzipSourceStream::IsGzipHeaderInvalid(DrainableIOBuffer* input_buffer) {
193 const size_t kGzipFooterBytes = 8;
194 const char* end = nullptr;
195 GZipHeader::Status status = gzip_header_.ReadMore(
196 input_buffer->data(), input_buffer->BytesRemaining(), &end);
197 if (status == GZipHeader::INCOMPLETE_HEADER) {
198 input_buffer->DidConsume(input_buffer->BytesRemaining());
199 return false;
200 }
201
202 should_check_gzip_header_ = false;
203 if (status == GZipHeader::COMPLETE_HEADER) {
204 // If there is a valid header, there should also be a valid footer.
205 gzip_footer_bytes_left_ = kGzipFooterBytes;
206 input_buffer->DidConsume(end - input_buffer->data());
207 }
208
209 return status == GZipHeader::INVALID_HEADER;
210 }
211
212 // Dumb heuristic. Gzip files always start with a two-byte magic value per RFC
213 // 1952 2.3.1, so if the first byte isn't the first byte of the gzip magic, and
214 // this filter is checking whether it should fallback, then fallback.
215 bool GzipSourceStream::ShouldFallbackToPlain(DrainableIOBuffer* input_buffer) {
Randy Smith (Not in Mondays) 2016/09/12 20:54:01 Could we not pass a DrainableIOBuffer to this func
Randy Smith (Not in Mondays) 2016/09/12 20:54:01 I'm also concerned that the interface contract for
xunjieli 2016/09/14 16:44:01 Done.
xunjieli 2016/09/14 16:44:01 Done.
216 static const char kGzipFirstByte = 0x1f;
217 if (mode_ != GZIP_SOURCE_STREAM_GZIP_WITH_FALLBACK)
218 return false;
219 if (!should_check_gzip_header_)
220 return false;
221 if (input_buffer->BytesRemaining() == 0)
222 return false;
223 return input_buffer->data()[0] != kGzipFirstByte;
224 }
225
226 void GzipSourceStream::SkipGzipFooterIfNeeded(DrainableIOBuffer* input_buffer) {
227 if (gzip_footer_bytes_left_ == 0)
228 return;
229 size_t to_read = gzip_footer_bytes_left_;
230 if (to_read > base::checked_cast<size_t>(input_buffer->BytesRemaining()))
231 to_read = input_buffer->BytesRemaining();
232 input_buffer->DidConsume(to_read);
233 gzip_footer_bytes_left_ -= to_read;
234 }
235
236 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698