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