Home | History | Annotate | Download | only in base
      1 // Copyright (c) 2006-2008 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/base/gzip_filter.h"
      6 
      7 #if defined(USE_SYSTEM_ZLIB)
      8 #include <zlib.h>
      9 // The code below uses the MOZ_Z_ forms of these functions in order that things
     10 // should work on Windows. In order to make this code cross platform, we map
     11 // back to the normal functions here in the case that we are using the system
     12 // zlib.
     13 #define MOZ_Z_inflate inflate
     14 #define MOZ_Z_inflateEnd inflateEnd
     15 #define MOZ_Z_inflateInit2_ inflateInit2_
     16 #define MOZ_Z_inflateInit_ inflateInit_
     17 #define MOZ_Z_inflateReset inflateReset
     18 #else
     19 #include "third_party/zlib/zlib.h"
     20 #endif
     21 
     22 #include "base/logging.h"
     23 #include "net/base/gzip_header.h"
     24 
     25 
     26 GZipFilter::GZipFilter(const FilterContext& filter_context)
     27     : Filter(filter_context),
     28       decoding_status_(DECODING_UNINITIALIZED),
     29       decoding_mode_(DECODE_MODE_UNKNOWN),
     30       gzip_header_status_(GZIP_CHECK_HEADER_IN_PROGRESS),
     31       zlib_header_added_(false),
     32       gzip_footer_bytes_(0),
     33       possible_sdch_pass_through_(false) {
     34 }
     35 
     36 GZipFilter::~GZipFilter() {
     37   if (decoding_status_ != DECODING_UNINITIALIZED) {
     38     MOZ_Z_inflateEnd(zlib_stream_.get());
     39   }
     40 }
     41 
     42 bool GZipFilter::InitDecoding(Filter::FilterType filter_type) {
     43   if (decoding_status_ != DECODING_UNINITIALIZED)
     44     return false;
     45 
     46   // Initialize zlib control block
     47   zlib_stream_.reset(new z_stream);
     48   if (!zlib_stream_.get())
     49     return false;
     50   memset(zlib_stream_.get(), 0, sizeof(z_stream));
     51 
     52   // Set decoding mode
     53   switch (filter_type) {
     54     case Filter::FILTER_TYPE_DEFLATE: {
     55       if (inflateInit(zlib_stream_.get()) != Z_OK)
     56         return false;
     57       decoding_mode_ = DECODE_MODE_DEFLATE;
     58       break;
     59     }
     60     case Filter::FILTER_TYPE_GZIP_HELPING_SDCH:
     61       possible_sdch_pass_through_ =  true;  // Needed to optionally help sdch.
     62       // Fall through to GZIP case.
     63     case Filter::FILTER_TYPE_GZIP: {
     64       gzip_header_.reset(new GZipHeader());
     65       if (!gzip_header_.get())
     66         return false;
     67       if (inflateInit2(zlib_stream_.get(), -MAX_WBITS) != Z_OK)
     68         return false;
     69       decoding_mode_ = DECODE_MODE_GZIP;
     70       break;
     71     }
     72     default: {
     73       return false;
     74     }
     75   }
     76 
     77   decoding_status_ = DECODING_IN_PROGRESS;
     78   return true;
     79 }
     80 
     81 Filter::FilterStatus GZipFilter::ReadFilteredData(char* dest_buffer,
     82                                                   int* dest_len) {
     83   if (!dest_buffer || !dest_len || *dest_len <= 0)
     84     return Filter::FILTER_ERROR;
     85 
     86   if (decoding_status_ == DECODING_DONE) {
     87     if (GZIP_GET_INVALID_HEADER != gzip_header_status_)
     88       SkipGZipFooter();
     89     // Some server might send extra data after the gzip footer. We just copy
     90     // them out. Mozilla does this too.
     91     return CopyOut(dest_buffer, dest_len);
     92   }
     93 
     94   if (decoding_status_ != DECODING_IN_PROGRESS)
     95     return Filter::FILTER_ERROR;
     96 
     97   Filter::FilterStatus status;
     98 
     99   if (decoding_mode_ == DECODE_MODE_GZIP &&
    100       gzip_header_status_ == GZIP_CHECK_HEADER_IN_PROGRESS) {
    101     // With gzip encoding the content is wrapped with a gzip header.
    102     // We need to parse and verify the header first.
    103     status = CheckGZipHeader();
    104     switch (status) {
    105       case Filter::FILTER_NEED_MORE_DATA: {
    106         // We have consumed all input data, either getting a complete header or
    107         // a partial header. Return now to get more data.
    108         *dest_len = 0;
    109         // Partial header means it can't be an SDCH header.
    110         // Reason: SDCH *always* starts with 8 printable characters [a-zA-Z/_].
    111         // Gzip always starts with two non-printable characters.  Hence even a
    112         // single character (partial header) means that this can't be an SDCH
    113         // encoded body masquerading as a GZIP body.
    114         possible_sdch_pass_through_ = false;
    115         return status;
    116       }
    117       case Filter::FILTER_OK: {
    118         // The header checking succeeds, and there are more data in the input.
    119         // We must have got a complete header here.
    120         DCHECK_EQ(gzip_header_status_, GZIP_GET_COMPLETE_HEADER);
    121         break;
    122       }
    123       case Filter::FILTER_ERROR: {
    124         if (possible_sdch_pass_through_ &&
    125             GZIP_GET_INVALID_HEADER == gzip_header_status_) {
    126           decoding_status_ = DECODING_DONE;  // Become a pass through filter.
    127           return CopyOut(dest_buffer, dest_len);
    128         }
    129         decoding_status_ = DECODING_ERROR;
    130         return status;
    131       }
    132       default: {
    133         status = Filter::FILTER_ERROR;    // Unexpected.
    134         decoding_status_ = DECODING_ERROR;
    135         return status;
    136       }
    137     }
    138   }
    139 
    140   int dest_orig_size = *dest_len;
    141   status = DoInflate(dest_buffer, dest_len);
    142 
    143   if (decoding_mode_ == DECODE_MODE_DEFLATE && status == Filter::FILTER_ERROR) {
    144     // As noted in Mozilla implementation, some servers such as Apache with
    145     // mod_deflate don't generate zlib headers.
    146     // See 677409 for instances where this work around is needed.
    147     // Insert a dummy zlib header and try again.
    148     if (InsertZlibHeader()) {
    149       *dest_len = dest_orig_size;
    150       status = DoInflate(dest_buffer, dest_len);
    151     }
    152   }
    153 
    154   if (status == Filter::FILTER_DONE) {
    155     decoding_status_ = DECODING_DONE;
    156   } else if (status == Filter::FILTER_ERROR) {
    157     decoding_status_ = DECODING_ERROR;
    158   }
    159 
    160   return status;
    161 }
    162 
    163 Filter::FilterStatus GZipFilter::CheckGZipHeader() {
    164   DCHECK_EQ(gzip_header_status_, GZIP_CHECK_HEADER_IN_PROGRESS);
    165 
    166   // Check input data in pre-filter buffer.
    167   if (!next_stream_data_ || stream_data_len_ <= 0)
    168     return Filter::FILTER_ERROR;
    169 
    170   const char* header_end = NULL;
    171   GZipHeader::Status header_status;
    172   header_status = gzip_header_->ReadMore(next_stream_data_, stream_data_len_,
    173                                          &header_end);
    174 
    175   switch (header_status) {
    176     case GZipHeader::INCOMPLETE_HEADER: {
    177       // We read all the data but only got a partial header.
    178       next_stream_data_ = NULL;
    179       stream_data_len_ = 0;
    180       return Filter::FILTER_NEED_MORE_DATA;
    181     }
    182     case GZipHeader::COMPLETE_HEADER: {
    183       // We have a complete header. Check whether there are more data.
    184       int num_chars_left = static_cast<int>(stream_data_len_ -
    185                                             (header_end - next_stream_data_));
    186       gzip_header_status_ = GZIP_GET_COMPLETE_HEADER;
    187 
    188       if (num_chars_left > 0) {
    189         next_stream_data_ = const_cast<char*>(header_end);
    190         stream_data_len_ = num_chars_left;
    191         return Filter::FILTER_OK;
    192       } else {
    193         next_stream_data_ = NULL;
    194         stream_data_len_ = 0;
    195         return Filter::FILTER_NEED_MORE_DATA;
    196       }
    197     }
    198     case GZipHeader::INVALID_HEADER: {
    199       gzip_header_status_ = GZIP_GET_INVALID_HEADER;
    200       return Filter::FILTER_ERROR;
    201     }
    202     default: {
    203       break;
    204     }
    205   }
    206 
    207   return Filter::FILTER_ERROR;
    208 }
    209 
    210 Filter::FilterStatus GZipFilter::DoInflate(char* dest_buffer, int* dest_len) {
    211   // Make sure we have both valid input data and output buffer.
    212   if (!dest_buffer || !dest_len || *dest_len <= 0)  // output
    213     return Filter::FILTER_ERROR;
    214 
    215   if (!next_stream_data_ || stream_data_len_ <= 0) {  // input
    216     *dest_len = 0;
    217     return Filter::FILTER_NEED_MORE_DATA;
    218   }
    219 
    220   // Fill in zlib control block
    221   zlib_stream_.get()->next_in = bit_cast<Bytef*>(next_stream_data_);
    222   zlib_stream_.get()->avail_in = stream_data_len_;
    223   zlib_stream_.get()->next_out = bit_cast<Bytef*>(dest_buffer);
    224   zlib_stream_.get()->avail_out = *dest_len;
    225 
    226   int inflate_code = MOZ_Z_inflate(zlib_stream_.get(), Z_NO_FLUSH);
    227   int bytesWritten = *dest_len - zlib_stream_.get()->avail_out;
    228 
    229   Filter::FilterStatus status;
    230 
    231   switch (inflate_code) {
    232     case Z_STREAM_END: {
    233       *dest_len = bytesWritten;
    234 
    235       stream_data_len_ = zlib_stream_.get()->avail_in;
    236       next_stream_data_ = bit_cast<char*>(zlib_stream_.get()->next_in);
    237 
    238       SkipGZipFooter();
    239 
    240       status = Filter::FILTER_DONE;
    241       break;
    242     }
    243     case Z_BUF_ERROR: {
    244       // According to zlib documentation, when calling inflate with Z_NO_FLUSH,
    245       // getting Z_BUF_ERROR means no progress is possible. Neither processing
    246       // more input nor producing more output can be done.
    247       // Since we have checked both input data and output buffer before calling
    248       // inflate, this result is unexpected.
    249       status = Filter::FILTER_ERROR;
    250       break;
    251     }
    252     case Z_OK: {
    253       // Some progress has been made (more input processed or more output
    254       // produced).
    255       *dest_len = bytesWritten;
    256 
    257       // Check whether we have consumed all input data.
    258       stream_data_len_ = zlib_stream_.get()->avail_in;
    259       if (stream_data_len_ == 0) {
    260         next_stream_data_ = NULL;
    261         status = Filter::FILTER_NEED_MORE_DATA;
    262       } else {
    263         next_stream_data_ = bit_cast<char*>(zlib_stream_.get()->next_in);
    264         status = Filter::FILTER_OK;
    265       }
    266       break;
    267     }
    268     default: {
    269       status = Filter::FILTER_ERROR;
    270       break;
    271     }
    272   }
    273 
    274   return status;
    275 }
    276 
    277 bool GZipFilter::InsertZlibHeader() {
    278   static char dummy_head[2] = { 0x78, 0x1 };
    279 
    280   char dummy_output[4];
    281 
    282   // We only try add additional header once.
    283   if (zlib_header_added_)
    284     return false;
    285 
    286   MOZ_Z_inflateReset(zlib_stream_.get());
    287   zlib_stream_.get()->next_in = bit_cast<Bytef*>(&dummy_head[0]);
    288   zlib_stream_.get()->avail_in = sizeof(dummy_head);
    289   zlib_stream_.get()->next_out = bit_cast<Bytef*>(&dummy_output[0]);
    290   zlib_stream_.get()->avail_out = sizeof(dummy_output);
    291 
    292   int code = MOZ_Z_inflate(zlib_stream_.get(), Z_NO_FLUSH);
    293   zlib_header_added_ = true;
    294 
    295   return (code == Z_OK);
    296 }
    297 
    298 
    299 void GZipFilter::SkipGZipFooter() {
    300   int footer_bytes_expected = kGZipFooterSize - gzip_footer_bytes_;
    301   if (footer_bytes_expected > 0) {
    302     int footer_byte_avail = std::min(footer_bytes_expected, stream_data_len_);
    303     stream_data_len_ -= footer_byte_avail;
    304     next_stream_data_ += footer_byte_avail;
    305     gzip_footer_bytes_ += footer_byte_avail;
    306 
    307     if (stream_data_len_ == 0)
    308       next_stream_data_ = NULL;
    309   }
    310 }
    311