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