1 // Copyright 2014 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.
5 #include "net/filter/gzip_filter.h"
7 #include "base/logging.h"
8 #include "net/filter/gzip_header.h"
9 #include "third_party/zlib/zlib.h"
13 GZipFilter::GZipFilter()
14 : decoding_status_(DECODING_UNINITIALIZED
),
15 decoding_mode_(DECODE_MODE_UNKNOWN
),
16 gzip_header_status_(GZIP_CHECK_HEADER_IN_PROGRESS
),
17 zlib_header_added_(false),
18 gzip_footer_bytes_(0),
19 possible_sdch_pass_through_(false) {
22 GZipFilter::~GZipFilter() {
23 if (decoding_status_
!= DECODING_UNINITIALIZED
) {
24 inflateEnd(zlib_stream_
.get());
28 bool GZipFilter::InitDecoding(Filter::FilterType filter_type
) {
29 if (decoding_status_
!= DECODING_UNINITIALIZED
)
32 // Initialize zlib control block
33 zlib_stream_
.reset(new z_stream
);
34 if (!zlib_stream_
.get())
36 memset(zlib_stream_
.get(), 0, sizeof(z_stream
));
39 switch (filter_type
) {
40 case Filter::FILTER_TYPE_DEFLATE
: {
41 if (inflateInit(zlib_stream_
.get()) != Z_OK
)
43 decoding_mode_
= DECODE_MODE_DEFLATE
;
46 case Filter::FILTER_TYPE_GZIP_HELPING_SDCH
:
47 possible_sdch_pass_through_
= true; // Needed to optionally help sdch.
48 // Fall through to GZIP case.
49 case Filter::FILTER_TYPE_GZIP
: {
50 gzip_header_
.reset(new GZipHeader());
51 if (!gzip_header_
.get())
53 if (inflateInit2(zlib_stream_
.get(), -MAX_WBITS
) != Z_OK
)
55 decoding_mode_
= DECODE_MODE_GZIP
;
63 decoding_status_
= DECODING_IN_PROGRESS
;
67 Filter::FilterStatus
GZipFilter::ReadFilteredData(char* dest_buffer
,
69 if (!dest_buffer
|| !dest_len
|| *dest_len
<= 0)
70 return Filter::FILTER_ERROR
;
72 if (decoding_status_
== DECODING_DONE
) {
73 if (GZIP_GET_INVALID_HEADER
!= gzip_header_status_
)
75 // Some server might send extra data after the gzip footer. We just copy
76 // them out. Mozilla does this too.
77 return CopyOut(dest_buffer
, dest_len
);
80 if (decoding_status_
!= DECODING_IN_PROGRESS
)
81 return Filter::FILTER_ERROR
;
83 Filter::FilterStatus status
;
85 if (decoding_mode_
== DECODE_MODE_GZIP
&&
86 gzip_header_status_
== GZIP_CHECK_HEADER_IN_PROGRESS
) {
87 // With gzip encoding the content is wrapped with a gzip header.
88 // We need to parse and verify the header first.
89 status
= CheckGZipHeader();
91 case Filter::FILTER_NEED_MORE_DATA
: {
92 // We have consumed all input data, either getting a complete header or
93 // a partial header. Return now to get more data.
95 // Partial header means it can't be an SDCH header.
96 // Reason: SDCH *always* starts with 8 printable characters [a-zA-Z/_].
97 // Gzip always starts with two non-printable characters. Hence even a
98 // single character (partial header) means that this can't be an SDCH
99 // encoded body masquerading as a GZIP body.
100 possible_sdch_pass_through_
= false;
103 case Filter::FILTER_OK
: {
104 // The header checking succeeds, and there are more data in the input.
105 // We must have got a complete header here.
106 DCHECK_EQ(gzip_header_status_
, GZIP_GET_COMPLETE_HEADER
);
109 case Filter::FILTER_ERROR
: {
110 if (possible_sdch_pass_through_
&&
111 GZIP_GET_INVALID_HEADER
== gzip_header_status_
) {
112 decoding_status_
= DECODING_DONE
; // Become a pass through filter.
113 return CopyOut(dest_buffer
, dest_len
);
115 decoding_status_
= DECODING_ERROR
;
119 status
= Filter::FILTER_ERROR
; // Unexpected.
120 decoding_status_
= DECODING_ERROR
;
126 int dest_orig_size
= *dest_len
;
127 status
= DoInflate(dest_buffer
, dest_len
);
129 if (decoding_mode_
== DECODE_MODE_DEFLATE
&& status
== Filter::FILTER_ERROR
) {
130 // As noted in Mozilla implementation, some servers such as Apache with
131 // mod_deflate don't generate zlib headers.
132 // See 677409 for instances where this work around is needed.
133 // Insert a dummy zlib header and try again.
134 if (InsertZlibHeader()) {
135 *dest_len
= dest_orig_size
;
136 status
= DoInflate(dest_buffer
, dest_len
);
140 if (status
== Filter::FILTER_DONE
) {
141 decoding_status_
= DECODING_DONE
;
142 } else if (status
== Filter::FILTER_ERROR
) {
143 decoding_status_
= DECODING_ERROR
;
149 Filter::FilterStatus
GZipFilter::CheckGZipHeader() {
150 DCHECK_EQ(gzip_header_status_
, GZIP_CHECK_HEADER_IN_PROGRESS
);
152 // Check input data in pre-filter buffer.
153 if (!next_stream_data_
|| stream_data_len_
<= 0)
154 return Filter::FILTER_ERROR
;
156 const char* header_end
= NULL
;
157 GZipHeader::Status header_status
;
158 header_status
= gzip_header_
->ReadMore(next_stream_data_
, stream_data_len_
,
161 switch (header_status
) {
162 case GZipHeader::INCOMPLETE_HEADER
: {
163 // We read all the data but only got a partial header.
164 next_stream_data_
= NULL
;
165 stream_data_len_
= 0;
166 return Filter::FILTER_NEED_MORE_DATA
;
168 case GZipHeader::COMPLETE_HEADER
: {
169 // We have a complete header. Check whether there are more data.
170 int num_chars_left
= static_cast<int>(stream_data_len_
-
171 (header_end
- next_stream_data_
));
172 gzip_header_status_
= GZIP_GET_COMPLETE_HEADER
;
174 if (num_chars_left
> 0) {
175 next_stream_data_
= const_cast<char*>(header_end
);
176 stream_data_len_
= num_chars_left
;
177 return Filter::FILTER_OK
;
179 next_stream_data_
= NULL
;
180 stream_data_len_
= 0;
181 return Filter::FILTER_NEED_MORE_DATA
;
184 case GZipHeader::INVALID_HEADER
: {
185 gzip_header_status_
= GZIP_GET_INVALID_HEADER
;
186 return Filter::FILTER_ERROR
;
193 return Filter::FILTER_ERROR
;
196 Filter::FilterStatus
GZipFilter::DoInflate(char* dest_buffer
, int* dest_len
) {
197 // Make sure we have both valid input data and output buffer.
198 if (!dest_buffer
|| !dest_len
|| *dest_len
<= 0) // output
199 return Filter::FILTER_ERROR
;
201 if (!next_stream_data_
|| stream_data_len_
<= 0) { // input
203 return Filter::FILTER_NEED_MORE_DATA
;
206 // Fill in zlib control block
207 zlib_stream_
.get()->next_in
= bit_cast
<Bytef
*>(next_stream_data_
);
208 zlib_stream_
.get()->avail_in
= stream_data_len_
;
209 zlib_stream_
.get()->next_out
= bit_cast
<Bytef
*>(dest_buffer
);
210 zlib_stream_
.get()->avail_out
= *dest_len
;
212 int inflate_code
= inflate(zlib_stream_
.get(), Z_NO_FLUSH
);
213 int bytesWritten
= *dest_len
- zlib_stream_
.get()->avail_out
;
215 Filter::FilterStatus status
;
217 switch (inflate_code
) {
219 *dest_len
= bytesWritten
;
221 stream_data_len_
= zlib_stream_
.get()->avail_in
;
222 next_stream_data_
= bit_cast
<char*>(zlib_stream_
.get()->next_in
);
226 status
= Filter::FILTER_DONE
;
230 // According to zlib documentation, when calling inflate with Z_NO_FLUSH,
231 // getting Z_BUF_ERROR means no progress is possible. Neither processing
232 // more input nor producing more output can be done.
233 // Since we have checked both input data and output buffer before calling
234 // inflate, this result is unexpected.
235 status
= Filter::FILTER_ERROR
;
239 // Some progress has been made (more input processed or more output
241 *dest_len
= bytesWritten
;
243 // Check whether we have consumed all input data.
244 stream_data_len_
= zlib_stream_
.get()->avail_in
;
245 if (stream_data_len_
== 0) {
246 next_stream_data_
= NULL
;
247 status
= Filter::FILTER_NEED_MORE_DATA
;
249 next_stream_data_
= bit_cast
<char*>(zlib_stream_
.get()->next_in
);
250 status
= Filter::FILTER_OK
;
255 status
= Filter::FILTER_ERROR
;
263 bool GZipFilter::InsertZlibHeader() {
264 static char dummy_head
[2] = { 0x78, 0x1 };
266 char dummy_output
[4];
268 // We only try add additional header once.
269 if (zlib_header_added_
)
272 inflateReset(zlib_stream_
.get());
273 zlib_stream_
.get()->next_in
= bit_cast
<Bytef
*>(&dummy_head
[0]);
274 zlib_stream_
.get()->avail_in
= sizeof(dummy_head
);
275 zlib_stream_
.get()->next_out
= bit_cast
<Bytef
*>(&dummy_output
[0]);
276 zlib_stream_
.get()->avail_out
= sizeof(dummy_output
);
278 int code
= inflate(zlib_stream_
.get(), Z_NO_FLUSH
);
279 zlib_header_added_
= true;
281 return (code
== Z_OK
);
285 void GZipFilter::SkipGZipFooter() {
286 int footer_bytes_expected
= kGZipFooterSize
- gzip_footer_bytes_
;
287 if (footer_bytes_expected
> 0) {
288 int footer_byte_avail
= std::min(footer_bytes_expected
, stream_data_len_
);
289 stream_data_len_
-= footer_byte_avail
;
290 next_stream_data_
+= footer_byte_avail
;
291 gzip_footer_bytes_
+= footer_byte_avail
;
293 if (stream_data_len_
== 0)
294 next_stream_data_
= NULL
;