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

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

Issue 2604233002: Fix bug in deflate code. (Closed)
Patch Set: Cleanups Created 3 years, 11 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
1 // Copyright 2016 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "net/filter/gzip_source_stream.h" 5 #include "net/filter/gzip_source_stream.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <utility> 8 #include <utility>
9 9
10 #include "base/bind.h" 10 #include "base/bind.h"
11 #include "base/bit_cast.h" 11 #include "base/bit_cast.h"
12 #include "base/logging.h" 12 #include "base/logging.h"
13 #include "net/base/io_buffer.h" 13 #include "net/base/io_buffer.h"
14 #include "third_party/zlib/zlib.h" 14 #include "third_party/zlib/zlib.h"
15 15
16 namespace net { 16 namespace net {
17 17
18 namespace { 18 namespace {
19 19
20 const char kDeflate[] = "DEFLATE"; 20 const char kDeflate[] = "DEFLATE";
21 const char kGzip[] = "GZIP"; 21 const char kGzip[] = "GZIP";
22 const char kGzipFallback[] = "GZIP_FALLBACK"; 22 const char kGzipFallback[] = "GZIP_FALLBACK";
23 23
24 // For deflate streams, if more than this many bytes have been received without
25 // an error and without adding a Zlib header, assume the original stream had a
26 // Zlib header. In practice, don't need nearly this much data, but since the
27 // detection logic is a heuristic, best to be safe. Data is freed once it's been
28 // determined whether the stream has a zlib header or not, so larger values
29 // shouldn't affect memory usage, in practice.
30 const int kMaxZlibHeaderSniffBytes = 1000;
xunjieli 2017/01/03 16:21:17 Do you mean 100 instead of 1000? In the linked bug
mmenke 2017/01/03 18:25:41 I meant 1000, out of paranoia (And because the cos
xunjieli 2017/01/03 18:52:03 Acknowledged.
31
24 } // namespace 32 } // namespace
25 33
26 GzipSourceStream::~GzipSourceStream() { 34 GzipSourceStream::~GzipSourceStream() {
27 if (zlib_stream_) 35 if (zlib_stream_)
28 inflateEnd(zlib_stream_.get()); 36 inflateEnd(zlib_stream_.get());
29 } 37 }
30 38
31 std::unique_ptr<GzipSourceStream> GzipSourceStream::Create( 39 std::unique_ptr<GzipSourceStream> GzipSourceStream::Create(
32 std::unique_ptr<SourceStream> upstream, 40 std::unique_ptr<SourceStream> upstream,
33 SourceStream::SourceType type) { 41 SourceStream::SourceType type) {
34 std::unique_ptr<GzipSourceStream> source( 42 std::unique_ptr<GzipSourceStream> source(
35 new GzipSourceStream(std::move(upstream), type)); 43 new GzipSourceStream(std::move(upstream), type));
36 44
37 if (!source->Init()) 45 if (!source->Init())
38 return nullptr; 46 return nullptr;
39 return source; 47 return source;
40 } 48 }
41 49
42 GzipSourceStream::GzipSourceStream(std::unique_ptr<SourceStream> upstream, 50 GzipSourceStream::GzipSourceStream(std::unique_ptr<SourceStream> upstream,
43 SourceStream::SourceType type) 51 SourceStream::SourceType type)
44 : FilterSourceStream(type, std::move(upstream)), 52 : FilterSourceStream(type, std::move(upstream)),
45 zlib_header_added_(false),
46 gzip_footer_bytes_left_(0), 53 gzip_footer_bytes_left_(0),
47 input_state_(STATE_START) {} 54 input_state_(STATE_START),
55 replay_state_(STATE_COMPRESSED_BODY) {}
48 56
49 bool GzipSourceStream::Init() { 57 bool GzipSourceStream::Init() {
50 zlib_stream_.reset(new z_stream); 58 zlib_stream_.reset(new z_stream);
51 if (!zlib_stream_) 59 if (!zlib_stream_)
52 return false; 60 return false;
53 memset(zlib_stream_.get(), 0, sizeof(z_stream)); 61 memset(zlib_stream_.get(), 0, sizeof(z_stream));
54 62
55 int ret; 63 int ret;
56 if (type() == TYPE_GZIP || type() == TYPE_GZIP_FALLBACK) { 64 if (type() == TYPE_GZIP || type() == TYPE_GZIP_FALLBACK) {
57 ret = inflateInit2(zlib_stream_.get(), -MAX_WBITS); 65 ret = inflateInit2(zlib_stream_.get(), -MAX_WBITS);
(...skipping 16 matching lines...) Expand all
74 NOTREACHED(); 82 NOTREACHED();
75 return ""; 83 return "";
76 } 84 }
77 } 85 }
78 86
79 int GzipSourceStream::FilterData(IOBuffer* output_buffer, 87 int GzipSourceStream::FilterData(IOBuffer* output_buffer,
80 int output_buffer_size, 88 int output_buffer_size,
81 IOBuffer* input_buffer, 89 IOBuffer* input_buffer,
82 int input_buffer_size, 90 int input_buffer_size,
83 int* consumed_bytes, 91 int* consumed_bytes,
84 bool /*upstream_end_reached*/) { 92 bool upstream_end_reached) {
85 *consumed_bytes = 0; 93 *consumed_bytes = 0;
86 char* input_data = input_buffer->data(); 94 char* input_data = input_buffer->data();
87 int input_data_size = input_buffer_size; 95 int input_data_size = input_buffer_size;
88 int bytes_out = 0; 96 int bytes_out = 0;
89 bool state_compressed_entered = false; 97 bool state_compressed_entered = false;
90 while (input_data_size > 0 && bytes_out < output_buffer_size) { 98 while (input_data_size > 0 && bytes_out < output_buffer_size) {
91 InputState state = input_state_; 99 InputState state = input_state_;
92 switch (state) { 100 switch (state) {
93 case STATE_START: { 101 case STATE_START: {
94 if (type() == TYPE_DEFLATE) { 102 if (type() == TYPE_DEFLATE) {
95 input_state_ = STATE_COMPRESSED_BODY; 103 input_state_ = STATE_SNIFFING_DEFLATE_HEADER;
96 break; 104 break;
97 } 105 }
98 // If this stream is not really gzipped as detected by 106 // If this stream is not really gzipped as detected by
99 // ShouldFallbackToPlain, pretend that the zlib stream has ended. 107 // ShouldFallbackToPlain, pretend that the zlib stream has ended.
100 DCHECK_LT(0, input_data_size); 108 DCHECK_LT(0, input_data_size);
101 if (ShouldFallbackToPlain(input_data[0])) { 109 if (ShouldFallbackToPlain(input_data[0])) {
102 input_state_ = STATE_UNCOMPRESSED_BODY; 110 input_state_ = STATE_UNCOMPRESSED_BODY;
103 } else { 111 } else {
104 input_state_ = STATE_GZIP_HEADER; 112 input_state_ = STATE_GZIP_HEADER;
105 } 113 }
106 break; 114 break;
107 } 115 }
108 case STATE_GZIP_HEADER: { 116 case STATE_GZIP_HEADER: {
109 const size_t kGzipFooterBytes = 8; 117 const size_t kGzipFooterBytes = 8;
110 const char* end = nullptr; 118 const char* end = nullptr;
111 GZipHeader::Status status = 119 GZipHeader::Status status =
112 gzip_header_.ReadMore(input_data, input_data_size, &end); 120 gzip_header_.ReadMore(input_data, input_data_size, &end);
113 if (status == GZipHeader::INCOMPLETE_HEADER) { 121 if (status == GZipHeader::INCOMPLETE_HEADER) {
114 input_data += input_data_size; 122 input_data += input_data_size;
115 input_data_size = 0; 123 input_data_size = 0;
116 } else if (status == GZipHeader::COMPLETE_HEADER) { 124 } else if (status == GZipHeader::COMPLETE_HEADER) {
117 // If there is a valid header, there should also be a valid footer. 125 // If there is a valid header, there should also be a valid footer.
118 gzip_footer_bytes_left_ = kGzipFooterBytes; 126 gzip_footer_bytes_left_ = kGzipFooterBytes;
119 int bytes_consumed = end - input_data; 127 int bytes_consumed = end - input_data;
120 input_data += bytes_consumed; 128 input_data += bytes_consumed;
121 input_data_size -= bytes_consumed; 129 input_data_size -= bytes_consumed;
122 input_state_ = STATE_COMPRESSED_BODY; 130 input_state_ = STATE_COMPRESSED_BODY;
mmenke 2016/12/29 22:39:31 This used to be able to go through the add zlib he
xunjieli 2017/01/03 16:21:17 Acknowledged. You are right. The old state change
123 } else if (status == GZipHeader::INVALID_HEADER) { 131 } else if (status == GZipHeader::INVALID_HEADER) {
124 return ERR_CONTENT_DECODING_FAILED; 132 return ERR_CONTENT_DECODING_FAILED;
125 } 133 }
126 break; 134 break;
127 } 135 }
128 case STATE_COMPRESSED_BODY: { 136 case STATE_SNIFFING_DEFLATE_HEADER: {
129 DCHECK(!state_compressed_entered);
130 DCHECK_LE(0, input_data_size);
131
132 state_compressed_entered = true;
133 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_data); 137 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_data);
134 zlib_stream_.get()->avail_in = input_data_size; 138 zlib_stream_.get()->avail_in = input_data_size;
135 zlib_stream_.get()->next_out = bit_cast<Bytef*>(output_buffer->data()); 139 zlib_stream_.get()->next_out = bit_cast<Bytef*>(output_buffer->data());
136 zlib_stream_.get()->avail_out = output_buffer_size; 140 zlib_stream_.get()->avail_out = output_buffer_size;
137 141
138 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH); 142 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
139 143
140 // Sometimes misconfigured servers omit the zlib header, relying on 144 // On error, try adding a zlib header and replaying the response. Note
141 // clients to splice it back in. 145 // that data just received doesn't have to be replayed, since it hasn't
142 if (ret < 0 && !zlib_header_added_) { 146 // been removed from input_data yet, only data from previous FilterData
143 zlib_header_added_ = true; 147 // calls needs to be replayed.
148 if (ret != Z_STREAM_END && ret != Z_OK) {
144 if (!InsertZlibHeader()) 149 if (!InsertZlibHeader())
145 return ERR_CONTENT_DECODING_FAILED; 150 return ERR_CONTENT_DECODING_FAILED;
151 input_state_ = STATE_REPLAY_DATA;
152 continue;
xunjieli 2017/01/03 16:21:17 I believe this is equivalent to "break". If so, ca
mmenke 2017/01/03 18:25:42 Done.
153 }
146 154
147 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_data); 155 int bytes_used = input_data_size - zlib_stream_.get()->avail_in;
148 zlib_stream_.get()->avail_in = input_data_size; 156 bytes_out = output_buffer_size - zlib_stream_.get()->avail_out;
149 zlib_stream_.get()->next_out = 157 // If any bytes are output, enough total bytes have been received, or at
150 bit_cast<Bytef*>(output_buffer->data()); 158 // the end of the stream, assume the response had a valid Zlib header.
151 zlib_stream_.get()->avail_out = output_buffer_size; 159 if (bytes_out > 0 ||
160 bytes_used + replay_data_.size() >= kMaxZlibHeaderSniffBytes ||
161 ret == Z_STREAM_END) {
162 std::move(replay_data_);
163 if (ret == Z_STREAM_END) {
164 input_state_ = STATE_GZIP_FOOTER;
165 } else {
166 input_state_ = STATE_COMPRESSED_BODY;
167 }
168 } else {
169 replay_data_.append(input_data, bytes_used);
170 }
152 171
153 ret = inflate(zlib_stream_.get(), Z_NO_FLUSH); 172 input_data_size -= bytes_used;
154 // TODO(xunjieli): add a histogram to see how often this happens. The 173 input_data += bytes_used;
155 // original bug for this behavior was ancient and maybe it doesn't 174 break;
156 // happen in the wild any more? crbug.com/649339 175 }
176 case STATE_REPLAY_DATA: {
xunjieli 2017/01/03 16:21:17 nit: I am assuming this state only applies to defl
mmenke 2017/01/03 18:25:41 Done.
177 if (!replay_data_.size()) {
mmenke 2016/12/29 22:39:31 Worth noting while we do go through both branches
xunjieli 2017/01/03 16:21:17 nit: Can we not enter STATE_REPLAY_DATA, if |repla
mmenke 2017/01/03 18:25:42 Done (x2). You're certainly right about empty(),
178 std::move(replay_data_);
179 input_state_ = replay_state_;
180 continue;
157 } 181 }
182
183 // Call FilterData recursively, after updating |input_state_|, with
184 // |replay_data_|. This recursive call makes handling data from
185 // |replay_data_| and |input_buffer| much simpler than the alternative
186 // operations, though it's not pretty.
187 input_state_ = replay_state_;
188 int bytes_used;
189 int result =
190 FilterData(output_buffer, output_buffer_size,
191 new WrappedIOBuffer(replay_data_.data()),
192 replay_data_.size(), &bytes_used, upstream_end_reached);
193 replay_data_.erase(0, bytes_used);
194 // Back up resulting state, and return state to STATE_REPLAY_DATA.
195 replay_state_ = input_state_;
196 input_state_ = STATE_REPLAY_DATA;
197
198 // On error, or if bytes were read, just return result immediately.
199 // Could continue consuming data in the success case, but simplest not
200 // to.
201 if (result != 0)
202 return result;
mmenke 2016/12/29 22:39:31 No unit tests exercise this line, unfortunately.
xunjieli 2017/01/03 16:21:17 Acknowledged.
203 continue;
xunjieli 2017/01/03 16:21:17 I believe this is equivalent to "break". If so, ca
mmenke 2017/01/03 18:25:41 Done.
204 }
205 case STATE_COMPRESSED_BODY: {
206 DCHECK(!state_compressed_entered);
207 DCHECK_LE(0, input_data_size);
208
209 state_compressed_entered = true;
210 zlib_stream_.get()->next_in = bit_cast<Bytef*>(input_data);
211 zlib_stream_.get()->avail_in = input_data_size;
212 zlib_stream_.get()->next_out = bit_cast<Bytef*>(output_buffer->data());
213 zlib_stream_.get()->avail_out = output_buffer_size;
214
215 int ret = inflate(zlib_stream_.get(), Z_NO_FLUSH);
158 if (ret != Z_STREAM_END && ret != Z_OK) 216 if (ret != Z_STREAM_END && ret != Z_OK)
159 return ERR_CONTENT_DECODING_FAILED; 217 return ERR_CONTENT_DECODING_FAILED;
160 218
161 int bytes_used = input_data_size - zlib_stream_.get()->avail_in; 219 int bytes_used = input_data_size - zlib_stream_.get()->avail_in;
162 bytes_out = output_buffer_size - zlib_stream_.get()->avail_out; 220 bytes_out = output_buffer_size - zlib_stream_.get()->avail_out;
163 input_data_size -= bytes_used; 221 input_data_size -= bytes_used;
164 input_data += bytes_used; 222 input_data += bytes_used;
165 if (ret == Z_STREAM_END) 223 if (ret == Z_STREAM_END)
166 input_state_ = STATE_GZIP_FOOTER; 224 input_state_ = STATE_GZIP_FOOTER;
167 // zlib has written as much data to |output_buffer| as it could. 225 // zlib has written as much data to |output_buffer| as it could.
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
211 // 1952 2.3.1, so if the first byte isn't the first byte of the gzip magic, and 269 // 1952 2.3.1, so if the first byte isn't the first byte of the gzip magic, and
212 // this filter is checking whether it should fallback, then fallback. 270 // this filter is checking whether it should fallback, then fallback.
213 bool GzipSourceStream::ShouldFallbackToPlain(char first_byte) { 271 bool GzipSourceStream::ShouldFallbackToPlain(char first_byte) {
214 if (type() != TYPE_GZIP_FALLBACK) 272 if (type() != TYPE_GZIP_FALLBACK)
215 return false; 273 return false;
216 static const char kGzipFirstByte = 0x1f; 274 static const char kGzipFirstByte = 0x1f;
217 return first_byte != kGzipFirstByte; 275 return first_byte != kGzipFirstByte;
218 } 276 }
219 277
220 } // namespace net 278 } // namespace net
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698