diff options
Diffstat (limited to 'net/base')
-rw-r--r-- | net/base/bzip2_filter_unittest.cc | 62 | ||||
-rw-r--r-- | net/base/filter.cc | 80 | ||||
-rw-r--r-- | net/base/filter.h | 72 | ||||
-rw-r--r-- | net/base/gzip_filter_unittest.cc | 61 | ||||
-rw-r--r-- | net/base/sdch_filter.cc | 170 | ||||
-rw-r--r-- | net/base/sdch_filter.h | 95 | ||||
-rw-r--r-- | net/base/sdch_filter_unitest.cc | 441 | ||||
-rw-r--r-- | net/base/sdch_manager.cc | 336 | ||||
-rw-r--r-- | net/base/sdch_manager.h | 202 |
9 files changed, 1459 insertions, 60 deletions
diff --git a/net/base/bzip2_filter_unittest.cc b/net/base/bzip2_filter_unittest.cc index a7f742b..19a36af 100644 --- a/net/base/bzip2_filter_unittest.cc +++ b/net/base/bzip2_filter_unittest.cc @@ -92,8 +92,8 @@ class BZip2FilterUnitTest : public PlatformTest { // Encoded_source and encoded_source_len are compressed data and its size. // Output_buffer_size specifies the size of buffer to read out data from // filter. - // get_extra_data specifies whether get the extra data because maybe some server - // might send extra data after finish sending compress data + // get_extra_data specifies whether get the extra data because maybe some + // server might send extra data after finish sending compress data. void DecodeAndCompareWithFilter(Filter* filter, const char* source, int source_len, @@ -130,7 +130,7 @@ class BZip2FilterUnitTest : public PlatformTest { while (1) { int decode_data_len = std::min(decode_avail_size, output_buffer_size); - code = filter->ReadFilteredData(decode_next, &decode_data_len); + code = filter->ReadData(decode_next, &decode_data_len); decode_next += decode_data_len; decode_avail_size -= decode_data_len; @@ -164,11 +164,13 @@ class BZip2FilterUnitTest : public PlatformTest { int* dest_len) { memcpy(filter->stream_buffer(), source, source_len); filter->FlushStreamBuffer(source_len); - return filter->ReadFilteredData(dest, dest_len); + return filter->ReadData(dest, dest_len); } const char* source_buffer() const { return source_buffer_.data(); } - int source_len() const { return static_cast<int>(source_buffer_.size()) - kExtraDataBufferSize; } + int source_len() const { + return static_cast<int>(source_buffer_.size()) - kExtraDataBufferSize; + } std::string source_buffer_; @@ -180,8 +182,10 @@ class BZip2FilterUnitTest : public PlatformTest { // Basic scenario: decoding bzip2 data with big enough buffer. TEST_F(BZip2FilterUnitTest, DecodeBZip2) { // Decode the compressed data with filter + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); memcpy(filter->stream_buffer(), bzip2_encode_buffer_, bzip2_encode_len_); filter->FlushStreamBuffer(bzip2_encode_len_); @@ -189,7 +193,7 @@ TEST_F(BZip2FilterUnitTest, DecodeBZip2) { char bzip2_decode_buffer[kDefaultBufferSize]; int bzip2_decode_size = kDefaultBufferSize; Filter::FilterStatus result = - filter->ReadFilteredData(bzip2_decode_buffer, &bzip2_decode_size); + filter->ReadData(bzip2_decode_buffer, &bzip2_decode_size); ASSERT_EQ(Filter::FILTER_DONE, result); // Compare the decoding result with source data @@ -201,8 +205,10 @@ TEST_F(BZip2FilterUnitTest, DecodeBZip2) { // To do that, we create a filter with a small buffer that can not hold all // the input data. TEST_F(BZip2FilterUnitTest, DecodeWithSmallInputBuffer) { + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, kSmallBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kSmallBufferSize)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(), bzip2_encode_buffer_, bzip2_encode_len_, @@ -211,8 +217,10 @@ TEST_F(BZip2FilterUnitTest, DecodeWithSmallInputBuffer) { // Tests we can decode when caller has small buffer to read out from filter. TEST_F(BZip2FilterUnitTest, DecodeWithSmallOutputBuffer) { + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(), bzip2_encode_buffer_, bzip2_encode_len_, @@ -224,8 +232,10 @@ TEST_F(BZip2FilterUnitTest, DecodeWithSmallOutputBuffer) { // header correctly. (2) Sometimes the filter will consume input without // generating output. Verify filter can handle it correctly. TEST_F(BZip2FilterUnitTest, DecodeWithOneByteInputBuffer) { + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, 1)); + Filter::Factory(filters, kApplicationOctetStream, 1)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(), bzip2_encode_buffer_, bzip2_encode_len_, @@ -235,8 +245,10 @@ TEST_F(BZip2FilterUnitTest, DecodeWithOneByteInputBuffer) { // Tests we can still decode with just 1 byte buffer in the filter and just 1 // byte buffer in the caller. TEST_F(BZip2FilterUnitTest, DecodeWithOneByteInputAndOutputBuffer) { + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, 1)); + Filter::Factory(filters, kApplicationOctetStream, 1)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(), bzip2_encode_buffer_, bzip2_encode_len_, 1, false); @@ -252,8 +264,10 @@ TEST_F(BZip2FilterUnitTest, DecodeCorruptedData) { int corrupt_decode_size = kDefaultBufferSize; // Decode the correct data with filter + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter1( - Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter1.get()); Filter::FilterStatus code = DecodeAllWithFilter(filter1.get(), @@ -267,7 +281,7 @@ TEST_F(BZip2FilterUnitTest, DecodeCorruptedData) { // Decode the corrupted data with filter scoped_ptr<Filter> filter2( - Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter2.get()); int pos = corrupt_data_len / 2; @@ -295,8 +309,10 @@ TEST_F(BZip2FilterUnitTest, DecodeMissingData) { --corrupt_data_len; // Decode the corrupted data with filter + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); char corrupt_decode_buffer[kDefaultBufferSize]; int corrupt_decode_size = kDefaultBufferSize; @@ -319,8 +335,10 @@ TEST_F(BZip2FilterUnitTest, DecodeCorruptedHeader) { corrupt_data[2] = !corrupt_data[2]; // Decode the corrupted data with filter + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); char corrupt_decode_buffer[kDefaultBufferSize]; int corrupt_decode_size = kDefaultBufferSize; @@ -344,11 +362,14 @@ TEST_F(BZip2FilterUnitTest, DecodeWithExtraDataAndSmallOutputBuffer) { memcpy(more_data, bzip2_encode_buffer_, bzip2_encode_len_); memcpy(more_data + bzip2_encode_len_, kExtraData, kExtraDataBufferSize); + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), - source_buffer(), source_len() + kExtraDataBufferSize, + source_buffer(), + source_len() + kExtraDataBufferSize, more_data, more_data_len, kSmallBufferSize, @@ -361,11 +382,14 @@ TEST_F(BZip2FilterUnitTest, DecodeWithExtraDataAndSmallInputBuffer) { memcpy(more_data, bzip2_encode_buffer_, bzip2_encode_len_); memcpy(more_data + bzip2_encode_len_, kExtraData, kExtraDataBufferSize); + std::vector<std::string> filters; + filters.push_back("bzip2"); scoped_ptr<Filter> filter( - Filter::Factory("bzip2", kApplicationOctetStream, kSmallBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kSmallBufferSize)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), - source_buffer(), source_len() + kExtraDataBufferSize, + source_buffer(), + source_len() + kExtraDataBufferSize, more_data, more_data_len, kDefaultBufferSize, diff --git a/net/base/filter.cc b/net/base/filter.cc index 80264fd..c435f9b 100644 --- a/net/base/filter.cc +++ b/net/base/filter.cc @@ -7,6 +7,7 @@ #include "base/string_util.h" #include "net/base/gzip_filter.h" #include "net/base/bzip2_filter.h" +#include "net/base/sdch_filter.h" namespace { @@ -16,6 +17,7 @@ const char kGZip[] = "gzip"; const char kXGZip[] = "x-gzip"; const char kBZip2[] = "bzip2"; const char kXBZip2[] = "x-bzip2"; +const char kSdch[] = "sdch"; // compress and x-compress are currently not supported. If we decide to support // them, we'll need the same mime type compatibility hack we have for gzip. For // more information, see Firefox's nsHttpChannel::ProcessNormal. @@ -33,12 +35,34 @@ const char kApplicationCompress[] = "application/compress"; } // namespace -Filter* Filter::Factory(const std::string& filter_type, +Filter* Filter::Factory(const std::vector<std::string>& filter_types, const std::string& mime_type, int buffer_size) { - if (filter_type.empty() || buffer_size < 0) + if (filter_types.empty() || buffer_size < 0) return NULL; + 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; + } + return filter_list; +} + +// static +Filter* Filter::SingleFilter(const std::string& filter_type, + const std::string& mime_type, + int buffer_size) { FilterType type_id; if (LowerCaseEqualsASCII(filter_type, kDeflate)) { type_id = FILTER_TYPE_DEFLATE; @@ -58,6 +82,8 @@ Filter* Filter::Factory(const std::string& filter_type, } else if (LowerCaseEqualsASCII(filter_type, kBZip2) || LowerCaseEqualsASCII(filter_type, kXBZip2)) { type_id = FILTER_TYPE_BZIP2; + } else if (LowerCaseEqualsASCII(filter_type, kSdch)) { + type_id = FILTER_TYPE_SDCH; } else { // Note we also consider "identity" and "uncompressed" UNSUPPORTED as // filter should be disabled in such cases. @@ -84,6 +110,15 @@ Filter* Filter::Factory(const std::string& filter_type, } break; } + case FILTER_TYPE_SDCH: { + scoped_ptr<SdchFilter> sdch_filter(new SdchFilter()); + if (sdch_filter->InitBuffer(buffer_size)) { + if (sdch_filter->InitDecoding()) { + return sdch_filter.release(); + } + } + break; + } default: { break; } @@ -96,7 +131,9 @@ Filter::Filter() : stream_buffer_(NULL), stream_buffer_size_(0), next_stream_data_(NULL), - stream_data_len_(0) { + stream_data_len_(0), + next_filter_(NULL), + last_status_(FILTER_OK) { } Filter::~Filter() {} @@ -143,6 +180,38 @@ Filter::FilterStatus Filter::ReadFilteredData(char* dest_buffer, return Filter::FILTER_ERROR; } +Filter::FilterStatus Filter::ReadData(char* dest_buffer, int* dest_len) { + if (last_status_ == FILTER_ERROR) + return last_status_; + if (!next_filter_.get()) + return last_status_ = ReadFilteredData(dest_buffer, dest_len); + if (last_status_ == FILTER_NEED_MORE_DATA && !stream_data_len()) + return next_filter_->ReadData(dest_buffer, dest_len); + if (next_filter_->last_status() == FILTER_NEED_MORE_DATA) { + // Push data into next filter's input. + char* next_buffer = next_filter_->stream_buffer(); + int next_size = next_filter_->stream_buffer_size(); + last_status_ = ReadFilteredData(next_buffer, &next_size); + next_filter_->FlushStreamBuffer(next_size); + switch (last_status_) { + case FILTER_ERROR: + return last_status_; + + case FILTER_NEED_MORE_DATA: + return next_filter_->ReadData(dest_buffer, dest_len); + + case FILTER_OK: + case FILTER_DONE: + break; + } + } + FilterStatus status = next_filter_->ReadData(dest_buffer, dest_len); + // We could loop to fill next_filter_ if it needs data, but we have to be + // careful about output buffer. Simpler is to just wait until we are called + // again, and return FILTER_OK. + return (status == FILTER_ERROR) ? FILTER_ERROR : FILTER_OK; +} + bool Filter::FlushStreamBuffer(int stream_data_len) { if (stream_data_len <= 0 || stream_data_len > stream_buffer_size_) return false; @@ -156,3 +225,8 @@ bool Filter::FlushStreamBuffer(int stream_data_len) { return true; } +void Filter::SetURL(const GURL& url) { + url_ = url; + if (next_filter_.get()) + next_filter_->SetURL(url); +} diff --git a/net/base/filter.h b/net/base/filter.h index ecba302..3551056 100644 --- a/net/base/filter.h +++ b/net/base/filter.h @@ -16,7 +16,7 @@ // int post_filter_data_len = kBufferSize; // filter->ReadFilteredData(post_filter_buf, &post_filter_data_len); // -// To filters a data stream, the caller first gets filter's stream_buffer_ +// To filter a data stream, the caller first gets filter's stream_buffer_ // through its accessor and fills in stream_buffer_ with pre-filter data, next // calls FlushStreamBuffer to notify Filter, then calls ReadFilteredData // repeatedly to get all the filtered data. After all data have been fitlered @@ -30,19 +30,29 @@ #define NET_BASE_FILTER_H__ #include <string> +#include <vector> #include "base/basictypes.h" #include "base/scoped_ptr.h" +#include "googleurl/src/gurl.h" class Filter { public: // Creates a Filter object. - // Parameters: Filter_type specifies the type of filter created; Buffer_size + // Parameters: Filter_types specifies the type of filter created; Buffer_size // specifies the size (in number of chars) of the buffer the filter should // allocate to hold pre-filter data. // If success, the function returns the pointer to the Filter object created. // If failed or a filter is not needed, the function returns NULL. - static Filter* Factory(const std::string& filter_type, + // + // Note: filter_types is an array of filter names (content encoding types as + // provided in an HTTP header), which will be chained together serially do + // successive filtering of data. The names in the vector are ordered based on + // encoding order, and the filters are chained to operate in the reverse + // (decoding) order. For example, types[0] = "sdch", types[1] = "gzip" will + // cause data to first be gunizip filtered, and the resulting output from that + // filter will be sdch decoded. + static Filter* Factory(const std::vector<std::string>& filter_types, const std::string& mime_type, int buffer_size); @@ -64,18 +74,9 @@ class Filter { FILTER_ERROR }; - // Filters the data stored in stream_buffer_ and writes the output into the - // dest_buffer passed in. - // - // Upon entry, *dest_len is the total size (in number of chars) of the - // destination buffer. Upon exit, *dest_len is the actual number of chars - // written into the destination buffer. - // - // This function will fail if there is no pre-filter data in the - // stream_buffer_. On the other hand, *dest_len can be 0 upon successful - // return. For example, a decoding filter may process some pre-filter data - // but not produce output yet. - virtual FilterStatus ReadFilteredData(char* dest_buffer, int* dest_len); + // 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); // Returns a pointer to the beginning of stream_buffer_. char* stream_buffer() const { return stream_buffer_.get(); } @@ -102,7 +103,23 @@ class Filter { // The function returns true if success, and false otherwise. bool FlushStreamBuffer(int stream_data_len); + void SetURL(const GURL& url); + const GURL& url() const { return url_; } + protected: + // Filters the data stored in stream_buffer_ and writes the output into the + // dest_buffer passed in. + // + // Upon entry, *dest_len is the total size (in number of chars) of the + // destination buffer. Upon exit, *dest_len is the actual number of chars + // written into the destination buffer. + // + // This function will fail if there is no pre-filter data in the + // stream_buffer_. On the other hand, *dest_len can be 0 upon successful + // return. For example, a decoding filter may process some pre-filter data + // 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. @@ -113,6 +130,7 @@ class Filter { FILTER_TYPE_DEFLATE, FILTER_TYPE_GZIP, FILTER_TYPE_BZIP2, + FILTER_TYPE_SDCH, // open-vcdiff compression relative to a dictionary. FILTER_TYPE_UNSUPPORTED }; @@ -120,6 +138,15 @@ class Filter { // Buffer_size is the maximum size of stream_buffer_ in number of chars. bool InitBuffer(int buffer_size); + // 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); + FilterStatus last_status() const { return last_status_; } + // Buffer to hold the data to be filtered. scoped_array<char> stream_buffer_; @@ -132,11 +159,18 @@ class Filter { // Total number of remaining chars in stream_buffer_ to be filtered. int stream_data_len_; - // Filter can be chained - // TODO (huanr) - // Filter* next_filter_; + // The URL that is currently being filtered. + // This is used by SDCH filters which need to restrict use of a dictionary to + // a specific URL or path. + GURL url_; + + // 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 + // chained filters. + FilterStatus last_status_; - DISALLOW_EVIL_CONSTRUCTORS(Filter); + DISALLOW_COPY_AND_ASSIGN(Filter); }; #endif // NET_BASE_FILTER_H__ diff --git a/net/base/gzip_filter_unittest.cc b/net/base/gzip_filter_unittest.cc index fcec028..22c1ae3 100644 --- a/net/base/gzip_filter_unittest.cc +++ b/net/base/gzip_filter_unittest.cc @@ -182,7 +182,7 @@ class GZipUnitTest : public PlatformTest { while (1) { int decode_data_len = std::min(decode_avail_size, output_buffer_size); - code = filter->ReadFilteredData(decode_next, &decode_data_len); + code = filter->ReadData(decode_next, &decode_data_len); decode_next += decode_data_len; decode_avail_size -= decode_data_len; @@ -210,7 +210,7 @@ class GZipUnitTest : public PlatformTest { char* dest, int* dest_len) { memcpy(filter->stream_buffer(), source, source_len); filter->FlushStreamBuffer(source_len); - return filter->ReadFilteredData(dest, dest_len); + return filter->ReadData(dest, dest_len); } const char* source_buffer() const { return source_buffer_.data(); } @@ -228,15 +228,17 @@ class GZipUnitTest : public PlatformTest { // Basic scenario: decoding deflate data with big enough buffer. TEST_F(GZipUnitTest, DecodeDeflate) { // Decode the compressed data with filter + std::vector<std::string> filters; + filters.push_back("deflate"); scoped_ptr<Filter> filter( - Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); memcpy(filter->stream_buffer(), deflate_encode_buffer_, deflate_encode_len_); filter->FlushStreamBuffer(deflate_encode_len_); char deflate_decode_buffer[kDefaultBufferSize]; int deflate_decode_size = kDefaultBufferSize; - filter->ReadFilteredData(deflate_decode_buffer, &deflate_decode_size); + filter->ReadData(deflate_decode_buffer, &deflate_decode_size); // Compare the decoding result with source data EXPECT_TRUE(deflate_decode_size == source_len()); @@ -246,15 +248,17 @@ TEST_F(GZipUnitTest, DecodeDeflate) { // Basic scenario: decoding gzip data with big enough buffer. TEST_F(GZipUnitTest, DecodeGZip) { // Decode the compressed data with filter + std::vector<std::string> filters; + filters.push_back("gzip"); scoped_ptr<Filter> filter( - Filter::Factory("gzip", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); memcpy(filter->stream_buffer(), gzip_encode_buffer_, gzip_encode_len_); filter->FlushStreamBuffer(gzip_encode_len_); char gzip_decode_buffer[kDefaultBufferSize]; int gzip_decode_size = kDefaultBufferSize; - filter->ReadFilteredData(gzip_decode_buffer, &gzip_decode_size); + filter->ReadData(gzip_decode_buffer, &gzip_decode_size); // Compare the decoding result with source data EXPECT_TRUE(gzip_decode_size == source_len()); @@ -265,8 +269,10 @@ TEST_F(GZipUnitTest, DecodeGZip) { // To do that, we create a filter with a small buffer that can not hold all // the input data. TEST_F(GZipUnitTest, DecodeWithSmallBuffer) { + std::vector<std::string> filters; + filters.push_back("deflate"); scoped_ptr<Filter> filter( - Filter::Factory("deflate", kApplicationOctetStream, kSmallBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kSmallBufferSize)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(), deflate_encode_buffer_, deflate_encode_len_, @@ -278,8 +284,10 @@ TEST_F(GZipUnitTest, DecodeWithSmallBuffer) { // header correctly. (2) Sometimes the filter will consume input without // generating output. Verify filter can handle it correctly. TEST_F(GZipUnitTest, DecodeWithOneByteBuffer) { + std::vector<std::string> filters; + filters.push_back("gzip"); scoped_ptr<Filter> filter( - Filter::Factory("gzip", kApplicationOctetStream, 1)); + Filter::Factory(filters, kApplicationOctetStream, 1)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(), gzip_encode_buffer_, gzip_encode_len_, @@ -288,8 +296,10 @@ TEST_F(GZipUnitTest, DecodeWithOneByteBuffer) { // Tests we can decode when caller has small buffer to read out from filter. TEST_F(GZipUnitTest, DecodeWithSmallOutputBuffer) { + std::vector<std::string> filters; + filters.push_back("deflate"); scoped_ptr<Filter> filter( - Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(), deflate_encode_buffer_, deflate_encode_len_, @@ -299,8 +309,10 @@ TEST_F(GZipUnitTest, DecodeWithSmallOutputBuffer) { // Tests we can still decode with just 1 byte buffer in the filter and just 1 // byte buffer in the caller. TEST_F(GZipUnitTest, DecodeWithOneByteInputAndOutputBuffer) { + std::vector<std::string> filters; + filters.push_back("gzip"); scoped_ptr<Filter> filter( - Filter::Factory("gzip", kApplicationOctetStream, 1)); + Filter::Factory(filters, kApplicationOctetStream, 1)); ASSERT_TRUE(filter.get()); DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(), gzip_encode_buffer_, gzip_encode_len_, 1); @@ -316,8 +328,10 @@ TEST_F(GZipUnitTest, DecodeCorruptedData) { corrupt_data[pos] = !corrupt_data[pos]; // Decode the corrupted data with filter + std::vector<std::string> filters; + filters.push_back("deflate"); scoped_ptr<Filter> filter( - Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); char corrupt_decode_buffer[kDefaultBufferSize]; int corrupt_decode_size = kDefaultBufferSize; @@ -341,8 +355,10 @@ TEST_F(GZipUnitTest, DecodeMissingData) { --corrupt_data_len; // Decode the corrupted data with filter + std::vector<std::string> filters; + filters.push_back("deflate"); scoped_ptr<Filter> filter( - Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); char corrupt_decode_buffer[kDefaultBufferSize]; int corrupt_decode_size = kDefaultBufferSize; @@ -363,8 +379,10 @@ TEST_F(GZipUnitTest, DecodeCorruptedHeader) { corrupt_data[2] = !corrupt_data[2]; // Decode the corrupted data with filter + std::vector<std::string> filters; + filters.push_back("gzip"); scoped_ptr<Filter> filter( - Filter::Factory("gzip", kApplicationOctetStream, kDefaultBufferSize)); + Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize)); ASSERT_TRUE(filter.get()); char corrupt_decode_buffer[kDefaultBufferSize]; int corrupt_decode_size = kDefaultBufferSize; @@ -380,18 +398,23 @@ TEST_F(GZipUnitTest, ApacheWorkaround) { const int kBufferSize = kDefaultBufferSize; // To fit in 80 cols. scoped_ptr<Filter> filter; - filter.reset(Filter::Factory("gzip", kApplicationXGzip, kBufferSize)); + std::vector<std::string> gzip_filters, x_gzip_filters; + gzip_filters.push_back("gzip"); + x_gzip_filters.push_back("x-gzip"); + + filter.reset(Filter::Factory(gzip_filters, kApplicationXGzip, kBufferSize)); EXPECT_FALSE(filter.get()); - filter.reset(Filter::Factory("gzip", kApplicationGzip, kBufferSize)); + filter.reset(Filter::Factory(gzip_filters, kApplicationGzip, kBufferSize)); EXPECT_FALSE(filter.get()); - filter.reset(Filter::Factory("gzip", kApplicationXGunzip, kBufferSize)); + filter.reset(Filter::Factory(gzip_filters, kApplicationXGunzip, kBufferSize)); EXPECT_FALSE(filter.get()); - filter.reset(Filter::Factory("x-gzip", kApplicationXGzip, kBufferSize)); + filter.reset(Filter::Factory(x_gzip_filters, kApplicationXGzip, kBufferSize)); EXPECT_FALSE(filter.get()); - filter.reset(Filter::Factory("x-gzip", kApplicationGzip, kBufferSize)); + filter.reset(Filter::Factory(x_gzip_filters, kApplicationGzip, kBufferSize)); EXPECT_FALSE(filter.get()); - filter.reset(Filter::Factory("x-gzip", kApplicationXGunzip, kBufferSize)); + filter.reset(Filter::Factory(x_gzip_filters, kApplicationXGunzip, + kBufferSize)); EXPECT_FALSE(filter.get()); } diff --git a/net/base/sdch_filter.cc b/net/base/sdch_filter.cc new file mode 100644 index 0000000..ef4b4f0 --- /dev/null +++ b/net/base/sdch_filter.cc @@ -0,0 +1,170 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> + +#include "base/file_util.h" +#include "base/histogram.h" +#include "base/logging.h" +#include "net/base/sdch_filter.h" +#include "net/base/sdch_manager.h" + +#include "sdch/open_vcdiff/depot/opensource/open-vcdiff/src/google/vcdecoder.h" + +SdchFilter::SdchFilter() + : decoding_status_(DECODING_UNINITIALIZED), + vcdiff_streaming_decoder_(NULL), + dictionary_(NULL), + dest_buffer_excess_(), + dest_buffer_excess_index_(0), + source_bytes_(0), + output_bytes_(0) { +} + +SdchFilter::~SdchFilter() { + 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_); + } + } + if (dictionary_) + dictionary_->Release(); +} + +bool SdchFilter::InitDecoding() { + if (decoding_status_ != DECODING_UNINITIALIZED) + return false; + + // Initialize decoder only after we have a dictionary in hand. + decoding_status_ = WAITING_FOR_DICTIONARY_SELECTION; + return true; +} + +Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer, + int* dest_len) { + int available_space = *dest_len; + *dest_len = 0; // Nothing output yet. + + if (!dest_buffer || available_space <= 0) + return FILTER_ERROR; + + char* dest_buffer_end = dest_buffer + available_space; + + 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 (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; + available_space -= amount; + DCHECK(available_space >= 0); + + if (available_space <= 0) + return FILTER_OK; + DCHECK(dest_buffer_excess_.empty()); + + if (!next_stream_data_ || stream_data_len_ <= 0) + return FILTER_NEED_MORE_DATA; + + bool ret = vcdiff_streaming_decoder_->DecodeChunk( + next_stream_data_, stream_data_len_, &dest_buffer_excess_); + // Assume all data was used in decoding. + next_stream_data_ = NULL; + source_bytes_ += stream_data_len_; + stream_data_len_ = 0; + output_bytes_ += dest_buffer_excess_.size(); + if (!ret) { + vcdiff_streaming_decoder_.reset(NULL); // Don't call it again. + decoding_status_ = DECODING_ERROR; + return FILTER_ERROR; + } + + amount = OutputBufferExcess(dest_buffer, available_space); + *dest_len += amount; + dest_buffer += amount; + if (0 == available_space && !dest_buffer_excess_.empty()) + return FILTER_OK; + return FILTER_NEED_MORE_DATA; +} + +Filter::FilterStatus SdchFilter::InitializeDictionary() { + const size_t kServerIdLength = 9; // Dictionary hash plus null from server. + size_t bytes_needed = kServerIdLength - dictionary_hash_.size(); + DCHECK(bytes_needed > 0); + if (!next_stream_data_) + return FILTER_NEED_MORE_DATA; + if (static_cast<size_t>(stream_data_len_) < bytes_needed) { + dictionary_hash_.append(next_stream_data_, stream_data_len_); + next_stream_data_ = NULL; + stream_data_len_ = 0; + return FILTER_NEED_MORE_DATA; + } + dictionary_hash_.append(next_stream_data_, bytes_needed); + DCHECK(kServerIdLength == dictionary_hash_.size()); + stream_data_len_ -= bytes_needed; + DCHECK(0 <= stream_data_len_); + if (stream_data_len_ > 0) + next_stream_data_ += bytes_needed; + 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_); + if (!dictionary_) { + decoding_status_ = DECODING_ERROR; + return FILTER_ERROR; + } + dictionary_->AddRef(); + vcdiff_streaming_decoder_.reset(new open_vcdiff::VCDiffStreamingDecoder); + vcdiff_streaming_decoder_->StartDecoding(dictionary_->text().data(), + dictionary_->text().size()); + decoding_status_ = DECODING_IN_PROGRESS; + return FILTER_OK; +} + +int SdchFilter::OutputBufferExcess(char* const dest_buffer, + size_t available_space) { + if (dest_buffer_excess_.empty()) + return 0; + DCHECK(dest_buffer_excess_.size() > dest_buffer_excess_index_); + size_t amount = std::min(available_space, + dest_buffer_excess_.size() - dest_buffer_excess_index_); + memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_, + amount); + dest_buffer_excess_index_ += amount; + if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) { + DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_); + dest_buffer_excess_.clear(); + dest_buffer_excess_index_ = 0; + } + return amount; +} diff --git a/net/base/sdch_filter.h b/net/base/sdch_filter.h new file mode 100644 index 0000000..a13bd38 --- /dev/null +++ b/net/base/sdch_filter.h @@ -0,0 +1,95 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// SdchFilter applies open_vcdiff content decoding to a datastream. +// This decoding uses a pre-cached dictionary of text fragments to decode +// (expand) the stream back to its original contents. +// +// This SdchFilter internally uses open_vcdiff/vcdec library to do decoding. +// +// SdchFilter is also a subclass of Filter. See the latter's header file +// filter.h for sample usage. + +#ifndef NET_BASE_SDCH_FILTER_H_ +#define NET_BASE_SDCH_FILTER_H_ + +#include <string> + +#include "base/scoped_ptr.h" +#include "net/base/filter.h" +#include "net/base/sdch_manager.h" + +class SafeOutputStringInterface; + +namespace open_vcdiff { + class VCDiffStreamingDecoder; +} + +class SdchFilter : public Filter { + public: + SdchFilter(); + + virtual ~SdchFilter(); + + // Initializes filter decoding mode and internal control blocks. + bool InitDecoding(); + + // Decode the pre-filter data and writes the output into |dest_buffer| + // The function returns FilterStatus. See filter.h for its description. + // + // Upon entry, *dest_len is the total size (in number of chars) of the + // destination buffer. Upon exit, *dest_len is the actual number of chars + // written into the destination buffer. + virtual FilterStatus ReadFilteredData(char* dest_buffer, int* dest_len); + + private: + // Internal status. Once we enter an error state, we stop processing data. + enum DecodingStatus { + DECODING_UNINITIALIZED, + WAITING_FOR_DICTIONARY_SELECTION, + DECODING_IN_PROGRESS, + DECODING_ERROR + }; + + // Identify the suggested dictionary, and initialize underlying decompressor. + Filter::FilterStatus InitializeDictionary(); + + // Move data that was internally buffered (after decompression) to the + // specified dest_buffer. + int OutputBufferExcess(char* const dest_buffer, size_t available_space); + + // Tracks the status of decoding. + // This variable is initialized by InitDecoding and updated only by + // ReadFilteredData. + DecodingStatus decoding_status_; + + // The underlying decoder that processes data. + // This data structure is initialized by InitDecoding and updated in + // ReadFilteredData. + 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." + std::string dictionary_hash_; + + // 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_; + + // The decoder may demand a larger output buffer than the target of + // ReadFilteredData so we buffer the excess output between calls. + std::string dest_buffer_excess_; + // To avoid moving strings around too much, we save the index into + // dest_buffer_excess_ that has the next byte to output. + size_t dest_buffer_excess_index_; + + // To get stats on activities, we keep track of source and target bytes. + // Visit about:histograms/Sdch to see histogram data. + size_t source_bytes_; + size_t output_bytes_; + + DISALLOW_COPY_AND_ASSIGN(SdchFilter); +}; + +#endif // NET_BASE_SDCH_FILTER_H_ diff --git a/net/base/sdch_filter_unitest.cc b/net/base/sdch_filter_unitest.cc new file mode 100644 index 0000000..c7bbb50 --- /dev/null +++ b/net/base/sdch_filter_unitest.cc @@ -0,0 +1,441 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <string> +#include <vector> + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "net/base/filter.h" +#include "net/base/sdch_filter.h" +#include "net/url_request/url_request_http_job.cc" +#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" + "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n"; +// Pre-compression test data. +const char kTestData[] = "TestData " + "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n"; +// Note SDCH compressed data will include a reference to the SDCH dictionary. +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), + expanded_(kTestData, sizeof(kTestData) - 1), + compressed_test_data_(kCompressedTestData, + sizeof(kCompressedTestData) - 1), + sdch_manager_(new SdchManager) { + } + + const std::string test_vcdiff_dictionary_; + const std::string compressed_test_data_; + const std::string expanded_; // Desired final, decompressed data. + + scoped_ptr<SdchManager> sdch_manager_; // A singleton database. +}; + + +TEST_F(SdchFilterTest, Hashing) { + std::string client_hash, server_hash; + std::string dictionary("test contents"); + SdchManager::GenerateHash(dictionary, &client_hash, &server_hash); + + EXPECT_EQ(client_hash, "lMQBjS3P"); + EXPECT_EQ(server_hash, "MyciMVll"); +} + + +//------------------------------------------------------------------------------ +// Provide a generic helper function for trying to filter data. +// This function repeatedly calls the filter to process data, until the entire +// source is consumed. The return value from the filter is appended to output. +// This allows us to vary input and output block sizes in order to test for edge +// effects (boundary effects?) during the filtering process. +// This function provides data to the filter in blocks of no-more-than the +// specified input_block_length. It allows the filter to fill no more than +// output_buffer_length in any one call to proccess (a.k.a., Read) data, and +// concatenates all these little output blocks into the singular output string. +static bool FilterTestData(const std::string& source, + size_t input_block_length, + const size_t output_buffer_length, + Filter* filter, std::string* output) { + CHECK(input_block_length > 0); + Filter::FilterStatus status(Filter::FILTER_NEED_MORE_DATA); + size_t source_index = 0; + scoped_array<char> output_buffer(new char[output_buffer_length]); + size_t input_amount = std::min(input_block_length, + static_cast<size_t>(filter->stream_buffer_size())); + + do { + int copy_amount = std::min(input_amount, source.size() - source_index); + if (copy_amount > 0 && status == Filter::FILTER_NEED_MORE_DATA) { + memcpy(filter->stream_buffer(), source.data() + source_index, + copy_amount); + filter->FlushStreamBuffer(copy_amount); + source_index += copy_amount; + } + int buffer_length = output_buffer_length; + status = filter->ReadData(output_buffer.get(), &buffer_length); + output->append(output_buffer.get(), buffer_length); + if (status == Filter::FILTER_ERROR) + return false; + if (copy_amount == 0 && buffer_length == 0) + return true; + } while (1); +} +//------------------------------------------------------------------------------ + + +TEST_F(SdchFilterTest, BasicBadDicitonary) { + SdchManager::enable_sdch_support(""); + + std::vector<std::string> filters; + filters.push_back("sdch"); + int kInputBufferSize(30); + char output_buffer[20]; + size_t kOutputBufferSize(20); + scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime", + kInputBufferSize)); + filter->SetURL(GURL("http://ignore.com")); + + + // With no input data, try to read output. + int output_bytes_or_buffer_size = sizeof(output_buffer); + Filter::FilterStatus status = filter->ReadData(output_buffer, + &output_bytes_or_buffer_size); + + EXPECT_EQ(0, output_bytes_or_buffer_size); + EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status); + + // Supply bogus data (which doesnt't yet specify a full dictionary hash). + // Dictionary hash is 8 characters followed by a null. + std::string dictionary_hash_prefix("123"); + + char* input_buffer = filter->stream_buffer(); + int input_buffer_size = filter->stream_buffer_size(); + EXPECT_EQ(kInputBufferSize, input_buffer_size); + + EXPECT_LT(static_cast<int>(dictionary_hash_prefix.size()), + input_buffer_size); + memcpy(input_buffer, dictionary_hash_prefix.data(), + dictionary_hash_prefix.size()); + filter->FlushStreamBuffer(dictionary_hash_prefix.size()); + + // With less than a dictionary specifier, try to read output. + output_bytes_or_buffer_size = sizeof(output_buffer); + status = filter->ReadData(output_buffer, &output_bytes_or_buffer_size); + + EXPECT_EQ(0, output_bytes_or_buffer_size); + EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status); + + // Provide enough data to complete *a* hash, but it is bogus, and not in our + // list of dictionaries, so the filter should error out immediately. + std::string dictionary_hash_postfix("4abcd\0", 6); + + CHECK(dictionary_hash_postfix.size() < + static_cast<size_t>(input_buffer_size)); + memcpy(input_buffer, dictionary_hash_postfix.data(), + dictionary_hash_postfix.size()); + filter->FlushStreamBuffer(dictionary_hash_postfix.size()); + + // With a non-existant dictionary specifier, try to read output. + output_bytes_or_buffer_size = sizeof(output_buffer); + status = filter->ReadData(output_buffer, &output_bytes_or_buffer_size); + + EXPECT_EQ(0, output_bytes_or_buffer_size); + EXPECT_EQ(Filter::FILTER_ERROR, status); +} + + +TEST_F(SdchFilterTest, BasicDictionary) { + 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. + + // Build compressed data that refers to our dictionary. + std::string compressed(server_hash); + compressed.append("\0", 1); + compressed.append(compressed_test_data_); + + 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); + scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime", + kInputBufferSize)); + filter->SetURL(url); + + 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_); + + // Now try 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_); + + // Now try 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")); + + 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. + EXPECT_TRUE(output == ""); // No output written. + + + // Now check that path restrictions on dictionary are being enforced. + + // Create a dictionary with a path restriction, by prefixing old 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_); + + // Test decode the path data, arriving from a valid path. + filter.reset((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_); + + // Test decode the path data, arriving from a invalid path. + filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize))); + filter->SetURL(GURL(url_string)); + + 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_TRUE(output == ""); // No output written. + + + // 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_); + + // Test decode the port data, arriving from a valid port. + filter.reset((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_); + + // Test decode the port data, arriving from a valid (default) port. + filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize))); + filter->SetURL(GURL(url_string)); // Default 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_); + + // Test decode the port data, arriving from a invalid port. + filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize))); + filter->SetURL(GURL(url_string + ":" + port + "1")); + + 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_TRUE(output == ""); // 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) { + 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. + + // 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_); + + // Use Gzip to compress the sdch sdch_compressed data. + z_stream zlib_stream; + memset(&zlib_stream, 0, sizeof(zlib_stream)); + int code; + + // Initialize zlib + code = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + -MAX_WBITS, + 8, // DEF_MEM_LEVEL + Z_DEFAULT_STRATEGY); + + 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(); + + // Assume we can compress into similar buffer (add 100 bytes to be sure). + size_t gzip_compressed_length = zlib_stream.avail_in + 100; + scoped_array<char> gzip_compressed(new char[gzip_compressed_length]); + zlib_stream.next_out = bit_cast<Bytef*>(gzip_compressed.get()); + zlib_stream.avail_out = gzip_compressed_length; + + // The GZIP header (see RFC 1952): + // +---+---+---+---+---+---+---+---+---+---+ + // |ID1|ID2|CM |FLG| MTIME |XFL|OS | + // +---+---+---+---+---+---+---+---+---+---+ + // ID1 \037 + // ID2 \213 + // CM \010 (compression method == DEFLATE) + // FLG \000 (special flags that we do not support) + // MTIME Unix format modification time (0 means not available) + // XFL 2-4? DEFLATE flags + // OS ???? Operating system indicator (255 means unknown) + // + // Header value we generate: + const char kGZipHeader[] = { '\037', '\213', '\010', '\000', '\000', + '\000', '\000', '\000', '\002', '\377' }; + CHECK(zlib_stream.avail_out > sizeof(kGZipHeader)); + memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader)); + zlib_stream.next_out += sizeof(kGZipHeader); + zlib_stream.avail_out -= sizeof(kGZipHeader); + + // Do deflate + code = MOZ_Z_deflate(&zlib_stream, Z_FINISH); + gzip_compressed_length -= zlib_stream.avail_out; + std::string compressed(gzip_compressed.get(), gzip_compressed_length); + + // Construct a chained filter. + std::vector<std::string> filters; + filters.push_back("sdch"); + filters.push_back("gzip"); + + // First try with a large buffer (larger than test input, or compressed data). + int kInputBufferSize(100); + scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime", + kInputBufferSize)); + filter->SetURL(url); + + 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_); + + // 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(); + status = FilterTestData(compressed, feed_block_size, output_block_size, + filter.get(), &output); + EXPECT_TRUE(status); + EXPECT_TRUE(output == expanded_); +} + +}; // namespace anonymous diff --git a/net/base/sdch_manager.cc b/net/base/sdch_manager.cc new file mode 100644 index 0000000..6c8bce3 --- /dev/null +++ b/net/base/sdch_manager.cc @@ -0,0 +1,336 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/histogram.h" +#include "base/logging.h" +#include "base/sha2.h" +#include "base/string_util.h" +#include "net/base/base64.h" +#include "net/base/registry_controlled_domain.h" +#include "net/base/sdch_manager.h" +#include "net/url_request/url_request_http_job.h" + + +//------------------------------------------------------------------------------ +// static +SdchManager* SdchManager::global_; + +// static +SdchManager* SdchManager::Global() { + return global_; +} + +//------------------------------------------------------------------------------ +SdchManager::SdchManager() : sdch_enabled_(false) { + DCHECK(!global_); + global_ = this; +} + +SdchManager::~SdchManager() { + DCHECK(global_ == this); + while (!dictionaries_.empty()) { + DictionaryMap::iterator it = dictionaries_.begin(); + it->second->Release(); + dictionaries_.erase(it->first); + } + global_ = NULL; +} + +const bool SdchManager::IsInSupportedDomain(const GURL& url) const { + return sdch_enabled_ && + (supported_domain_.empty() || + url.DomainIs(supported_domain_.data(), supported_domain_.size())); +} + +void SdchManager::FetchDictionary(const GURL& referring_url, + const GURL& dictionary_url) { + /* The user agent may retrieve a dictionary from the dictionary URL if all of + the following are true: + 1 The dictionary URL host name matches the referrer URL host name + 2 The dictionary URL host name domain matches the parent domain of the + referrer URL host name + 3 The parent domain of the referrer URL host name is not a top level + domain + 4 The dictionary URL is not an HTTPS 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()) + return; + if (referring_url.SchemeIs("https")) + return; + if (fetcher_.get()) + fetcher_->Schedule(dictionary_url); +} + +bool SdchManager::AddSdchDictionary(const std::string& dictionary_text, + const GURL& dictionary_url) { + std::string client_hash; + std::string server_hash; + GenerateHash(dictionary_text, &client_hash, &server_hash); + if (dictionaries_.find(server_hash) != dictionaries_.end()) + 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) + 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); + DCHECK(std::string::npos != line_end); + DCHECK(line_end <= header_end); + + size_t colon_index = dictionary_text.find(':', line_start); + if (std::string::npos == colon_index) + return false; // Illegal line missing a colon. + + if (colon_index > line_end) + break; + + size_t value_start = dictionary_text.find_first_not_of(" \t", + colon_index + 1); + if (std::string::npos != value_start) { + if (value_start >= line_end) + break; + std::string name(dictionary_text, line_start, colon_index - line_start); + std::string value(dictionary_text, value_start, line_end - value_start); + name = StringToLowerASCII(name); + if (name == "domain") { + domain = value; + } else if (name == "path") { + path = value; + } else if (name == "format-version") { + if (value != "1.0") + return false; + } else if (name == "max-age") { + expiration = Time::Now() + TimeDelta::FromSeconds(StringToInt64(value)); + } else if (name == "port") { + int port = StringToInt(value); + if (port >= 0) + ports.insert(port); + } + } + + if (line_end >= header_end) + break; + line_start = line_end + 1; + } + + if (!Dictionary::CanSet(domain, path, ports, dictionary_url)) + return false; + + DHISTOGRAM_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 = + new Dictionary(dictionary_text, header_end + 2, client_hash, + dictionary_url, domain, path, expiration, ports); + dictionary->AddRef(); + dictionaries_[server_hash] = dictionary; + return true; +} + +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()) + return; + Dictionary* matching_dictionary = it->second; + if (!matching_dictionary->CanUse(referring_url)) + return; + *dictionary = matching_dictionary; +} + +// TODO(jar): If we have evictions from the dictionaries_, then we need to +// change this interface to return a list of reference counted Dictionary +// instances that can be used if/when a server specifies one. +void SdchManager::GetAvailDictionaryList(const GURL& target_url, + std::string* list) { + for (DictionaryMap::iterator it = dictionaries_.begin(); + it != dictionaries_.end(); ++it) { + if (!it->second->CanAdvertise(target_url)) + continue; + if (!list->empty()) + list->append(","); + list->append(it->second->client_hash()); + } +} + +SdchManager::Dictionary::Dictionary(const std::string& dictionary_text, + size_t offset, const std::string& client_hash, const GURL& gurl, + const std::string& domain, const std::string& path, const Time& expiration, + const std::set<int> ports) + : text_(dictionary_text, offset), + client_hash_(client_hash), + url_(gurl), + domain_(domain), + path_(path), + expiration_(expiration), + ports_(ports) { +} + +// static +void SdchManager::GenerateHash(const std::string& dictionary_text, + std::string* client_hash, std::string* server_hash) { + char binary_hash[32]; + base::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash)); + + std::string first_48_bits(&binary_hash[0], 6); + std::string second_48_bits(&binary_hash[6], 6); + UrlSafeBase64Encode(first_48_bits, client_hash); + UrlSafeBase64Encode(second_48_bits, server_hash); + + DCHECK(server_hash->length() == 8); + DCHECK(client_hash->length() == 8); +} + +// static +void SdchManager::UrlSafeBase64Encode(const std::string& input, + std::string* output) { + // Since this is only done during a dictionary load, and hashes are only 8 + // characters, we just do the simple fixup, rather than rewriting the encoder. + net::Base64Encode(input, output); + for (size_t i = 0; i < output->size(); ++i) { + switch (output->data()[i]) { + case '+': + (*output)[i] = '-'; + continue; + case '/': + (*output)[i] = '_'; + continue; + default: + continue; + } + } +} + +//------------------------------------------------------------------------------ +// Security functions restricting loads and use of dictionaries. + +// static +int SdchManager::Dictionary::GetPortIncludingDefault(const GURL& url) { + std::string port(url.port()); + if (port.length()) + return StringToInt(port); + if (url.scheme() == "http") + return 80; // Default port value. + // TODO(jar): If sdch supports other schemes, then write a general function + // or surface functionality hidden in url_cannon_stdurl.cc into url_canon.h. + return -1; +} + +// static +bool SdchManager::Dictionary::CanSet(const std::string& domain, + const std::string& path, + const std::set<int> ports, + const GURL& dictionary_url) { + if (!SdchManager::Global()->IsInSupportedDomain(dictionary_url)) + return false; + /* + A dictionary is invalid and must not be stored if any of the following are + true: + 1. The dictionary has no Domain attribute. + 2. The effective host name that derives from the referer URL host name does + not domain-match the Domain attribute. + 3. The Domain attribute is a top level domain. + 4. The referer URL host is a host domain name (not IP address) and has the + form HD, where D is the value of the Domain attribute, and H is a string + that contains one or more dots. + 5. If the dictionary has a Port attribute and the referer URL's port was not + in the list. + */ + if (domain.empty()) + return false; // Domain is required. + if (0 == + net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size()) + return false; // domain was a TLD. + if (!Dictionary::DomainMatch(dictionary_url, domain)) + return false; + + // TODO(jar): Enforce item 4 above. + + if (!ports.empty() + && 0 == ports.count(GetPortIncludingDefault(dictionary_url))) + return false; + return true; +} + +// static +bool SdchManager::Dictionary::CanUse(const GURL referring_url) { + if (!SdchManager::Global()->IsInSupportedDomain(referring_url)) + return false; + /* + 1. The request URL's host name domain-matches the Domain attribute of the + dictionary. + 2. If the dictionary has a Port attribute, the request port is one of the + ports listed in the Port attribute. + 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_)) + return false; + if (!ports_.empty() + && 0 == ports_.count(GetPortIncludingDefault(referring_url))) + return false; + if (path_.size() && !PathMatch(referring_url.path(), path_)) + return false; + if (referring_url.SchemeIsSecure()) + return false; + return true; +} + +bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) { + if (!SdchManager::Global()->IsInSupportedDomain(target_url)) + return false; + /* The specific rules of when a dictionary should be advertised in an + Avail-Dictionary header are modeled after the rules for cookie scoping. The + terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A + dictionary may be advertised in the Avail-Dictionaries header exactly when + all of the following are true: + 1. The server's effective host name domain-matches the Domain attribute of + the dictionary. + 2. If the dictionary has a Port attribute, the request port is one of the + ports listed in the Port attribute. + 3. The request URI path-matches the path header of the dictionary. + 4. The request is not an HTTPS request. + */ + if (!DomainMatch(target_url, domain_)) + return false; + if (!ports_.empty() && 0 == ports_.count(GetPortIncludingDefault(target_url))) + return false; + if (path_.size() && !PathMatch(target_url.path(), path_)) + return false; + if (target_url.SchemeIsSecure()) + return false; + return true; +} + +bool SdchManager::Dictionary::PathMatch(const std::string& path, + const std::string& restriction) { + /* Must be either: + 1. P2 is equal to P1 + 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the + character following P2 in P1 is "/". + */ + if (path == restriction) + return true; + size_t prefix_length = restriction.size(); + if (prefix_length > path.size()) + return false; // Can't be a prefix. + if (0 != restriction.compare(0, prefix_length, path)) + return false; + return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/'; +} + +// static +bool SdchManager::Dictionary::DomainMatch(const GURL& gurl, + const std::string& restriction) { + // TODO(jar): This is not precisely a domain match definition. + return gurl.DomainIs(restriction.data(), restriction.size()); +} diff --git a/net/base/sdch_manager.h b/net/base/sdch_manager.h new file mode 100644 index 0000000..36679f2 --- /dev/null +++ b/net/base/sdch_manager.h @@ -0,0 +1,202 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Provides global database of differential decompression dictionaries for the +// SDCH filter (processes sdch enconded content). + +// Exactly one instance of SdchManager is built, and all references are made +// into that collection. +// +// The SdchManager maintains a collection of memory resident dictionaries. It +// can find a dictionary (based on a server specification of a hash), store a +// dictionary, and make judgements about what URLs can use, set, etc. a +// dictionary. + +// These dictionaries are acquired over the net, and include a header +// (containing metadata) as well as a VCDIFF dictionary (for use by a VCDIFF +// module) to decompress data. + +#ifndef NET_BASE_SDCH_MANAGER_H_ +#define NET_BASE_SDCH_MANAGER_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/ref_counted.h" +#include "base/time.h" +#include "googleurl/src/gurl.h" + + +//------------------------------------------------------------------------------ +// Create a public interface to help us load SDCH dictionaries. +// The SdchManager class allows registration to support this interface. +// A browser may register a fetcher that is used by the dictionary managers to +// get data from a specified URL. This allows us to use very high level browser +// functionality in this base (when the functionaity can be provided). +class SdchFetcher { + public: + SdchFetcher() {} + virtual ~SdchFetcher() {} + + // The Schedule() method is called when there is a need to get a dictionary + // from a server. The callee is responsible for getting that dictionary_text, + // and then calling back to AddSdchDictionary() to the SdchManager instance. + virtual void Schedule(const GURL& dictionary_url) = 0; + private: + DISALLOW_COPY_AND_ASSIGN(SdchFetcher); +}; +//------------------------------------------------------------------------------ + +class SdchManager { + public: + // There is one instance of |Dictionary| for each memory-cached SDCH + // dictionary. + class Dictionary : public base::RefCounted<Dictionary> { + public: + // Sdch filters can get our text to use in decoding compressed data. + const std::string& text() const { return text_; } + + private: + friend class SdchManager; // Only manager can construct an instance. + + // Construct a vc-diff usable dictionary from the dictionary_text starting + // at the given offset. The supplied client_hash should be used to + // advertise the dictionary's availability relative to the suppplied URL. + Dictionary(const std::string& dictionary_text, size_t offset, + const std::string& client_hash, const GURL& url, + const std::string& domain, const std::string& path, + const Time& expiration, const std::set<int> ports); + + const GURL& url() const { return url_; } + const std::string& client_hash() const { return client_hash_; } + + // For a given URL, get the actual or default port. + static int GetPortIncludingDefault(const GURL& url); + + // Security method to check if we can advertise this dictionary for use + // if the |target_url| returns SDCH compressed data. + bool CanAdvertise(const GURL& target_url); + + // Security methods to check if we can establish a new dictionary with the + // given data, that arrived in response to get of dictionary_url. + static bool CanSet(const std::string& domain, const std::string& path, + const std::set<int> ports, const GURL& dictionary_url); + + // Security method to check if we can use a dictionary to decompress a + // target that arrived with a reference to this dictionary. + bool CanUse(const GURL referring_url); + + // Compare paths to see if they "match" for dictionary use. + static bool PathMatch(const std::string& path, + const std::string& restriction); + + // Compare domains to see if the "match" for dictionary use. + static bool DomainMatch(const GURL& url, const std::string& restriction); + + // Each dictionary payload consists of several headers, followed by the text + // of the dictionary. The following are the known headers. + std::string domain_attribute_; + std::set<int> ports_; + + // The actual text of the dictionary. + std::string text_; + + // Part of the hash of text_ that the client uses to advertise the fact that + // it has a specific dictionary pre-cached. + std::string client_hash_; + + // The GURL that arrived with the text_ in a URL request to specify where + // this dictionary may be used. + const GURL url_; + + // Metadate "headers" in before dictionary text contained the following: + const std::string domain_; + const std::string path_; + const Time expiration_; // Implied by max-age. + const std::set<int> ports; + + DISALLOW_COPY_AND_ASSIGN(Dictionary); + }; + + SdchManager(); + ~SdchManager(); + + // Provide access to the single instance of this class. + static SdchManager* Global(); + + // 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; + } + + const bool IsInSupportedDomain(const GURL& url) const; + + // Schedule the URL fetching to load a dictionary. This will generally return + // long before the dictionary is actually loaded and added. + // After the implied task does completes, the dictionary will have been + // cached in memory. + void FetchDictionary(const GURL& referring_url, const GURL& dictionary_url); + + // Add an SDCH dictionary to our list of availible dictionaries. This addition + // will fail (return false) if addition is illegal (data in the dictionary is + // not acceptable from the dictionary_url; dictionary already added, etc.). + bool AddSdchDictionary(const std::string& dictionary_text, + const GURL& dictionary_url); + + // Find the vcdiff dictionary (the body of the sdch dictionary that appears + // after the meta-data headers like Domain:...) with the given |server_hash| + // to use to decompreses data that arrived as SDCH encoded content. Check to + // be sure the returned |dictionary| can be used for decoding content supplied + // in response to a request for |referring_url|. + // Caller is responsible for AddRef()ing the dictionary, and Release()ing it + // when done. + // Return null in |dictionary| if there is no matching legal dictionary. + void GetVcdiffDictionary(const std::string& server_hash, const GURL& referring_url, + Dictionary** dictionary); + + // Get list of available (pre-cached) dictionaries that we have already loaded + // into memory. The list is a comma separated list of (client) hashes per + // the SDCH spec. + void GetAvailDictionaryList(const GURL& target_url, std::string* list); + + // Construct the pair of hashes for client and server to identify an SDCH + // dictionary. This is only made public to facilitate unit testing, but is + // otherwise private + static void GenerateHash(const std::string& dictionary_text, + std::string* client_hash, std::string* server_hash); + + private: + // A map of dictionaries info indexed by the hash that the server provides. + typedef std::map<std::string, Dictionary*> DictionaryMap; + + // The one global instance of that holds all the data. + static SdchManager* global_; + + // A simple implementatino of a RFC 3548 "URL safe" base64 encoder. + static void UrlSafeBase64Encode(const std::string& input, + std::string* output); + DictionaryMap dictionaries_; + + // An instance that can fetch a dictionary given a URL. + scoped_ptr<SdchFetcher> fetcher_; + + // Support SDCH compression, by advertising in headers. + bool sdch_enabled_; + + // Empty string means all domains. Non-empty means support only the given + // domain is supported. + std::string supported_domain_; + + DISALLOW_COPY_AND_ASSIGN(SdchManager); +}; + +#endif // NET_BASE_SDCH_MANAGER_H_ |