| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include <limits.h> | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <string> | |
| 9 #include <vector> | |
| 10 | |
| 11 #include "base/logging.h" | |
| 12 #include "base/memory/scoped_ptr.h" | |
| 13 #include "base/test/simple_test_clock.h" | |
| 14 #include "net/base/io_buffer.h" | |
| 15 #include "net/base/sdch_observer.h" | |
| 16 #include "net/filter/mock_filter_context.h" | |
| 17 #include "net/filter/sdch_filter.h" | |
| 18 #include "net/url_request/url_request_context.h" | |
| 19 #include "net/url_request/url_request_http_job.h" | |
| 20 #include "testing/gtest/include/gtest/gtest.h" | |
| 21 #include "third_party/zlib/zlib.h" | |
| 22 | |
| 23 namespace net { | |
| 24 | |
| 25 //------------------------------------------------------------------------------ | |
| 26 // Provide sample data and compression results with a sample VCDIFF dictionary. | |
| 27 // Note an SDCH dictionary has extra meta-data before the VCDIFF dictionary. | |
| 28 static const char kTestVcdiffDictionary[] = "DictionaryFor" | |
| 29 "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n"; | |
| 30 // Pre-compression test data. Note that we pad with a lot of highly gzip | |
| 31 // compressible content to help to exercise the chaining pipeline. That is why | |
| 32 // there are a PILE of zeros at the start and end. | |
| 33 // This will ensure that gzip compressed data can be fed to the chain in one | |
| 34 // gulp, but (with careful selection of intermediate buffers) that it takes | |
| 35 // several sdch buffers worth of data to satisfy the sdch filter. See detailed | |
| 36 // CHECK() calls in FilterChaining test for specifics. | |
| 37 static const char kTestData[] = "0000000000000000000000000000000000000000000000" | |
| 38 "0000000000000000000000000000TestData " | |
| 39 "SdchCompression1SdchCompression2SdchCompression3SdchCompression" | |
| 40 "00000000000000000000000000000000000000000000000000000000000000000000000000" | |
| 41 "000000000000000000000000000000000000000\n"; | |
| 42 | |
| 43 // Note SDCH compressed data will include a reference to the SDCH dictionary. | |
| 44 static const char kSdchCompressedTestData[] = | |
| 45 "\326\303\304\0\0\001M\0\201S\202\004\0\201E\006\001" | |
| 46 "00000000000000000000000000000000000000000000000000000000000000000000000000" | |
| 47 "TestData 00000000000000000000000000000000000000000000000000000000000000000" | |
| 48 "000000000000000000000000000000000000000000000000\n\001S\023\077\001r\r"; | |
| 49 | |
| 50 //------------------------------------------------------------------------------ | |
| 51 | |
| 52 class SdchFilterTest : public testing::Test { | |
| 53 protected: | |
| 54 SdchFilterTest() | |
| 55 : test_vcdiff_dictionary_(kTestVcdiffDictionary, | |
| 56 sizeof(kTestVcdiffDictionary) - 1), | |
| 57 vcdiff_compressed_data_(kSdchCompressedTestData, | |
| 58 sizeof(kSdchCompressedTestData) - 1), | |
| 59 expanded_(kTestData, sizeof(kTestData) - 1), | |
| 60 sdch_manager_(new SdchManager), | |
| 61 filter_context_(new MockFilterContext) { | |
| 62 URLRequestContext* url_request_context = | |
| 63 filter_context_->GetModifiableURLRequestContext(); | |
| 64 | |
| 65 url_request_context->set_sdch_manager(sdch_manager_.get()); | |
| 66 } | |
| 67 | |
| 68 // Attempt to add a dictionary to the manager and probe for success or | |
| 69 // failure. | |
| 70 bool AddSdchDictionary(const std::string& dictionary_text, | |
| 71 const GURL& gurl) { | |
| 72 return sdch_manager_->AddSdchDictionary(dictionary_text, gurl, nullptr) == | |
| 73 SDCH_OK; | |
| 74 } | |
| 75 | |
| 76 MockFilterContext* filter_context() { return filter_context_.get(); } | |
| 77 | |
| 78 // Sets both the GURL and the SDCH response for a filter context. | |
| 79 void SetupFilterContextWithGURL(GURL url) { | |
| 80 filter_context_->SetURL(url); | |
| 81 filter_context_->SetSdchResponse( | |
| 82 sdch_manager_->GetDictionarySet(url).Pass()); | |
| 83 } | |
| 84 | |
| 85 std::string NewSdchCompressedData(const std::string dictionary) { | |
| 86 std::string client_hash; | |
| 87 std::string server_hash; | |
| 88 SdchManager::GenerateHash(dictionary, &client_hash, &server_hash); | |
| 89 | |
| 90 // Build compressed data that refers to our dictionary. | |
| 91 std::string compressed(server_hash); | |
| 92 compressed.append("\0", 1); | |
| 93 compressed.append(vcdiff_compressed_data_); | |
| 94 return compressed; | |
| 95 } | |
| 96 | |
| 97 const std::string test_vcdiff_dictionary_; | |
| 98 const std::string vcdiff_compressed_data_; | |
| 99 const std::string expanded_; // Desired final, decompressed data. | |
| 100 | |
| 101 scoped_ptr<SdchManager> sdch_manager_; | |
| 102 scoped_ptr<MockFilterContext> filter_context_; | |
| 103 }; | |
| 104 | |
| 105 TEST_F(SdchFilterTest, Hashing) { | |
| 106 std::string client_hash, server_hash; | |
| 107 std::string dictionary("test contents"); | |
| 108 SdchManager::GenerateHash(dictionary, &client_hash, &server_hash); | |
| 109 | |
| 110 EXPECT_EQ(client_hash, "lMQBjS3P"); | |
| 111 EXPECT_EQ(server_hash, "MyciMVll"); | |
| 112 } | |
| 113 | |
| 114 //------------------------------------------------------------------------------ | |
| 115 // Provide a generic helper function for trying to filter data. | |
| 116 // This function repeatedly calls the filter to process data, until the entire | |
| 117 // source is consumed. The return value from the filter is appended to output. | |
| 118 // This allows us to vary input and output block sizes in order to test for edge | |
| 119 // effects (boundary effects?) during the filtering process. | |
| 120 // This function provides data to the filter in blocks of no-more-than the | |
| 121 // specified input_block_length. It allows the filter to fill no more than | |
| 122 // output_buffer_length in any one call to proccess (a.k.a., Read) data, and | |
| 123 // concatenates all these little output blocks into the singular output string. | |
| 124 static bool FilterTestData(const std::string& source, | |
| 125 size_t input_block_length, | |
| 126 const size_t output_buffer_length, | |
| 127 Filter* filter, std::string* output) { | |
| 128 CHECK_GT(input_block_length, 0u); | |
| 129 Filter::FilterStatus status(Filter::FILTER_NEED_MORE_DATA); | |
| 130 size_t source_index = 0; | |
| 131 scoped_ptr<char[]> output_buffer(new char[output_buffer_length]); | |
| 132 size_t input_amount = std::min(input_block_length, | |
| 133 static_cast<size_t>(filter->stream_buffer_size())); | |
| 134 | |
| 135 do { | |
| 136 int copy_amount = std::min(input_amount, source.size() - source_index); | |
| 137 if (copy_amount > 0 && status == Filter::FILTER_NEED_MORE_DATA) { | |
| 138 memcpy(filter->stream_buffer()->data(), source.data() + source_index, | |
| 139 copy_amount); | |
| 140 filter->FlushStreamBuffer(copy_amount); | |
| 141 source_index += copy_amount; | |
| 142 } | |
| 143 int buffer_length = output_buffer_length; | |
| 144 status = filter->ReadData(output_buffer.get(), &buffer_length); | |
| 145 output->append(output_buffer.get(), buffer_length); | |
| 146 if (status == Filter::FILTER_ERROR) | |
| 147 return false; | |
| 148 // Callers assume that FILTER_OK with no output buffer means FILTER_DONE. | |
| 149 if (Filter::FILTER_OK == status && 0 == buffer_length) | |
| 150 return true; | |
| 151 if (copy_amount == 0 && buffer_length == 0) | |
| 152 return true; | |
| 153 } while (1); | |
| 154 } | |
| 155 | |
| 156 static std::string NewSdchDictionary(const std::string& domain) { | |
| 157 std::string dictionary; | |
| 158 if (!domain.empty()) { | |
| 159 dictionary.append("Domain: "); | |
| 160 dictionary.append(domain); | |
| 161 dictionary.append("\n"); | |
| 162 } | |
| 163 dictionary.append("\n"); | |
| 164 dictionary.append(kTestVcdiffDictionary, sizeof(kTestVcdiffDictionary) - 1); | |
| 165 return dictionary; | |
| 166 } | |
| 167 | |
| 168 static std::string NewSdchExpiredDictionary(const std::string& domain) { | |
| 169 std::string dictionary; | |
| 170 if (!domain.empty()) { | |
| 171 dictionary.append("Domain: "); | |
| 172 dictionary.append(domain); | |
| 173 dictionary.append("\n"); | |
| 174 } | |
| 175 dictionary.append("Max-Age: 0\n"); | |
| 176 dictionary.append("\n"); | |
| 177 dictionary.append(kTestVcdiffDictionary, sizeof(kTestVcdiffDictionary) - 1); | |
| 178 return dictionary; | |
| 179 } | |
| 180 | |
| 181 TEST_F(SdchFilterTest, EmptyInputOk) { | |
| 182 std::vector<Filter::FilterType> filter_types; | |
| 183 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 184 char output_buffer[20]; | |
| 185 std::string url_string("http://ignore.com"); | |
| 186 filter_context()->SetURL(GURL(url_string)); | |
| 187 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 188 | |
| 189 // With no input data, try to read output. | |
| 190 int output_bytes_or_buffer_size = sizeof(output_buffer); | |
| 191 Filter::FilterStatus status = filter->ReadData(output_buffer, | |
| 192 &output_bytes_or_buffer_size); | |
| 193 | |
| 194 EXPECT_EQ(0, output_bytes_or_buffer_size); | |
| 195 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status); | |
| 196 } | |
| 197 | |
| 198 // Make sure that the filter context has everything that might be | |
| 199 // nuked from it during URLRequest teardown before the SdchFilter | |
| 200 // destructor. | |
| 201 TEST_F(SdchFilterTest, SparseContextOk) { | |
| 202 std::vector<Filter::FilterType> filter_types; | |
| 203 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 204 char output_buffer[20]; | |
| 205 std::string url_string("http://ignore.com"); | |
| 206 filter_context()->SetURL(GURL(url_string)); | |
| 207 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 208 | |
| 209 // With no input data, try to read output. | |
| 210 int output_bytes_or_buffer_size = sizeof(output_buffer); | |
| 211 Filter::FilterStatus status = filter->ReadData(output_buffer, | |
| 212 &output_bytes_or_buffer_size); | |
| 213 | |
| 214 EXPECT_EQ(0, output_bytes_or_buffer_size); | |
| 215 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status); | |
| 216 | |
| 217 // Partially tear down context. Anything that goes through request() | |
| 218 // without checking it for null in the URLRequestJob::HttpFilterContext | |
| 219 // implementation is suspect. Everything that does check it for null should | |
| 220 // return null. This is to test for incorrectly relying on filter_context() | |
| 221 // from the SdchFilter destructor. | |
| 222 filter_context()->NukeUnstableInterfaces(); | |
| 223 } | |
| 224 | |
| 225 TEST_F(SdchFilterTest, PassThroughWhenTentative) { | |
| 226 std::vector<Filter::FilterType> filter_types; | |
| 227 // Selective a tentative filter (which can fall back to pass through). | |
| 228 filter_types.push_back(Filter::FILTER_TYPE_GZIP_HELPING_SDCH); | |
| 229 char output_buffer[20]; | |
| 230 // Response code needs to be 200 to allow a pass through. | |
| 231 filter_context()->SetResponseCode(200); | |
| 232 std::string url_string("http://ignore.com"); | |
| 233 filter_context()->SetURL(GURL(url_string)); | |
| 234 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 235 | |
| 236 // Supply enough data to force a pass-through mode.. | |
| 237 std::string non_gzip_content("not GZIPed data"); | |
| 238 | |
| 239 char* input_buffer = filter->stream_buffer()->data(); | |
| 240 int input_buffer_size = filter->stream_buffer_size(); | |
| 241 | |
| 242 EXPECT_LT(static_cast<int>(non_gzip_content.size()), | |
| 243 input_buffer_size); | |
| 244 memcpy(input_buffer, non_gzip_content.data(), | |
| 245 non_gzip_content.size()); | |
| 246 filter->FlushStreamBuffer(non_gzip_content.size()); | |
| 247 | |
| 248 // Try to read output. | |
| 249 int output_bytes_or_buffer_size = sizeof(output_buffer); | |
| 250 Filter::FilterStatus status = filter->ReadData(output_buffer, | |
| 251 &output_bytes_or_buffer_size); | |
| 252 | |
| 253 EXPECT_EQ(non_gzip_content.size(), | |
| 254 static_cast<size_t>(output_bytes_or_buffer_size)); | |
| 255 ASSERT_GT(sizeof(output_buffer), | |
| 256 static_cast<size_t>(output_bytes_or_buffer_size)); | |
| 257 output_buffer[output_bytes_or_buffer_size] = '\0'; | |
| 258 EXPECT_TRUE(non_gzip_content == output_buffer); | |
| 259 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status); | |
| 260 } | |
| 261 | |
| 262 TEST_F(SdchFilterTest, RefreshBadReturnCode) { | |
| 263 std::vector<Filter::FilterType> filter_types; | |
| 264 // Selective a tentative filter (which can fall back to pass through). | |
| 265 filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE); | |
| 266 char output_buffer[20]; | |
| 267 // Response code needs to be 200 to allow a pass through. | |
| 268 filter_context()->SetResponseCode(403); | |
| 269 // Meta refresh will only appear for html content | |
| 270 filter_context()->SetMimeType("text/html"); | |
| 271 std::string url_string("http://ignore.com"); | |
| 272 filter_context()->SetURL(GURL(url_string)); | |
| 273 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 274 | |
| 275 // Supply enough data to force a pass-through mode, which means we have | |
| 276 // provided more than 9 characters that can't be a dictionary hash. | |
| 277 std::string non_sdch_content("This is not SDCH"); | |
| 278 | |
| 279 char* input_buffer = filter->stream_buffer()->data(); | |
| 280 int input_buffer_size = filter->stream_buffer_size(); | |
| 281 | |
| 282 EXPECT_LT(static_cast<int>(non_sdch_content.size()), | |
| 283 input_buffer_size); | |
| 284 memcpy(input_buffer, non_sdch_content.data(), | |
| 285 non_sdch_content.size()); | |
| 286 filter->FlushStreamBuffer(non_sdch_content.size()); | |
| 287 | |
| 288 // Try to read output. | |
| 289 int output_bytes_or_buffer_size = sizeof(output_buffer); | |
| 290 Filter::FilterStatus status = filter->ReadData(output_buffer, | |
| 291 &output_bytes_or_buffer_size); | |
| 292 | |
| 293 // We should have read a long and complicated meta-refresh request. | |
| 294 EXPECT_TRUE(sizeof(output_buffer) == output_bytes_or_buffer_size); | |
| 295 // Check at least the prefix of the return. | |
| 296 EXPECT_EQ(0, strncmp(output_buffer, | |
| 297 "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>", | |
| 298 sizeof(output_buffer))); | |
| 299 EXPECT_EQ(Filter::FILTER_OK, status); | |
| 300 } | |
| 301 | |
| 302 TEST_F(SdchFilterTest, ErrorOnBadReturnCode) { | |
| 303 std::vector<Filter::FilterType> filter_types; | |
| 304 // Selective a tentative filter (which can fall back to pass through). | |
| 305 filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE); | |
| 306 char output_buffer[20]; | |
| 307 // Response code needs to be 200 to allow a pass through. | |
| 308 filter_context()->SetResponseCode(403); | |
| 309 // Meta refresh will only appear for html content, so set to something else | |
| 310 // to induce an error (we can't meta refresh). | |
| 311 filter_context()->SetMimeType("anything"); | |
| 312 std::string url_string("http://ignore.com"); | |
| 313 filter_context()->SetURL(GURL(url_string)); | |
| 314 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 315 | |
| 316 // Supply enough data to force a pass-through mode, which means we have | |
| 317 // provided more than 9 characters that can't be a dictionary hash. | |
| 318 std::string non_sdch_content("This is not SDCH"); | |
| 319 | |
| 320 char* input_buffer = filter->stream_buffer()->data(); | |
| 321 int input_buffer_size = filter->stream_buffer_size(); | |
| 322 | |
| 323 EXPECT_LT(static_cast<int>(non_sdch_content.size()), | |
| 324 input_buffer_size); | |
| 325 memcpy(input_buffer, non_sdch_content.data(), | |
| 326 non_sdch_content.size()); | |
| 327 filter->FlushStreamBuffer(non_sdch_content.size()); | |
| 328 | |
| 329 // Try to read output. | |
| 330 int output_bytes_or_buffer_size = sizeof(output_buffer); | |
| 331 Filter::FilterStatus status = filter->ReadData(output_buffer, | |
| 332 &output_bytes_or_buffer_size); | |
| 333 | |
| 334 EXPECT_EQ(0, output_bytes_or_buffer_size); | |
| 335 EXPECT_EQ(Filter::FILTER_ERROR, status); | |
| 336 } | |
| 337 | |
| 338 TEST_F(SdchFilterTest, ErrorOnBadReturnCodeWithHtml) { | |
| 339 std::vector<Filter::FilterType> filter_types; | |
| 340 // Selective a tentative filter (which can fall back to pass through). | |
| 341 filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE); | |
| 342 char output_buffer[20]; | |
| 343 // Response code needs to be 200 to allow a pass through. | |
| 344 filter_context()->SetResponseCode(403); | |
| 345 // Meta refresh will only appear for html content | |
| 346 filter_context()->SetMimeType("text/html"); | |
| 347 std::string url_string("http://ignore.com"); | |
| 348 filter_context()->SetURL(GURL(url_string)); | |
| 349 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 350 | |
| 351 // Supply enough data to force a pass-through mode, which means we have | |
| 352 // provided more than 9 characters that can't be a dictionary hash. | |
| 353 std::string non_sdch_content("This is not SDCH"); | |
| 354 | |
| 355 char* input_buffer = filter->stream_buffer()->data(); | |
| 356 int input_buffer_size = filter->stream_buffer_size(); | |
| 357 | |
| 358 EXPECT_LT(static_cast<int>(non_sdch_content.size()), | |
| 359 input_buffer_size); | |
| 360 memcpy(input_buffer, non_sdch_content.data(), | |
| 361 non_sdch_content.size()); | |
| 362 filter->FlushStreamBuffer(non_sdch_content.size()); | |
| 363 | |
| 364 // Try to read output. | |
| 365 int output_bytes_or_buffer_size = sizeof(output_buffer); | |
| 366 Filter::FilterStatus status = filter->ReadData(output_buffer, | |
| 367 &output_bytes_or_buffer_size); | |
| 368 | |
| 369 // We should have read a long and complicated meta-refresh request. | |
| 370 EXPECT_EQ(sizeof(output_buffer), | |
| 371 static_cast<size_t>(output_bytes_or_buffer_size)); | |
| 372 // Check at least the prefix of the return. | |
| 373 EXPECT_EQ(0, strncmp(output_buffer, | |
| 374 "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>", | |
| 375 sizeof(output_buffer))); | |
| 376 EXPECT_EQ(Filter::FILTER_OK, status); | |
| 377 } | |
| 378 | |
| 379 TEST_F(SdchFilterTest, BasicBadDictionary) { | |
| 380 std::vector<Filter::FilterType> filter_types; | |
| 381 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 382 char output_buffer[20]; | |
| 383 std::string url_string("http://ignore.com"); | |
| 384 filter_context()->SetURL(GURL(url_string)); | |
| 385 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 386 | |
| 387 // Supply bogus data (which doesn't yet specify a full dictionary hash). | |
| 388 // Dictionary hash is 8 characters followed by a null. | |
| 389 std::string dictionary_hash_prefix("123"); | |
| 390 | |
| 391 char* input_buffer = filter->stream_buffer()->data(); | |
| 392 int input_buffer_size = filter->stream_buffer_size(); | |
| 393 | |
| 394 EXPECT_LT(static_cast<int>(dictionary_hash_prefix.size()), | |
| 395 input_buffer_size); | |
| 396 memcpy(input_buffer, dictionary_hash_prefix.data(), | |
| 397 dictionary_hash_prefix.size()); | |
| 398 filter->FlushStreamBuffer(dictionary_hash_prefix.size()); | |
| 399 | |
| 400 // With less than a dictionary specifier, try to read output. | |
| 401 int output_bytes_or_buffer_size = sizeof(output_buffer); | |
| 402 Filter::FilterStatus status = filter->ReadData(output_buffer, | |
| 403 &output_bytes_or_buffer_size); | |
| 404 | |
| 405 EXPECT_EQ(0, output_bytes_or_buffer_size); | |
| 406 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status); | |
| 407 | |
| 408 // Provide enough data to complete *a* hash, but it is bogus, and not in our | |
| 409 // list of dictionaries, so the filter should error out immediately. | |
| 410 std::string dictionary_hash_postfix("4abcd\0", 6); | |
| 411 | |
| 412 CHECK_LT(dictionary_hash_postfix.size(), | |
| 413 static_cast<size_t>(input_buffer_size)); | |
| 414 memcpy(input_buffer, dictionary_hash_postfix.data(), | |
| 415 dictionary_hash_postfix.size()); | |
| 416 filter->FlushStreamBuffer(dictionary_hash_postfix.size()); | |
| 417 | |
| 418 // With a non-existant dictionary specifier, try to read output. | |
| 419 output_bytes_or_buffer_size = sizeof(output_buffer); | |
| 420 status = filter->ReadData(output_buffer, &output_bytes_or_buffer_size); | |
| 421 | |
| 422 EXPECT_EQ(0, output_bytes_or_buffer_size); | |
| 423 EXPECT_EQ(Filter::FILTER_ERROR, status); | |
| 424 | |
| 425 EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET, | |
| 426 sdch_manager_->IsInSupportedDomain(GURL(url_string))); | |
| 427 sdch_manager_->ClearBlacklistings(); | |
| 428 EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(GURL(url_string))); | |
| 429 } | |
| 430 | |
| 431 TEST_F(SdchFilterTest, DictionaryAddOnce) { | |
| 432 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 433 const std::string kSampleDomain = "sdchtest.com"; | |
| 434 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 435 | |
| 436 std::string url_string = "http://" + kSampleDomain; | |
| 437 GURL url(url_string); | |
| 438 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 439 | |
| 440 // Check we can't add it twice. | |
| 441 EXPECT_FALSE(AddSdchDictionary(dictionary, url)); | |
| 442 | |
| 443 const std::string kSampleDomain2 = "sdchtest2.com"; | |
| 444 | |
| 445 // Construct a second SDCH dictionary from a VCDIFF dictionary. | |
| 446 std::string dictionary2(NewSdchDictionary(kSampleDomain2)); | |
| 447 | |
| 448 std::string url_string2 = "http://" + kSampleDomain2; | |
| 449 GURL url2(url_string2); | |
| 450 EXPECT_TRUE(AddSdchDictionary(dictionary2, url2)); | |
| 451 } | |
| 452 | |
| 453 TEST_F(SdchFilterTest, BasicDictionary) { | |
| 454 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 455 const std::string kSampleDomain = "sdchtest.com"; | |
| 456 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 457 | |
| 458 std::string url_string = "http://" + kSampleDomain; | |
| 459 | |
| 460 GURL url(url_string); | |
| 461 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 462 | |
| 463 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 464 | |
| 465 std::vector<Filter::FilterType> filter_types; | |
| 466 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 467 | |
| 468 SetupFilterContextWithGURL(url); | |
| 469 | |
| 470 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 471 | |
| 472 size_t feed_block_size = 100; | |
| 473 size_t output_block_size = 100; | |
| 474 std::string output; | |
| 475 EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 476 filter.get(), &output)); | |
| 477 EXPECT_EQ(output, expanded_); | |
| 478 | |
| 479 // Decode with really small buffers (size 1) to check for edge effects. | |
| 480 filter.reset(Filter::Factory(filter_types, *filter_context())); | |
| 481 | |
| 482 feed_block_size = 1; | |
| 483 output_block_size = 1; | |
| 484 output.clear(); | |
| 485 EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 486 filter.get(), &output)); | |
| 487 EXPECT_EQ(output, expanded_); | |
| 488 } | |
| 489 | |
| 490 TEST_F(SdchFilterTest, NoDecodeHttps) { | |
| 491 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 492 const std::string kSampleDomain = "sdchtest.com"; | |
| 493 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 494 | |
| 495 std::string url_string = "http://" + kSampleDomain; | |
| 496 | |
| 497 GURL url(url_string); | |
| 498 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 499 | |
| 500 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 501 | |
| 502 std::vector<Filter::FilterType> filter_types; | |
| 503 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 504 | |
| 505 GURL filter_context_gurl("https://" + kSampleDomain); | |
| 506 SetupFilterContextWithGURL(GURL("https://" + kSampleDomain)); | |
| 507 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 508 | |
| 509 const size_t feed_block_size(100); | |
| 510 const size_t output_block_size(100); | |
| 511 std::string output; | |
| 512 | |
| 513 EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 514 filter.get(), &output)); | |
| 515 } | |
| 516 | |
| 517 // Current failsafe TODO/hack refuses to decode any content that doesn't use | |
| 518 // http as the scheme (see use of DICTIONARY_SELECTED_FOR_NON_HTTP). | |
| 519 // The following tests this blockage. Note that blacklisting results, so we | |
| 520 // we need separate tests for each of these. | |
| 521 TEST_F(SdchFilterTest, NoDecodeFtp) { | |
| 522 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 523 const std::string kSampleDomain = "sdchtest.com"; | |
| 524 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 525 | |
| 526 std::string url_string = "http://" + kSampleDomain; | |
| 527 | |
| 528 GURL url(url_string); | |
| 529 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 530 | |
| 531 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 532 | |
| 533 std::vector<Filter::FilterType> filter_types; | |
| 534 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 535 | |
| 536 SetupFilterContextWithGURL(GURL("ftp://" + kSampleDomain)); | |
| 537 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 538 | |
| 539 const size_t feed_block_size(100); | |
| 540 const size_t output_block_size(100); | |
| 541 std::string output; | |
| 542 | |
| 543 EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 544 filter.get(), &output)); | |
| 545 } | |
| 546 | |
| 547 TEST_F(SdchFilterTest, NoDecodeFileColon) { | |
| 548 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 549 const std::string kSampleDomain = "sdchtest.com"; | |
| 550 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 551 | |
| 552 std::string url_string = "http://" + kSampleDomain; | |
| 553 | |
| 554 GURL url(url_string); | |
| 555 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 556 | |
| 557 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 558 | |
| 559 std::vector<Filter::FilterType> filter_types; | |
| 560 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 561 | |
| 562 SetupFilterContextWithGURL(GURL("file://" + kSampleDomain)); | |
| 563 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 564 | |
| 565 const size_t feed_block_size(100); | |
| 566 const size_t output_block_size(100); | |
| 567 std::string output; | |
| 568 | |
| 569 EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 570 filter.get(), &output)); | |
| 571 } | |
| 572 | |
| 573 TEST_F(SdchFilterTest, NoDecodeAboutColon) { | |
| 574 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 575 const std::string kSampleDomain = "sdchtest.com"; | |
| 576 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 577 | |
| 578 std::string url_string = "http://" + kSampleDomain; | |
| 579 | |
| 580 GURL url(url_string); | |
| 581 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 582 | |
| 583 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 584 | |
| 585 std::vector<Filter::FilterType> filter_types; | |
| 586 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 587 | |
| 588 SetupFilterContextWithGURL(GURL("about://" + kSampleDomain)); | |
| 589 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 590 | |
| 591 const size_t feed_block_size(100); | |
| 592 const size_t output_block_size(100); | |
| 593 std::string output; | |
| 594 | |
| 595 EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 596 filter.get(), &output)); | |
| 597 } | |
| 598 | |
| 599 TEST_F(SdchFilterTest, NoDecodeJavaScript) { | |
| 600 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 601 const std::string kSampleDomain = "sdchtest.com"; | |
| 602 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 603 | |
| 604 std::string url_string = "http://" + kSampleDomain; | |
| 605 | |
| 606 GURL url(url_string); | |
| 607 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 608 | |
| 609 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 610 | |
| 611 std::vector<Filter::FilterType> filter_types; | |
| 612 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 613 | |
| 614 SetupFilterContextWithGURL(GURL("javascript://" + kSampleDomain)); | |
| 615 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 616 | |
| 617 const size_t feed_block_size(100); | |
| 618 const size_t output_block_size(100); | |
| 619 std::string output; | |
| 620 | |
| 621 EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 622 filter.get(), &output)); | |
| 623 } | |
| 624 | |
| 625 TEST_F(SdchFilterTest, CanStillDecodeHttp) { | |
| 626 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 627 const std::string kSampleDomain = "sdchtest.com"; | |
| 628 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 629 | |
| 630 std::string url_string = "http://" + kSampleDomain; | |
| 631 | |
| 632 GURL url(url_string); | |
| 633 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 634 | |
| 635 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 636 | |
| 637 std::vector<Filter::FilterType> filter_types; | |
| 638 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 639 | |
| 640 SetupFilterContextWithGURL(GURL("http://" + kSampleDomain)); | |
| 641 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 642 | |
| 643 const size_t feed_block_size(100); | |
| 644 const size_t output_block_size(100); | |
| 645 std::string output; | |
| 646 | |
| 647 EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 648 filter.get(), &output)); | |
| 649 } | |
| 650 | |
| 651 TEST_F(SdchFilterTest, CrossDomainDictionaryUse) { | |
| 652 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 653 const std::string kSampleDomain = "sdchtest.com"; | |
| 654 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 655 | |
| 656 std::string url_string = "http://" + kSampleDomain; | |
| 657 | |
| 658 GURL url(url_string); | |
| 659 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 660 | |
| 661 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 662 | |
| 663 std::vector<Filter::FilterType> filter_types; | |
| 664 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 665 | |
| 666 // Decode with content arriving from the "wrong" domain. | |
| 667 // This tests SdchManager::CanSet(). | |
| 668 GURL wrong_domain_url("http://www.wrongdomain.com"); | |
| 669 SetupFilterContextWithGURL(wrong_domain_url); | |
| 670 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 671 | |
| 672 size_t feed_block_size = 100; | |
| 673 size_t output_block_size = 100; | |
| 674 std::string output; | |
| 675 EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 676 filter.get(), &output)); | |
| 677 EXPECT_EQ(output.size(), 0u); // No output written. | |
| 678 | |
| 679 EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(GURL(url_string))); | |
| 680 EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET, | |
| 681 sdch_manager_->IsInSupportedDomain(wrong_domain_url)); | |
| 682 sdch_manager_->ClearBlacklistings(); | |
| 683 EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(wrong_domain_url)); | |
| 684 } | |
| 685 | |
| 686 TEST_F(SdchFilterTest, DictionaryPathValidation) { | |
| 687 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 688 const std::string kSampleDomain = "sdchtest.com"; | |
| 689 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 690 | |
| 691 std::string url_string = "http://" + kSampleDomain; | |
| 692 | |
| 693 GURL url(url_string); | |
| 694 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 695 | |
| 696 // Create a dictionary with a path restriction, by prefixing dictionary. | |
| 697 const std::string path("/special_path/bin"); | |
| 698 std::string dictionary_with_path("Path: " + path + "\n"); | |
| 699 dictionary_with_path.append(dictionary); | |
| 700 GURL url2(url_string + path); | |
| 701 EXPECT_TRUE(AddSdchDictionary(dictionary_with_path, url2)); | |
| 702 | |
| 703 std::string compressed_for_path(NewSdchCompressedData(dictionary_with_path)); | |
| 704 | |
| 705 std::vector<Filter::FilterType> filter_types; | |
| 706 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 707 | |
| 708 // Test decode the path data, arriving from a valid path. | |
| 709 SetupFilterContextWithGURL(GURL(url_string + path)); | |
| 710 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 711 | |
| 712 size_t feed_block_size = 100; | |
| 713 size_t output_block_size = 100; | |
| 714 std::string output; | |
| 715 | |
| 716 EXPECT_TRUE(FilterTestData(compressed_for_path, feed_block_size, | |
| 717 output_block_size, filter.get(), &output)); | |
| 718 EXPECT_EQ(output, expanded_); | |
| 719 | |
| 720 // Test decode the path data, arriving from a invalid path. | |
| 721 SetupFilterContextWithGURL(GURL(url_string)); | |
| 722 filter.reset(Filter::Factory(filter_types, *filter_context())); | |
| 723 | |
| 724 feed_block_size = 100; | |
| 725 output_block_size = 100; | |
| 726 output.clear(); | |
| 727 EXPECT_FALSE(FilterTestData(compressed_for_path, feed_block_size, | |
| 728 output_block_size, filter.get(), &output)); | |
| 729 EXPECT_EQ(output.size(), 0u); // No output written. | |
| 730 | |
| 731 EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET, | |
| 732 sdch_manager_->IsInSupportedDomain(GURL(url_string))); | |
| 733 sdch_manager_->ClearBlacklistings(); | |
| 734 EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(GURL(url_string))); | |
| 735 } | |
| 736 | |
| 737 TEST_F(SdchFilterTest, DictionaryPortValidation) { | |
| 738 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 739 const std::string kSampleDomain = "sdchtest.com"; | |
| 740 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 741 | |
| 742 std::string url_string = "http://" + kSampleDomain; | |
| 743 | |
| 744 GURL url(url_string); | |
| 745 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 746 | |
| 747 // Create a dictionary with a port restriction, by prefixing old dictionary. | |
| 748 const std::string port("502"); | |
| 749 std::string dictionary_with_port("Port: " + port + "\n"); | |
| 750 dictionary_with_port.append("Port: 80\n"); // Add default port. | |
| 751 dictionary_with_port.append(dictionary); | |
| 752 EXPECT_TRUE(AddSdchDictionary(dictionary_with_port, | |
| 753 GURL(url_string + ":" + port))); | |
| 754 | |
| 755 std::string compressed_for_port(NewSdchCompressedData(dictionary_with_port)); | |
| 756 | |
| 757 std::vector<Filter::FilterType> filter_types; | |
| 758 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 759 | |
| 760 // Test decode the port data, arriving from a valid port. | |
| 761 SetupFilterContextWithGURL(GURL(url_string + ":" + port)); | |
| 762 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 763 | |
| 764 size_t feed_block_size = 100; | |
| 765 size_t output_block_size = 100; | |
| 766 std::string output; | |
| 767 EXPECT_TRUE(FilterTestData(compressed_for_port, feed_block_size, | |
| 768 output_block_size, filter.get(), &output)); | |
| 769 EXPECT_EQ(output, expanded_); | |
| 770 | |
| 771 // Test decode the port data, arriving from a valid (default) port. | |
| 772 SetupFilterContextWithGURL(GURL(url_string)); // Default port. | |
| 773 filter.reset(Filter::Factory(filter_types, *filter_context())); | |
| 774 | |
| 775 feed_block_size = 100; | |
| 776 output_block_size = 100; | |
| 777 output.clear(); | |
| 778 EXPECT_TRUE(FilterTestData(compressed_for_port, feed_block_size, | |
| 779 output_block_size, filter.get(), &output)); | |
| 780 EXPECT_EQ(output, expanded_); | |
| 781 | |
| 782 // Test decode the port data, arriving from a invalid port. | |
| 783 SetupFilterContextWithGURL(GURL(url_string + ":" + port + "1")); | |
| 784 filter.reset(Filter::Factory(filter_types, *filter_context())); | |
| 785 | |
| 786 feed_block_size = 100; | |
| 787 output_block_size = 100; | |
| 788 output.clear(); | |
| 789 EXPECT_FALSE(FilterTestData(compressed_for_port, feed_block_size, | |
| 790 output_block_size, filter.get(), &output)); | |
| 791 EXPECT_EQ(output.size(), 0u); // No output written. | |
| 792 | |
| 793 EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET, | |
| 794 sdch_manager_->IsInSupportedDomain(GURL(url_string))); | |
| 795 sdch_manager_->ClearBlacklistings(); | |
| 796 EXPECT_EQ(SDCH_OK, sdch_manager_->IsInSupportedDomain(GURL(url_string))); | |
| 797 } | |
| 798 | |
| 799 // Helper function to perform gzip compression of data. | |
| 800 static std::string gzip_compress(const std::string &input) { | |
| 801 z_stream zlib_stream; | |
| 802 memset(&zlib_stream, 0, sizeof(zlib_stream)); | |
| 803 int code; | |
| 804 | |
| 805 // Initialize zlib | |
| 806 code = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, | |
| 807 -MAX_WBITS, | |
| 808 8, // DEF_MEM_LEVEL | |
| 809 Z_DEFAULT_STRATEGY); | |
| 810 | |
| 811 CHECK_EQ(Z_OK, code); | |
| 812 | |
| 813 // Fill in zlib control block | |
| 814 zlib_stream.next_in = bit_cast<Bytef*>(input.data()); | |
| 815 zlib_stream.avail_in = input.size(); | |
| 816 | |
| 817 // Assume we can compress into similar buffer (add 100 bytes to be sure). | |
| 818 size_t gzip_compressed_length = zlib_stream.avail_in + 100; | |
| 819 scoped_ptr<char[]> gzip_compressed(new char[gzip_compressed_length]); | |
| 820 zlib_stream.next_out = bit_cast<Bytef*>(gzip_compressed.get()); | |
| 821 zlib_stream.avail_out = gzip_compressed_length; | |
| 822 | |
| 823 // The GZIP header (see RFC 1952): | |
| 824 // +---+---+---+---+---+---+---+---+---+---+ | |
| 825 // |ID1|ID2|CM |FLG| MTIME |XFL|OS | | |
| 826 // +---+---+---+---+---+---+---+---+---+---+ | |
| 827 // ID1 \037 | |
| 828 // ID2 \213 | |
| 829 // CM \010 (compression method == DEFLATE) | |
| 830 // FLG \000 (special flags that we do not support) | |
| 831 // MTIME Unix format modification time (0 means not available) | |
| 832 // XFL 2-4? DEFLATE flags | |
| 833 // OS ???? Operating system indicator (255 means unknown) | |
| 834 // | |
| 835 // Header value we generate: | |
| 836 const char kGZipHeader[] = { '\037', '\213', '\010', '\000', '\000', | |
| 837 '\000', '\000', '\000', '\002', '\377' }; | |
| 838 CHECK_GT(zlib_stream.avail_out, sizeof(kGZipHeader)); | |
| 839 memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader)); | |
| 840 zlib_stream.next_out += sizeof(kGZipHeader); | |
| 841 zlib_stream.avail_out -= sizeof(kGZipHeader); | |
| 842 | |
| 843 // Do deflate | |
| 844 code = deflate(&zlib_stream, Z_FINISH); | |
| 845 gzip_compressed_length -= zlib_stream.avail_out; | |
| 846 std::string compressed(gzip_compressed.get(), gzip_compressed_length); | |
| 847 deflateEnd(&zlib_stream); | |
| 848 return compressed; | |
| 849 } | |
| 850 | |
| 851 //------------------------------------------------------------------------------ | |
| 852 | |
| 853 class SdchFilterChainingTest { | |
| 854 public: | |
| 855 static Filter* Factory(const std::vector<Filter::FilterType>& types, | |
| 856 const FilterContext& context, int size) { | |
| 857 return Filter::FactoryForTests(types, context, size); | |
| 858 } | |
| 859 }; | |
| 860 | |
| 861 // Test that filters can be cascaded (chained) so that the output of one filter | |
| 862 // is processed by the next one. This is most critical for SDCH, which is | |
| 863 // routinely followed by gzip (during encoding). The filter we'll test for will | |
| 864 // do the gzip decoding first, and then decode the SDCH content. | |
| 865 TEST_F(SdchFilterTest, FilterChaining) { | |
| 866 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 867 const std::string kSampleDomain = "sdchtest.com"; | |
| 868 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 869 | |
| 870 std::string url_string = "http://" + kSampleDomain; | |
| 871 | |
| 872 GURL url(url_string); | |
| 873 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 874 | |
| 875 std::string sdch_compressed(NewSdchCompressedData(dictionary)); | |
| 876 | |
| 877 // Use Gzip to compress the sdch sdch_compressed data. | |
| 878 std::string gzip_compressed_sdch = gzip_compress(sdch_compressed); | |
| 879 | |
| 880 // Construct a chained filter. | |
| 881 std::vector<Filter::FilterType> filter_types; | |
| 882 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 883 filter_types.push_back(Filter::FILTER_TYPE_GZIP); | |
| 884 | |
| 885 // First try with a large buffer (larger than test input, or compressed data). | |
| 886 const size_t kLargeInputBufferSize(1000); // Used internally in filters. | |
| 887 CHECK_GT(kLargeInputBufferSize, gzip_compressed_sdch.size()); | |
| 888 CHECK_GT(kLargeInputBufferSize, sdch_compressed.size()); | |
| 889 CHECK_GT(kLargeInputBufferSize, expanded_.size()); | |
| 890 SetupFilterContextWithGURL(url); | |
| 891 scoped_ptr<Filter> filter( | |
| 892 SdchFilterChainingTest::Factory(filter_types, *filter_context(), | |
| 893 kLargeInputBufferSize)); | |
| 894 EXPECT_EQ(static_cast<int>(kLargeInputBufferSize), | |
| 895 filter->stream_buffer_size()); | |
| 896 | |
| 897 // Verify that chained filter is waiting for data. | |
| 898 char tiny_output_buffer[10]; | |
| 899 int tiny_output_size = sizeof(tiny_output_buffer); | |
| 900 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, | |
| 901 filter->ReadData(tiny_output_buffer, &tiny_output_size)); | |
| 902 | |
| 903 // Make chain process all data. | |
| 904 size_t feed_block_size = kLargeInputBufferSize; | |
| 905 size_t output_block_size = kLargeInputBufferSize; | |
| 906 std::string output; | |
| 907 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 908 output_block_size, filter.get(), &output)); | |
| 909 EXPECT_EQ(output, expanded_); | |
| 910 | |
| 911 // Next try with a mid-sized internal buffer size. | |
| 912 const size_t kMidSizedInputBufferSize(100); | |
| 913 // Buffer should be big enough to swallow whole gzip content. | |
| 914 CHECK_GT(kMidSizedInputBufferSize, gzip_compressed_sdch.size()); | |
| 915 // Buffer should be small enough that entire SDCH content can't fit. | |
| 916 // We'll go even further, and force the chain to flush the buffer between the | |
| 917 // two filters more than once (that is why we multiply by 2). | |
| 918 CHECK_LT(kMidSizedInputBufferSize * 2, sdch_compressed.size()); | |
| 919 filter_context()->SetURL(url); | |
| 920 filter.reset( | |
| 921 SdchFilterChainingTest::Factory(filter_types, *filter_context(), | |
| 922 kMidSizedInputBufferSize)); | |
| 923 EXPECT_EQ(static_cast<int>(kMidSizedInputBufferSize), | |
| 924 filter->stream_buffer_size()); | |
| 925 | |
| 926 feed_block_size = kMidSizedInputBufferSize; | |
| 927 output_block_size = kMidSizedInputBufferSize; | |
| 928 output.clear(); | |
| 929 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 930 output_block_size, filter.get(), &output)); | |
| 931 EXPECT_EQ(output, expanded_); | |
| 932 | |
| 933 // Next try with a tiny input and output buffer to cover edge effects. | |
| 934 filter.reset(SdchFilterChainingTest::Factory(filter_types, *filter_context(), | |
| 935 kLargeInputBufferSize)); | |
| 936 EXPECT_EQ(static_cast<int>(kLargeInputBufferSize), | |
| 937 filter->stream_buffer_size()); | |
| 938 | |
| 939 feed_block_size = 1; | |
| 940 output_block_size = 1; | |
| 941 output.clear(); | |
| 942 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 943 output_block_size, filter.get(), &output)); | |
| 944 EXPECT_EQ(output, expanded_); | |
| 945 } | |
| 946 | |
| 947 TEST_F(SdchFilterTest, DefaultGzipIfSdch) { | |
| 948 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 949 const std::string kSampleDomain = "sdchtest.com"; | |
| 950 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 951 | |
| 952 std::string url_string = "http://" + kSampleDomain; | |
| 953 | |
| 954 GURL url(url_string); | |
| 955 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 956 | |
| 957 std::string sdch_compressed(NewSdchCompressedData(dictionary)); | |
| 958 | |
| 959 // Use Gzip to compress the sdch sdch_compressed data. | |
| 960 std::string gzip_compressed_sdch = gzip_compress(sdch_compressed); | |
| 961 | |
| 962 // Only claim to have sdch content, but really use the gzipped sdch content. | |
| 963 // System should automatically add the missing (optional) gzip. | |
| 964 std::vector<Filter::FilterType> filter_types; | |
| 965 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 966 | |
| 967 filter_context()->SetMimeType("anything/mime"); | |
| 968 SetupFilterContextWithGURL(url); | |
| 969 | |
| 970 Filter::FixupEncodingTypes(*filter_context(), &filter_types); | |
| 971 ASSERT_EQ(filter_types.size(), 2u); | |
| 972 EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH); | |
| 973 EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH); | |
| 974 | |
| 975 // First try with a large buffer (larger than test input, or compressed data). | |
| 976 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 977 | |
| 978 // Verify that chained filter is waiting for data. | |
| 979 char tiny_output_buffer[10]; | |
| 980 int tiny_output_size = sizeof(tiny_output_buffer); | |
| 981 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, | |
| 982 filter->ReadData(tiny_output_buffer, &tiny_output_size)); | |
| 983 | |
| 984 size_t feed_block_size = 100; | |
| 985 size_t output_block_size = 100; | |
| 986 std::string output; | |
| 987 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 988 output_block_size, filter.get(), &output)); | |
| 989 EXPECT_EQ(output, expanded_); | |
| 990 | |
| 991 // Next try with a tiny buffer to cover edge effects. | |
| 992 filter.reset(Filter::Factory(filter_types, *filter_context())); | |
| 993 | |
| 994 feed_block_size = 1; | |
| 995 output_block_size = 1; | |
| 996 output.clear(); | |
| 997 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 998 output_block_size, filter.get(), &output)); | |
| 999 EXPECT_EQ(output, expanded_); | |
| 1000 } | |
| 1001 | |
| 1002 TEST_F(SdchFilterTest, AcceptGzipSdchIfGzip) { | |
| 1003 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 1004 const std::string kSampleDomain = "sdchtest.com"; | |
| 1005 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 1006 | |
| 1007 std::string url_string = "http://" + kSampleDomain; | |
| 1008 | |
| 1009 GURL url(url_string); | |
| 1010 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 1011 | |
| 1012 std::string sdch_compressed(NewSdchCompressedData(dictionary)); | |
| 1013 | |
| 1014 // Use Gzip to compress the sdch sdch_compressed data. | |
| 1015 std::string gzip_compressed_sdch = gzip_compress(sdch_compressed); | |
| 1016 | |
| 1017 // Some proxies strip the content encoding statement down to a mere gzip, but | |
| 1018 // pass through the original content (with full sdch,gzip encoding). | |
| 1019 // Only claim to have gzip content, but really use the gzipped sdch content. | |
| 1020 // System should automatically add the missing (optional) sdch. | |
| 1021 std::vector<Filter::FilterType> filter_types; | |
| 1022 filter_types.push_back(Filter::FILTER_TYPE_GZIP); | |
| 1023 | |
| 1024 filter_context()->SetMimeType("anything/mime"); | |
| 1025 SetupFilterContextWithGURL(url); | |
| 1026 Filter::FixupEncodingTypes(*filter_context(), &filter_types); | |
| 1027 ASSERT_EQ(filter_types.size(), 3u); | |
| 1028 EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE); | |
| 1029 EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH); | |
| 1030 EXPECT_EQ(filter_types[2], Filter::FILTER_TYPE_GZIP); | |
| 1031 | |
| 1032 // First try with a large buffer (larger than test input, or compressed data). | |
| 1033 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 1034 | |
| 1035 // Verify that chained filter is waiting for data. | |
| 1036 char tiny_output_buffer[10]; | |
| 1037 int tiny_output_size = sizeof(tiny_output_buffer); | |
| 1038 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, | |
| 1039 filter->ReadData(tiny_output_buffer, &tiny_output_size)); | |
| 1040 | |
| 1041 size_t feed_block_size = 100; | |
| 1042 size_t output_block_size = 100; | |
| 1043 std::string output; | |
| 1044 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 1045 output_block_size, filter.get(), &output)); | |
| 1046 EXPECT_EQ(output, expanded_); | |
| 1047 | |
| 1048 // Next try with a tiny buffer to cover edge effects. | |
| 1049 filter.reset(Filter::Factory(filter_types, *filter_context())); | |
| 1050 | |
| 1051 feed_block_size = 1; | |
| 1052 output_block_size = 1; | |
| 1053 output.clear(); | |
| 1054 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 1055 output_block_size, filter.get(), &output)); | |
| 1056 EXPECT_EQ(output, expanded_); | |
| 1057 } | |
| 1058 | |
| 1059 TEST_F(SdchFilterTest, DefaultSdchGzipIfEmpty) { | |
| 1060 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 1061 const std::string kSampleDomain = "sdchtest.com"; | |
| 1062 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 1063 | |
| 1064 std::string url_string = "http://" + kSampleDomain; | |
| 1065 | |
| 1066 GURL url(url_string); | |
| 1067 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 1068 | |
| 1069 std::string sdch_compressed(NewSdchCompressedData(dictionary)); | |
| 1070 | |
| 1071 // Use Gzip to compress the sdch sdch_compressed data. | |
| 1072 std::string gzip_compressed_sdch = gzip_compress(sdch_compressed); | |
| 1073 | |
| 1074 // Only claim to have non-encoded content, but really use the gzipped sdch | |
| 1075 // content. | |
| 1076 // System should automatically add the missing (optional) sdch,gzip. | |
| 1077 std::vector<Filter::FilterType> filter_types; | |
| 1078 | |
| 1079 filter_context()->SetMimeType("anything/mime"); | |
| 1080 SetupFilterContextWithGURL(url); | |
| 1081 Filter::FixupEncodingTypes(*filter_context(), &filter_types); | |
| 1082 ASSERT_EQ(filter_types.size(), 2u); | |
| 1083 EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE); | |
| 1084 EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH); | |
| 1085 | |
| 1086 // First try with a large buffer (larger than test input, or compressed data). | |
| 1087 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 1088 | |
| 1089 // Verify that chained filter is waiting for data. | |
| 1090 char tiny_output_buffer[10]; | |
| 1091 int tiny_output_size = sizeof(tiny_output_buffer); | |
| 1092 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, | |
| 1093 filter->ReadData(tiny_output_buffer, &tiny_output_size)); | |
| 1094 | |
| 1095 size_t feed_block_size = 100; | |
| 1096 size_t output_block_size = 100; | |
| 1097 std::string output; | |
| 1098 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 1099 output_block_size, filter.get(), &output)); | |
| 1100 EXPECT_EQ(output, expanded_); | |
| 1101 | |
| 1102 // Next try with a tiny buffer to cover edge effects. | |
| 1103 filter.reset(Filter::Factory(filter_types, *filter_context())); | |
| 1104 | |
| 1105 feed_block_size = 1; | |
| 1106 output_block_size = 1; | |
| 1107 output.clear(); | |
| 1108 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size, | |
| 1109 output_block_size, filter.get(), &output)); | |
| 1110 EXPECT_EQ(output, expanded_); | |
| 1111 } | |
| 1112 | |
| 1113 TEST_F(SdchFilterTest, AcceptGzipGzipSdchIfGzip) { | |
| 1114 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 1115 const std::string kSampleDomain = "sdchtest.com"; | |
| 1116 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 1117 | |
| 1118 std::string url_string = "http://" + kSampleDomain; | |
| 1119 | |
| 1120 GURL url(url_string); | |
| 1121 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 1122 | |
| 1123 std::string sdch_compressed(NewSdchCompressedData(dictionary)); | |
| 1124 | |
| 1125 // Vodaphone (UK) Mobile Broadband provides double gzipped sdch with a content | |
| 1126 // encoding of merely gzip (apparently, only listing the extra level of | |
| 1127 // wrapper compression they added, but discarding the actual content encoding. | |
| 1128 // Use Gzip to double compress the sdch sdch_compressed data. | |
| 1129 std::string double_gzip_compressed_sdch = gzip_compress(gzip_compress( | |
| 1130 sdch_compressed)); | |
| 1131 | |
| 1132 // Only claim to have gzip content, but really use the double gzipped sdch | |
| 1133 // content. | |
| 1134 // System should automatically add the missing (optional) sdch, gzip decoders. | |
| 1135 std::vector<Filter::FilterType> filter_types; | |
| 1136 filter_types.push_back(Filter::FILTER_TYPE_GZIP); | |
| 1137 | |
| 1138 filter_context()->SetMimeType("anything/mime"); | |
| 1139 SetupFilterContextWithGURL(url); | |
| 1140 Filter::FixupEncodingTypes(*filter_context(), &filter_types); | |
| 1141 ASSERT_EQ(filter_types.size(), 3u); | |
| 1142 EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE); | |
| 1143 EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH); | |
| 1144 EXPECT_EQ(filter_types[2], Filter::FILTER_TYPE_GZIP); | |
| 1145 | |
| 1146 // First try with a large buffer (larger than test input, or compressed data). | |
| 1147 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 1148 | |
| 1149 // Verify that chained filter is waiting for data. | |
| 1150 char tiny_output_buffer[10]; | |
| 1151 int tiny_output_size = sizeof(tiny_output_buffer); | |
| 1152 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, | |
| 1153 filter->ReadData(tiny_output_buffer, &tiny_output_size)); | |
| 1154 | |
| 1155 size_t feed_block_size = 100; | |
| 1156 size_t output_block_size = 100; | |
| 1157 std::string output; | |
| 1158 EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch, feed_block_size, | |
| 1159 output_block_size, filter.get(), &output)); | |
| 1160 EXPECT_EQ(output, expanded_); | |
| 1161 | |
| 1162 // Next try with a tiny buffer to cover edge effects. | |
| 1163 filter.reset(Filter::Factory(filter_types, *filter_context())); | |
| 1164 | |
| 1165 feed_block_size = 1; | |
| 1166 output_block_size = 1; | |
| 1167 output.clear(); | |
| 1168 EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch, feed_block_size, | |
| 1169 output_block_size, filter.get(), &output)); | |
| 1170 EXPECT_EQ(output, expanded_); | |
| 1171 } | |
| 1172 | |
| 1173 // Test to make sure we decode properly with an unexpected dictionary. | |
| 1174 TEST_F(SdchFilterTest, UnexpectedDictionary) { | |
| 1175 // Setup a dictionary, add it to the filter context, and create a filter | |
| 1176 // based on that dictionary. | |
| 1177 const std::string kSampleDomain = "sdchtest.com"; | |
| 1178 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 1179 std::string url_string = "http://" + kSampleDomain; | |
| 1180 GURL url(url_string); | |
| 1181 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 1182 | |
| 1183 SetupFilterContextWithGURL(url); | |
| 1184 | |
| 1185 std::vector<Filter::FilterType> filter_types; | |
| 1186 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 1187 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 1188 | |
| 1189 // Setup another dictionary, expired. Don't add it to the filter context. | |
| 1190 // Delete stored dictionaries first to handle platforms which only | |
| 1191 // have room for a single dictionary. | |
| 1192 sdch_manager_->ClearData(); | |
| 1193 std::string expired_dictionary(NewSdchExpiredDictionary(kSampleDomain)); | |
| 1194 | |
| 1195 // Don't use the Helper function since its insertion check is indeterminate | |
| 1196 // for a Max-Age: 0 dictionary. | |
| 1197 sdch_manager_->AddSdchDictionary(expired_dictionary, url, nullptr); | |
| 1198 | |
| 1199 std::string client_hash; | |
| 1200 std::string server_hash; | |
| 1201 SdchManager::GenerateHash(expired_dictionary, &client_hash, &server_hash); | |
| 1202 | |
| 1203 // Make sure Max-Age: 0 shows up as expired. | |
| 1204 scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock); | |
| 1205 clock->SetNow(base::Time::Now()); | |
| 1206 clock->Advance(base::TimeDelta::FromMinutes(5)); | |
| 1207 SdchProblemCode problem_code; | |
| 1208 scoped_ptr<SdchManager::DictionarySet> hash_set( | |
| 1209 sdch_manager_->GetDictionarySetByHash( | |
| 1210 url, server_hash, &problem_code).Pass()); | |
| 1211 ASSERT_TRUE(hash_set); | |
| 1212 ASSERT_EQ(SDCH_OK, problem_code); | |
| 1213 | |
| 1214 const_cast<SdchManager::Dictionary*>( | |
| 1215 hash_set->GetDictionary(server_hash))->SetClockForTesting( | |
| 1216 clock.Pass()); | |
| 1217 | |
| 1218 // Encode output with the second dictionary. | |
| 1219 std::string sdch_compressed(NewSdchCompressedData(expired_dictionary)); | |
| 1220 | |
| 1221 // See if the filter decodes it. | |
| 1222 std::string output; | |
| 1223 EXPECT_TRUE(FilterTestData(sdch_compressed, 100, 100, filter.get(), &output)); | |
| 1224 EXPECT_EQ(expanded_, output); | |
| 1225 } | |
| 1226 | |
| 1227 class SimpleSdchObserver : public SdchObserver { | |
| 1228 public: | |
| 1229 explicit SimpleSdchObserver(SdchManager* manager) | |
| 1230 : dictionary_used_(0), manager_(manager) { | |
| 1231 manager_->AddObserver(this); | |
| 1232 } | |
| 1233 ~SimpleSdchObserver() override { manager_->RemoveObserver(this); } | |
| 1234 | |
| 1235 // SdchObserver | |
| 1236 void OnDictionaryUsed(SdchManager* manager, | |
| 1237 const std::string& server_hash) override { | |
| 1238 dictionary_used_++; | |
| 1239 last_server_hash_ = server_hash; | |
| 1240 } | |
| 1241 | |
| 1242 int dictionary_used_calls() const { return dictionary_used_; } | |
| 1243 std::string last_server_hash() const { return last_server_hash_; } | |
| 1244 | |
| 1245 void OnGetDictionary(SdchManager* /* manager */, | |
| 1246 const GURL& /* request_url */, | |
| 1247 const GURL& /* dictionary_url */) override {} | |
| 1248 void OnClearDictionaries(SdchManager* /* manager */) override {} | |
| 1249 | |
| 1250 private: | |
| 1251 int dictionary_used_; | |
| 1252 std::string last_server_hash_; | |
| 1253 SdchManager* manager_; | |
| 1254 | |
| 1255 DISALLOW_COPY_AND_ASSIGN(SimpleSdchObserver); | |
| 1256 }; | |
| 1257 | |
| 1258 TEST_F(SdchFilterTest, DictionaryUsedSignaled) { | |
| 1259 // Construct a valid SDCH dictionary from a VCDIFF dictionary. | |
| 1260 const std::string kSampleDomain = "sdchtest.com"; | |
| 1261 std::string dictionary(NewSdchDictionary(kSampleDomain)); | |
| 1262 SimpleSdchObserver observer(sdch_manager_.get()); | |
| 1263 | |
| 1264 std::string url_string = "http://" + kSampleDomain; | |
| 1265 | |
| 1266 GURL url(url_string); | |
| 1267 EXPECT_TRUE(AddSdchDictionary(dictionary, url)); | |
| 1268 | |
| 1269 std::string client_hash; | |
| 1270 std::string server_hash; | |
| 1271 SdchManager::GenerateHash(dictionary, &client_hash, &server_hash); | |
| 1272 | |
| 1273 std::string compressed(NewSdchCompressedData(dictionary)); | |
| 1274 | |
| 1275 std::vector<Filter::FilterType> filter_types; | |
| 1276 filter_types.push_back(Filter::FILTER_TYPE_SDCH); | |
| 1277 | |
| 1278 SetupFilterContextWithGURL(url); | |
| 1279 | |
| 1280 scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context())); | |
| 1281 | |
| 1282 size_t feed_block_size = 100; | |
| 1283 size_t output_block_size = 100; | |
| 1284 std::string output; | |
| 1285 EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size, | |
| 1286 filter.get(), &output)); | |
| 1287 EXPECT_EQ(output, expanded_); | |
| 1288 | |
| 1289 filter.reset(nullptr); | |
| 1290 | |
| 1291 // Confirm that we got a "DictionaryUsed" signal from the SdchManager | |
| 1292 // for our dictionary. | |
| 1293 EXPECT_EQ(1, observer.dictionary_used_calls()); | |
| 1294 EXPECT_EQ(server_hash, observer.last_server_hash()); | |
| 1295 } | |
| 1296 | |
| 1297 } // namespace net | |
| OLD | NEW |