summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/browser_main.cc2
-rw-r--r--net/base/filter.cc61
-rw-r--r--net/base/filter.h83
-rw-r--r--net/base/gzip_filter.cc20
-rw-r--r--net/base/gzip_filter.h9
-rw-r--r--net/base/load_flags.h4
-rw-r--r--net/base/sdch_filter.cc114
-rw-r--r--net/base/sdch_filter.h14
-rw-r--r--net/base/sdch_filter_unitest.cc498
-rw-r--r--net/base/sdch_manager.cc103
-rw-r--r--net/base/sdch_manager.h74
-rw-r--r--net/url_request/url_request_http_job.cc60
-rw-r--r--net/url_request/url_request_http_job.h1
-rw-r--r--net/url_request/url_request_job.cc4
-rw-r--r--net/url_request/url_request_job.h4
15 files changed, 785 insertions, 266 deletions
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index 1d8dda4..758d261 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -480,7 +480,7 @@ int BrowserMain(CommandLine &parsed_command_line, int show_command,
sdch_manager.set_sdch_fetcher(new SdchDictionaryFetcher);
std::wstring switch_domain =
parsed_command_line.GetSwitchValue(switches::kSdchFilter);
- sdch_manager.enable_sdch_support(WideToASCII(switch_domain));
+ sdch_manager.EnableSdchSupport(WideToASCII(switch_domain));
}
MetricsService* metrics = NULL;
diff --git a/net/base/filter.cc b/net/base/filter.cc
index c59e7f0..1174c2f 100644
--- a/net/base/filter.cc
+++ b/net/base/filter.cc
@@ -43,26 +43,24 @@ Filter* Filter::Factory(const std::vector<std::string>& filter_types,
std::string safe_mime_type = (filter_types.size() > 1) ? "" : mime_type;
Filter* filter_list = NULL; // Linked list of filters.
- for (size_t i = 0; i < filter_types.size(); ++i) {
- Filter* first_filter;
- first_filter = SingleFilter(filter_types[i], safe_mime_type, buffer_size);
- if (!first_filter) {
- // Cleanup and exit, since we can't construct this filter list.
- if (filter_list)
- delete filter_list;
- filter_list = NULL;
- break;
- }
- first_filter->next_filter_.reset(filter_list);
- filter_list = first_filter;
+ FilterType type_id = FILTER_TYPE_UNSUPPORTED;
+ for (size_t i = 0; i < filter_types.size(); i++) {
+ type_id = ConvertEncodingToType(filter_types[i], safe_mime_type);
+ filter_list = PrependNewFilter(type_id, buffer_size, filter_list);
+ if (!filter_list)
+ return NULL;
}
+
+ // Handle proxy that changes content encoding "sdch,gzip" into "sdch".
+ if (1 == filter_types.size() && FILTER_TYPE_SDCH == type_id)
+ filter_list = PrependNewFilter(FILTER_TYPE_GZIP_HELPING_SDCH, buffer_size,
+ filter_list);
return filter_list;
}
// static
-Filter* Filter::SingleFilter(const std::string& filter_type,
- const std::string& mime_type,
- int buffer_size) {
+Filter::FilterType Filter::ConvertEncodingToType(const std::string& filter_type,
+ const std::string& mime_type) {
FilterType type_id;
if (LowerCaseEqualsASCII(filter_type, kDeflate)) {
type_id = FILTER_TYPE_DEFLATE;
@@ -75,6 +73,8 @@ Filter* Filter::SingleFilter(const std::string& filter_type,
// content encoding. Sadly, Apache mistakenly sets these headers for all
// .gz files. We match Firefox's nsHttpChannel::ProcessNormal and ignore
// the Content-Encoding here.
+ // TODO(jar): Move all this encoding type "fixup" into the
+ // GetContentEncoding() methods. Combine this defaulting with SDCH fixup.
type_id = FILTER_TYPE_UNSUPPORTED;
} else {
type_id = FILTER_TYPE_GZIP;
@@ -89,14 +89,21 @@ Filter* Filter::SingleFilter(const std::string& filter_type,
// filter should be disabled in such cases.
type_id = FILTER_TYPE_UNSUPPORTED;
}
+ return type_id;
+}
+// static
+Filter* Filter::PrependNewFilter(FilterType type_id, int buffer_size,
+ Filter* filter_list) {
+ Filter* first_filter = NULL; // Soon to be start of chain.
switch (type_id) {
+ case FILTER_TYPE_GZIP_HELPING_SDCH:
case FILTER_TYPE_DEFLATE:
case FILTER_TYPE_GZIP: {
scoped_ptr<GZipFilter> gz_filter(new GZipFilter());
if (gz_filter->InitBuffer(buffer_size)) {
if (gz_filter->InitDecoding(type_id)) {
- return gz_filter.release();
+ first_filter = gz_filter.release();
}
}
break;
@@ -105,7 +112,7 @@ Filter* Filter::SingleFilter(const std::string& filter_type,
scoped_ptr<BZip2Filter> bzip2_filter(new BZip2Filter());
if (bzip2_filter->InitBuffer(buffer_size)) {
if (bzip2_filter->InitDecoding(false)) {
- return bzip2_filter.release();
+ first_filter = bzip2_filter.release();
}
}
break;
@@ -114,7 +121,7 @@ Filter* Filter::SingleFilter(const std::string& filter_type,
scoped_ptr<SdchFilter> sdch_filter(new SdchFilter());
if (sdch_filter->InitBuffer(buffer_size)) {
if (sdch_filter->InitDecoding()) {
- return sdch_filter.release();
+ first_filter = sdch_filter.release();
}
}
break;
@@ -124,7 +131,14 @@ Filter* Filter::SingleFilter(const std::string& filter_type,
}
}
- return NULL;
+ if (first_filter) {
+ first_filter->next_filter_.reset(filter_list);
+ } else {
+ // Cleanup and exit, since we can't construct this filter list.
+ delete filter_list;
+ filter_list = NULL;
+ }
+ return first_filter;
}
Filter::Filter()
@@ -132,6 +146,8 @@ Filter::Filter()
stream_buffer_size_(0),
next_stream_data_(NULL),
stream_data_len_(0),
+ url_(),
+ mime_type_(),
next_filter_(NULL),
last_status_(FILTER_NEED_MORE_DATA) {
}
@@ -230,3 +246,10 @@ void Filter::SetURL(const GURL& url) {
if (next_filter_.get())
next_filter_->SetURL(url);
}
+
+void Filter::SetMimeType(std::string& mime_type) {
+ mime_type_ = mime_type;
+ if (next_filter_.get())
+ next_filter_->SetMimeType(mime_type);
+}
+
diff --git a/net/base/filter.h b/net/base/filter.h
index 3551056..ab61732 100644
--- a/net/base/filter.h
+++ b/net/base/filter.h
@@ -35,9 +35,28 @@
#include "base/basictypes.h"
#include "base/scoped_ptr.h"
#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
class Filter {
public:
+ // Return values of function ReadFilteredData.
+ enum FilterStatus {
+ // Read filtered data successfully
+ FILTER_OK,
+ // Read filtered data successfully, and the data in the buffer has been
+ // consumed by the filter, but more data is needed in order to continue
+ // filtering. At this point, the caller is free to reuse the filter
+ // buffer to provide more data.
+ FILTER_NEED_MORE_DATA,
+ // Read filtered data successfully, and filter reaches the end of the data
+ // stream.
+ FILTER_DONE,
+ // There is an error during filtering.
+ FILTER_ERROR
+ };
+
+ virtual ~Filter();
+
// Creates a Filter object.
// Parameters: Filter_types specifies the type of filter created; Buffer_size
// specifies the size (in number of chars) of the buffer the filter should
@@ -56,24 +75,6 @@ class Filter {
const std::string& mime_type,
int buffer_size);
- virtual ~Filter();
-
- // Return values of function ReadFilteredData.
- enum FilterStatus {
- // Read filtered data successfully
- FILTER_OK,
- // Read filtered data successfully, and the data in the buffer has been
- // consumed by the filter, but more data is needed in order to continue
- // filtering. At this point, the caller is free to reuse the filter
- // buffer to provide more data.
- FILTER_NEED_MORE_DATA,
- // Read filtered data successfully, and filter reaches the end of the data
- // stream.
- FILTER_DONE,
- // There is an error during filtering.
- FILTER_ERROR
- };
-
// External call to obtain data from this filter chain. If ther is no
// next_filter_, then it obtains data from this specific filter.
FilterStatus ReadData(char* dest_buffer, int* dest_len);
@@ -106,7 +107,23 @@ class Filter {
void SetURL(const GURL& url);
const GURL& url() const { return url_; }
+ void SetMimeType(std::string& mime_type);
+ const std::string& mime_type() const { return mime_type_; }
+
protected:
+ // Specifies type of filters that can be created.
+ enum FilterType {
+ FILTER_TYPE_DEFLATE,
+ FILTER_TYPE_GZIP,
+ FILTER_TYPE_BZIP2,
+ FILTER_TYPE_GZIP_HELPING_SDCH,
+ FILTER_TYPE_SDCH, // open-vcdiff compression relative to a dictionary.
+ FILTER_TYPE_UNSUPPORTED
+ };
+
+ Filter();
+
+ FRIEND_TEST(SdchFilterTest, ContentTypeId);
// Filters the data stored in stream_buffer_ and writes the output into the
// dest_buffer passed in.
//
@@ -120,31 +137,26 @@ class Filter {
// but not produce output yet.
virtual FilterStatus ReadFilteredData(char* dest_buffer, int* dest_len);
- Filter();
-
// Copy pre-filter data directly to destination buffer without decoding.
FilterStatus CopyOut(char* dest_buffer, int* dest_len);
- // Specifies type of filters that can be created.
- enum FilterType {
- FILTER_TYPE_DEFLATE,
- FILTER_TYPE_GZIP,
- FILTER_TYPE_BZIP2,
- FILTER_TYPE_SDCH, // open-vcdiff compression relative to a dictionary.
- FILTER_TYPE_UNSUPPORTED
- };
-
// Allocates and initializes stream_buffer_.
// Buffer_size is the maximum size of stream_buffer_ in number of chars.
bool InitBuffer(int buffer_size);
+ // Translate the text of a filter name (from Content-Encoding header) into a
+ // FilterType, in the context of a mime type.
+ static FilterType ConvertEncodingToType(const std::string& filter_type,
+ const std::string& mime_type);
+
// A factory helper for creating filters for within a chain of potentially
// multiple encodings. If a chain of filters is created, then this may be
// called multiple times during the filter creation process. In most simple
- // cases, this is only called once.
- static Filter* SingleFilter(const std::string& filter_type,
- const std::string& mime_type,
- int buffer_size);
+ // cases, this is only called once. Returns NULL and cleans up (deleting
+ // filter_list) if a new filter can't be constructed.
+ static Filter* PrependNewFilter(FilterType type_id, int buffer_size,
+ Filter* filter_list);
+
FilterStatus last_status() const { return last_status_; }
// Buffer to hold the data to be filtered.
@@ -164,6 +176,11 @@ class Filter {
// a specific URL or path.
GURL url_;
+ // To facilitate error recovery in SDCH filters, allow filter to know if
+ // content is text/html by checking within this mime type (SDCH filter may
+ // do a meta-refresh via html).
+ std::string mime_type_;
+
// An optional filter to process output from this filter.
scoped_ptr<Filter> next_filter_;
// Remember what status or local filter last returned so we can better handle
diff --git a/net/base/gzip_filter.cc b/net/base/gzip_filter.cc
index 097a6e4..af7609b 100644
--- a/net/base/gzip_filter.cc
+++ b/net/base/gzip_filter.cc
@@ -13,7 +13,8 @@ GZipFilter::GZipFilter()
decoding_mode_(DECODE_MODE_UNKNOWN),
gzip_header_status_(GZIP_CHECK_HEADER_IN_PROGRESS),
zlib_header_added_(false),
- gzip_footer_bytes_(0) {
+ gzip_footer_bytes_(0),
+ possible_sdch_pass_through_(false) {
}
GZipFilter::~GZipFilter() {
@@ -40,6 +41,9 @@ bool GZipFilter::InitDecoding(Filter::FilterType filter_type) {
decoding_mode_ = DECODE_MODE_DEFLATE;
break;
}
+ case Filter::FILTER_TYPE_GZIP_HELPING_SDCH:
+ possible_sdch_pass_through_ = true; // Needed to optionally help sdch.
+ // Fall through to GZIP case.
case Filter::FILTER_TYPE_GZIP: {
gzip_header_.reset(new GZipHeader());
if (!gzip_header_.get())
@@ -64,9 +68,10 @@ Filter::FilterStatus GZipFilter::ReadFilteredData(char* dest_buffer,
return Filter::FILTER_ERROR;
if (decoding_status_ == DECODING_DONE) {
+ if (GZIP_GET_INVALID_HEADER != gzip_header_status_)
+ SkipGZipFooter();
// Some server might send extra data after the gzip footer. We just copy
// them out. Mozilla does this too.
- SkipGZipFooter();
return CopyOut(dest_buffer, dest_len);
}
@@ -85,6 +90,12 @@ Filter::FilterStatus GZipFilter::ReadFilteredData(char* dest_buffer,
// We have consumed all input data, either getting a complete header or
// a partial header. Return now to get more data.
*dest_len = 0;
+ // Partial header means it can't be an SDCH header.
+ // Reason: SDCH *always* starts with 8 printable characters [a-zA-Z/_].
+ // Gzip always starts with two non-printable characters. Hence even a
+ // single character (partial header) means that this can't be an SDCH
+ // encoded body masquerading as a GZIP body.
+ possible_sdch_pass_through_ = false;
return status;
}
case Filter::FILTER_OK: {
@@ -94,6 +105,11 @@ Filter::FilterStatus GZipFilter::ReadFilteredData(char* dest_buffer,
break;
}
case Filter::FILTER_ERROR: {
+ if (possible_sdch_pass_through_ &&
+ GZIP_GET_INVALID_HEADER == gzip_header_status_) {
+ decoding_status_ = DECODING_DONE; // Become a pass through filter.
+ return CopyOut(dest_buffer, dest_len);
+ }
decoding_status_ = DECODING_ERROR;
return status;
}
diff --git a/net/base/gzip_filter.h b/net/base/gzip_filter.h
index 5d042cf..f0fc3f2 100644
--- a/net/base/gzip_filter.h
+++ b/net/base/gzip_filter.h
@@ -127,6 +127,15 @@ class GZipFilter : public Filter {
// DoInflate, with InsertZlibHeader being the exception as a workaround.
scoped_ptr<z_stream> zlib_stream_;
+ // For robustness, when we see the solo sdch filter, we chain in a gzip filter
+ // in front of it, with this flag to indicate that the gzip decoding might not
+ // be needed. This handles a strange case where "Content-Encoding: sdch,gzip"
+ // is reduced by an errant proxy to "Content-Encoding: sdch", while the
+ // content is indeed really gzipped result of sdch :-/.
+ // If this flag is set, then we will revert to being a pass through filter if
+ // we don't get a valid gzip header.
+ bool possible_sdch_pass_through_;
+
DISALLOW_EVIL_CONSTRUCTORS(GZipFilter);
};
diff --git a/net/base/load_flags.h b/net/base/load_flags.h
index 7ca5ffb..bf5e02f 100644
--- a/net/base/load_flags.h
+++ b/net/base/load_flags.h
@@ -60,6 +60,10 @@ enum {
// This load will not make any changes to cookies, including storing new
// cookies or updating existing ones.
LOAD_DO_NOT_SAVE_COOKIES = 1 << 13,
+
+ // An SDCH dictionary was advertised, and an SDCH encoded response is
+ // possible.
+ LOAD_SDCH_DICTIONARY_ADVERTISED = 1 << 14,
};
} // namespace net
diff --git a/net/base/sdch_filter.cc b/net/base/sdch_filter.cc
index 158c4bb..9be9530 100644
--- a/net/base/sdch_filter.cc
+++ b/net/base/sdch_filter.cc
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include <ctype.h>
#include <algorithm>
#include "base/file_util.h"
@@ -15,6 +16,8 @@
SdchFilter::SdchFilter()
: decoding_status_(DECODING_UNINITIALIZED),
vcdiff_streaming_decoder_(NULL),
+ dictionary_hash_(),
+ dictionary_hash_is_plausible_(false),
dictionary_(NULL),
dest_buffer_excess_(),
dest_buffer_excess_index_(0),
@@ -23,20 +26,21 @@ SdchFilter::SdchFilter()
}
SdchFilter::~SdchFilter() {
+ static int filter_use_count = 0;
+ ++filter_use_count;
+ if (META_REFRESH_RECOVERY == decoding_status_) {
+ HISTOGRAM_COUNTS(L"Sdch.FilterUseBeforeDisabling", filter_use_count);
+ }
+
if (vcdiff_streaming_decoder_.get()) {
if (!vcdiff_streaming_decoder_->FinishDecoding())
decoding_status_ = DECODING_ERROR;
}
- // TODO(jar): Use DHISTOGRAM when we turn sdch on by default.
- if (decoding_status_ == DECODING_ERROR) {
- HISTOGRAM_COUNTS(L"Sdch.Decoding Error bytes read", source_bytes_);
- HISTOGRAM_COUNTS(L"Sdch.Decoding Error bytes output", output_bytes_);
- } else {
- if (decoding_status_ == DECODING_IN_PROGRESS) {
- HISTOGRAM_COUNTS(L"Sdch.Bytes read", source_bytes_);
- HISTOGRAM_COUNTS(L"Sdch.Bytes output", output_bytes_);
- }
- }
+
+ // TODO(jar): Use UMA_HISTOGRAM when we turn sdch on by default.
+ HISTOGRAM_COUNTS(L"Sdch.Bytes read", source_bytes_);
+ HISTOGRAM_COUNTS(L"Sdch.Bytes output", output_bytes_);
+
if (dictionary_)
dictionary_->Release();
}
@@ -50,6 +54,16 @@ bool SdchFilter::InitDecoding() {
return true;
}
+static const char* kDecompressionErrorHtml =
+ "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>"
+ "<div style=\"position:fixed;top:0;left:0;width:100%;border-width:thin;"
+ "border-color:black;border-style:solid;text-align:left;font-family:arial;"
+ "font-size:10pt;foreground-color:black;background-color:white\">"
+ "An error occurred. This page will be reloaded shortly. "
+ "Or press the \"reload\" button now to reload it immediately."
+ "</div>";
+
+
Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer,
int* dest_len) {
int available_space = *dest_len;
@@ -58,20 +72,35 @@ Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer,
if (!dest_buffer || available_space <= 0)
return FILTER_ERROR;
+
if (WAITING_FOR_DICTIONARY_SELECTION == decoding_status_) {
FilterStatus status = InitializeDictionary();
- if (DECODING_IN_PROGRESS != decoding_status_) {
- DCHECK(status == FILTER_ERROR || status == FILTER_NEED_MORE_DATA);
- return status;
+ if (FILTER_NEED_MORE_DATA == status)
+ return FILTER_NEED_MORE_DATA;
+ if (FILTER_ERROR == status) {
+ DCHECK(DECODING_ERROR == decoding_status_);
+ DCHECK(0 == dest_buffer_excess_index_);
+ DCHECK(dest_buffer_excess_.empty());
+ if (!dictionary_hash_is_plausible_) {
+ SdchManager::SdchErrorRecovery(SdchManager::PASSING_THROUGH_NON_SDCH);
+ decoding_status_ = PASS_THROUGH;
+ dest_buffer_excess_ = dictionary_hash_; // Send what we scanned.
+ } else {
+ SdchManager::BlacklistDomain(url());
+ if (std::string::npos == mime_type().find_first_of("text/html")) {
+ SdchManager::SdchErrorRecovery(SdchManager::UNRECOVERABLE_ERROR);
+ return FILTER_ERROR;
+ }
+ SdchManager::SdchErrorRecovery(SdchManager::META_REFRESH_RECOVERY);
+ decoding_status_ = META_REFRESH_RECOVERY;
+ // Issue a meta redirect with SDCH disabled.
+ dest_buffer_excess_ = kDecompressionErrorHtml;
+ }
+ } else {
+ DCHECK(DECODING_IN_PROGRESS == decoding_status_);
}
}
- if (decoding_status_ != DECODING_IN_PROGRESS) {
- decoding_status_ = DECODING_ERROR;
- return FILTER_ERROR;
- }
-
-
int amount = OutputBufferExcess(dest_buffer, available_space);
*dest_len += amount;
dest_buffer += amount;
@@ -81,6 +110,23 @@ Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer,
if (available_space <= 0)
return FILTER_OK;
DCHECK(dest_buffer_excess_.empty());
+ DCHECK(0 == dest_buffer_excess_index_);
+
+ if (decoding_status_ != DECODING_IN_PROGRESS) {
+ if (META_REFRESH_RECOVERY == decoding_status_) {
+ // Absorb all input data. We've already output page reload HTML.
+ next_stream_data_ = NULL;
+ stream_data_len_ = 0;
+ return FILTER_NEED_MORE_DATA;
+ }
+ if (PASS_THROUGH == decoding_status_) {
+ return CopyOut(dest_buffer, dest_len);
+ }
+ DCHECK(false);
+ decoding_status_ = DECODING_ERROR;
+ return FILTER_ERROR;
+ }
+
if (!next_stream_data_ || stream_data_len_ <= 0)
return FILTER_NEED_MORE_DATA;
@@ -95,6 +141,7 @@ Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer,
if (!ret) {
vcdiff_streaming_decoder_.reset(NULL); // Don't call it again.
decoding_status_ = DECODING_ERROR;
+ SdchManager::SdchErrorRecovery(SdchManager::DECODE_BODY_ERROR);
return FILTER_ERROR;
}
@@ -127,17 +174,28 @@ Filter::FilterStatus SdchFilter::InitializeDictionary() {
else
next_stream_data_ = NULL;
- if ('\0' != dictionary_hash_[kServerIdLength - 1] ||
- (kServerIdLength - 1) != strlen(dictionary_hash_.data())) {
- decoding_status_ = DECODING_ERROR;
- return FILTER_ERROR; // No dictionary hash.
- }
- dictionary_hash_.erase(kServerIdLength - 1);
-
DCHECK(!dictionary_);
- SdchManager::Global()->GetVcdiffDictionary(dictionary_hash_, url(),
- &dictionary_);
+ dictionary_hash_is_plausible_ = true; // Assume plausible, but check.
+ if ('\0' == dictionary_hash_[kServerIdLength - 1])
+ SdchManager::Global()->GetVcdiffDictionary(std::string(dictionary_hash_, 0,
+ kServerIdLength - 1),
+ url(), &dictionary_);
+ else
+ dictionary_hash_is_plausible_ = false;
+
if (!dictionary_) {
+ DCHECK(dictionary_hash_.size() == kServerIdLength);
+ for (size_t i = 0; i < kServerIdLength - 1; ++i) {
+ char base64_char = dictionary_hash_[i];
+ if (!isalnum(base64_char) && '-' != base64_char && '_' != base64_char) {
+ dictionary_hash_is_plausible_ = false;
+ break;
+ }
+ }
+ if (dictionary_hash_is_plausible_)
+ SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_NOT_FOUND);
+ else
+ SdchManager::SdchErrorRecovery(SdchManager::DICTIONARY_HASH_MALFORMED);
decoding_status_ = DECODING_ERROR;
return FILTER_ERROR;
}
diff --git a/net/base/sdch_filter.h b/net/base/sdch_filter.h
index a13bd38..6bd0ed9 100644
--- a/net/base/sdch_filter.h
+++ b/net/base/sdch_filter.h
@@ -49,7 +49,9 @@ class SdchFilter : public Filter {
DECODING_UNINITIALIZED,
WAITING_FOR_DICTIONARY_SELECTION,
DECODING_IN_PROGRESS,
- DECODING_ERROR
+ DECODING_ERROR,
+ META_REFRESH_RECOVERY, // Decoding error being handled by a meta-refresh.
+ PASS_THROUGH, // Non-sdch content being passed without alteration.
};
// Identify the suggested dictionary, and initialize underlying decompressor.
@@ -70,9 +72,17 @@ class SdchFilter : public Filter {
scoped_ptr<open_vcdiff::VCDiffStreamingDecoder> vcdiff_streaming_decoder_;
// In case we need to assemble the hash piecemeal, we have a place to store
- // a part of the hash until we "get all 8 bytes."
+ // a part of the hash until we "get all 8 bytes plus a null."
std::string dictionary_hash_;
+ // After assembling an entire dictionary hash (the first 9 bytes of the
+ // sdch payload, we check to see if it is plausible, meaning it has a null
+ // termination, and has 8 characters that are possible in a net-safe base64
+ // encoding. If the hash is not plausible, then the payload is probably not
+ // an SDCH encoded bundle, and various error recovery strategies can be
+ // attempted.
+ bool dictionary_hash_is_plausible_;
+
// We hold an in-memory copy of the dictionary during the entire decoding.
// The char* data is embedded in a RefCounted dictionary_.
SdchManager::Dictionary* dictionary_;
diff --git a/net/base/sdch_filter_unitest.cc b/net/base/sdch_filter_unitest.cc
index d7a5b88..0b0ba8c 100644
--- a/net/base/sdch_filter_unitest.cc
+++ b/net/base/sdch_filter_unitest.cc
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+// TODO(jar): Change file name to *_unittest.cc (with double "t').
+
#include <algorithm>
#include <string>
#include <vector>
@@ -14,37 +16,55 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/zlib.h"
+//------------------------------------------------------------------------------
// Provide sample data and compression results with a sample VCDIFF dictionary.
-// Note an SDCH dictionary has extra meta-data before the VCDIFF text.
-const char kTtestVcdiffDictionary[] = "DictionaryFor"
+// Note an SDCH dictionary has extra meta-data before the VCDIFF dictionary.
+static const char kTestVcdiffDictionary[] = "DictionaryFor"
"SdchCompression1SdchCompression2SdchCompression3SdchCompression\n";
// Pre-compression test data.
-const char kTestData[] = "TestData "
+static const char kTestData[] = "TestData "
"SdchCompression1SdchCompression2SdchCompression3SdchCompression\n";
// Note SDCH compressed data will include a reference to the SDCH dictionary.
-const char kCompressedTestData[] =
+static const char kCompressedTestData[] =
"\326\303\304\0\0\001M\0\022I\0\t\003\001TestData \n\023\100\r";
-namespace {
+//------------------------------------------------------------------------------
class SdchFilterTest : public testing::Test {
protected:
SdchFilterTest()
- : test_vcdiff_dictionary_(kTtestVcdiffDictionary,
- sizeof(kTtestVcdiffDictionary) - 1),
- compressed_test_data_(kCompressedTestData,
- sizeof(kCompressedTestData) - 1),
+ : test_vcdiff_dictionary_(kTestVcdiffDictionary,
+ sizeof(kTestVcdiffDictionary) - 1),
+ vcdiff_compressed_data_(kCompressedTestData,
+ sizeof(kCompressedTestData) - 1),
expanded_(kTestData, sizeof(kTestData) - 1),
sdch_manager_(new SdchManager) {
+ sdch_manager_->EnableSdchSupport("");
}
+ std::string NewSdchCompressedData(const std::string dictionary);
+
const std::string test_vcdiff_dictionary_;
- const std::string compressed_test_data_;
+ const std::string vcdiff_compressed_data_;
const std::string expanded_; // Desired final, decompressed data.
scoped_ptr<SdchManager> sdch_manager_; // A singleton database.
};
+std::string SdchFilterTest::NewSdchCompressedData(const std::string dictionary) {
+ std::string client_hash;
+ std::string server_hash;
+ SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
+
+ // Build compressed data that refers to our dictionary.
+ std::string compressed(server_hash);
+ compressed.append("\0", 1);
+ compressed.append(vcdiff_compressed_data_);
+ return compressed;
+}
+
+//------------------------------------------------------------------------------
+
TEST_F(SdchFilterTest, Hashing) {
std::string client_hash, server_hash;
@@ -95,18 +115,29 @@ static bool FilterTestData(const std::string& source,
} while (1);
}
//------------------------------------------------------------------------------
+static std::string NewSdchDictionary(const std::string& domain) {
+ std::string dictionary;
+ if (!domain.empty()) {
+ dictionary.append("Domain: ");
+ dictionary.append(domain);
+ dictionary.append("\n");
+ }
+ dictionary.append("\n");
+ dictionary.append(kTestVcdiffDictionary, sizeof(kTestVcdiffDictionary) - 1);
+ return dictionary;
+}
+//------------------------------------------------------------------------------
-TEST_F(SdchFilterTest, BasicBadDicitonary) {
- SdchManager::enable_sdch_support("");
-
+TEST_F(SdchFilterTest, BasicBadDictionary) {
std::vector<std::string> filters;
filters.push_back("sdch");
- int kInputBufferSize(30);
+ const int kInputBufferSize(30);
char output_buffer[20];
scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime",
kInputBufferSize));
- filter->SetURL(GURL("http://ignore.com"));
+ std::string url_string("http://ignore.com");
+ filter->SetURL(GURL(url_string));
// With no input data, try to read output.
@@ -154,42 +185,51 @@ TEST_F(SdchFilterTest, BasicBadDicitonary) {
EXPECT_EQ(0, output_bytes_or_buffer_size);
EXPECT_EQ(Filter::FILTER_ERROR, status);
+
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+ SdchManager::ClearBlacklistings();
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
}
+TEST_F(SdchFilterTest, DictionaryAddOnce) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
-TEST_F(SdchFilterTest, BasicDictionary) {
- SdchManager::enable_sdch_support("");
+ std::string url_string = "http://" + kSampleDomain;
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
- const std::string kSampleDomain = "sdchtest.com";
+ // Check we can't add it twice.
+ EXPECT_FALSE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ const std::string kSampleDomain2 = "sdchtest2.com";
+
+ // Construct a second SDCH dictionary from a VCDIFF dictionary.
+ std::string dictionary2(NewSdchDictionary(kSampleDomain2));
+ std::string url_string2 = "http://" + kSampleDomain2;
+ GURL url2(url_string2);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary2, url2));
+}
+
+TEST_F(SdchFilterTest, BasicDictionary) {
// Construct a valid SDCH dictionary from a VCDIFF dictionary.
- std::string dictionary("Domain: ");
- dictionary.append(kSampleDomain);
- dictionary.append("\n\n");
- dictionary.append(test_vcdiff_dictionary_);
- std::string client_hash;
- std::string server_hash;
- SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
std::string url_string = "http://" + kSampleDomain;
GURL url(url_string);
- bool status = sdch_manager_->AddSdchDictionary(dictionary, url);
- EXPECT_TRUE(status);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
- // Check we can't add it twice.
- status = sdch_manager_->AddSdchDictionary(dictionary, url);
- EXPECT_FALSE(status); // Already loaded.
-
- // Build compressed data that refers to our dictionary.
- std::string compressed(server_hash);
- compressed.append("\0", 1);
- compressed.append(compressed_test_data_);
+ std::string compressed(NewSdchCompressedData(dictionary));
std::vector<std::string> filters;
filters.push_back("sdch");
- // First try with a large buffer (larger than test input, or compressed data).
- int kInputBufferSize(100);
+ // Decode with a large buffer (larger than test input, or compressed data).
+ const int kInputBufferSize(100);
scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime",
kInputBufferSize));
filter->SetURL(url);
@@ -197,66 +237,92 @@ TEST_F(SdchFilterTest, BasicDictionary) {
size_t feed_block_size = 100;
size_t output_block_size = 100;
std::string output;
- status = FilterTestData(compressed, feed_block_size, output_block_size,
- filter.get(), &output);
- EXPECT_TRUE(status);
- EXPECT_TRUE(output == expanded_);
+ EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
- // Now try with really small buffers (size 1) to check for edge effects.
+ // Decode with really small buffers (size 1) to check for edge effects.
filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
filter->SetURL(url);
feed_block_size = 1;
output_block_size = 1;
output.clear();
- status = FilterTestData(compressed, feed_block_size, output_block_size,
- filter.get(), &output);
- EXPECT_TRUE(status);
- EXPECT_TRUE(output == expanded_);
+ EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
+
+TEST_F(SdchFilterTest, CrossDomainDictionaryUse) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string compressed(NewSdchCompressedData(dictionary));
- // Now try with content arriving from the "wrong" domain.
+ std::vector<std::string> filters;
+ filters.push_back("sdch");
+ const int kInputBufferSize(100);
+
+ // Decode with content arriving from the "wrong" domain.
// This tests CanSet() in the sdch_manager_->
- filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
- filter->SetURL(GURL("http://www.wrongdomain.com"));
+ scoped_ptr<Filter> filter((Filter::Factory(filters, "missing-mime",
+ kInputBufferSize)));
+ GURL wrong_domain_url("http://www.wrongdomain.com");
+ filter->SetURL(wrong_domain_url);
- feed_block_size = 100;
- output_block_size = 100;
- output.clear();
- status = FilterTestData(compressed, feed_block_size, output_block_size,
- filter.get(), &output);
- EXPECT_FALSE(status); // Couldn't decode.
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output));
EXPECT_EQ(output.size(), 0u); // No output written.
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(wrong_domain_url));
+ SdchManager::ClearBlacklistings();
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(wrong_domain_url));
+}
+
+TEST_F(SdchFilterTest, DictionaryPathValidation) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
- // Now check that path restrictions on dictionary are being enforced.
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
- // Create a dictionary with a path restriction, by prefixing old dictionary.
+ // Create a dictionary with a path restriction, by prefixing dictionary.
const std::string path("/special_path/bin");
std::string dictionary_with_path("Path: " + path + "\n");
dictionary_with_path.append(dictionary);
- std::string pathed_client_hash;
- std::string pathed_server_hash;
- SdchManager::GenerateHash(dictionary_with_path,
- &pathed_client_hash, &pathed_server_hash);
- status = sdch_manager_->AddSdchDictionary(dictionary_with_path, url);
- EXPECT_TRUE(status);
-
- // Build compressed data that refers to our dictionary
- std::string compressed_for_path(pathed_server_hash);
- compressed_for_path.append("\0", 1);
- compressed_for_path.append(compressed_test_data_);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary_with_path, url));
+
+ std::string compressed_for_path(NewSdchCompressedData(dictionary_with_path));
+
+ std::vector<std::string> filters;
+ filters.push_back("sdch");
+ const int kInputBufferSize(100);
// Test decode the path data, arriving from a valid path.
- filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ scoped_ptr<Filter> filter((Filter::Factory(filters, "missing-mime",
+ kInputBufferSize)));
filter->SetURL(GURL(url_string + path));
- feed_block_size = 100;
- output_block_size = 100;
- output.clear();
- status = FilterTestData(compressed_for_path, feed_block_size,
- output_block_size, filter.get(), &output);
- EXPECT_TRUE(status);
- EXPECT_TRUE(output == expanded_);
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+
+ EXPECT_TRUE(FilterTestData(compressed_for_path, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
// Test decode the path data, arriving from a invalid path.
filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
@@ -265,41 +331,51 @@ TEST_F(SdchFilterTest, BasicDictionary) {
feed_block_size = 100;
output_block_size = 100;
output.clear();
- status = FilterTestData(compressed_for_path, feed_block_size,
- output_block_size, filter.get(), &output);
- EXPECT_FALSE(status); // Couldn't decode.
+ EXPECT_FALSE(FilterTestData(compressed_for_path, feed_block_size,
+ output_block_size, filter.get(), &output));
EXPECT_EQ(output.size(), 0u); // No output written.
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+ SdchManager::ClearBlacklistings();
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+}
+
+TEST_F(SdchFilterTest, DictionaryPortValidation) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
// Create a dictionary with a port restriction, by prefixing old dictionary.
const std::string port("502");
std::string dictionary_with_port("Port: " + port + "\n");
dictionary_with_port.append("Port: 80\n"); // Add default port.
dictionary_with_port.append(dictionary);
- std::string ported_client_hash;
- std::string ported_server_hash;
- SdchManager::GenerateHash(dictionary_with_port,
- &ported_client_hash, &ported_server_hash);
- status = sdch_manager_->AddSdchDictionary(dictionary_with_port,
- GURL(url_string + ":" + port));
- EXPECT_TRUE(status);
-
- // Build compressed data that refers to our dictionary
- std::string compressed_for_port(ported_server_hash);
- compressed_for_port.append("\0", 1);
- compressed_for_port.append(compressed_test_data_);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary_with_port,
+ GURL(url_string + ":" + port)));
+
+ std::string compressed_for_port(NewSdchCompressedData(dictionary_with_port));
+
+ std::vector<std::string> filters;
+ filters.push_back("sdch");
+ const int kInputBufferSize(100);
// Test decode the port data, arriving from a valid port.
- filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ scoped_ptr<Filter> filter((Filter::Factory(filters, "missing-mime",
+ kInputBufferSize)));
filter->SetURL(GURL(url_string + ":" + port));
- feed_block_size = 100;
- output_block_size = 100;
- output.clear();
- status = FilterTestData(compressed_for_port, feed_block_size,
- output_block_size, filter.get(), &output);
- EXPECT_TRUE(status);
- EXPECT_TRUE(output == expanded_);
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
// Test decode the port data, arriving from a valid (default) port.
filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
@@ -308,10 +384,9 @@ TEST_F(SdchFilterTest, BasicDictionary) {
feed_block_size = 100;
output_block_size = 100;
output.clear();
- status = FilterTestData(compressed_for_port, feed_block_size,
- output_block_size, filter.get(), &output);
- EXPECT_TRUE(status);
- EXPECT_TRUE(output == expanded_);
+ EXPECT_TRUE(FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
// Test decode the port data, arriving from a invalid port.
filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
@@ -320,46 +395,19 @@ TEST_F(SdchFilterTest, BasicDictionary) {
feed_block_size = 100;
output_block_size = 100;
output.clear();
- status = FilterTestData(compressed_for_port, feed_block_size,
- output_block_size, filter.get(), &output);
- EXPECT_FALSE(status); // Couldn't decode.
+ EXPECT_FALSE(FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output));
EXPECT_EQ(output.size(), 0u); // No output written.
-}
-
-
-// Test that filters can be cascaded (chained) so that the output of one filter
-// is processed by the next one. This is most critical for SDCH, which is
-// routinely followed by gzip (during encoding). The filter we'll test for will
-// do the gzip decoding first, and then decode the SDCH content.
-TEST_F(SdchFilterTest, FilterChaining) {
- SdchManager::enable_sdch_support("");
-
- const std::string kSampleDomain = "sdchtest.com";
-
- // Construct a valid SDCH dictionary from a VCDIFF dictionary.
- std::string dictionary("Domain: ");
- dictionary.append(kSampleDomain);
- dictionary.append("\n\n");
- dictionary.append(test_vcdiff_dictionary_);
- std::string client_hash;
- std::string server_hash;
- SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
- std::string url_string = "http://" + kSampleDomain;
- GURL url(url_string);
- bool status = sdch_manager_->AddSdchDictionary(dictionary, url);
- EXPECT_TRUE(status);
-
- // Check we can't add it twice.
- status = sdch_manager_->AddSdchDictionary(dictionary, url);
- EXPECT_FALSE(status); // Already loaded.
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+ SdchManager::ClearBlacklistings();
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(GURL(url_string)));
+}
- // Build compressed sdch encoded data that refers to our dictionary.
- std::string sdch_compressed(server_hash);
- sdch_compressed.append("\0", 1);
- sdch_compressed.append(compressed_test_data_);
+//------------------------------------------------------------------------------
+// Helper function to perform gzip compression of data.
- // Use Gzip to compress the sdch sdch_compressed data.
+static std::string gzip_compress(const std::string &input) {
z_stream zlib_stream;
memset(&zlib_stream, 0, sizeof(zlib_stream));
int code;
@@ -373,8 +421,8 @@ TEST_F(SdchFilterTest, FilterChaining) {
CHECK(code == Z_OK);
// Fill in zlib control block
- zlib_stream.next_in = bit_cast<Bytef*>(sdch_compressed.data());
- zlib_stream.avail_in = sdch_compressed.size();
+ zlib_stream.next_in = bit_cast<Bytef*>(input.data());
+ zlib_stream.avail_in = input.size();
// Assume we can compress into similar buffer (add 100 bytes to be sure).
size_t gzip_compressed_length = zlib_stream.avail_in + 100;
@@ -406,6 +454,30 @@ TEST_F(SdchFilterTest, FilterChaining) {
code = MOZ_Z_deflate(&zlib_stream, Z_FINISH);
gzip_compressed_length -= zlib_stream.avail_out;
std::string compressed(gzip_compressed.get(), gzip_compressed_length);
+ MOZ_Z_deflateEnd(&zlib_stream);
+ return compressed;
+}
+
+//------------------------------------------------------------------------------
+
+// Test that filters can be cascaded (chained) so that the output of one filter
+// is processed by the next one. This is most critical for SDCH, which is
+// routinely followed by gzip (during encoding). The filter we'll test for will
+// do the gzip decoding first, and then decode the SDCH content.
+TEST_F(SdchFilterTest, FilterChaining) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string sdch_compressed(NewSdchCompressedData(dictionary));
+
+ // Use Gzip to compress the sdch sdch_compressed data.
+ std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
// Construct a chained filter.
std::vector<std::string> filters;
@@ -413,7 +485,7 @@ TEST_F(SdchFilterTest, FilterChaining) {
filters.push_back("gzip");
// First try with a large buffer (larger than test input, or compressed data).
- int kInputBufferSize(100);
+ const int kInputBufferSize(100);
scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime",
kInputBufferSize));
filter->SetURL(url);
@@ -427,10 +499,9 @@ TEST_F(SdchFilterTest, FilterChaining) {
size_t feed_block_size = 100;
size_t output_block_size = 100;
std::string output;
- status = FilterTestData(compressed, feed_block_size, output_block_size,
- filter.get(), &output);
- EXPECT_TRUE(status);
- EXPECT_TRUE(output == expanded_);
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
// Next try with a tiny buffer to cover edge effects.
filter.reset(Filter::Factory(filters, "missing-mime", kInputBufferSize));
@@ -439,12 +510,135 @@ TEST_F(SdchFilterTest, FilterChaining) {
feed_block_size = 1;
output_block_size = 1;
output.clear();
- status = FilterTestData(compressed, feed_block_size, output_block_size,
- filter.get(), &output);
- EXPECT_TRUE(status);
- EXPECT_TRUE(output == expanded_);
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
- MOZ_Z_deflateEnd(&zlib_stream);
+TEST_F(SdchFilterTest, DefaultGzipIfSdch) {
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ const std::string kSampleDomain = "sdchtest.com";
+ std::string dictionary(NewSdchDictionary(kSampleDomain));
+
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ EXPECT_TRUE(sdch_manager_->AddSdchDictionary(dictionary, url));
+
+ std::string sdch_compressed(NewSdchCompressedData(dictionary));
+
+ // Use Gzip to compress the sdch sdch_compressed data.
+ std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
+
+ // Only claim to have sdch content, but really usethe gzipped sdch content.
+ // System should automatically add the missing (optional) gzip.
+ std::vector<std::string> filters;
+ filters.push_back("sdch");
+
+ // First try with a large buffer (larger than test input, or compressed data).
+ const int kInputBufferSize(100);
+ scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime",
+ kInputBufferSize));
+ filter->SetURL(url);
+
+ // Verify that chained filter is waiting for data.
+ char tiny_output_buffer[10];
+ int tiny_output_size = sizeof(tiny_output_buffer);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
+ filter->ReadData(tiny_output_buffer, &tiny_output_size));
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+
+ // Next try with a tiny buffer to cover edge effects.
+ filter.reset(Filter::Factory(filters, "missing-mime", kInputBufferSize));
+ filter->SetURL(url);
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
+ output_block_size, filter.get(), &output));
+ EXPECT_EQ(output, expanded_);
+}
+
+TEST_F(SdchFilterTest, DomainSupported) {
+ GURL test_url("http://www.test.com");
+ GURL google_url("http://www.google.com");
+
+ EXPECT_TRUE(SdchManager::sdch_enabled());
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(test_url));
+ sdch_manager_->EnableSdchSupport(".google.com");
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(test_url));
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(google_url));
}
-}; // namespace anonymous
+TEST_F(SdchFilterTest, DomainBlacklisting) {
+ GURL test_url("http://www.test.com");
+ GURL google_url("http://www.google.com");
+
+ SdchManager::BlacklistDomain(test_url);
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(test_url));
+ EXPECT_TRUE(SdchManager::Global()->IsInSupportedDomain(google_url));
+
+ SdchManager::BlacklistDomain(google_url);
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(test_url));
+ EXPECT_FALSE(SdchManager::Global()->IsInSupportedDomain(google_url));
+}
+
+
+// TODO(jar): move this sort of test into filter_unittest.cc, or
+// url_request_http_job_unittest.cc if that is more applicable after refactoring
+// to use array of enums rather than array of strings to express content
+// encodings.
+TEST_F(SdchFilterTest, ContentTypeId) {
+ // Check for basic translation of Content-Encoding, including case variations.
+ EXPECT_EQ(Filter::FILTER_TYPE_DEFLATE,
+ Filter::ConvertEncodingToType("deflate", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_DEFLATE,
+ Filter::ConvertEncodingToType("deflAte", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP,
+ Filter::ConvertEncodingToType("gzip", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP,
+ Filter::ConvertEncodingToType("GzIp", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP,
+ Filter::ConvertEncodingToType("x-gzip", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_GZIP,
+ Filter::ConvertEncodingToType("X-GzIp", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_BZIP2,
+ Filter::ConvertEncodingToType("bzip2", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_BZIP2,
+ Filter::ConvertEncodingToType("BZiP2", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_BZIP2,
+ Filter::ConvertEncodingToType("x-bzip2", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_BZIP2,
+ Filter::ConvertEncodingToType("X-BZiP2", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH,
+ Filter::ConvertEncodingToType("sdch", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_SDCH,
+ Filter::ConvertEncodingToType("sDcH", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("weird", "nothing"));
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("strange", "nothing"));
+
+ // Check to see that apache bug (marking things as gzipped because of their
+ // on-disk file type) is ignored.
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("gzip", "application/x-gzip"));
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("gzip", "application/gzip"));
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("gzip", "application/x-gunzip"));
+
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("x-gzip", "application/x-gzip"));
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("x-gzip", "application/gzip"));
+ EXPECT_EQ(Filter::FILTER_TYPE_UNSUPPORTED,
+ Filter::ConvertEncodingToType("x-gzip", "application/x-gunzip"));
+}
diff --git a/net/base/sdch_manager.cc b/net/base/sdch_manager.cc
index de1e0b0..5b86a650 100644
--- a/net/base/sdch_manager.cc
+++ b/net/base/sdch_manager.cc
@@ -21,6 +21,20 @@ SdchManager* SdchManager::Global() {
return global_;
}
+// static
+void SdchManager::SdchErrorRecovery(ProblemCodes problem) {
+ static LinearHistogram histogram(L"Sdch.ProblemCodes", MIN_PROBLEM_CODE,
+ MAX_PROBLEM_CODE - 1, MAX_PROBLEM_CODE);
+ // TODO(jar): Set UMA flag for uploading.
+ histogram.Add(problem);
+}
+
+// static
+void SdchManager::ClearBlacklistings() {
+ Global()->blacklisted_domains_.clear();
+}
+
+
//------------------------------------------------------------------------------
SdchManager::SdchManager() : sdch_enabled_(false) {
DCHECK(!global_);
@@ -37,10 +51,33 @@ SdchManager::~SdchManager() {
global_ = NULL;
}
+// static
+bool SdchManager::BlacklistDomain(const GURL& url) {
+ if (!global_ )
+ return false;
+ std::string domain(url.host());
+ global_->blacklisted_domains_.insert(url.host());
+ return true;
+}
+
+void SdchManager::EnableSdchSupport(const std::string& domain) {
+ // We presume that there is a SDCH manager instance.
+ global_->supported_domain_ = domain;
+ global_->sdch_enabled_ = true;
+}
+
const bool SdchManager::IsInSupportedDomain(const GURL& url) const {
- return sdch_enabled_ &&
- (supported_domain_.empty() ||
- url.DomainIs(supported_domain_.data(), supported_domain_.size()));
+ if (!sdch_enabled_ )
+ return false;
+ if (!supported_domain_.empty() &&
+ !url.DomainIs(supported_domain_.data(), supported_domain_.size()))
+ return false; // It is not the singular supported domain.
+
+ if (blacklisted_domains_.empty())
+ return true;
+
+ std::string domain = StringToLowerASCII(url.host());
+ return blacklisted_domains_.end() == blacklisted_domains_.find(domain);
}
void SdchManager::FetchDictionary(const GURL& referring_url,
@@ -56,10 +93,14 @@ void SdchManager::FetchDictionary(const GURL& referring_url,
*/
// Item (1) above implies item (2). Spec should be updated.
// I take "host name match" to be "is identical to"
- if (referring_url.host() != dictionary_url.host())
+ if (referring_url.host() != dictionary_url.host()) {
+ SdchErrorRecovery(DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST);
return;
- if (referring_url.SchemeIs("https"))
+ }
+ if (referring_url.SchemeIs("https")) {
+ SdchErrorRecovery(DICTIONARY_SELECTED_FOR_SSL);
return;
+ }
if (fetcher_.get())
fetcher_->Schedule(dictionary_url);
}
@@ -69,16 +110,20 @@ bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
std::string client_hash;
std::string server_hash;
GenerateHash(dictionary_text, &client_hash, &server_hash);
- if (dictionaries_.find(server_hash) != dictionaries_.end())
+ if (dictionaries_.find(server_hash) != dictionaries_.end()) {
+ SdchErrorRecovery(DICTIONARY_ALREADY_LOADED);
return false; // Already loaded.
+ }
std::string domain, path;
std::set<int> ports;
Time expiration;
size_t header_end = dictionary_text.find("\n\n");
- if (std::string::npos == header_end)
+ if (std::string::npos == header_end) {
+ SdchErrorRecovery(DICTIONARY_HAS_NO_HEADER);
return false; // Missing header.
+ }
size_t line_start = 0; // Start of line being parsed.
while (1) {
size_t line_end = dictionary_text.find('\n', line_start);
@@ -86,8 +131,10 @@ bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
DCHECK(line_end <= header_end);
size_t colon_index = dictionary_text.find(':', line_start);
- if (std::string::npos == colon_index)
+ if (std::string::npos == colon_index) {
+ SdchErrorRecovery(DICTIONARY_HEADER_LINE_MISSING_COLON);
return false; // Illegal line missing a colon.
+ }
if (colon_index > line_end)
break;
@@ -124,7 +171,7 @@ bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
if (!Dictionary::CanSet(domain, path, ports, dictionary_url))
return false;
- DHISTOGRAM_COUNTS(L"Sdch.Dictionary size loaded", dictionary_text.size());
+ HISTOGRAM_COUNTS(L"Sdch.Dictionary size loaded", dictionary_text.size());
DLOG(INFO) << "Loaded dictionary with client hash " << client_hash <<
" and server hash " << server_hash;
Dictionary* dictionary =
@@ -139,8 +186,10 @@ void SdchManager::GetVcdiffDictionary(const std::string& server_hash,
const GURL& referring_url, Dictionary** dictionary) {
*dictionary = NULL;
DictionaryMap::iterator it = dictionaries_.find(server_hash);
- if (it == dictionaries_.end())
+ if (it == dictionaries_.end()) {
+ SdchErrorRecovery(DICTIONARY_NOT_FOUND_FOR_HASH);
return;
+ }
Dictionary* matching_dictionary = it->second;
if (!matching_dictionary->CanUse(referring_url))
return;
@@ -233,19 +282,27 @@ bool SdchManager::Dictionary::CanSet(const std::string& domain,
5. If the dictionary has a Port attribute and the referer URL's port was not
in the list.
*/
- if (domain.empty())
+ if (domain.empty()) {
+ SdchErrorRecovery(DICTIONARY_MISSING_DOMAIN_SPECIFIER);
return false; // Domain is required.
- if (0 ==
- net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size())
+ }
+ if (net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size()
+ == 0) {
+ SdchErrorRecovery(DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN);
return false; // domain was a TLD.
- if (!Dictionary::DomainMatch(dictionary_url, domain))
+ }
+ if (!Dictionary::DomainMatch(dictionary_url, domain)) {
+ SdchErrorRecovery(DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL);
return false;
+ }
// TODO(jar): Enforce item 4 above.
if (!ports.empty()
- && 0 == ports.count(dictionary_url.EffectiveIntPort()))
+ && 0 == ports.count(dictionary_url.EffectiveIntPort())) {
+ SdchErrorRecovery(DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL);
return false;
+ }
return true;
}
@@ -261,15 +318,23 @@ bool SdchManager::Dictionary::CanUse(const GURL referring_url) {
3. The request URL path-matches the path attribute of the dictionary.
4. The request is not an HTTPS request.
*/
- if (!DomainMatch(referring_url, domain_))
+ if (!DomainMatch(referring_url, domain_)) {
+ SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_DOMAIN);
return false;
+ }
if (!ports_.empty()
- && 0 == ports_.count(referring_url.EffectiveIntPort()))
+ && 0 == ports_.count(referring_url.EffectiveIntPort())) {
+ SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PORT_LIST);
return false;
- if (path_.size() && !PathMatch(referring_url.path(), path_))
+ }
+ if (path_.size() && !PathMatch(referring_url.path(), path_)) {
+ SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_PATH);
return false;
- if (referring_url.SchemeIsSecure())
+ }
+ if (referring_url.SchemeIsSecure()) {
+ SdchErrorRecovery(DICTIONARY_FOUND_HAS_WRONG_SCHEME);
return false;
+ }
return true;
}
diff --git a/net/base/sdch_manager.h b/net/base/sdch_manager.h
index ae5f2d3..1d00163 100644
--- a/net/base/sdch_manager.h
+++ b/net/base/sdch_manager.h
@@ -51,6 +51,50 @@ class SdchFetcher {
class SdchManager {
public:
+ // A list of errors that appeared and were either resolved, or used to turn
+ // off sdch encoding.
+ enum ProblemCodes {
+ MIN_PROBLEM_CODE,
+
+ // Content Decode problems.
+ ADDED_CONTENT_ENCODING,
+ FIXED_CONTENT_ENCODING,
+ FIXED_CONTENT_ENCODINGS,
+
+ // Content decoding errors.
+ DECODE_HEADER_ERROR,
+ DECODE_BODY_ERROR,
+
+ // Dictionary selection for use problems.
+ DICTIONARY_NOT_FOUND_FOR_HASH = 10,
+ DICTIONARY_FOUND_HAS_WRONG_DOMAIN,
+ DICTIONARY_FOUND_HAS_WRONG_PORT_LIST,
+ DICTIONARY_FOUND_HAS_WRONG_PATH,
+ DICTIONARY_FOUND_HAS_WRONG_SCHEME,
+ DICTIONARY_HASH_NOT_FOUND,
+ DICTIONARY_HASH_MALFORMED,
+
+ // Decode recovery methods.
+ META_REFRESH_RECOVERY,
+ PASSING_THROUGH_NON_SDCH,
+ UNRECOVERABLE_ERROR,
+
+ // Dictionary saving problems.
+ DICTIONARY_HAS_NO_HEADER = 20,
+ DICTIONARY_HEADER_LINE_MISSING_COLON,
+ DICTIONARY_MISSING_DOMAIN_SPECIFIER,
+ DICTIONARY_SPECIFIES_TOP_LEVEL_DOMAIN,
+ DICTIONARY_DOMAIN_NOT_MATCHING_SOURCE_URL,
+ DICTIONARY_PORT_NOT_MATCHING_SOURCE_URL,
+
+ // Dictionary loading problems.
+ DICTIONARY_LOAD_ATTEMPT_FROM_DIFFERENT_HOST = 30,
+ DICTIONARY_SELECTED_FOR_SSL,
+ DICTIONARY_ALREADY_LOADED,
+
+ MAX_PROBLEM_CODE // Used to bound histogram
+ };
+
// There is one instance of |Dictionary| for each memory-cached SDCH
// dictionary.
class Dictionary : public base::RefCounted<Dictionary> {
@@ -121,18 +165,31 @@ class SdchManager {
// Provide access to the single instance of this class.
static SdchManager* Global();
+ // Record stats on various errors.
+ static void SdchErrorRecovery(ProblemCodes problem);
+
// Register a fetcher that this class can use to obtain dictionaries.
void set_sdch_fetcher(SdchFetcher* fetcher) { fetcher_.reset(fetcher); }
// If called with an empty string, advertise and support sdch on all domains.
// If called with a specific string, advertise and support only the specified
- // domain.
- static void enable_sdch_support(const std::string& domain) {
- // We presume that there is a SDCH manager instance.
- global_->supported_domain_ = domain;
- global_->sdch_enabled_ = true;
- }
+ // domain. Function assumes the existence of a global SdchManager instance.
+ void EnableSdchSupport(const std::string& domain);
+
+ static bool sdch_enabled() { return global_ && global_->sdch_enabled_; }
+ // Prevent further advertising of SDCH on this domain (if SDCH is enabled).
+ // Used when filter errors are found from a given domain, to prevent further
+ // use of SDCH on that domain.
+ static bool BlacklistDomain(const GURL& url);
+
+ // For testing only, tihs function resets enabling of sdch, and clears the
+ // blacklist.
+ static void ClearBlacklistings();
+
+ // Check to see if SDCH is enabled (globally), and the given URL is in a
+ // supported domain (i.e., not blacklisted, and either the specific supported
+ // domain, or all domains were assumed supported).
const bool IsInSupportedDomain(const GURL& url) const;
// Schedule the URL fetching to load a dictionary. This will generally return
@@ -177,7 +234,7 @@ class SdchManager {
// The one global instance of that holds all the data.
static SdchManager* global_;
- // A simple implementatino of a RFC 3548 "URL safe" base64 encoder.
+ // A simple implementation of a RFC 3548 "URL safe" base64 encoder.
static void UrlSafeBase64Encode(const std::string& input,
std::string* output);
DictionaryMap dictionaries_;
@@ -192,6 +249,9 @@ class SdchManager {
// domain is supported.
std::string supported_domain_;
+ // List domains where decode failures have required disabling sdch.
+ std::set<std::string> blacklisted_domains_;
+
DISALLOW_COPY_AND_ASSIGN(SdchManager);
};
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index fe1332d..4539f13 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -173,11 +173,65 @@ bool URLRequestHttpJob::GetContentEncodings(
void* iter = NULL;
while (response_info_->headers->EnumerateHeader(&iter, "Content-Encoding",
&encoding_type)) {
- encoding_types->push_back(encoding_type);
+ encoding_types->push_back(StringToLowerASCII(encoding_type));
}
+
+ // TODO(jar): Transition to returning enums, rather than strings, and perform
+ // all content encoding fixups here, rather than doing some in the
+ // FilterFactor(). Note that enums generated can be more specific than mere
+ // restatement of strings. For example, rather than just having a GZIP
+ // encoding we can have a GZIP_OPTIONAL encoding to help with odd SDCH related
+ // fixups.
+
+ // TODO(jar): Refactor code so that content-encoding error recovery is
+ // testable via unit tests.
+
+ if (!IsSdchResponse())
+ return !encoding_types->empty();
+
+ // If content encoding included SDCH, then everything is fine.
+ if (!encoding_types->empty() && ("sdch" == encoding_types->front()))
+ return !encoding_types->empty();
+
+ // SDCH "search results" protective hack: To make sure we don't break the only
+ // currently deployed SDCH enabled server, be VERY cautious about proxies that
+ // strip all content-encoding to not include sdch. IF we don't see content
+ // encodings that seem to match what we'd expect from a server that asked us
+ // to use a dictionary (and we advertised said dictionary in the GET), then
+ // we set the encoding to (try to) use SDCH to decode. Note that SDCH will
+ // degrade into a pass-through filter if it doesn't have a viable dictionary
+ // hash in its header. Also note that a solo "sdch" will implicitly create
+ // a "sdch,gzip" decoding filter, where the gzip portion will degrade to a
+ // pass through if a gzip header is not encountered. Hence we can replace
+ // "gzip" with "sdch" and "everything will work."
+ // The one failure mode comes when we advertise a dictionary, and the server
+ // tries to *send* a gzipped file (not gzip encode content), and then we could
+ // do a gzip decode :-(. Since current server support does not ever see such
+ // a transfer, we are safe (for now).
+
+ std::string mime_type;
+ GetMimeType(&mime_type);
+ if (std::string::npos != mime_type.find_first_of("text/html")) {
+ // Suspicious case: Advertised dictionary, but server didn't use sdch, even
+ // though it is text_html content.
+ if (encoding_types->empty())
+ SdchManager::SdchErrorRecovery(SdchManager::ADDED_CONTENT_ENCODING);
+ else if (encoding_types->size() == 1)
+ SdchManager::SdchErrorRecovery(SdchManager::FIXED_CONTENT_ENCODING);
+ else
+ SdchManager::SdchErrorRecovery(SdchManager::FIXED_CONTENT_ENCODINGS);
+ encoding_types->clear();
+ encoding_types->push_back("sdch"); // Handle SDCH/GZIP-opt encoding.
+ }
+
return !encoding_types->empty();
}
+bool URLRequestHttpJob::IsSdchResponse() const {
+ return response_info_ &&
+ (request_info_.load_flags & net::LOAD_SDCH_DICTIONARY_ADVERTISED);
+}
+
bool URLRequestHttpJob::IsRedirectResponse(GURL* location,
int* http_status_code) {
if (!response_info_)
@@ -516,9 +570,11 @@ void URLRequestHttpJob::AddExtraHeaders() {
std::string avail_dictionaries;
SdchManager::Global()->GetAvailDictionaryList(request_->url(),
&avail_dictionaries);
- if (!avail_dictionaries.empty())
+ if (!avail_dictionaries.empty()) {
request_info_.extra_headers += "Avail-Dictionary: "
+ avail_dictionaries + "\r\n";
+ request_info_.load_flags |= net::LOAD_SDCH_DICTIONARY_ADVERTISED;
+ }
scoped_ptr<FileVersionInfo> file_version_info(
FileVersionInfo::CreateFileVersionInfoForCurrentModule());
diff --git a/net/url_request/url_request_http_job.h b/net/url_request/url_request_http_job.h
index 908db2c..b984722 100644
--- a/net/url_request/url_request_http_job.h
+++ b/net/url_request/url_request_http_job.h
@@ -42,6 +42,7 @@ class URLRequestHttpJob : public URLRequestJob {
virtual bool GetResponseCookies(std::vector<std::string>* cookies);
virtual int GetResponseCode();
virtual bool GetContentEncodings(std::vector<std::string>* encoding_type);
+ virtual bool IsSdchResponse() const;
virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
virtual bool IsSafeRedirect(const GURL& location);
virtual bool NeedsAuth();
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
index 0501286..f4924bb 100644
--- a/net/url_request/url_request_job.cc
+++ b/net/url_request/url_request_job.cc
@@ -52,8 +52,10 @@ void URLRequestJob::SetupFilter() {
std::string mime_type;
GetMimeType(&mime_type);
filter_.reset(Filter::Factory(encoding_types, mime_type, kFilterBufSize));
- if (filter_.get())
+ if (filter_.get()) {
filter_->SetURL(request_->url());
+ filter_->SetMimeType(mime_type);
+ }
}
}
diff --git a/net/url_request/url_request_job.h b/net/url_request/url_request_job.h
index 1e4a089..2df62fd 100644
--- a/net/url_request/url_request_job.h
+++ b/net/url_request/url_request_job.h
@@ -123,6 +123,10 @@ class URLRequestJob : public base::RefCountedThreadSafe<URLRequestJob> {
return false;
}
+ // Find out if this is a response to a request that advertised an SDCH
+ // dictionary. Only makes sense for some types of requests.
+ virtual bool IsSdchResponse() const { return false; }
+
// Called to setup stream filter for this request. An example of filter is
// content encoding/decoding.
void SetupFilter();