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.
11 #include "base/logging.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/test/histogram_tester.h"
14 #include "base/test/simple_test_clock.h"
15 #include "net/base/io_buffer.h"
16 #include "net/base/sdch_dictionary.h"
17 #include "net/base/sdch_manager.h"
18 #include "net/base/sdch_observer.h"
19 #include "net/filter/mock_filter_context.h"
20 #include "net/filter/sdch_filter.h"
21 #include "net/url_request/url_request_context.h"
22 #include "net/url_request/url_request_http_job.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24 #include "third_party/zlib/zlib.h"
28 //------------------------------------------------------------------------------
29 // Provide sample data and compression results with a sample VCDIFF dictionary.
30 // Note an SDCH dictionary has extra meta-data before the VCDIFF dictionary.
31 static const char kTestVcdiffDictionary
[] = "DictionaryFor"
32 "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n";
33 // Pre-compression test data. Note that we pad with a lot of highly gzip
34 // compressible content to help to exercise the chaining pipeline. That is why
35 // there are a PILE of zeros at the start and end.
36 // This will ensure that gzip compressed data can be fed to the chain in one
37 // gulp, but (with careful selection of intermediate buffers) that it takes
38 // several sdch buffers worth of data to satisfy the sdch filter. See detailed
39 // CHECK() calls in FilterChaining test for specifics.
40 static const char kTestData
[] = "0000000000000000000000000000000000000000000000"
41 "0000000000000000000000000000TestData "
42 "SdchCompression1SdchCompression2SdchCompression3SdchCompression"
43 "00000000000000000000000000000000000000000000000000000000000000000000000000"
44 "000000000000000000000000000000000000000\n";
46 // Note SDCH compressed data will include a reference to the SDCH dictionary.
47 static const char kSdchCompressedTestData
[] =
48 "\326\303\304\0\0\001M\0\201S\202\004\0\201E\006\001"
49 "00000000000000000000000000000000000000000000000000000000000000000000000000"
50 "TestData 00000000000000000000000000000000000000000000000000000000000000000"
51 "000000000000000000000000000000000000000000000000\n\001S\023\077\001r\r";
53 //------------------------------------------------------------------------------
55 class SdchFilterTest
: public testing::Test
{
58 : test_vcdiff_dictionary_(kTestVcdiffDictionary
,
59 sizeof(kTestVcdiffDictionary
) - 1),
60 vcdiff_compressed_data_(kSdchCompressedTestData
,
61 sizeof(kSdchCompressedTestData
) - 1),
62 expanded_(kTestData
, sizeof(kTestData
) - 1),
63 sdch_manager_(new SdchManager
),
64 filter_context_(new MockFilterContext
) {
65 URLRequestContext
* url_request_context
=
66 filter_context_
->GetModifiableURLRequestContext();
68 url_request_context
->set_sdch_manager(sdch_manager_
.get());
71 // Attempt to add a dictionary to the manager and probe for success or
73 bool AddSdchDictionary(const std::string
& dictionary_text
,
75 return sdch_manager_
->AddSdchDictionary(dictionary_text
, gurl
, nullptr) ==
79 MockFilterContext
* filter_context() { return filter_context_
.get(); }
81 // Sets both the GURL and the SDCH response for a filter context.
82 void SetupFilterContextWithGURL(GURL url
) {
83 filter_context_
->SetURL(url
);
84 filter_context_
->SetSdchResponse(
85 sdch_manager_
->GetDictionarySet(url
).Pass());
88 std::string
NewSdchCompressedData(const std::string dictionary
) {
89 std::string client_hash
;
90 std::string server_hash
;
91 SdchManager::GenerateHash(dictionary
, &client_hash
, &server_hash
);
93 // Build compressed data that refers to our dictionary.
94 std::string
compressed(server_hash
);
95 compressed
.append("\0", 1);
96 compressed
.append(vcdiff_compressed_data_
);
100 const std::string test_vcdiff_dictionary_
;
101 const std::string vcdiff_compressed_data_
;
102 const std::string expanded_
; // Desired final, decompressed data.
104 scoped_ptr
<SdchManager
> sdch_manager_
;
105 scoped_ptr
<MockFilterContext
> filter_context_
;
108 TEST_F(SdchFilterTest
, Hashing
) {
109 std::string client_hash
, server_hash
;
110 std::string
dictionary("test contents");
111 SdchManager::GenerateHash(dictionary
, &client_hash
, &server_hash
);
113 EXPECT_EQ(client_hash
, "lMQBjS3P");
114 EXPECT_EQ(server_hash
, "MyciMVll");
117 //------------------------------------------------------------------------------
118 // Provide a generic helper function for trying to filter data.
119 // This function repeatedly calls the filter to process data, until the entire
120 // source is consumed. The return value from the filter is appended to output.
121 // This allows us to vary input and output block sizes in order to test for edge
122 // effects (boundary effects?) during the filtering process.
123 // This function provides data to the filter in blocks of no-more-than the
124 // specified input_block_length. It allows the filter to fill no more than
125 // output_buffer_length in any one call to proccess (a.k.a., Read) data, and
126 // concatenates all these little output blocks into the singular output string.
127 static bool FilterTestData(const std::string
& source
,
128 size_t input_block_length
,
129 const size_t output_buffer_length
,
130 Filter
* filter
, std::string
* output
) {
131 CHECK_GT(input_block_length
, 0u);
132 Filter::FilterStatus
status(Filter::FILTER_NEED_MORE_DATA
);
133 size_t source_index
= 0;
134 scoped_ptr
<char[]> output_buffer(new char[output_buffer_length
]);
135 size_t input_amount
= std::min(input_block_length
,
136 static_cast<size_t>(filter
->stream_buffer_size()));
139 int copy_amount
= std::min(input_amount
, source
.size() - source_index
);
140 if (copy_amount
> 0 && status
== Filter::FILTER_NEED_MORE_DATA
) {
141 memcpy(filter
->stream_buffer()->data(), source
.data() + source_index
,
143 filter
->FlushStreamBuffer(copy_amount
);
144 source_index
+= copy_amount
;
146 int buffer_length
= output_buffer_length
;
147 status
= filter
->ReadData(output_buffer
.get(), &buffer_length
);
148 output
->append(output_buffer
.get(), buffer_length
);
149 if (status
== Filter::FILTER_ERROR
)
151 // Callers assume that FILTER_OK with no output buffer means FILTER_DONE.
152 if (Filter::FILTER_OK
== status
&& 0 == buffer_length
)
154 if (copy_amount
== 0 && buffer_length
== 0)
159 static std::string
NewSdchDictionary(const std::string
& domain
) {
160 std::string dictionary
;
161 if (!domain
.empty()) {
162 dictionary
.append("Domain: ");
163 dictionary
.append(domain
);
164 dictionary
.append("\n");
166 dictionary
.append("\n");
167 dictionary
.append(kTestVcdiffDictionary
, sizeof(kTestVcdiffDictionary
) - 1);
171 static std::string
NewSdchExpiredDictionary(const std::string
& domain
) {
172 std::string dictionary
;
173 if (!domain
.empty()) {
174 dictionary
.append("Domain: ");
175 dictionary
.append(domain
);
176 dictionary
.append("\n");
178 dictionary
.append("Max-Age: -1\n");
179 dictionary
.append("\n");
180 dictionary
.append(kTestVcdiffDictionary
, sizeof(kTestVcdiffDictionary
) - 1);
184 TEST_F(SdchFilterTest
, EmptyInputOk
) {
185 std::vector
<Filter::FilterType
> filter_types
;
186 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
187 char output_buffer
[20];
188 std::string
url_string("http://ignore.com");
189 filter_context()->SetURL(GURL(url_string
));
190 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
192 // With no input data, try to read output.
193 int output_bytes_or_buffer_size
= sizeof(output_buffer
);
194 Filter::FilterStatus status
= filter
->ReadData(output_buffer
,
195 &output_bytes_or_buffer_size
);
197 EXPECT_EQ(0, output_bytes_or_buffer_size
);
198 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
, status
);
201 // Make sure that the filter context has everything that might be
202 // nuked from it during URLRequest teardown before the SdchFilter
204 TEST_F(SdchFilterTest
, SparseContextOk
) {
205 std::vector
<Filter::FilterType
> filter_types
;
206 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
207 char output_buffer
[20];
208 std::string
url_string("http://ignore.com");
209 filter_context()->SetURL(GURL(url_string
));
210 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
212 // With no input data, try to read output.
213 int output_bytes_or_buffer_size
= sizeof(output_buffer
);
214 Filter::FilterStatus status
= filter
->ReadData(output_buffer
,
215 &output_bytes_or_buffer_size
);
217 EXPECT_EQ(0, output_bytes_or_buffer_size
);
218 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
, status
);
220 // Partially tear down context. Anything that goes through request()
221 // without checking it for null in the URLRequestJob::HttpFilterContext
222 // implementation is suspect. Everything that does check it for null should
223 // return null. This is to test for incorrectly relying on filter_context()
224 // from the SdchFilter destructor.
225 filter_context()->NukeUnstableInterfaces();
228 TEST_F(SdchFilterTest
, PassThroughWhenTentative
) {
229 std::vector
<Filter::FilterType
> filter_types
;
230 // Selective a tentative filter (which can fall back to pass through).
231 filter_types
.push_back(Filter::FILTER_TYPE_GZIP_HELPING_SDCH
);
232 char output_buffer
[20];
233 // Response code needs to be 200 to allow a pass through.
234 filter_context()->SetResponseCode(200);
235 std::string
url_string("http://ignore.com");
236 filter_context()->SetURL(GURL(url_string
));
237 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
239 // Supply enough data to force a pass-through mode..
240 std::string
non_gzip_content("not GZIPed data");
242 char* input_buffer
= filter
->stream_buffer()->data();
243 int input_buffer_size
= filter
->stream_buffer_size();
245 EXPECT_LT(static_cast<int>(non_gzip_content
.size()),
247 memcpy(input_buffer
, non_gzip_content
.data(),
248 non_gzip_content
.size());
249 filter
->FlushStreamBuffer(non_gzip_content
.size());
251 // Try to read output.
252 int output_bytes_or_buffer_size
= sizeof(output_buffer
);
253 Filter::FilterStatus status
= filter
->ReadData(output_buffer
,
254 &output_bytes_or_buffer_size
);
256 EXPECT_EQ(non_gzip_content
.size(),
257 static_cast<size_t>(output_bytes_or_buffer_size
));
258 ASSERT_GT(sizeof(output_buffer
),
259 static_cast<size_t>(output_bytes_or_buffer_size
));
260 output_buffer
[output_bytes_or_buffer_size
] = '\0';
261 EXPECT_TRUE(non_gzip_content
== output_buffer
);
262 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
, status
);
265 TEST_F(SdchFilterTest
, RefreshBadReturnCode
) {
266 std::vector
<Filter::FilterType
> filter_types
;
267 // Selective a tentative filter (which can fall back to pass through).
268 filter_types
.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE
);
269 char output_buffer
[20];
270 // Response code needs to be 200 to allow a pass through.
271 filter_context()->SetResponseCode(403);
272 // Meta refresh will only appear for html content
273 filter_context()->SetMimeType("text/html");
274 std::string
url_string("http://ignore.com");
275 filter_context()->SetURL(GURL(url_string
));
276 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
278 // Supply enough data to force a pass-through mode, which means we have
279 // provided more than 9 characters that can't be a dictionary hash.
280 std::string
non_sdch_content("This is not SDCH");
282 char* input_buffer
= filter
->stream_buffer()->data();
283 int input_buffer_size
= filter
->stream_buffer_size();
285 EXPECT_LT(static_cast<int>(non_sdch_content
.size()),
287 memcpy(input_buffer
, non_sdch_content
.data(),
288 non_sdch_content
.size());
289 filter
->FlushStreamBuffer(non_sdch_content
.size());
291 // Try to read output.
292 int output_bytes_or_buffer_size
= sizeof(output_buffer
);
293 Filter::FilterStatus status
= filter
->ReadData(output_buffer
,
294 &output_bytes_or_buffer_size
);
296 // We should have read a long and complicated meta-refresh request.
297 EXPECT_TRUE(sizeof(output_buffer
) == output_bytes_or_buffer_size
);
298 // Check at least the prefix of the return.
299 EXPECT_EQ(0, strncmp(output_buffer
,
300 "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>",
301 sizeof(output_buffer
)));
302 EXPECT_EQ(Filter::FILTER_OK
, status
);
305 TEST_F(SdchFilterTest
, ErrorOnBadReturnCode
) {
306 std::vector
<Filter::FilterType
> filter_types
;
307 // Selective a tentative filter (which can fall back to pass through).
308 filter_types
.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE
);
309 char output_buffer
[20];
310 // Response code needs to be 200 to allow a pass through.
311 filter_context()->SetResponseCode(403);
312 // Meta refresh will only appear for html content, so set to something else
313 // to induce an error (we can't meta refresh).
314 filter_context()->SetMimeType("anything");
315 std::string
url_string("http://ignore.com");
316 filter_context()->SetURL(GURL(url_string
));
317 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
319 // Supply enough data to force a pass-through mode, which means we have
320 // provided more than 9 characters that can't be a dictionary hash.
321 std::string
non_sdch_content("This is not SDCH");
323 char* input_buffer
= filter
->stream_buffer()->data();
324 int input_buffer_size
= filter
->stream_buffer_size();
326 EXPECT_LT(static_cast<int>(non_sdch_content
.size()),
328 memcpy(input_buffer
, non_sdch_content
.data(),
329 non_sdch_content
.size());
330 filter
->FlushStreamBuffer(non_sdch_content
.size());
332 // Try to read output.
333 int output_bytes_or_buffer_size
= sizeof(output_buffer
);
334 Filter::FilterStatus status
= filter
->ReadData(output_buffer
,
335 &output_bytes_or_buffer_size
);
337 EXPECT_EQ(0, output_bytes_or_buffer_size
);
338 EXPECT_EQ(Filter::FILTER_ERROR
, status
);
341 TEST_F(SdchFilterTest
, ErrorOnBadReturnCodeWithHtml
) {
342 std::vector
<Filter::FilterType
> filter_types
;
343 // Selective a tentative filter (which can fall back to pass through).
344 filter_types
.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE
);
345 char output_buffer
[20];
346 // Response code needs to be 200 to allow a pass through.
347 filter_context()->SetResponseCode(403);
348 // Meta refresh will only appear for html content
349 filter_context()->SetMimeType("text/html");
350 std::string
url_string("http://ignore.com");
351 filter_context()->SetURL(GURL(url_string
));
352 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
354 // Supply enough data to force a pass-through mode, which means we have
355 // provided more than 9 characters that can't be a dictionary hash.
356 std::string
non_sdch_content("This is not SDCH");
358 char* input_buffer
= filter
->stream_buffer()->data();
359 int input_buffer_size
= filter
->stream_buffer_size();
361 EXPECT_LT(static_cast<int>(non_sdch_content
.size()),
363 memcpy(input_buffer
, non_sdch_content
.data(),
364 non_sdch_content
.size());
365 filter
->FlushStreamBuffer(non_sdch_content
.size());
367 // Try to read output.
368 int output_bytes_or_buffer_size
= sizeof(output_buffer
);
369 Filter::FilterStatus status
= filter
->ReadData(output_buffer
,
370 &output_bytes_or_buffer_size
);
372 // We should have read a long and complicated meta-refresh request.
373 EXPECT_EQ(sizeof(output_buffer
),
374 static_cast<size_t>(output_bytes_or_buffer_size
));
375 // Check at least the prefix of the return.
376 EXPECT_EQ(0, strncmp(output_buffer
,
377 "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>",
378 sizeof(output_buffer
)));
379 EXPECT_EQ(Filter::FILTER_OK
, status
);
382 TEST_F(SdchFilterTest
, BasicBadDictionary
) {
383 std::vector
<Filter::FilterType
> filter_types
;
384 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
385 char output_buffer
[20];
386 std::string
url_string("http://ignore.com");
387 filter_context()->SetURL(GURL(url_string
));
388 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
390 // Supply bogus data (which doesn't yet specify a full dictionary hash).
391 // Dictionary hash is 8 characters followed by a null.
392 std::string
dictionary_hash_prefix("123");
394 char* input_buffer
= filter
->stream_buffer()->data();
395 int input_buffer_size
= filter
->stream_buffer_size();
397 EXPECT_LT(static_cast<int>(dictionary_hash_prefix
.size()),
399 memcpy(input_buffer
, dictionary_hash_prefix
.data(),
400 dictionary_hash_prefix
.size());
401 filter
->FlushStreamBuffer(dictionary_hash_prefix
.size());
403 // With less than a dictionary specifier, try to read output.
404 int output_bytes_or_buffer_size
= sizeof(output_buffer
);
405 Filter::FilterStatus status
= filter
->ReadData(output_buffer
,
406 &output_bytes_or_buffer_size
);
408 EXPECT_EQ(0, output_bytes_or_buffer_size
);
409 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
, status
);
411 // Provide enough data to complete *a* hash, but it is bogus, and not in our
412 // list of dictionaries, so the filter should error out immediately.
413 std::string
dictionary_hash_postfix("4abcd\0", 6);
415 CHECK_LT(dictionary_hash_postfix
.size(),
416 static_cast<size_t>(input_buffer_size
));
417 memcpy(input_buffer
, dictionary_hash_postfix
.data(),
418 dictionary_hash_postfix
.size());
419 filter
->FlushStreamBuffer(dictionary_hash_postfix
.size());
421 // With a non-existant dictionary specifier, try to read output.
422 output_bytes_or_buffer_size
= sizeof(output_buffer
);
423 status
= filter
->ReadData(output_buffer
, &output_bytes_or_buffer_size
);
425 EXPECT_EQ(0, output_bytes_or_buffer_size
);
426 EXPECT_EQ(Filter::FILTER_ERROR
, status
);
428 EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET
,
429 sdch_manager_
->IsInSupportedDomain(GURL(url_string
)));
430 sdch_manager_
->ClearBlacklistings();
431 EXPECT_EQ(SDCH_OK
, sdch_manager_
->IsInSupportedDomain(GURL(url_string
)));
434 TEST_F(SdchFilterTest
, DictionaryAddOnce
) {
435 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
436 const std::string kSampleDomain
= "sdchtest.com";
437 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
439 std::string url_string
= "http://" + kSampleDomain
;
440 GURL
url(url_string
);
441 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
443 // Check we can't add it twice.
444 EXPECT_FALSE(AddSdchDictionary(dictionary
, url
));
446 const std::string kSampleDomain2
= "sdchtest2.com";
448 // Construct a second SDCH dictionary from a VCDIFF dictionary.
449 std::string
dictionary2(NewSdchDictionary(kSampleDomain2
));
451 std::string url_string2
= "http://" + kSampleDomain2
;
452 GURL
url2(url_string2
);
453 EXPECT_TRUE(AddSdchDictionary(dictionary2
, url2
));
456 TEST_F(SdchFilterTest
, BasicDictionary
) {
457 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
458 const std::string kSampleDomain
= "sdchtest.com";
459 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
461 std::string url_string
= "http://" + kSampleDomain
;
463 GURL
url(url_string
);
464 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
466 std::string
compressed(NewSdchCompressedData(dictionary
));
468 std::vector
<Filter::FilterType
> filter_types
;
469 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
471 SetupFilterContextWithGURL(url
);
473 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
475 size_t feed_block_size
= 100;
476 size_t output_block_size
= 100;
478 EXPECT_TRUE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
479 filter
.get(), &output
));
480 EXPECT_EQ(output
, expanded_
);
482 // Decode with really small buffers (size 1) to check for edge effects.
483 filter
.reset(Filter::Factory(filter_types
, *filter_context()));
486 output_block_size
= 1;
488 EXPECT_TRUE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
489 filter
.get(), &output
));
490 EXPECT_EQ(output
, expanded_
);
493 TEST_F(SdchFilterTest
, NoDecodeHttps
) {
494 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
495 const std::string kSampleDomain
= "sdchtest.com";
496 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
498 std::string url_string
= "http://" + kSampleDomain
;
500 GURL
url(url_string
);
501 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
503 std::string
compressed(NewSdchCompressedData(dictionary
));
505 std::vector
<Filter::FilterType
> filter_types
;
506 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
508 GURL
filter_context_gurl("https://" + kSampleDomain
);
509 SetupFilterContextWithGURL(GURL("https://" + kSampleDomain
));
510 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
512 const size_t feed_block_size(100);
513 const size_t output_block_size(100);
516 EXPECT_FALSE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
517 filter
.get(), &output
));
520 // Current failsafe TODO/hack refuses to decode any content that doesn't use
521 // http as the scheme (see use of DICTIONARY_SELECTED_FOR_NON_HTTP).
522 // The following tests this blockage. Note that blacklisting results, so we
523 // we need separate tests for each of these.
524 TEST_F(SdchFilterTest
, NoDecodeFtp
) {
525 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
526 const std::string kSampleDomain
= "sdchtest.com";
527 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
529 std::string url_string
= "http://" + kSampleDomain
;
531 GURL
url(url_string
);
532 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
534 std::string
compressed(NewSdchCompressedData(dictionary
));
536 std::vector
<Filter::FilterType
> filter_types
;
537 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
539 SetupFilterContextWithGURL(GURL("ftp://" + kSampleDomain
));
540 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
542 const size_t feed_block_size(100);
543 const size_t output_block_size(100);
546 EXPECT_FALSE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
547 filter
.get(), &output
));
550 TEST_F(SdchFilterTest
, NoDecodeFileColon
) {
551 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
552 const std::string kSampleDomain
= "sdchtest.com";
553 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
555 std::string url_string
= "http://" + kSampleDomain
;
557 GURL
url(url_string
);
558 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
560 std::string
compressed(NewSdchCompressedData(dictionary
));
562 std::vector
<Filter::FilterType
> filter_types
;
563 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
565 SetupFilterContextWithGURL(GURL("file://" + kSampleDomain
));
566 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
568 const size_t feed_block_size(100);
569 const size_t output_block_size(100);
572 EXPECT_FALSE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
573 filter
.get(), &output
));
576 TEST_F(SdchFilterTest
, NoDecodeAboutColon
) {
577 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
578 const std::string kSampleDomain
= "sdchtest.com";
579 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
581 std::string url_string
= "http://" + kSampleDomain
;
583 GURL
url(url_string
);
584 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
586 std::string
compressed(NewSdchCompressedData(dictionary
));
588 std::vector
<Filter::FilterType
> filter_types
;
589 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
591 SetupFilterContextWithGURL(GURL("about://" + kSampleDomain
));
592 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
594 const size_t feed_block_size(100);
595 const size_t output_block_size(100);
598 EXPECT_FALSE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
599 filter
.get(), &output
));
602 TEST_F(SdchFilterTest
, NoDecodeJavaScript
) {
603 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
604 const std::string kSampleDomain
= "sdchtest.com";
605 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
607 std::string url_string
= "http://" + kSampleDomain
;
609 GURL
url(url_string
);
610 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
612 std::string
compressed(NewSdchCompressedData(dictionary
));
614 std::vector
<Filter::FilterType
> filter_types
;
615 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
617 SetupFilterContextWithGURL(GURL("javascript://" + kSampleDomain
));
618 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
620 const size_t feed_block_size(100);
621 const size_t output_block_size(100);
624 EXPECT_FALSE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
625 filter
.get(), &output
));
628 TEST_F(SdchFilterTest
, CanStillDecodeHttp
) {
629 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
630 const std::string kSampleDomain
= "sdchtest.com";
631 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
633 std::string url_string
= "http://" + kSampleDomain
;
635 GURL
url(url_string
);
636 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
638 std::string
compressed(NewSdchCompressedData(dictionary
));
640 std::vector
<Filter::FilterType
> filter_types
;
641 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
643 SetupFilterContextWithGURL(GURL("http://" + kSampleDomain
));
644 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
646 const size_t feed_block_size(100);
647 const size_t output_block_size(100);
650 base::HistogramTester tester
;
652 EXPECT_TRUE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
653 filter
.get(), &output
));
654 // The filter's destructor is responsible for uploading total ratio
658 tester
.ExpectTotalCount("Sdch3.Network_Decode_Ratio_a", 1);
659 tester
.ExpectTotalCount("Sdch3.NetworkBytesSavedByCompression", 1);
662 TEST_F(SdchFilterTest
, CrossDomainDictionaryUse
) {
663 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
664 const std::string kSampleDomain
= "sdchtest.com";
665 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
667 std::string url_string
= "http://" + kSampleDomain
;
669 GURL
url(url_string
);
670 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
672 std::string
compressed(NewSdchCompressedData(dictionary
));
674 std::vector
<Filter::FilterType
> filter_types
;
675 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
677 // Decode with content arriving from the "wrong" domain.
678 // This tests SdchManager::CanSet().
679 GURL
wrong_domain_url("http://www.wrongdomain.com");
680 SetupFilterContextWithGURL(wrong_domain_url
);
681 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
683 size_t feed_block_size
= 100;
684 size_t output_block_size
= 100;
686 EXPECT_FALSE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
687 filter
.get(), &output
));
688 EXPECT_EQ(output
.size(), 0u); // No output written.
690 EXPECT_EQ(SDCH_OK
, sdch_manager_
->IsInSupportedDomain(GURL(url_string
)));
691 EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET
,
692 sdch_manager_
->IsInSupportedDomain(wrong_domain_url
));
693 sdch_manager_
->ClearBlacklistings();
694 EXPECT_EQ(SDCH_OK
, sdch_manager_
->IsInSupportedDomain(wrong_domain_url
));
697 TEST_F(SdchFilterTest
, DictionaryPathValidation
) {
698 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
699 const std::string kSampleDomain
= "sdchtest.com";
700 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
702 std::string url_string
= "http://" + kSampleDomain
;
704 GURL
url(url_string
);
705 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
707 // Create a dictionary with a path restriction, by prefixing dictionary.
708 const std::string
path("/special_path/bin");
709 std::string
dictionary_with_path("Path: " + path
+ "\n");
710 dictionary_with_path
.append(dictionary
);
711 GURL
url2(url_string
+ path
);
712 EXPECT_TRUE(AddSdchDictionary(dictionary_with_path
, url2
));
714 std::string
compressed_for_path(NewSdchCompressedData(dictionary_with_path
));
716 std::vector
<Filter::FilterType
> filter_types
;
717 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
719 // Test decode the path data, arriving from a valid path.
720 SetupFilterContextWithGURL(GURL(url_string
+ path
));
721 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
723 size_t feed_block_size
= 100;
724 size_t output_block_size
= 100;
727 EXPECT_TRUE(FilterTestData(compressed_for_path
, feed_block_size
,
728 output_block_size
, filter
.get(), &output
));
729 EXPECT_EQ(output
, expanded_
);
731 // Test decode the path data, arriving from a invalid path.
732 SetupFilterContextWithGURL(GURL(url_string
));
733 filter
.reset(Filter::Factory(filter_types
, *filter_context()));
735 feed_block_size
= 100;
736 output_block_size
= 100;
738 EXPECT_FALSE(FilterTestData(compressed_for_path
, feed_block_size
,
739 output_block_size
, filter
.get(), &output
));
740 EXPECT_EQ(output
.size(), 0u); // No output written.
742 EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET
,
743 sdch_manager_
->IsInSupportedDomain(GURL(url_string
)));
744 sdch_manager_
->ClearBlacklistings();
745 EXPECT_EQ(SDCH_OK
, sdch_manager_
->IsInSupportedDomain(GURL(url_string
)));
748 TEST_F(SdchFilterTest
, DictionaryPortValidation
) {
749 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
750 const std::string kSampleDomain
= "sdchtest.com";
751 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
753 std::string url_string
= "http://" + kSampleDomain
;
755 GURL
url(url_string
);
756 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
758 // Create a dictionary with a port restriction, by prefixing old dictionary.
759 const std::string
port("502");
760 std::string
dictionary_with_port("Port: " + port
+ "\n");
761 dictionary_with_port
.append("Port: 80\n"); // Add default port.
762 dictionary_with_port
.append(dictionary
);
763 EXPECT_TRUE(AddSdchDictionary(dictionary_with_port
,
764 GURL(url_string
+ ":" + port
)));
766 std::string
compressed_for_port(NewSdchCompressedData(dictionary_with_port
));
768 std::vector
<Filter::FilterType
> filter_types
;
769 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
771 // Test decode the port data, arriving from a valid port.
772 SetupFilterContextWithGURL(GURL(url_string
+ ":" + port
));
773 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
775 size_t feed_block_size
= 100;
776 size_t output_block_size
= 100;
778 EXPECT_TRUE(FilterTestData(compressed_for_port
, feed_block_size
,
779 output_block_size
, filter
.get(), &output
));
780 EXPECT_EQ(output
, expanded_
);
782 // Test decode the port data, arriving from a valid (default) port.
783 SetupFilterContextWithGURL(GURL(url_string
)); // Default port.
784 filter
.reset(Filter::Factory(filter_types
, *filter_context()));
786 feed_block_size
= 100;
787 output_block_size
= 100;
789 EXPECT_TRUE(FilterTestData(compressed_for_port
, feed_block_size
,
790 output_block_size
, filter
.get(), &output
));
791 EXPECT_EQ(output
, expanded_
);
793 // Test decode the port data, arriving from a invalid port.
794 SetupFilterContextWithGURL(GURL(url_string
+ ":" + port
+ "1"));
795 filter
.reset(Filter::Factory(filter_types
, *filter_context()));
797 feed_block_size
= 100;
798 output_block_size
= 100;
800 EXPECT_FALSE(FilterTestData(compressed_for_port
, feed_block_size
,
801 output_block_size
, filter
.get(), &output
));
802 EXPECT_EQ(output
.size(), 0u); // No output written.
804 EXPECT_EQ(SDCH_DOMAIN_BLACKLIST_INCLUDES_TARGET
,
805 sdch_manager_
->IsInSupportedDomain(GURL(url_string
)));
806 sdch_manager_
->ClearBlacklistings();
807 EXPECT_EQ(SDCH_OK
, sdch_manager_
->IsInSupportedDomain(GURL(url_string
)));
810 // Helper function to perform gzip compression of data.
811 static std::string
gzip_compress(const std::string
&input
) {
812 z_stream zlib_stream
;
813 memset(&zlib_stream
, 0, sizeof(zlib_stream
));
817 code
= deflateInit2(&zlib_stream
, Z_DEFAULT_COMPRESSION
, Z_DEFLATED
,
822 CHECK_EQ(Z_OK
, code
);
824 // Fill in zlib control block
825 zlib_stream
.next_in
= bit_cast
<Bytef
*>(input
.data());
826 zlib_stream
.avail_in
= input
.size();
828 // Assume we can compress into similar buffer (add 100 bytes to be sure).
829 size_t gzip_compressed_length
= zlib_stream
.avail_in
+ 100;
830 scoped_ptr
<char[]> gzip_compressed(new char[gzip_compressed_length
]);
831 zlib_stream
.next_out
= bit_cast
<Bytef
*>(gzip_compressed
.get());
832 zlib_stream
.avail_out
= gzip_compressed_length
;
834 // The GZIP header (see RFC 1952):
835 // +---+---+---+---+---+---+---+---+---+---+
836 // |ID1|ID2|CM |FLG| MTIME |XFL|OS |
837 // +---+---+---+---+---+---+---+---+---+---+
840 // CM \010 (compression method == DEFLATE)
841 // FLG \000 (special flags that we do not support)
842 // MTIME Unix format modification time (0 means not available)
843 // XFL 2-4? DEFLATE flags
844 // OS ???? Operating system indicator (255 means unknown)
846 // Header value we generate:
847 const char kGZipHeader
[] = { '\037', '\213', '\010', '\000', '\000',
848 '\000', '\000', '\000', '\002', '\377' };
849 CHECK_GT(zlib_stream
.avail_out
, sizeof(kGZipHeader
));
850 memcpy(zlib_stream
.next_out
, kGZipHeader
, sizeof(kGZipHeader
));
851 zlib_stream
.next_out
+= sizeof(kGZipHeader
);
852 zlib_stream
.avail_out
-= sizeof(kGZipHeader
);
855 code
= deflate(&zlib_stream
, Z_FINISH
);
856 gzip_compressed_length
-= zlib_stream
.avail_out
;
857 std::string
compressed(gzip_compressed
.get(), gzip_compressed_length
);
858 deflateEnd(&zlib_stream
);
862 //------------------------------------------------------------------------------
864 class SdchFilterChainingTest
{
866 static Filter
* Factory(const std::vector
<Filter::FilterType
>& types
,
867 const FilterContext
& context
, int size
) {
868 return Filter::FactoryForTests(types
, context
, size
);
872 // Test that filters can be cascaded (chained) so that the output of one filter
873 // is processed by the next one. This is most critical for SDCH, which is
874 // routinely followed by gzip (during encoding). The filter we'll test for will
875 // do the gzip decoding first, and then decode the SDCH content.
876 TEST_F(SdchFilterTest
, FilterChaining
) {
877 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
878 const std::string kSampleDomain
= "sdchtest.com";
879 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
881 std::string url_string
= "http://" + kSampleDomain
;
883 GURL
url(url_string
);
884 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
886 std::string
sdch_compressed(NewSdchCompressedData(dictionary
));
888 // Use Gzip to compress the sdch sdch_compressed data.
889 std::string gzip_compressed_sdch
= gzip_compress(sdch_compressed
);
891 // Construct a chained filter.
892 std::vector
<Filter::FilterType
> filter_types
;
893 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
894 filter_types
.push_back(Filter::FILTER_TYPE_GZIP
);
896 // First try with a large buffer (larger than test input, or compressed data).
897 const size_t kLargeInputBufferSize(1000); // Used internally in filters.
898 CHECK_GT(kLargeInputBufferSize
, gzip_compressed_sdch
.size());
899 CHECK_GT(kLargeInputBufferSize
, sdch_compressed
.size());
900 CHECK_GT(kLargeInputBufferSize
, expanded_
.size());
901 SetupFilterContextWithGURL(url
);
902 scoped_ptr
<Filter
> filter(
903 SdchFilterChainingTest::Factory(filter_types
, *filter_context(),
904 kLargeInputBufferSize
));
905 EXPECT_EQ(static_cast<int>(kLargeInputBufferSize
),
906 filter
->stream_buffer_size());
908 // Verify that chained filter is waiting for data.
909 char tiny_output_buffer
[10];
910 int tiny_output_size
= sizeof(tiny_output_buffer
);
911 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
,
912 filter
->ReadData(tiny_output_buffer
, &tiny_output_size
));
914 // Make chain process all data.
915 size_t feed_block_size
= kLargeInputBufferSize
;
916 size_t output_block_size
= kLargeInputBufferSize
;
918 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
919 output_block_size
, filter
.get(), &output
));
920 EXPECT_EQ(output
, expanded_
);
922 // Next try with a mid-sized internal buffer size.
923 const size_t kMidSizedInputBufferSize(100);
924 // Buffer should be big enough to swallow whole gzip content.
925 CHECK_GT(kMidSizedInputBufferSize
, gzip_compressed_sdch
.size());
926 // Buffer should be small enough that entire SDCH content can't fit.
927 // We'll go even further, and force the chain to flush the buffer between the
928 // two filters more than once (that is why we multiply by 2).
929 CHECK_LT(kMidSizedInputBufferSize
* 2, sdch_compressed
.size());
930 filter_context()->SetURL(url
);
932 SdchFilterChainingTest::Factory(filter_types
, *filter_context(),
933 kMidSizedInputBufferSize
));
934 EXPECT_EQ(static_cast<int>(kMidSizedInputBufferSize
),
935 filter
->stream_buffer_size());
937 feed_block_size
= kMidSizedInputBufferSize
;
938 output_block_size
= kMidSizedInputBufferSize
;
940 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
941 output_block_size
, filter
.get(), &output
));
942 EXPECT_EQ(output
, expanded_
);
944 // Next try with a tiny input and output buffer to cover edge effects.
945 filter
.reset(SdchFilterChainingTest::Factory(filter_types
, *filter_context(),
946 kLargeInputBufferSize
));
947 EXPECT_EQ(static_cast<int>(kLargeInputBufferSize
),
948 filter
->stream_buffer_size());
951 output_block_size
= 1;
953 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
954 output_block_size
, filter
.get(), &output
));
955 EXPECT_EQ(output
, expanded_
);
958 TEST_F(SdchFilterTest
, DefaultGzipIfSdch
) {
959 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
960 const std::string kSampleDomain
= "sdchtest.com";
961 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
963 std::string url_string
= "http://" + kSampleDomain
;
965 GURL
url(url_string
);
966 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
968 std::string
sdch_compressed(NewSdchCompressedData(dictionary
));
970 // Use Gzip to compress the sdch sdch_compressed data.
971 std::string gzip_compressed_sdch
= gzip_compress(sdch_compressed
);
973 // Only claim to have sdch content, but really use the gzipped sdch content.
974 // System should automatically add the missing (optional) gzip.
975 std::vector
<Filter::FilterType
> filter_types
;
976 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
978 filter_context()->SetMimeType("anything/mime");
979 SetupFilterContextWithGURL(url
);
981 Filter::FixupEncodingTypes(*filter_context(), &filter_types
);
982 ASSERT_EQ(filter_types
.size(), 2u);
983 EXPECT_EQ(filter_types
[0], Filter::FILTER_TYPE_SDCH
);
984 EXPECT_EQ(filter_types
[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH
);
986 // First try with a large buffer (larger than test input, or compressed data).
987 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
989 // Verify that chained filter is waiting for data.
990 char tiny_output_buffer
[10];
991 int tiny_output_size
= sizeof(tiny_output_buffer
);
992 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
,
993 filter
->ReadData(tiny_output_buffer
, &tiny_output_size
));
995 size_t feed_block_size
= 100;
996 size_t output_block_size
= 100;
998 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
999 output_block_size
, filter
.get(), &output
));
1000 EXPECT_EQ(output
, expanded_
);
1002 // Next try with a tiny buffer to cover edge effects.
1003 filter
.reset(Filter::Factory(filter_types
, *filter_context()));
1005 feed_block_size
= 1;
1006 output_block_size
= 1;
1008 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
1009 output_block_size
, filter
.get(), &output
));
1010 EXPECT_EQ(output
, expanded_
);
1013 TEST_F(SdchFilterTest
, AcceptGzipSdchIfGzip
) {
1014 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
1015 const std::string kSampleDomain
= "sdchtest.com";
1016 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
1018 std::string url_string
= "http://" + kSampleDomain
;
1020 GURL
url(url_string
);
1021 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
1023 std::string
sdch_compressed(NewSdchCompressedData(dictionary
));
1025 // Use Gzip to compress the sdch sdch_compressed data.
1026 std::string gzip_compressed_sdch
= gzip_compress(sdch_compressed
);
1028 // Some proxies strip the content encoding statement down to a mere gzip, but
1029 // pass through the original content (with full sdch,gzip encoding).
1030 // Only claim to have gzip content, but really use the gzipped sdch content.
1031 // System should automatically add the missing (optional) sdch.
1032 std::vector
<Filter::FilterType
> filter_types
;
1033 filter_types
.push_back(Filter::FILTER_TYPE_GZIP
);
1035 filter_context()->SetMimeType("anything/mime");
1036 SetupFilterContextWithGURL(url
);
1037 Filter::FixupEncodingTypes(*filter_context(), &filter_types
);
1038 ASSERT_EQ(filter_types
.size(), 3u);
1039 EXPECT_EQ(filter_types
[0], Filter::FILTER_TYPE_SDCH_POSSIBLE
);
1040 EXPECT_EQ(filter_types
[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH
);
1041 EXPECT_EQ(filter_types
[2], Filter::FILTER_TYPE_GZIP
);
1043 // First try with a large buffer (larger than test input, or compressed data).
1044 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
1046 // Verify that chained filter is waiting for data.
1047 char tiny_output_buffer
[10];
1048 int tiny_output_size
= sizeof(tiny_output_buffer
);
1049 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
,
1050 filter
->ReadData(tiny_output_buffer
, &tiny_output_size
));
1052 size_t feed_block_size
= 100;
1053 size_t output_block_size
= 100;
1055 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
1056 output_block_size
, filter
.get(), &output
));
1057 EXPECT_EQ(output
, expanded_
);
1059 // Next try with a tiny buffer to cover edge effects.
1060 filter
.reset(Filter::Factory(filter_types
, *filter_context()));
1062 feed_block_size
= 1;
1063 output_block_size
= 1;
1065 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
1066 output_block_size
, filter
.get(), &output
));
1067 EXPECT_EQ(output
, expanded_
);
1070 TEST_F(SdchFilterTest
, DefaultSdchGzipIfEmpty
) {
1071 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
1072 const std::string kSampleDomain
= "sdchtest.com";
1073 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
1075 std::string url_string
= "http://" + kSampleDomain
;
1077 GURL
url(url_string
);
1078 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
1080 std::string
sdch_compressed(NewSdchCompressedData(dictionary
));
1082 // Use Gzip to compress the sdch sdch_compressed data.
1083 std::string gzip_compressed_sdch
= gzip_compress(sdch_compressed
);
1085 // Only claim to have non-encoded content, but really use the gzipped sdch
1087 // System should automatically add the missing (optional) sdch,gzip.
1088 std::vector
<Filter::FilterType
> filter_types
;
1090 filter_context()->SetMimeType("anything/mime");
1091 SetupFilterContextWithGURL(url
);
1092 Filter::FixupEncodingTypes(*filter_context(), &filter_types
);
1093 ASSERT_EQ(filter_types
.size(), 2u);
1094 EXPECT_EQ(filter_types
[0], Filter::FILTER_TYPE_SDCH_POSSIBLE
);
1095 EXPECT_EQ(filter_types
[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH
);
1097 // First try with a large buffer (larger than test input, or compressed data).
1098 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
1100 // Verify that chained filter is waiting for data.
1101 char tiny_output_buffer
[10];
1102 int tiny_output_size
= sizeof(tiny_output_buffer
);
1103 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
,
1104 filter
->ReadData(tiny_output_buffer
, &tiny_output_size
));
1106 size_t feed_block_size
= 100;
1107 size_t output_block_size
= 100;
1109 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
1110 output_block_size
, filter
.get(), &output
));
1111 EXPECT_EQ(output
, expanded_
);
1113 // Next try with a tiny buffer to cover edge effects.
1114 filter
.reset(Filter::Factory(filter_types
, *filter_context()));
1116 feed_block_size
= 1;
1117 output_block_size
= 1;
1119 EXPECT_TRUE(FilterTestData(gzip_compressed_sdch
, feed_block_size
,
1120 output_block_size
, filter
.get(), &output
));
1121 EXPECT_EQ(output
, expanded_
);
1124 TEST_F(SdchFilterTest
, AcceptGzipGzipSdchIfGzip
) {
1125 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
1126 const std::string kSampleDomain
= "sdchtest.com";
1127 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
1129 std::string url_string
= "http://" + kSampleDomain
;
1131 GURL
url(url_string
);
1132 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
1134 std::string
sdch_compressed(NewSdchCompressedData(dictionary
));
1136 // Vodaphone (UK) Mobile Broadband provides double gzipped sdch with a content
1137 // encoding of merely gzip (apparently, only listing the extra level of
1138 // wrapper compression they added, but discarding the actual content encoding.
1139 // Use Gzip to double compress the sdch sdch_compressed data.
1140 std::string double_gzip_compressed_sdch
= gzip_compress(gzip_compress(
1143 // Only claim to have gzip content, but really use the double gzipped sdch
1145 // System should automatically add the missing (optional) sdch, gzip decoders.
1146 std::vector
<Filter::FilterType
> filter_types
;
1147 filter_types
.push_back(Filter::FILTER_TYPE_GZIP
);
1149 filter_context()->SetMimeType("anything/mime");
1150 SetupFilterContextWithGURL(url
);
1151 Filter::FixupEncodingTypes(*filter_context(), &filter_types
);
1152 ASSERT_EQ(filter_types
.size(), 3u);
1153 EXPECT_EQ(filter_types
[0], Filter::FILTER_TYPE_SDCH_POSSIBLE
);
1154 EXPECT_EQ(filter_types
[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH
);
1155 EXPECT_EQ(filter_types
[2], Filter::FILTER_TYPE_GZIP
);
1157 // First try with a large buffer (larger than test input, or compressed data).
1158 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
1160 // Verify that chained filter is waiting for data.
1161 char tiny_output_buffer
[10];
1162 int tiny_output_size
= sizeof(tiny_output_buffer
);
1163 EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA
,
1164 filter
->ReadData(tiny_output_buffer
, &tiny_output_size
));
1166 size_t feed_block_size
= 100;
1167 size_t output_block_size
= 100;
1169 EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch
, feed_block_size
,
1170 output_block_size
, filter
.get(), &output
));
1171 EXPECT_EQ(output
, expanded_
);
1173 // Next try with a tiny buffer to cover edge effects.
1174 filter
.reset(Filter::Factory(filter_types
, *filter_context()));
1176 feed_block_size
= 1;
1177 output_block_size
= 1;
1179 EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch
, feed_block_size
,
1180 output_block_size
, filter
.get(), &output
));
1181 EXPECT_EQ(output
, expanded_
);
1184 // Test to make sure we decode properly with an unexpected dictionary.
1185 TEST_F(SdchFilterTest
, UnexpectedDictionary
) {
1186 // Setup a dictionary, add it to the filter context, and create a filter
1187 // based on that dictionary.
1188 const std::string kSampleDomain
= "sdchtest.com";
1189 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
1190 std::string url_string
= "http://" + kSampleDomain
;
1191 GURL
url(url_string
);
1192 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
1194 SetupFilterContextWithGURL(url
);
1196 std::vector
<Filter::FilterType
> filter_types
;
1197 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
1198 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
1200 // Setup another dictionary, expired. Don't add it to the filter context.
1201 // Delete stored dictionaries first to handle platforms which only
1202 // have room for a single dictionary.
1203 sdch_manager_
->ClearData();
1204 std::string
expired_dictionary(NewSdchExpiredDictionary(kSampleDomain
));
1206 // Don't use the Helper function since its insertion check is indeterminate
1207 // for a Max-Age: 0 dictionary.
1208 sdch_manager_
->AddSdchDictionary(expired_dictionary
, url
, nullptr);
1210 std::string client_hash
;
1211 std::string server_hash
;
1212 SdchManager::GenerateHash(expired_dictionary
, &client_hash
, &server_hash
);
1214 SdchProblemCode problem_code
;
1215 scoped_ptr
<SdchManager::DictionarySet
> hash_set(
1216 sdch_manager_
->GetDictionarySetByHash(
1217 url
, server_hash
, &problem_code
).Pass());
1218 ASSERT_TRUE(hash_set
);
1219 ASSERT_EQ(SDCH_OK
, problem_code
);
1221 // Encode output with the second dictionary.
1222 std::string
sdch_compressed(NewSdchCompressedData(expired_dictionary
));
1224 // See if the filter decodes it.
1226 EXPECT_TRUE(FilterTestData(sdch_compressed
, 100, 100, filter
.get(), &output
));
1227 EXPECT_EQ(expanded_
, output
);
1230 class SimpleSdchObserver
: public SdchObserver
{
1232 explicit SimpleSdchObserver(SdchManager
* manager
)
1233 : dictionary_used_(0), manager_(manager
) {
1234 manager_
->AddObserver(this);
1236 ~SimpleSdchObserver() override
{ manager_
->RemoveObserver(this); }
1239 void OnDictionaryUsed(SdchManager
* manager
,
1240 const std::string
& server_hash
) override
{
1242 last_server_hash_
= server_hash
;
1245 int dictionary_used_calls() const { return dictionary_used_
; }
1246 std::string
last_server_hash() const { return last_server_hash_
; }
1248 void OnGetDictionary(SdchManager
* /* manager */,
1249 const GURL
& /* request_url */,
1250 const GURL
& /* dictionary_url */) override
{}
1251 void OnClearDictionaries(SdchManager
* /* manager */) override
{}
1254 int dictionary_used_
;
1255 std::string last_server_hash_
;
1256 SdchManager
* manager_
;
1258 DISALLOW_COPY_AND_ASSIGN(SimpleSdchObserver
);
1261 TEST_F(SdchFilterTest
, DictionaryUsedSignaled
) {
1262 // Construct a valid SDCH dictionary from a VCDIFF dictionary.
1263 const std::string kSampleDomain
= "sdchtest.com";
1264 std::string
dictionary(NewSdchDictionary(kSampleDomain
));
1265 SimpleSdchObserver
observer(sdch_manager_
.get());
1267 std::string url_string
= "http://" + kSampleDomain
;
1269 GURL
url(url_string
);
1270 EXPECT_TRUE(AddSdchDictionary(dictionary
, url
));
1272 std::string client_hash
;
1273 std::string server_hash
;
1274 SdchManager::GenerateHash(dictionary
, &client_hash
, &server_hash
);
1276 std::string
compressed(NewSdchCompressedData(dictionary
));
1278 std::vector
<Filter::FilterType
> filter_types
;
1279 filter_types
.push_back(Filter::FILTER_TYPE_SDCH
);
1281 SetupFilterContextWithGURL(url
);
1283 scoped_ptr
<Filter
> filter(Filter::Factory(filter_types
, *filter_context()));
1285 size_t feed_block_size
= 100;
1286 size_t output_block_size
= 100;
1288 EXPECT_TRUE(FilterTestData(compressed
, feed_block_size
, output_block_size
,
1289 filter
.get(), &output
));
1290 EXPECT_EQ(output
, expanded_
);
1292 filter
.reset(nullptr);
1294 // Confirm that we got a "DictionaryUsed" signal from the SdchManager
1295 // for our dictionary.
1296 EXPECT_EQ(1, observer
.dictionary_used_calls());
1297 EXPECT_EQ(server_hash
, observer
.last_server_hash());