1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <limits.h>
6
7#include <algorithm>
8#include <string>
9#include <vector>
10
11#include "base/logging.h"
12#include "base/memory/scoped_ptr.h"
13#include "net/base/io_buffer.h"
14#include "net/filter/mock_filter_context.h"
15#include "net/filter/sdch_filter.h"
16#include "net/url_request/url_request_context.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      filter_context_(new MockFilterContext) {
60    URLRequestContext* url_request_context =
61        filter_context_->GetModifiableURLRequestContext();
62
63    url_request_context->set_sdch_manager(sdch_manager_.get());
64  }
65
66  // Attempt to add a dictionary to the manager; returns whether or not
67  // the attempt succeeded.
68  bool AddSdchDictionary(const std::string& dictionary_text,
69                         const GURL& gurl) {
70    std::string list;
71    sdch_manager_->GetAvailDictionaryList(gurl, &list);
72    sdch_manager_->AddSdchDictionary(dictionary_text, gurl);
73    std::string list2;
74    sdch_manager_->GetAvailDictionaryList(gurl, &list2);
75
76    // The list of hashes should change iff the addition succeeds.
77    return (list != list2);
78  }
79
80  MockFilterContext* filter_context() { return filter_context_.get(); }
81
82  std::string NewSdchCompressedData(const std::string dictionary) {
83    std::string client_hash;
84    std::string server_hash;
85    SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
86
87    // Build compressed data that refers to our dictionary.
88    std::string compressed(server_hash);
89    compressed.append("\0", 1);
90    compressed.append(vcdiff_compressed_data_);
91    return compressed;
92  }
93
94  const std::string test_vcdiff_dictionary_;
95  const std::string vcdiff_compressed_data_;
96  const std::string expanded_;  // Desired final, decompressed data.
97
98  scoped_ptr<SdchManager> sdch_manager_;
99  scoped_ptr<MockFilterContext> filter_context_;
100};
101
102//------------------------------------------------------------------------------
103
104
105TEST_F(SdchFilterTest, Hashing) {
106  std::string client_hash, server_hash;
107  std::string dictionary("test contents");
108  SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
109
110  EXPECT_EQ(client_hash, "lMQBjS3P");
111  EXPECT_EQ(server_hash, "MyciMVll");
112}
113
114
115//------------------------------------------------------------------------------
116// Provide a generic helper function for trying to filter data.
117// This function repeatedly calls the filter to process data, until the entire
118// source is consumed.  The return value from the filter is appended to output.
119// This allows us to vary input and output block sizes in order to test for edge
120// effects (boundary effects?) during the filtering process.
121// This function provides data to the filter in blocks of no-more-than the
122// specified input_block_length.  It allows the filter to fill no more than
123// output_buffer_length in any one call to proccess (a.k.a., Read) data, and
124// concatenates all these little output blocks into the singular output string.
125static bool FilterTestData(const std::string& source,
126                           size_t input_block_length,
127                           const size_t output_buffer_length,
128                           Filter* filter, std::string* output) {
129  CHECK_GT(input_block_length, 0u);
130  Filter::FilterStatus status(Filter::FILTER_NEED_MORE_DATA);
131  size_t source_index = 0;
132  scoped_ptr<char[]> output_buffer(new char[output_buffer_length]);
133  size_t input_amount = std::min(input_block_length,
134      static_cast<size_t>(filter->stream_buffer_size()));
135
136  do {
137    int copy_amount = std::min(input_amount, source.size() - source_index);
138    if (copy_amount > 0 && status == Filter::FILTER_NEED_MORE_DATA) {
139      memcpy(filter->stream_buffer()->data(), source.data() + source_index,
140             copy_amount);
141      filter->FlushStreamBuffer(copy_amount);
142      source_index += copy_amount;
143    }
144    int buffer_length = output_buffer_length;
145    status = filter->ReadData(output_buffer.get(), &buffer_length);
146    output->append(output_buffer.get(), buffer_length);
147    if (status == Filter::FILTER_ERROR)
148      return false;
149    // Callers assume that FILTER_OK with no output buffer means FILTER_DONE.
150    if (Filter::FILTER_OK == status && 0 == buffer_length)
151      return true;
152    if (copy_amount == 0 && buffer_length == 0)
153      return true;
154  } while (1);
155}
156//------------------------------------------------------------------------------
157static std::string NewSdchDictionary(const std::string& domain) {
158  std::string dictionary;
159  if (!domain.empty()) {
160    dictionary.append("Domain: ");
161    dictionary.append(domain);
162    dictionary.append("\n");
163  }
164  dictionary.append("\n");
165  dictionary.append(kTestVcdiffDictionary, sizeof(kTestVcdiffDictionary) - 1);
166  return dictionary;
167}
168
169//------------------------------------------------------------------------------
170
171TEST_F(SdchFilterTest, EmptyInputOk) {
172  std::vector<Filter::FilterType> filter_types;
173  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
174  char output_buffer[20];
175  std::string url_string("http://ignore.com");
176  filter_context()->SetURL(GURL(url_string));
177  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
178
179  // With no input data, try to read output.
180  int output_bytes_or_buffer_size = sizeof(output_buffer);
181  Filter::FilterStatus status = filter->ReadData(output_buffer,
182                                                 &output_bytes_or_buffer_size);
183
184  EXPECT_EQ(0, output_bytes_or_buffer_size);
185  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
186}
187
188// Make sure that the filter context has everything that might be
189// nuked from it during URLRequest teardown before the SdchFilter
190// destructor.
191TEST_F(SdchFilterTest, SparseContextOk) {
192  std::vector<Filter::FilterType> filter_types;
193  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
194  char output_buffer[20];
195  std::string url_string("http://ignore.com");
196  filter_context()->SetURL(GURL(url_string));
197  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
198
199  // With no input data, try to read output.
200  int output_bytes_or_buffer_size = sizeof(output_buffer);
201  Filter::FilterStatus status = filter->ReadData(output_buffer,
202                                                 &output_bytes_or_buffer_size);
203
204  EXPECT_EQ(0, output_bytes_or_buffer_size);
205  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
206
207  // Partially tear down context.  Anything that goes through request()
208  // without checking it for null in the URLRequestJob::HttpFilterContext
209  // implementation is suspect.  Everything that does check it for null should
210  // return null.  This is to test for incorrectly relying on filter_context()
211  // from the SdchFilter destructor.
212  filter_context()->NukeUnstableInterfaces();
213}
214
215TEST_F(SdchFilterTest, PassThroughWhenTentative) {
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_GZIP_HELPING_SDCH);
219  char output_buffer[20];
220  // Response code needs to be 200 to allow a pass through.
221  filter_context()->SetResponseCode(200);
222  std::string url_string("http://ignore.com");
223  filter_context()->SetURL(GURL(url_string));
224  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
225
226  // Supply enough data to force a pass-through mode..
227  std::string non_gzip_content("not GZIPed data");
228
229  char* input_buffer = filter->stream_buffer()->data();
230  int input_buffer_size = filter->stream_buffer_size();
231
232  EXPECT_LT(static_cast<int>(non_gzip_content.size()),
233            input_buffer_size);
234  memcpy(input_buffer, non_gzip_content.data(),
235         non_gzip_content.size());
236  filter->FlushStreamBuffer(non_gzip_content.size());
237
238  // Try to read output.
239  int output_bytes_or_buffer_size = sizeof(output_buffer);
240  Filter::FilterStatus status = filter->ReadData(output_buffer,
241                                                 &output_bytes_or_buffer_size);
242
243  EXPECT_EQ(non_gzip_content.size(),
244              static_cast<size_t>(output_bytes_or_buffer_size));
245  ASSERT_GT(sizeof(output_buffer),
246              static_cast<size_t>(output_bytes_or_buffer_size));
247  output_buffer[output_bytes_or_buffer_size] = '\0';
248  EXPECT_TRUE(non_gzip_content == output_buffer);
249  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
250}
251
252TEST_F(SdchFilterTest, RefreshBadReturnCode) {
253  std::vector<Filter::FilterType> filter_types;
254  // Selective a tentative filter (which can fall back to pass through).
255  filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE);
256  char output_buffer[20];
257  // Response code needs to be 200 to allow a pass through.
258  filter_context()->SetResponseCode(403);
259  // Meta refresh will only appear for html content
260  filter_context()->SetMimeType("text/html");
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  // We should have read a long and complicated meta-refresh request.
284  EXPECT_TRUE(sizeof(output_buffer) == output_bytes_or_buffer_size);
285  // Check at least the prefix of the return.
286  EXPECT_EQ(0, strncmp(output_buffer,
287      "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>",
288      sizeof(output_buffer)));
289  EXPECT_EQ(Filter::FILTER_OK, status);
290}
291
292TEST_F(SdchFilterTest, ErrorOnBadReturnCode) {
293  std::vector<Filter::FilterType> filter_types;
294  // Selective a tentative filter (which can fall back to pass through).
295  filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE);
296  char output_buffer[20];
297  // Response code needs to be 200 to allow a pass through.
298  filter_context()->SetResponseCode(403);
299  // Meta refresh will only appear for html content, so set to something else
300  // to induce an error (we can't meta refresh).
301  filter_context()->SetMimeType("anything");
302  std::string url_string("http://ignore.com");
303  filter_context()->SetURL(GURL(url_string));
304  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
305
306  // Supply enough data to force a pass-through mode, which means we have
307  // provided more than 9 characters that can't be a dictionary hash.
308  std::string non_sdch_content("This is not SDCH");
309
310  char* input_buffer = filter->stream_buffer()->data();
311  int input_buffer_size = filter->stream_buffer_size();
312
313  EXPECT_LT(static_cast<int>(non_sdch_content.size()),
314            input_buffer_size);
315  memcpy(input_buffer, non_sdch_content.data(),
316         non_sdch_content.size());
317  filter->FlushStreamBuffer(non_sdch_content.size());
318
319  // Try to read output.
320  int output_bytes_or_buffer_size = sizeof(output_buffer);
321  Filter::FilterStatus status = filter->ReadData(output_buffer,
322                                                 &output_bytes_or_buffer_size);
323
324  EXPECT_EQ(0, output_bytes_or_buffer_size);
325  EXPECT_EQ(Filter::FILTER_ERROR, status);
326}
327
328TEST_F(SdchFilterTest, ErrorOnBadReturnCodeWithHtml) {
329  std::vector<Filter::FilterType> filter_types;
330  // Selective a tentative filter (which can fall back to pass through).
331  filter_types.push_back(Filter::FILTER_TYPE_SDCH_POSSIBLE);
332  char output_buffer[20];
333  // Response code needs to be 200 to allow a pass through.
334  filter_context()->SetResponseCode(403);
335  // Meta refresh will only appear for html content
336  filter_context()->SetMimeType("text/html");
337  std::string url_string("http://ignore.com");
338  filter_context()->SetURL(GURL(url_string));
339  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
340
341  // Supply enough data to force a pass-through mode, which means we have
342  // provided more than 9 characters that can't be a dictionary hash.
343  std::string non_sdch_content("This is not SDCH");
344
345  char* input_buffer = filter->stream_buffer()->data();
346  int input_buffer_size = filter->stream_buffer_size();
347
348  EXPECT_LT(static_cast<int>(non_sdch_content.size()),
349            input_buffer_size);
350  memcpy(input_buffer, non_sdch_content.data(),
351         non_sdch_content.size());
352  filter->FlushStreamBuffer(non_sdch_content.size());
353
354  // Try to read output.
355  int output_bytes_or_buffer_size = sizeof(output_buffer);
356  Filter::FilterStatus status = filter->ReadData(output_buffer,
357                                                 &output_bytes_or_buffer_size);
358
359  // We should have read a long and complicated meta-refresh request.
360  EXPECT_EQ(sizeof(output_buffer),
361            static_cast<size_t>(output_bytes_or_buffer_size));
362  // Check at least the prefix of the return.
363  EXPECT_EQ(0, strncmp(output_buffer,
364      "<head><META HTTP-EQUIV=\"Refresh\" CONTENT=\"0\"></head>",
365      sizeof(output_buffer)));
366  EXPECT_EQ(Filter::FILTER_OK, status);
367}
368
369TEST_F(SdchFilterTest, BasicBadDictionary) {
370  std::vector<Filter::FilterType> filter_types;
371  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
372  char output_buffer[20];
373  std::string url_string("http://ignore.com");
374  filter_context()->SetURL(GURL(url_string));
375  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
376
377  // Supply bogus data (which doesn't yet specify a full dictionary hash).
378  // Dictionary hash is 8 characters followed by a null.
379  std::string dictionary_hash_prefix("123");
380
381  char* input_buffer = filter->stream_buffer()->data();
382  int input_buffer_size = filter->stream_buffer_size();
383
384  EXPECT_LT(static_cast<int>(dictionary_hash_prefix.size()),
385            input_buffer_size);
386  memcpy(input_buffer, dictionary_hash_prefix.data(),
387         dictionary_hash_prefix.size());
388  filter->FlushStreamBuffer(dictionary_hash_prefix.size());
389
390  // With less than a dictionary specifier, try to read output.
391  int output_bytes_or_buffer_size = sizeof(output_buffer);
392  Filter::FilterStatus status = filter->ReadData(output_buffer,
393                                                 &output_bytes_or_buffer_size);
394
395  EXPECT_EQ(0, output_bytes_or_buffer_size);
396  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
397
398  // Provide enough data to complete *a* hash, but it is bogus, and not in our
399  // list of dictionaries, so the filter should error out immediately.
400  std::string dictionary_hash_postfix("4abcd\0", 6);
401
402  CHECK_LT(dictionary_hash_postfix.size(),
403           static_cast<size_t>(input_buffer_size));
404  memcpy(input_buffer, dictionary_hash_postfix.data(),
405         dictionary_hash_postfix.size());
406  filter->FlushStreamBuffer(dictionary_hash_postfix.size());
407
408  // With a non-existant dictionary specifier, try to read output.
409  output_bytes_or_buffer_size = sizeof(output_buffer);
410  status = filter->ReadData(output_buffer, &output_bytes_or_buffer_size);
411
412  EXPECT_EQ(0, output_bytes_or_buffer_size);
413  EXPECT_EQ(Filter::FILTER_ERROR, status);
414
415  EXPECT_FALSE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
416  sdch_manager_->ClearBlacklistings();
417  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
418}
419
420TEST_F(SdchFilterTest, DictionaryAddOnce) {
421  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
422  const std::string kSampleDomain = "sdchtest.com";
423  std::string dictionary(NewSdchDictionary(kSampleDomain));
424
425  std::string url_string = "http://" + kSampleDomain;
426  GURL url(url_string);
427  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
428
429  // Check we can't add it twice.
430  EXPECT_FALSE(AddSdchDictionary(dictionary, url));
431
432  const std::string kSampleDomain2 = "sdchtest2.com";
433
434  // Don't test adding a second dictionary if our limits are tight.
435  if (SdchManager::kMaxDictionaryCount > 1) {
436    // Construct a second SDCH dictionary from a VCDIFF dictionary.
437    std::string dictionary2(NewSdchDictionary(kSampleDomain2));
438
439    std::string url_string2 = "http://" + kSampleDomain2;
440    GURL url2(url_string2);
441    EXPECT_TRUE(AddSdchDictionary(dictionary2, url2));
442  }
443}
444
445TEST_F(SdchFilterTest, BasicDictionary) {
446  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
447  const std::string kSampleDomain = "sdchtest.com";
448  std::string dictionary(NewSdchDictionary(kSampleDomain));
449
450  std::string url_string = "http://" + kSampleDomain;
451
452  GURL url(url_string);
453  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
454
455  std::string compressed(NewSdchCompressedData(dictionary));
456
457  std::vector<Filter::FilterType> filter_types;
458  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
459
460  filter_context()->SetURL(url);
461
462  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
463
464  size_t feed_block_size = 100;
465  size_t output_block_size = 100;
466  std::string output;
467  EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size,
468                             filter.get(), &output));
469  EXPECT_EQ(output, expanded_);
470
471  // Decode with really small buffers (size 1) to check for edge effects.
472  filter.reset(Filter::Factory(filter_types, *filter_context()));
473
474  feed_block_size = 1;
475  output_block_size = 1;
476  output.clear();
477  EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size,
478                             filter.get(), &output));
479  EXPECT_EQ(output, expanded_);
480}
481
482TEST_F(SdchFilterTest, NoDecodeHttps) {
483  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
484  const std::string kSampleDomain = "sdchtest.com";
485  std::string dictionary(NewSdchDictionary(kSampleDomain));
486
487  std::string url_string = "http://" + kSampleDomain;
488
489  GURL url(url_string);
490  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
491
492  std::string compressed(NewSdchCompressedData(dictionary));
493
494  std::vector<Filter::FilterType> filter_types;
495  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
496
497  filter_context()->SetURL(GURL("https://" + kSampleDomain));
498  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
499
500  const size_t feed_block_size(100);
501  const size_t output_block_size(100);
502  std::string output;
503
504  EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
505                             filter.get(), &output));
506}
507
508// Current failsafe TODO/hack refuses to decode any content that doesn't use
509// http as the scheme (see use of DICTIONARY_SELECTED_FOR_NON_HTTP).
510// The following tests this blockage.  Note that blacklisting results, so we
511// we need separate tests for each of these.
512TEST_F(SdchFilterTest, NoDecodeFtp) {
513  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
514  const std::string kSampleDomain = "sdchtest.com";
515  std::string dictionary(NewSdchDictionary(kSampleDomain));
516
517  std::string url_string = "http://" + kSampleDomain;
518
519  GURL url(url_string);
520  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
521
522  std::string compressed(NewSdchCompressedData(dictionary));
523
524  std::vector<Filter::FilterType> filter_types;
525  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
526
527  filter_context()->SetURL(GURL("ftp://" + kSampleDomain));
528  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
529
530  const size_t feed_block_size(100);
531  const size_t output_block_size(100);
532  std::string output;
533
534  EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
535                             filter.get(), &output));
536}
537
538TEST_F(SdchFilterTest, NoDecodeFileColon) {
539  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
540  const std::string kSampleDomain = "sdchtest.com";
541  std::string dictionary(NewSdchDictionary(kSampleDomain));
542
543  std::string url_string = "http://" + kSampleDomain;
544
545  GURL url(url_string);
546  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
547
548  std::string compressed(NewSdchCompressedData(dictionary));
549
550  std::vector<Filter::FilterType> filter_types;
551  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
552
553  filter_context()->SetURL(GURL("file://" + kSampleDomain));
554  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
555
556  const size_t feed_block_size(100);
557  const size_t output_block_size(100);
558  std::string output;
559
560  EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
561                             filter.get(), &output));
562}
563
564TEST_F(SdchFilterTest, NoDecodeAboutColon) {
565  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
566  const std::string kSampleDomain = "sdchtest.com";
567  std::string dictionary(NewSdchDictionary(kSampleDomain));
568
569  std::string url_string = "http://" + kSampleDomain;
570
571  GURL url(url_string);
572  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
573
574  std::string compressed(NewSdchCompressedData(dictionary));
575
576  std::vector<Filter::FilterType> filter_types;
577  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
578
579  filter_context()->SetURL(GURL("about://" + kSampleDomain));
580  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
581
582  const size_t feed_block_size(100);
583  const size_t output_block_size(100);
584  std::string output;
585
586  EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
587                             filter.get(), &output));
588}
589
590TEST_F(SdchFilterTest, NoDecodeJavaScript) {
591  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
592  const std::string kSampleDomain = "sdchtest.com";
593  std::string dictionary(NewSdchDictionary(kSampleDomain));
594
595  std::string url_string = "http://" + kSampleDomain;
596
597  GURL url(url_string);
598  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
599
600  std::string compressed(NewSdchCompressedData(dictionary));
601
602  std::vector<Filter::FilterType> filter_types;
603  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
604
605  filter_context()->SetURL(GURL("javascript://" + kSampleDomain));
606  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
607
608  const size_t feed_block_size(100);
609  const size_t output_block_size(100);
610  std::string output;
611
612  EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
613                             filter.get(), &output));
614}
615
616TEST_F(SdchFilterTest, CanStillDecodeHttp) {
617  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
618  const std::string kSampleDomain = "sdchtest.com";
619  std::string dictionary(NewSdchDictionary(kSampleDomain));
620
621  std::string url_string = "http://" + kSampleDomain;
622
623  GURL url(url_string);
624  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
625
626  std::string compressed(NewSdchCompressedData(dictionary));
627
628  std::vector<Filter::FilterType> filter_types;
629  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
630
631  filter_context()->SetURL(GURL("http://" + kSampleDomain));
632  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
633
634  const size_t feed_block_size(100);
635  const size_t output_block_size(100);
636  std::string output;
637
638  EXPECT_TRUE(FilterTestData(compressed, feed_block_size, output_block_size,
639                             filter.get(), &output));
640}
641
642TEST_F(SdchFilterTest, CrossDomainDictionaryUse) {
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(AddSdchDictionary(dictionary, url));
651
652  std::string compressed(NewSdchCompressedData(dictionary));
653
654  std::vector<Filter::FilterType> filter_types;
655  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
656
657  // Decode with content arriving from the "wrong" domain.
658  // This tests SdchManager::CanSet().
659  GURL wrong_domain_url("http://www.wrongdomain.com");
660  filter_context()->SetURL(wrong_domain_url);
661  scoped_ptr<Filter> filter(Filter::Factory(filter_types,  *filter_context()));
662
663  size_t feed_block_size = 100;
664  size_t output_block_size = 100;
665  std::string output;
666  EXPECT_FALSE(FilterTestData(compressed, feed_block_size, output_block_size,
667                              filter.get(), &output));
668  EXPECT_EQ(output.size(), 0u);  // No output written.
669
670  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
671  EXPECT_FALSE(sdch_manager_->IsInSupportedDomain(wrong_domain_url));
672  sdch_manager_->ClearBlacklistings();
673  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(wrong_domain_url));
674}
675
676TEST_F(SdchFilterTest, DictionaryPathValidation) {
677  // Can't test path distinction between dictionaries if we aren't allowed
678  // more than one dictionary.
679  if (SdchManager::kMaxDictionaryCount <= 1)
680    return;
681
682  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
683  const std::string kSampleDomain = "sdchtest.com";
684  std::string dictionary(NewSdchDictionary(kSampleDomain));
685
686  std::string url_string = "http://" + kSampleDomain;
687
688  GURL url(url_string);
689  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
690
691  // Create a dictionary with a path restriction, by prefixing dictionary.
692  const std::string path("/special_path/bin");
693  std::string dictionary_with_path("Path: " + path + "\n");
694  dictionary_with_path.append(dictionary);
695  GURL url2(url_string + path);
696  EXPECT_TRUE(AddSdchDictionary(dictionary_with_path, url2));
697
698  std::string compressed_for_path(NewSdchCompressedData(dictionary_with_path));
699
700  std::vector<Filter::FilterType> filter_types;
701  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
702
703  // Test decode the path data, arriving from a valid path.
704  filter_context()->SetURL(GURL(url_string + path));
705  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
706
707  size_t feed_block_size = 100;
708  size_t output_block_size = 100;
709  std::string output;
710
711  EXPECT_TRUE(FilterTestData(compressed_for_path, feed_block_size,
712                             output_block_size, filter.get(), &output));
713  EXPECT_EQ(output, expanded_);
714
715  // Test decode the path data, arriving from a invalid path.
716  filter_context()->SetURL(GURL(url_string));
717  filter.reset(Filter::Factory(filter_types, *filter_context()));
718
719  feed_block_size = 100;
720  output_block_size = 100;
721  output.clear();
722  EXPECT_FALSE(FilterTestData(compressed_for_path, feed_block_size,
723                              output_block_size, filter.get(), &output));
724  EXPECT_EQ(output.size(), 0u);  // No output written.
725
726  EXPECT_FALSE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
727  sdch_manager_->ClearBlacklistings();
728  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
729}
730
731TEST_F(SdchFilterTest, DictionaryPortValidation) {
732  // Can't test port distinction between dictionaries if we aren't allowed
733  // more than one dictionary.
734  if (SdchManager::kMaxDictionaryCount <= 1)
735    return;
736
737  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
738  const std::string kSampleDomain = "sdchtest.com";
739  std::string dictionary(NewSdchDictionary(kSampleDomain));
740
741  std::string url_string = "http://" + kSampleDomain;
742
743  GURL url(url_string);
744  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
745
746  // Create a dictionary with a port restriction, by prefixing old dictionary.
747  const std::string port("502");
748  std::string dictionary_with_port("Port: " + port + "\n");
749  dictionary_with_port.append("Port: 80\n");  // Add default port.
750  dictionary_with_port.append(dictionary);
751  EXPECT_TRUE(AddSdchDictionary(dictionary_with_port,
752                                GURL(url_string + ":" + port)));
753
754  std::string compressed_for_port(NewSdchCompressedData(dictionary_with_port));
755
756  std::vector<Filter::FilterType> filter_types;
757  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
758
759  // Test decode the port data, arriving from a valid port.
760  filter_context()->SetURL(GURL(url_string + ":" + port));
761  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
762
763  size_t feed_block_size = 100;
764  size_t output_block_size = 100;
765  std::string output;
766  EXPECT_TRUE(FilterTestData(compressed_for_port, feed_block_size,
767                             output_block_size, filter.get(), &output));
768  EXPECT_EQ(output, expanded_);
769
770  // Test decode the port data, arriving from a valid (default) port.
771  filter_context()->SetURL(GURL(url_string));  // Default port.
772  filter.reset(Filter::Factory(filter_types, *filter_context()));
773
774  feed_block_size = 100;
775  output_block_size = 100;
776  output.clear();
777  EXPECT_TRUE(FilterTestData(compressed_for_port, feed_block_size,
778                             output_block_size, filter.get(), &output));
779  EXPECT_EQ(output, expanded_);
780
781  // Test decode the port data, arriving from a invalid port.
782  filter_context()->SetURL(GURL(url_string + ":" + port + "1"));
783  filter.reset(Filter::Factory(filter_types, *filter_context()));
784
785  feed_block_size = 100;
786  output_block_size = 100;
787  output.clear();
788  EXPECT_FALSE(FilterTestData(compressed_for_port, feed_block_size,
789                              output_block_size, filter.get(), &output));
790  EXPECT_EQ(output.size(), 0u);  // No output written.
791
792  EXPECT_FALSE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
793  sdch_manager_->ClearBlacklistings();
794  EXPECT_TRUE(sdch_manager_->IsInSupportedDomain(GURL(url_string)));
795}
796
797//------------------------------------------------------------------------------
798// Helper function to perform gzip compression of data.
799
800static std::string gzip_compress(const std::string &input) {
801  z_stream zlib_stream;
802  memset(&zlib_stream, 0, sizeof(zlib_stream));
803  int code;
804
805  // Initialize zlib
806  code = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
807                      -MAX_WBITS,
808                      8,  // DEF_MEM_LEVEL
809                      Z_DEFAULT_STRATEGY);
810
811  CHECK_EQ(Z_OK, code);
812
813  // Fill in zlib control block
814  zlib_stream.next_in = bit_cast<Bytef*>(input.data());
815  zlib_stream.avail_in = input.size();
816
817  // Assume we can compress into similar buffer (add 100 bytes to be sure).
818  size_t gzip_compressed_length = zlib_stream.avail_in + 100;
819  scoped_ptr<char[]> gzip_compressed(new char[gzip_compressed_length]);
820  zlib_stream.next_out = bit_cast<Bytef*>(gzip_compressed.get());
821  zlib_stream.avail_out = gzip_compressed_length;
822
823  // The GZIP header (see RFC 1952):
824  //   +---+---+---+---+---+---+---+---+---+---+
825  //   |ID1|ID2|CM |FLG|     MTIME     |XFL|OS |
826  //   +---+---+---+---+---+---+---+---+---+---+
827  //     ID1     \037
828  //     ID2     \213
829  //     CM      \010 (compression method == DEFLATE)
830  //     FLG     \000 (special flags that we do not support)
831  //     MTIME   Unix format modification time (0 means not available)
832  //     XFL     2-4? DEFLATE flags
833  //     OS      ???? Operating system indicator (255 means unknown)
834  //
835  // Header value we generate:
836  const char kGZipHeader[] = { '\037', '\213', '\010', '\000', '\000',
837                               '\000', '\000', '\000', '\002', '\377' };
838  CHECK_GT(zlib_stream.avail_out, sizeof(kGZipHeader));
839  memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader));
840  zlib_stream.next_out += sizeof(kGZipHeader);
841  zlib_stream.avail_out -= sizeof(kGZipHeader);
842
843  // Do deflate
844  code = deflate(&zlib_stream, Z_FINISH);
845  gzip_compressed_length -= zlib_stream.avail_out;
846  std::string compressed(gzip_compressed.get(), gzip_compressed_length);
847  deflateEnd(&zlib_stream);
848  return compressed;
849}
850
851//------------------------------------------------------------------------------
852
853class SdchFilterChainingTest {
854 public:
855  static Filter* Factory(const std::vector<Filter::FilterType>& types,
856                           const FilterContext& context, int size) {
857    return Filter::FactoryForTests(types, context, size);
858  }
859};
860
861// Test that filters can be cascaded (chained) so that the output of one filter
862// is processed by the next one.  This is most critical for SDCH, which is
863// routinely followed by gzip (during encoding).  The filter we'll test for will
864// do the gzip decoding first, and then decode the SDCH content.
865TEST_F(SdchFilterTest, FilterChaining) {
866  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
867  const std::string kSampleDomain = "sdchtest.com";
868  std::string dictionary(NewSdchDictionary(kSampleDomain));
869
870  std::string url_string = "http://" + kSampleDomain;
871
872  GURL url(url_string);
873  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
874
875  std::string sdch_compressed(NewSdchCompressedData(dictionary));
876
877  // Use Gzip to compress the sdch sdch_compressed data.
878  std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
879
880  // Construct a chained filter.
881  std::vector<Filter::FilterType> filter_types;
882  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
883  filter_types.push_back(Filter::FILTER_TYPE_GZIP);
884
885  // First try with a large buffer (larger than test input, or compressed data).
886  const size_t kLargeInputBufferSize(1000);  // Used internally in filters.
887  CHECK_GT(kLargeInputBufferSize, gzip_compressed_sdch.size());
888  CHECK_GT(kLargeInputBufferSize, sdch_compressed.size());
889  CHECK_GT(kLargeInputBufferSize, expanded_.size());
890  filter_context()->SetURL(url);
891  scoped_ptr<Filter> filter(
892      SdchFilterChainingTest::Factory(filter_types, *filter_context(),
893                                      kLargeInputBufferSize));
894  EXPECT_EQ(static_cast<int>(kLargeInputBufferSize),
895            filter->stream_buffer_size());
896
897  // Verify that chained filter is waiting for data.
898  char tiny_output_buffer[10];
899  int tiny_output_size = sizeof(tiny_output_buffer);
900  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
901            filter->ReadData(tiny_output_buffer, &tiny_output_size));
902
903  // Make chain process all data.
904  size_t feed_block_size = kLargeInputBufferSize;
905  size_t output_block_size = kLargeInputBufferSize;
906  std::string output;
907  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
908                             output_block_size, filter.get(), &output));
909  EXPECT_EQ(output, expanded_);
910
911  // Next try with a mid-sized internal buffer size.
912  const size_t kMidSizedInputBufferSize(100);
913  // Buffer should be big enough to swallow whole gzip content.
914  CHECK_GT(kMidSizedInputBufferSize, gzip_compressed_sdch.size());
915  // Buffer should be small enough that entire SDCH content can't fit.
916  // We'll go even further, and force the chain to flush the buffer between the
917  // two filters more than once (that is why we multiply by 2).
918  CHECK_LT(kMidSizedInputBufferSize * 2, sdch_compressed.size());
919  filter_context()->SetURL(url);
920  filter.reset(
921      SdchFilterChainingTest::Factory(filter_types, *filter_context(),
922                                      kMidSizedInputBufferSize));
923  EXPECT_EQ(static_cast<int>(kMidSizedInputBufferSize),
924            filter->stream_buffer_size());
925
926  feed_block_size = kMidSizedInputBufferSize;
927  output_block_size = kMidSizedInputBufferSize;
928  output.clear();
929  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
930                             output_block_size, filter.get(), &output));
931  EXPECT_EQ(output, expanded_);
932
933  // Next try with a tiny input and output buffer to cover edge effects.
934  filter.reset(SdchFilterChainingTest::Factory(filter_types, *filter_context(),
935                                               kLargeInputBufferSize));
936  EXPECT_EQ(static_cast<int>(kLargeInputBufferSize),
937            filter->stream_buffer_size());
938
939  feed_block_size = 1;
940  output_block_size = 1;
941  output.clear();
942  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
943                             output_block_size, filter.get(), &output));
944  EXPECT_EQ(output, expanded_);
945}
946
947TEST_F(SdchFilterTest, DefaultGzipIfSdch) {
948  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
949  const std::string kSampleDomain = "sdchtest.com";
950  std::string dictionary(NewSdchDictionary(kSampleDomain));
951
952  std::string url_string = "http://" + kSampleDomain;
953
954  GURL url(url_string);
955  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
956
957  std::string sdch_compressed(NewSdchCompressedData(dictionary));
958
959  // Use Gzip to compress the sdch sdch_compressed data.
960  std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
961
962  // Only claim to have sdch content, but really use the gzipped sdch content.
963  // System should automatically add the missing (optional) gzip.
964  std::vector<Filter::FilterType> filter_types;
965  filter_types.push_back(Filter::FILTER_TYPE_SDCH);
966
967  filter_context()->SetMimeType("anything/mime");
968  filter_context()->SetSdchResponse(true);
969  Filter::FixupEncodingTypes(*filter_context(), &filter_types);
970  ASSERT_EQ(filter_types.size(), 2u);
971  EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH);
972  EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
973
974  // First try with a large buffer (larger than test input, or compressed data).
975  filter_context()->SetURL(url);
976  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
977
978
979  // Verify that chained filter is waiting for data.
980  char tiny_output_buffer[10];
981  int tiny_output_size = sizeof(tiny_output_buffer);
982  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
983            filter->ReadData(tiny_output_buffer, &tiny_output_size));
984
985  size_t feed_block_size = 100;
986  size_t output_block_size = 100;
987  std::string output;
988  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
989                             output_block_size, filter.get(), &output));
990  EXPECT_EQ(output, expanded_);
991
992  // Next try with a tiny buffer to cover edge effects.
993  filter.reset(Filter::Factory(filter_types, *filter_context()));
994
995  feed_block_size = 1;
996  output_block_size = 1;
997  output.clear();
998  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
999                             output_block_size, filter.get(), &output));
1000  EXPECT_EQ(output, expanded_);
1001}
1002
1003TEST_F(SdchFilterTest, AcceptGzipSdchIfGzip) {
1004  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
1005  const std::string kSampleDomain = "sdchtest.com";
1006  std::string dictionary(NewSdchDictionary(kSampleDomain));
1007
1008  std::string url_string = "http://" + kSampleDomain;
1009
1010  GURL url(url_string);
1011  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
1012
1013  std::string sdch_compressed(NewSdchCompressedData(dictionary));
1014
1015  // Use Gzip to compress the sdch sdch_compressed data.
1016  std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
1017
1018  // Some proxies strip the content encoding statement down to a mere gzip, but
1019  // pass through the original content (with full sdch,gzip encoding).
1020  // Only claim to have gzip content, but really use the gzipped sdch content.
1021  // System should automatically add the missing (optional) sdch.
1022  std::vector<Filter::FilterType> filter_types;
1023  filter_types.push_back(Filter::FILTER_TYPE_GZIP);
1024
1025  filter_context()->SetMimeType("anything/mime");
1026  filter_context()->SetSdchResponse(true);
1027  Filter::FixupEncodingTypes(*filter_context(), &filter_types);
1028  ASSERT_EQ(filter_types.size(), 3u);
1029  EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE);
1030  EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
1031  EXPECT_EQ(filter_types[2], Filter::FILTER_TYPE_GZIP);
1032
1033  // First try with a large buffer (larger than test input, or compressed data).
1034  filter_context()->SetURL(url);
1035  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
1036
1037
1038  // Verify that chained filter is waiting for data.
1039  char tiny_output_buffer[10];
1040  int tiny_output_size = sizeof(tiny_output_buffer);
1041  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
1042            filter->ReadData(tiny_output_buffer, &tiny_output_size));
1043
1044  size_t feed_block_size = 100;
1045  size_t output_block_size = 100;
1046  std::string output;
1047  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
1048                             output_block_size, filter.get(), &output));
1049  EXPECT_EQ(output, expanded_);
1050
1051  // Next try with a tiny buffer to cover edge effects.
1052  filter.reset(Filter::Factory(filter_types, *filter_context()));
1053
1054  feed_block_size = 1;
1055  output_block_size = 1;
1056  output.clear();
1057  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
1058                             output_block_size, filter.get(), &output));
1059  EXPECT_EQ(output, expanded_);
1060}
1061
1062TEST_F(SdchFilterTest, DefaultSdchGzipIfEmpty) {
1063  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
1064  const std::string kSampleDomain = "sdchtest.com";
1065  std::string dictionary(NewSdchDictionary(kSampleDomain));
1066
1067  std::string url_string = "http://" + kSampleDomain;
1068
1069  GURL url(url_string);
1070  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
1071
1072  std::string sdch_compressed(NewSdchCompressedData(dictionary));
1073
1074  // Use Gzip to compress the sdch sdch_compressed data.
1075  std::string gzip_compressed_sdch = gzip_compress(sdch_compressed);
1076
1077  // Only claim to have non-encoded content, but really use the gzipped sdch
1078  // content.
1079  // System should automatically add the missing (optional) sdch,gzip.
1080  std::vector<Filter::FilterType> filter_types;
1081
1082  filter_context()->SetMimeType("anything/mime");
1083  filter_context()->SetSdchResponse(true);
1084  Filter::FixupEncodingTypes(*filter_context(), &filter_types);
1085  ASSERT_EQ(filter_types.size(), 2u);
1086  EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE);
1087  EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
1088
1089  // First try with a large buffer (larger than test input, or compressed data).
1090  filter_context()->SetURL(url);
1091  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
1092
1093
1094  // Verify that chained filter is waiting for data.
1095  char tiny_output_buffer[10];
1096  int tiny_output_size = sizeof(tiny_output_buffer);
1097  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
1098            filter->ReadData(tiny_output_buffer, &tiny_output_size));
1099
1100  size_t feed_block_size = 100;
1101  size_t output_block_size = 100;
1102  std::string output;
1103  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
1104                             output_block_size, filter.get(), &output));
1105  EXPECT_EQ(output, expanded_);
1106
1107  // Next try with a tiny buffer to cover edge effects.
1108  filter.reset(Filter::Factory(filter_types, *filter_context()));
1109
1110  feed_block_size = 1;
1111  output_block_size = 1;
1112  output.clear();
1113  EXPECT_TRUE(FilterTestData(gzip_compressed_sdch, feed_block_size,
1114                             output_block_size, filter.get(), &output));
1115  EXPECT_EQ(output, expanded_);
1116}
1117
1118TEST_F(SdchFilterTest, AcceptGzipGzipSdchIfGzip) {
1119  // Construct a valid SDCH dictionary from a VCDIFF dictionary.
1120  const std::string kSampleDomain = "sdchtest.com";
1121  std::string dictionary(NewSdchDictionary(kSampleDomain));
1122
1123  std::string url_string = "http://" + kSampleDomain;
1124
1125  GURL url(url_string);
1126  EXPECT_TRUE(AddSdchDictionary(dictionary, url));
1127
1128  std::string sdch_compressed(NewSdchCompressedData(dictionary));
1129
1130  // Vodaphone (UK) Mobile Broadband provides double gzipped sdch with a content
1131  // encoding of merely gzip (apparently, only listing the extra level of
1132  // wrapper compression they added, but discarding the actual content encoding.
1133  // Use Gzip to double compress the sdch sdch_compressed data.
1134  std::string double_gzip_compressed_sdch = gzip_compress(gzip_compress(
1135      sdch_compressed));
1136
1137  // Only claim to have gzip content, but really use the double gzipped sdch
1138  // content.
1139  // System should automatically add the missing (optional) sdch, gzip decoders.
1140  std::vector<Filter::FilterType> filter_types;
1141  filter_types.push_back(Filter::FILTER_TYPE_GZIP);
1142
1143  filter_context()->SetMimeType("anything/mime");
1144  filter_context()->SetSdchResponse(true);
1145  Filter::FixupEncodingTypes(*filter_context(), &filter_types);
1146  ASSERT_EQ(filter_types.size(), 3u);
1147  EXPECT_EQ(filter_types[0], Filter::FILTER_TYPE_SDCH_POSSIBLE);
1148  EXPECT_EQ(filter_types[1], Filter::FILTER_TYPE_GZIP_HELPING_SDCH);
1149  EXPECT_EQ(filter_types[2], Filter::FILTER_TYPE_GZIP);
1150
1151  // First try with a large buffer (larger than test input, or compressed data).
1152  filter_context()->SetURL(url);
1153  scoped_ptr<Filter> filter(Filter::Factory(filter_types, *filter_context()));
1154
1155  // Verify that chained filter is waiting for data.
1156  char tiny_output_buffer[10];
1157  int tiny_output_size = sizeof(tiny_output_buffer);
1158  EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA,
1159            filter->ReadData(tiny_output_buffer, &tiny_output_size));
1160
1161  size_t feed_block_size = 100;
1162  size_t output_block_size = 100;
1163  std::string output;
1164  EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch, feed_block_size,
1165                             output_block_size, filter.get(), &output));
1166  EXPECT_EQ(output, expanded_);
1167
1168  // Next try with a tiny buffer to cover edge effects.
1169  filter.reset(Filter::Factory(filter_types, *filter_context()));
1170
1171  feed_block_size = 1;
1172  output_block_size = 1;
1173  output.clear();
1174  EXPECT_TRUE(FilterTestData(double_gzip_compressed_sdch, feed_block_size,
1175                             output_block_size, filter.get(), &output));
1176  EXPECT_EQ(output, expanded_);
1177}
1178
1179}  // namespace net
1180