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 |