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