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