summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/browser.vcproj8
-rw-r--r--chrome/browser/browser_main.cc11
-rw-r--r--chrome/browser/net/sdch_dictionary_fetcher.cc45
-rw-r--r--chrome/browser/net/sdch_dictionary_fetcher.h66
-rw-r--r--chrome/chrome.sln16
-rw-r--r--chrome/chrome_kjs.sln15
-rw-r--r--chrome/common/chrome_switches.cc7
-rw-r--r--chrome/common/chrome_switches.h2
-rw-r--r--net/base/bzip2_filter_unittest.cc62
-rw-r--r--net/base/filter.cc80
-rw-r--r--net/base/filter.h72
-rw-r--r--net/base/gzip_filter_unittest.cc61
-rw-r--r--net/base/sdch_filter.cc170
-rw-r--r--net/base/sdch_filter.h95
-rw-r--r--net/base/sdch_filter_unitest.cc441
-rw-r--r--net/base/sdch_manager.cc336
-rw-r--r--net/base/sdch_manager.h202
-rw-r--r--net/build/net.vcproj32
-rw-r--r--net/build/net_unittests.vcproj4
-rw-r--r--net/url_request/url_request_http_job.cc59
-rw-r--r--net/url_request/url_request_http_job.h20
-rw-r--r--net/url_request/url_request_job.cc10
-rw-r--r--net/url_request/url_request_job.h18
23 files changed, 1746 insertions, 86 deletions
diff --git a/chrome/browser/browser.vcproj b/chrome/browser/browser.vcproj
index fc3c31b..e87a3e0 100644
--- a/chrome/browser/browser.vcproj
+++ b/chrome/browser/browser.vcproj
@@ -1861,6 +1861,14 @@
RelativePath=".\net\dns_slave.h"
>
</File>
+ <File
+ RelativePath=".\net\sdch_dictionary_fetcher.cc"
+ >
+ </File>
+ <File
+ RelativePath=".\net\sdch_dictionary_fetcher.h"
+ >
+ </File>
</Filter>
<Filter
Name="RLZ"
diff --git a/chrome/browser/browser_main.cc b/chrome/browser/browser_main.cc
index 15703a2..fbb216a9 100644
--- a/chrome/browser/browser_main.cc
+++ b/chrome/browser/browser_main.cc
@@ -30,6 +30,7 @@
#include "chrome/browser/jankometer.h"
#include "chrome/browser/metrics_service.h"
#include "chrome/browser/net/dns_global.h"
+#include "chrome/browser/net/sdch_dictionary_fetcher.h"
#include "chrome/browser/plugin_service.h"
#include "chrome/browser/printing/print_job_manager.h"
#include "chrome/browser/rlz/rlz.h"
@@ -53,6 +54,7 @@
#include "net/base/net_module.h"
#include "net/base/net_resources.h"
#include "net/base/net_util.h"
+#include "net/base/sdch_manager.h"
#include "net/base/winsock_init.h"
#include "net/http/http_network_layer.h"
@@ -475,6 +477,15 @@ int BrowserMain(CommandLine &parsed_command_line, int show_command,
// Initialize the CertStore.
CertStore::Initialize();
+ // Prepare for memory caching of SDCH dictionaries.
+ SdchManager sdch_manager; // Construct singleton database.
+ if (parsed_command_line.HasSwitch(switches::kSdchFilter)) {
+ sdch_manager.set_sdch_fetcher(new SdchDictionaryFetcher);
+ std::wstring switch_domain =
+ parsed_command_line.GetSwitchValue(switches::kSdchFilter);
+ sdch_manager.enable_sdch_support(WideToASCII(switch_domain));
+ }
+
MetricsService* metrics = NULL;
if (!parsed_command_line.HasSwitch(switches::kDisableMetrics)) {
if (parsed_command_line.HasSwitch(switches::kDisableMetricsReporting)) {
diff --git a/chrome/browser/net/sdch_dictionary_fetcher.cc b/chrome/browser/net/sdch_dictionary_fetcher.cc
new file mode 100644
index 0000000..1b5c21c
--- /dev/null
+++ b/chrome/browser/net/sdch_dictionary_fetcher.cc
@@ -0,0 +1,45 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/net/sdch_dictionary_fetcher.h"
+#include "chrome/browser/profile.h"
+
+void SdchDictionaryFetcher::Schedule(const GURL& dictionary_url) {
+ fetch_queue_.push(dictionary_url);
+ ScheduleDelayedRun();
+}
+
+// TODO(jar): If QOS low priority is supported, switch to using that instead of
+// just waiting to do the fetch.
+void SdchDictionaryFetcher::ScheduleDelayedRun() {
+ if (fetch_queue_.empty() || current_fetch_.get() || task_is_pending_)
+ return;
+ MessageLoop::current()->PostDelayedTask(FROM_HERE,
+ method_factory_.NewRunnableMethod(&SdchDictionaryFetcher::StartFetching),
+ kMsDelayFromRequestTillDownload);
+ task_is_pending_ = true;
+}
+
+void SdchDictionaryFetcher::StartFetching() {
+ DCHECK(task_is_pending_);
+ task_is_pending_ = false;
+
+ current_fetch_.reset(new URLFetcher(fetch_queue_.front(), URLFetcher::GET,
+ this));
+ fetch_queue_.pop();
+ current_fetch_->set_request_context(Profile::GetDefaultRequestContext());
+ current_fetch_->Start();
+}
+
+void SdchDictionaryFetcher::OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data) {
+ if (200 == response_code)
+ SdchManager::Global()->AddSdchDictionary(data, url);
+ current_fetch_.reset(NULL);
+ ScheduleDelayedRun();
+}
diff --git a/chrome/browser/net/sdch_dictionary_fetcher.h b/chrome/browser/net/sdch_dictionary_fetcher.h
new file mode 100644
index 0000000..34d3f28
--- /dev/null
+++ b/chrome/browser/net/sdch_dictionary_fetcher.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Support modularity by calling to load a new SDCH filter dictionary.
+// Note that this sort of calling can't be done in the /net directory, as it has
+// no concept of the HTTP cache (which is only visible at the browser level).
+
+#ifndef CHROME_BROWSER_NET_SDCH_DICTIONARY_FETCHER_H_
+#define CHROME_BROWSER_NET_SDCH_DICTIONARY_FETCHER_H_
+
+#include <queue>
+#include <string>
+
+#include "base/task.h"
+#include "chrome/browser/url_fetcher.h"
+#include "net/base/sdch_manager.h"
+
+class SdchDictionaryFetcher : public URLFetcher::Delegate,
+ public SdchFetcher {
+ public:
+ #pragma warning(suppress: 4355) // OK to pass "this" here.
+ SdchDictionaryFetcher() : method_factory_(this), task_is_pending_(false) {}
+ virtual ~SdchDictionaryFetcher() {}
+
+ // Implementation of SdchFetcher class.
+ // This method gets the requested dictionary, and then calls back into the
+ // SdchManager class with the dictionary's text.
+ virtual void Schedule(const GURL& dictionary_url);
+
+ private:
+ // Delay between Schedule and actual download.
+ static const int kMsDelayFromRequestTillDownload = 15000;
+
+ // Ensure the download after the above delay.
+ void ScheduleDelayedRun();
+
+ // Make sure we're processing (or waiting for) the the arrival of the next URL
+ // in the |fetch_queue_|.
+ void StartFetching();
+
+ // Implementation of URLFetcher::Delegate. Called after transmission
+ // completes (either successfully or with failure).
+ virtual void OnURLFetchComplete(const URLFetcher* source,
+ const GURL& url,
+ const URLRequestStatus& status,
+ int response_code,
+ const ResponseCookies& cookies,
+ const std::string& data);
+
+ // A queue of URLs that are being used to download dictionaries.
+ std::queue<GURL> fetch_queue_;
+ // The currently outstanding URL fetch of a dicitonary.
+ // If this is null, then there is no outstanding request.
+ scoped_ptr<URLFetcher> current_fetch_;
+
+ // Always spread out the dictionary fetches, so that they don't steal
+ // bandwidth from the actual page load. Create delayed tasks to spread out
+ // the download.
+ ScopedRunnableMethodFactory<SdchDictionaryFetcher> method_factory_;
+ bool task_is_pending_;
+
+ DISALLOW_COPY_AND_ASSIGN(SdchDictionaryFetcher);
+};
+
+#endif // CHROME_BROWSER_NET_SDCH_DICTIONARY_FETCHER_H_
diff --git a/chrome/chrome.sln b/chrome/chrome.sln
index 246e950..3423cf4 100644
--- a/chrome/chrome.sln
+++ b/chrome/chrome.sln
@@ -162,6 +162,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "chrome_dll", "app\chrome_dl
{EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {EF5E94AB-B646-4E5B-A058-52EF07B8351C}
{EFBB1436-A63F-4CD8-9E99-B89226E782EC} = {EFBB1436-A63F-4CD8-9E99-B89226E782EC}
{F4F4BCAA-EA59-445C-A119-3E6C29647A51} = {F4F4BCAA-EA59-445C-A119-3E6C29647A51}
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0} = {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}
{FA537565-7B03-4FFC-AF15-F7A979B72E22} = {FA537565-7B03-4FFC-AF15-F7A979B72E22}
{FC0E1FD0-5DD7-4041-A1C9-CD3C376E4EED} = {FC0E1FD0-5DD7-4041-A1C9-CD3C376E4EED}
EndProjectSection
@@ -508,6 +509,7 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "net", "..\net\build\net.vcproj", "{326E9795-E760-410A-B69A-3F79DB3F5243}"
ProjectSection(ProjectDependencies) = postProject
{E13045CD-7E1F-4A41-9B18-8D288B2E7B41} = {E13045CD-7E1F-4A41-9B18-8D288B2E7B41}
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0} = {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tld_cleanup", "..\net\build\tld_cleanup.vcproj", "{E13045CD-7E1F-4A41-9B18-8D288B2E7B41}"
@@ -527,6 +529,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "net_unittests", "..\net\bui
{8C27D792-2648-4F5E-9ED0-374276327308} = {8C27D792-2648-4F5E-9ED0-374276327308}
{BFE8E2A7-3B3B-43B0-A994-3058B852DB8B} = {BFE8E2A7-3B3B-43B0-A994-3058B852DB8B}
{EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {EF5E94AB-B646-4E5B-A058-52EF07B8351C}
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0} = {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "net_perftests", "..\net\build\net_perftests.vcproj", "{AAC78796-B9A2-4CD9-BF89-09B03E92BF73}"
@@ -1122,6 +1125,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "v8_shell_sample", "..\v8\to
{C0334F9A-1168-4101-9DD8-C30FB252D435} = {C0334F9A-1168-4101-9DD8-C30FB252D435}
EndProjectSection
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdch", "..\sdch\sdch.vcproj", "{F54ABC59-5C00-414A-A9BA-BAF26D1699F0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -2431,6 +2436,16 @@ Global
{F4F4BCAA-EA59-445C-A119-3E6C29647A51}.Release|Mixed Platforms.Build.0 = Release|Win32
{F4F4BCAA-EA59-445C-A119-3E6C29647A51}.Release|Win32.ActiveCfg = Release|Win32
{F4F4BCAA-EA59-445C-A119-3E6C29647A51}.Release|Win32.Build.0 = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Mixed Platforms.Build.0 = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Win32.Build.0 = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Any CPU.ActiveCfg = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Mixed Platforms.Build.0 = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Win32.ActiveCfg = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Win32.Build.0 = Release|Win32
{F7790A54-4078-4E4A-8231-818BE9FB1F94}.Debug|Any CPU.ActiveCfg = Debug|Win32
{F7790A54-4078-4E4A-8231-818BE9FB1F94}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
{F7790A54-4078-4E4A-8231-818BE9FB1F94}.Debug|Mixed Platforms.Build.0 = Debug|Win32
@@ -2641,6 +2656,7 @@ Global
{EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {EF78C1F9-AA17-4CA5-B6CB-39B37A73A3DA}
{EFBB1436-A63F-4CD8-9E99-B89226E782EC} = {EB684A4B-98F7-4E68-8EA7-EA74ACF7060B}
{F4F4BCAA-EA59-445C-A119-3E6C29647A51} = {CB43561E-A6F8-49E2-96A2-3F2BA1FFF21E}
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0} = {EF78C1F9-AA17-4CA5-B6CB-39B37A73A3DA}
{F7790A54-4078-4E4A-8231-818BE9FB1F94} = {2325D8C4-8EF5-42AC-8900-492225750DE4}
{F9810DE8-CBC3-4605-A7B1-ECA2D5292FD7} = {032541FB-1E7C-4423-B657-4A71FE180C8A}
{FA39524D-3067-4141-888D-28A86C66F2B9} = {1174D37F-6ABB-45DA-81B3-C631281273B7}
diff --git a/chrome/chrome_kjs.sln b/chrome/chrome_kjs.sln
index ab358f4..b9f37dd 100644
--- a/chrome/chrome_kjs.sln
+++ b/chrome/chrome_kjs.sln
@@ -164,6 +164,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "chrome_dll", "app\chrome_dl
{EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {EF5E94AB-B646-4E5B-A058-52EF07B8351C}
{EFBB1436-A63F-4CD8-9E99-B89226E782EC} = {EFBB1436-A63F-4CD8-9E99-B89226E782EC}
{F4F4BCAA-EA59-445C-A119-3E6C29647A51} = {F4F4BCAA-EA59-445C-A119-3E6C29647A51}
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0} = {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}
{FA537565-7B03-4FFC-AF15-F7A979B72E22} = {FA537565-7B03-4FFC-AF15-F7A979B72E22}
{FC0E1FD0-5DD7-4041-A1C9-CD3C376E4EED} = {FC0E1FD0-5DD7-4041-A1C9-CD3C376E4EED}
EndProjectSection
@@ -512,6 +513,7 @@ EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "net", "..\net\build\net.vcproj", "{326E9795-E760-410A-B69A-3F79DB3F5243}"
ProjectSection(ProjectDependencies) = postProject
{E13045CD-7E1F-4A41-9B18-8D288B2E7B41} = {E13045CD-7E1F-4A41-9B18-8D288B2E7B41}
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0} = {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tld_cleanup", "..\net\build\tld_cleanup.vcproj", "{E13045CD-7E1F-4A41-9B18-8D288B2E7B41}"
@@ -1071,6 +1073,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "browser_views", "browser\vi
{D9DDAF60-663F-49CC-90DC-3D08CC3D1B28} = {D9DDAF60-663F-49CC-90DC-3D08CC3D1B28}
EndProjectSection
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sdch", "..\sdch\sdch.vcproj", "{F54ABC59-5C00-414A-A9BA-BAF26D1699F0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -2320,6 +2324,16 @@ Global
{F4F4BCAA-EA59-445C-A119-3E6C29647A51}.Release|Mixed Platforms.Build.0 = Release|Win32
{F4F4BCAA-EA59-445C-A119-3E6C29647A51}.Release|Win32.ActiveCfg = Release|Win32
{F4F4BCAA-EA59-445C-A119-3E6C29647A51}.Release|Win32.Build.0 = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Mixed Platforms.Build.0 = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Debug|Win32.Build.0 = Debug|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Any CPU.ActiveCfg = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Mixed Platforms.ActiveCfg = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Mixed Platforms.Build.0 = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Win32.ActiveCfg = Release|Win32
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0}.Release|Win32.Build.0 = Release|Win32
{F7790A54-4078-4E4A-8231-818BE9FB1F94}.Debug|Any CPU.ActiveCfg = Debug|Win32
{F7790A54-4078-4E4A-8231-818BE9FB1F94}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32
{F7790A54-4078-4E4A-8231-818BE9FB1F94}.Debug|Mixed Platforms.Build.0 = Debug|Win32
@@ -2524,6 +2538,7 @@ Global
{EF5E94AB-B646-4E5B-A058-52EF07B8351C} = {EF78C1F9-AA17-4CA5-B6CB-39B37A73A3DA}
{EFBB1436-A63F-4CD8-9E99-B89226E782EC} = {EB684A4B-98F7-4E68-8EA7-EA74ACF7060B}
{F4F4BCAA-EA59-445C-A119-3E6C29647A51} = {CB43561E-A6F8-49E2-96A2-3F2BA1FFF21E}
+ {F54ABC59-5C00-414A-A9BA-BAF26D1699F0} = {EF78C1F9-AA17-4CA5-B6CB-39B37A73A3DA}
{F7790A54-4078-4E4A-8231-818BE9FB1F94} = {2325D8C4-8EF5-42AC-8900-492225750DE4}
{F9810DE8-CBC3-4605-A7B1-ECA2D5292FD7} = {BBD3C9B1-AC02-41F4-AB89-D01B70009795}
{FA39524D-3067-4141-888D-28A86C66F2B9} = {1174D37F-6ABB-45DA-81B3-C631281273B7}
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index 56a3936..7dec663 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -317,5 +317,12 @@ const wchar_t kJavaScriptDebuggerPath[] = L"javascript-debugger-path";
const wchar_t kEnableP13n[] = L"enable-p13n";
+// Enable support for SDCH filtering (dictionary based expansion of content).
+// Optional argument is *the* only domain name that will have SDCH suppport.
+// Default is "-enable-sdch" to advertise SDCH on all domains.
+// Sample usage with argument: "-enable-sdch=.google.com"
+// SDCH is currently only supported server-side for searches on google.com.
+const wchar_t kSdchFilter[] = L"enable-sdch";
+
} // namespace switches
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index 4ec96f4..241170e8 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -123,6 +123,8 @@ extern const wchar_t kJavaScriptDebuggerPath[];
extern const wchar_t kEnableP13n[];
+extern const wchar_t kSdchFilter[];
+
} // namespace switches
#endif // CHROME_COMMON_CHROME_SWITCHES_H__
diff --git a/net/base/bzip2_filter_unittest.cc b/net/base/bzip2_filter_unittest.cc
index a7f742b..19a36af 100644
--- a/net/base/bzip2_filter_unittest.cc
+++ b/net/base/bzip2_filter_unittest.cc
@@ -92,8 +92,8 @@ class BZip2FilterUnitTest : public PlatformTest {
// Encoded_source and encoded_source_len are compressed data and its size.
// Output_buffer_size specifies the size of buffer to read out data from
// filter.
- // get_extra_data specifies whether get the extra data because maybe some server
- // might send extra data after finish sending compress data
+ // get_extra_data specifies whether get the extra data because maybe some
+ // server might send extra data after finish sending compress data.
void DecodeAndCompareWithFilter(Filter* filter,
const char* source,
int source_len,
@@ -130,7 +130,7 @@ class BZip2FilterUnitTest : public PlatformTest {
while (1) {
int decode_data_len = std::min(decode_avail_size, output_buffer_size);
- code = filter->ReadFilteredData(decode_next, &decode_data_len);
+ code = filter->ReadData(decode_next, &decode_data_len);
decode_next += decode_data_len;
decode_avail_size -= decode_data_len;
@@ -164,11 +164,13 @@ class BZip2FilterUnitTest : public PlatformTest {
int* dest_len) {
memcpy(filter->stream_buffer(), source, source_len);
filter->FlushStreamBuffer(source_len);
- return filter->ReadFilteredData(dest, dest_len);
+ return filter->ReadData(dest, dest_len);
}
const char* source_buffer() const { return source_buffer_.data(); }
- int source_len() const { return static_cast<int>(source_buffer_.size()) - kExtraDataBufferSize; }
+ int source_len() const {
+ return static_cast<int>(source_buffer_.size()) - kExtraDataBufferSize;
+ }
std::string source_buffer_;
@@ -180,8 +182,10 @@ class BZip2FilterUnitTest : public PlatformTest {
// Basic scenario: decoding bzip2 data with big enough buffer.
TEST_F(BZip2FilterUnitTest, DecodeBZip2) {
// Decode the compressed data with filter
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
memcpy(filter->stream_buffer(), bzip2_encode_buffer_, bzip2_encode_len_);
filter->FlushStreamBuffer(bzip2_encode_len_);
@@ -189,7 +193,7 @@ TEST_F(BZip2FilterUnitTest, DecodeBZip2) {
char bzip2_decode_buffer[kDefaultBufferSize];
int bzip2_decode_size = kDefaultBufferSize;
Filter::FilterStatus result =
- filter->ReadFilteredData(bzip2_decode_buffer, &bzip2_decode_size);
+ filter->ReadData(bzip2_decode_buffer, &bzip2_decode_size);
ASSERT_EQ(Filter::FILTER_DONE, result);
// Compare the decoding result with source data
@@ -201,8 +205,10 @@ TEST_F(BZip2FilterUnitTest, DecodeBZip2) {
// To do that, we create a filter with a small buffer that can not hold all
// the input data.
TEST_F(BZip2FilterUnitTest, DecodeWithSmallInputBuffer) {
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, kSmallBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kSmallBufferSize));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
bzip2_encode_buffer_, bzip2_encode_len_,
@@ -211,8 +217,10 @@ TEST_F(BZip2FilterUnitTest, DecodeWithSmallInputBuffer) {
// Tests we can decode when caller has small buffer to read out from filter.
TEST_F(BZip2FilterUnitTest, DecodeWithSmallOutputBuffer) {
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
bzip2_encode_buffer_, bzip2_encode_len_,
@@ -224,8 +232,10 @@ TEST_F(BZip2FilterUnitTest, DecodeWithSmallOutputBuffer) {
// header correctly. (2) Sometimes the filter will consume input without
// generating output. Verify filter can handle it correctly.
TEST_F(BZip2FilterUnitTest, DecodeWithOneByteInputBuffer) {
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, 1));
+ Filter::Factory(filters, kApplicationOctetStream, 1));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
bzip2_encode_buffer_, bzip2_encode_len_,
@@ -235,8 +245,10 @@ TEST_F(BZip2FilterUnitTest, DecodeWithOneByteInputBuffer) {
// Tests we can still decode with just 1 byte buffer in the filter and just 1
// byte buffer in the caller.
TEST_F(BZip2FilterUnitTest, DecodeWithOneByteInputAndOutputBuffer) {
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, 1));
+ Filter::Factory(filters, kApplicationOctetStream, 1));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
bzip2_encode_buffer_, bzip2_encode_len_, 1, false);
@@ -252,8 +264,10 @@ TEST_F(BZip2FilterUnitTest, DecodeCorruptedData) {
int corrupt_decode_size = kDefaultBufferSize;
// Decode the correct data with filter
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter1(
- Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter1.get());
Filter::FilterStatus code = DecodeAllWithFilter(filter1.get(),
@@ -267,7 +281,7 @@ TEST_F(BZip2FilterUnitTest, DecodeCorruptedData) {
// Decode the corrupted data with filter
scoped_ptr<Filter> filter2(
- Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter2.get());
int pos = corrupt_data_len / 2;
@@ -295,8 +309,10 @@ TEST_F(BZip2FilterUnitTest, DecodeMissingData) {
--corrupt_data_len;
// Decode the corrupted data with filter
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
char corrupt_decode_buffer[kDefaultBufferSize];
int corrupt_decode_size = kDefaultBufferSize;
@@ -319,8 +335,10 @@ TEST_F(BZip2FilterUnitTest, DecodeCorruptedHeader) {
corrupt_data[2] = !corrupt_data[2];
// Decode the corrupted data with filter
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
char corrupt_decode_buffer[kDefaultBufferSize];
int corrupt_decode_size = kDefaultBufferSize;
@@ -344,11 +362,14 @@ TEST_F(BZip2FilterUnitTest, DecodeWithExtraDataAndSmallOutputBuffer) {
memcpy(more_data, bzip2_encode_buffer_, bzip2_encode_len_);
memcpy(more_data + bzip2_encode_len_, kExtraData, kExtraDataBufferSize);
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(),
- source_buffer(), source_len() + kExtraDataBufferSize,
+ source_buffer(),
+ source_len() + kExtraDataBufferSize,
more_data,
more_data_len,
kSmallBufferSize,
@@ -361,11 +382,14 @@ TEST_F(BZip2FilterUnitTest, DecodeWithExtraDataAndSmallInputBuffer) {
memcpy(more_data, bzip2_encode_buffer_, bzip2_encode_len_);
memcpy(more_data + bzip2_encode_len_, kExtraData, kExtraDataBufferSize);
+ std::vector<std::string> filters;
+ filters.push_back("bzip2");
scoped_ptr<Filter> filter(
- Filter::Factory("bzip2", kApplicationOctetStream, kSmallBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kSmallBufferSize));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(),
- source_buffer(), source_len() + kExtraDataBufferSize,
+ source_buffer(),
+ source_len() + kExtraDataBufferSize,
more_data,
more_data_len,
kDefaultBufferSize,
diff --git a/net/base/filter.cc b/net/base/filter.cc
index 80264fd..c435f9b 100644
--- a/net/base/filter.cc
+++ b/net/base/filter.cc
@@ -7,6 +7,7 @@
#include "base/string_util.h"
#include "net/base/gzip_filter.h"
#include "net/base/bzip2_filter.h"
+#include "net/base/sdch_filter.h"
namespace {
@@ -16,6 +17,7 @@ const char kGZip[] = "gzip";
const char kXGZip[] = "x-gzip";
const char kBZip2[] = "bzip2";
const char kXBZip2[] = "x-bzip2";
+const char kSdch[] = "sdch";
// compress and x-compress are currently not supported. If we decide to support
// them, we'll need the same mime type compatibility hack we have for gzip. For
// more information, see Firefox's nsHttpChannel::ProcessNormal.
@@ -33,12 +35,34 @@ const char kApplicationCompress[] = "application/compress";
} // namespace
-Filter* Filter::Factory(const std::string& filter_type,
+Filter* Filter::Factory(const std::vector<std::string>& filter_types,
const std::string& mime_type,
int buffer_size) {
- if (filter_type.empty() || buffer_size < 0)
+ if (filter_types.empty() || buffer_size < 0)
return NULL;
+ std::string safe_mime_type = (filter_types.size() > 1) ? "" : mime_type;
+ Filter* filter_list = NULL; // Linked list of filters.
+ for (size_t i = 0; i < filter_types.size(); ++i) {
+ Filter* first_filter;
+ first_filter = SingleFilter(filter_types[i], safe_mime_type, buffer_size);
+ if (!first_filter) {
+ // Cleanup and exit, since we can't construct this filter list.
+ if (filter_list)
+ delete filter_list;
+ filter_list = NULL;
+ break;
+ }
+ first_filter->next_filter_.reset(filter_list);
+ filter_list = first_filter;
+ }
+ return filter_list;
+}
+
+// static
+Filter* Filter::SingleFilter(const std::string& filter_type,
+ const std::string& mime_type,
+ int buffer_size) {
FilterType type_id;
if (LowerCaseEqualsASCII(filter_type, kDeflate)) {
type_id = FILTER_TYPE_DEFLATE;
@@ -58,6 +82,8 @@ Filter* Filter::Factory(const std::string& filter_type,
} else if (LowerCaseEqualsASCII(filter_type, kBZip2) ||
LowerCaseEqualsASCII(filter_type, kXBZip2)) {
type_id = FILTER_TYPE_BZIP2;
+ } else if (LowerCaseEqualsASCII(filter_type, kSdch)) {
+ type_id = FILTER_TYPE_SDCH;
} else {
// Note we also consider "identity" and "uncompressed" UNSUPPORTED as
// filter should be disabled in such cases.
@@ -84,6 +110,15 @@ Filter* Filter::Factory(const std::string& filter_type,
}
break;
}
+ case FILTER_TYPE_SDCH: {
+ scoped_ptr<SdchFilter> sdch_filter(new SdchFilter());
+ if (sdch_filter->InitBuffer(buffer_size)) {
+ if (sdch_filter->InitDecoding()) {
+ return sdch_filter.release();
+ }
+ }
+ break;
+ }
default: {
break;
}
@@ -96,7 +131,9 @@ Filter::Filter()
: stream_buffer_(NULL),
stream_buffer_size_(0),
next_stream_data_(NULL),
- stream_data_len_(0) {
+ stream_data_len_(0),
+ next_filter_(NULL),
+ last_status_(FILTER_OK) {
}
Filter::~Filter() {}
@@ -143,6 +180,38 @@ Filter::FilterStatus Filter::ReadFilteredData(char* dest_buffer,
return Filter::FILTER_ERROR;
}
+Filter::FilterStatus Filter::ReadData(char* dest_buffer, int* dest_len) {
+ if (last_status_ == FILTER_ERROR)
+ return last_status_;
+ if (!next_filter_.get())
+ return last_status_ = ReadFilteredData(dest_buffer, dest_len);
+ if (last_status_ == FILTER_NEED_MORE_DATA && !stream_data_len())
+ return next_filter_->ReadData(dest_buffer, dest_len);
+ if (next_filter_->last_status() == FILTER_NEED_MORE_DATA) {
+ // Push data into next filter's input.
+ char* next_buffer = next_filter_->stream_buffer();
+ int next_size = next_filter_->stream_buffer_size();
+ last_status_ = ReadFilteredData(next_buffer, &next_size);
+ next_filter_->FlushStreamBuffer(next_size);
+ switch (last_status_) {
+ case FILTER_ERROR:
+ return last_status_;
+
+ case FILTER_NEED_MORE_DATA:
+ return next_filter_->ReadData(dest_buffer, dest_len);
+
+ case FILTER_OK:
+ case FILTER_DONE:
+ break;
+ }
+ }
+ FilterStatus status = next_filter_->ReadData(dest_buffer, dest_len);
+ // We could loop to fill next_filter_ if it needs data, but we have to be
+ // careful about output buffer. Simpler is to just wait until we are called
+ // again, and return FILTER_OK.
+ return (status == FILTER_ERROR) ? FILTER_ERROR : FILTER_OK;
+}
+
bool Filter::FlushStreamBuffer(int stream_data_len) {
if (stream_data_len <= 0 || stream_data_len > stream_buffer_size_)
return false;
@@ -156,3 +225,8 @@ bool Filter::FlushStreamBuffer(int stream_data_len) {
return true;
}
+void Filter::SetURL(const GURL& url) {
+ url_ = url;
+ if (next_filter_.get())
+ next_filter_->SetURL(url);
+}
diff --git a/net/base/filter.h b/net/base/filter.h
index ecba302..3551056 100644
--- a/net/base/filter.h
+++ b/net/base/filter.h
@@ -16,7 +16,7 @@
// int post_filter_data_len = kBufferSize;
// filter->ReadFilteredData(post_filter_buf, &post_filter_data_len);
//
-// To filters a data stream, the caller first gets filter's stream_buffer_
+// To filter a data stream, the caller first gets filter's stream_buffer_
// through its accessor and fills in stream_buffer_ with pre-filter data, next
// calls FlushStreamBuffer to notify Filter, then calls ReadFilteredData
// repeatedly to get all the filtered data. After all data have been fitlered
@@ -30,19 +30,29 @@
#define NET_BASE_FILTER_H__
#include <string>
+#include <vector>
#include "base/basictypes.h"
#include "base/scoped_ptr.h"
+#include "googleurl/src/gurl.h"
class Filter {
public:
// Creates a Filter object.
- // Parameters: Filter_type specifies the type of filter created; Buffer_size
+ // Parameters: Filter_types specifies the type of filter created; Buffer_size
// specifies the size (in number of chars) of the buffer the filter should
// allocate to hold pre-filter data.
// If success, the function returns the pointer to the Filter object created.
// If failed or a filter is not needed, the function returns NULL.
- static Filter* Factory(const std::string& filter_type,
+ //
+ // Note: filter_types is an array of filter names (content encoding types as
+ // provided in an HTTP header), which will be chained together serially do
+ // successive filtering of data. The names in the vector are ordered based on
+ // encoding order, and the filters are chained to operate in the reverse
+ // (decoding) order. For example, types[0] = "sdch", types[1] = "gzip" will
+ // cause data to first be gunizip filtered, and the resulting output from that
+ // filter will be sdch decoded.
+ static Filter* Factory(const std::vector<std::string>& filter_types,
const std::string& mime_type,
int buffer_size);
@@ -64,18 +74,9 @@ class Filter {
FILTER_ERROR
};
- // Filters the data stored in stream_buffer_ and writes the output into the
- // dest_buffer passed in.
- //
- // Upon entry, *dest_len is the total size (in number of chars) of the
- // destination buffer. Upon exit, *dest_len is the actual number of chars
- // written into the destination buffer.
- //
- // This function will fail if there is no pre-filter data in the
- // stream_buffer_. On the other hand, *dest_len can be 0 upon successful
- // return. For example, a decoding filter may process some pre-filter data
- // but not produce output yet.
- virtual FilterStatus ReadFilteredData(char* dest_buffer, int* dest_len);
+ // External call to obtain data from this filter chain. If ther is no
+ // next_filter_, then it obtains data from this specific filter.
+ FilterStatus ReadData(char* dest_buffer, int* dest_len);
// Returns a pointer to the beginning of stream_buffer_.
char* stream_buffer() const { return stream_buffer_.get(); }
@@ -102,7 +103,23 @@ class Filter {
// The function returns true if success, and false otherwise.
bool FlushStreamBuffer(int stream_data_len);
+ void SetURL(const GURL& url);
+ const GURL& url() const { return url_; }
+
protected:
+ // Filters the data stored in stream_buffer_ and writes the output into the
+ // dest_buffer passed in.
+ //
+ // Upon entry, *dest_len is the total size (in number of chars) of the
+ // destination buffer. Upon exit, *dest_len is the actual number of chars
+ // written into the destination buffer.
+ //
+ // This function will fail if there is no pre-filter data in the
+ // stream_buffer_. On the other hand, *dest_len can be 0 upon successful
+ // return. For example, a decoding filter may process some pre-filter data
+ // but not produce output yet.
+ virtual FilterStatus ReadFilteredData(char* dest_buffer, int* dest_len);
+
Filter();
// Copy pre-filter data directly to destination buffer without decoding.
@@ -113,6 +130,7 @@ class Filter {
FILTER_TYPE_DEFLATE,
FILTER_TYPE_GZIP,
FILTER_TYPE_BZIP2,
+ FILTER_TYPE_SDCH, // open-vcdiff compression relative to a dictionary.
FILTER_TYPE_UNSUPPORTED
};
@@ -120,6 +138,15 @@ class Filter {
// Buffer_size is the maximum size of stream_buffer_ in number of chars.
bool InitBuffer(int buffer_size);
+ // A factory helper for creating filters for within a chain of potentially
+ // multiple encodings. If a chain of filters is created, then this may be
+ // called multiple times during the filter creation process. In most simple
+ // cases, this is only called once.
+ static Filter* SingleFilter(const std::string& filter_type,
+ const std::string& mime_type,
+ int buffer_size);
+ FilterStatus last_status() const { return last_status_; }
+
// Buffer to hold the data to be filtered.
scoped_array<char> stream_buffer_;
@@ -132,11 +159,18 @@ class Filter {
// Total number of remaining chars in stream_buffer_ to be filtered.
int stream_data_len_;
- // Filter can be chained
- // TODO (huanr)
- // Filter* next_filter_;
+ // The URL that is currently being filtered.
+ // This is used by SDCH filters which need to restrict use of a dictionary to
+ // a specific URL or path.
+ GURL url_;
+
+ // An optional filter to process output from this filter.
+ scoped_ptr<Filter> next_filter_;
+ // Remember what status or local filter last returned so we can better handle
+ // chained filters.
+ FilterStatus last_status_;
- DISALLOW_EVIL_CONSTRUCTORS(Filter);
+ DISALLOW_COPY_AND_ASSIGN(Filter);
};
#endif // NET_BASE_FILTER_H__
diff --git a/net/base/gzip_filter_unittest.cc b/net/base/gzip_filter_unittest.cc
index fcec028..22c1ae3 100644
--- a/net/base/gzip_filter_unittest.cc
+++ b/net/base/gzip_filter_unittest.cc
@@ -182,7 +182,7 @@ class GZipUnitTest : public PlatformTest {
while (1) {
int decode_data_len = std::min(decode_avail_size, output_buffer_size);
- code = filter->ReadFilteredData(decode_next, &decode_data_len);
+ code = filter->ReadData(decode_next, &decode_data_len);
decode_next += decode_data_len;
decode_avail_size -= decode_data_len;
@@ -210,7 +210,7 @@ class GZipUnitTest : public PlatformTest {
char* dest, int* dest_len) {
memcpy(filter->stream_buffer(), source, source_len);
filter->FlushStreamBuffer(source_len);
- return filter->ReadFilteredData(dest, dest_len);
+ return filter->ReadData(dest, dest_len);
}
const char* source_buffer() const { return source_buffer_.data(); }
@@ -228,15 +228,17 @@ class GZipUnitTest : public PlatformTest {
// Basic scenario: decoding deflate data with big enough buffer.
TEST_F(GZipUnitTest, DecodeDeflate) {
// Decode the compressed data with filter
+ std::vector<std::string> filters;
+ filters.push_back("deflate");
scoped_ptr<Filter> filter(
- Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
memcpy(filter->stream_buffer(), deflate_encode_buffer_, deflate_encode_len_);
filter->FlushStreamBuffer(deflate_encode_len_);
char deflate_decode_buffer[kDefaultBufferSize];
int deflate_decode_size = kDefaultBufferSize;
- filter->ReadFilteredData(deflate_decode_buffer, &deflate_decode_size);
+ filter->ReadData(deflate_decode_buffer, &deflate_decode_size);
// Compare the decoding result with source data
EXPECT_TRUE(deflate_decode_size == source_len());
@@ -246,15 +248,17 @@ TEST_F(GZipUnitTest, DecodeDeflate) {
// Basic scenario: decoding gzip data with big enough buffer.
TEST_F(GZipUnitTest, DecodeGZip) {
// Decode the compressed data with filter
+ std::vector<std::string> filters;
+ filters.push_back("gzip");
scoped_ptr<Filter> filter(
- Filter::Factory("gzip", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
memcpy(filter->stream_buffer(), gzip_encode_buffer_, gzip_encode_len_);
filter->FlushStreamBuffer(gzip_encode_len_);
char gzip_decode_buffer[kDefaultBufferSize];
int gzip_decode_size = kDefaultBufferSize;
- filter->ReadFilteredData(gzip_decode_buffer, &gzip_decode_size);
+ filter->ReadData(gzip_decode_buffer, &gzip_decode_size);
// Compare the decoding result with source data
EXPECT_TRUE(gzip_decode_size == source_len());
@@ -265,8 +269,10 @@ TEST_F(GZipUnitTest, DecodeGZip) {
// To do that, we create a filter with a small buffer that can not hold all
// the input data.
TEST_F(GZipUnitTest, DecodeWithSmallBuffer) {
+ std::vector<std::string> filters;
+ filters.push_back("deflate");
scoped_ptr<Filter> filter(
- Filter::Factory("deflate", kApplicationOctetStream, kSmallBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kSmallBufferSize));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
deflate_encode_buffer_, deflate_encode_len_,
@@ -278,8 +284,10 @@ TEST_F(GZipUnitTest, DecodeWithSmallBuffer) {
// header correctly. (2) Sometimes the filter will consume input without
// generating output. Verify filter can handle it correctly.
TEST_F(GZipUnitTest, DecodeWithOneByteBuffer) {
+ std::vector<std::string> filters;
+ filters.push_back("gzip");
scoped_ptr<Filter> filter(
- Filter::Factory("gzip", kApplicationOctetStream, 1));
+ Filter::Factory(filters, kApplicationOctetStream, 1));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
gzip_encode_buffer_, gzip_encode_len_,
@@ -288,8 +296,10 @@ TEST_F(GZipUnitTest, DecodeWithOneByteBuffer) {
// Tests we can decode when caller has small buffer to read out from filter.
TEST_F(GZipUnitTest, DecodeWithSmallOutputBuffer) {
+ std::vector<std::string> filters;
+ filters.push_back("deflate");
scoped_ptr<Filter> filter(
- Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
deflate_encode_buffer_, deflate_encode_len_,
@@ -299,8 +309,10 @@ TEST_F(GZipUnitTest, DecodeWithSmallOutputBuffer) {
// Tests we can still decode with just 1 byte buffer in the filter and just 1
// byte buffer in the caller.
TEST_F(GZipUnitTest, DecodeWithOneByteInputAndOutputBuffer) {
+ std::vector<std::string> filters;
+ filters.push_back("gzip");
scoped_ptr<Filter> filter(
- Filter::Factory("gzip", kApplicationOctetStream, 1));
+ Filter::Factory(filters, kApplicationOctetStream, 1));
ASSERT_TRUE(filter.get());
DecodeAndCompareWithFilter(filter.get(), source_buffer(), source_len(),
gzip_encode_buffer_, gzip_encode_len_, 1);
@@ -316,8 +328,10 @@ TEST_F(GZipUnitTest, DecodeCorruptedData) {
corrupt_data[pos] = !corrupt_data[pos];
// Decode the corrupted data with filter
+ std::vector<std::string> filters;
+ filters.push_back("deflate");
scoped_ptr<Filter> filter(
- Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
char corrupt_decode_buffer[kDefaultBufferSize];
int corrupt_decode_size = kDefaultBufferSize;
@@ -341,8 +355,10 @@ TEST_F(GZipUnitTest, DecodeMissingData) {
--corrupt_data_len;
// Decode the corrupted data with filter
+ std::vector<std::string> filters;
+ filters.push_back("deflate");
scoped_ptr<Filter> filter(
- Filter::Factory("deflate", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
char corrupt_decode_buffer[kDefaultBufferSize];
int corrupt_decode_size = kDefaultBufferSize;
@@ -363,8 +379,10 @@ TEST_F(GZipUnitTest, DecodeCorruptedHeader) {
corrupt_data[2] = !corrupt_data[2];
// Decode the corrupted data with filter
+ std::vector<std::string> filters;
+ filters.push_back("gzip");
scoped_ptr<Filter> filter(
- Filter::Factory("gzip", kApplicationOctetStream, kDefaultBufferSize));
+ Filter::Factory(filters, kApplicationOctetStream, kDefaultBufferSize));
ASSERT_TRUE(filter.get());
char corrupt_decode_buffer[kDefaultBufferSize];
int corrupt_decode_size = kDefaultBufferSize;
@@ -380,18 +398,23 @@ TEST_F(GZipUnitTest, ApacheWorkaround) {
const int kBufferSize = kDefaultBufferSize; // To fit in 80 cols.
scoped_ptr<Filter> filter;
- filter.reset(Filter::Factory("gzip", kApplicationXGzip, kBufferSize));
+ std::vector<std::string> gzip_filters, x_gzip_filters;
+ gzip_filters.push_back("gzip");
+ x_gzip_filters.push_back("x-gzip");
+
+ filter.reset(Filter::Factory(gzip_filters, kApplicationXGzip, kBufferSize));
EXPECT_FALSE(filter.get());
- filter.reset(Filter::Factory("gzip", kApplicationGzip, kBufferSize));
+ filter.reset(Filter::Factory(gzip_filters, kApplicationGzip, kBufferSize));
EXPECT_FALSE(filter.get());
- filter.reset(Filter::Factory("gzip", kApplicationXGunzip, kBufferSize));
+ filter.reset(Filter::Factory(gzip_filters, kApplicationXGunzip, kBufferSize));
EXPECT_FALSE(filter.get());
- filter.reset(Filter::Factory("x-gzip", kApplicationXGzip, kBufferSize));
+ filter.reset(Filter::Factory(x_gzip_filters, kApplicationXGzip, kBufferSize));
EXPECT_FALSE(filter.get());
- filter.reset(Filter::Factory("x-gzip", kApplicationGzip, kBufferSize));
+ filter.reset(Filter::Factory(x_gzip_filters, kApplicationGzip, kBufferSize));
EXPECT_FALSE(filter.get());
- filter.reset(Filter::Factory("x-gzip", kApplicationXGunzip, kBufferSize));
+ filter.reset(Filter::Factory(x_gzip_filters, kApplicationXGunzip,
+ kBufferSize));
EXPECT_FALSE(filter.get());
}
diff --git a/net/base/sdch_filter.cc b/net/base/sdch_filter.cc
new file mode 100644
index 0000000..b160b60
--- /dev/null
+++ b/net/base/sdch_filter.cc
@@ -0,0 +1,170 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/file_util.h"
+#include "base/histogram.h"
+#include "base/logging.h"
+#include "net/base/sdch_filter.h"
+#include "net/base/sdch_manager.h"
+
+#include "google/vcdecoder.h"
+
+SdchFilter::SdchFilter()
+ : decoding_status_(DECODING_UNINITIALIZED),
+ vcdiff_streaming_decoder_(NULL),
+ dictionary_(NULL),
+ dest_buffer_excess_(),
+ dest_buffer_excess_index_(0),
+ source_bytes_(0),
+ output_bytes_(0) {
+}
+
+SdchFilter::~SdchFilter() {
+ if (vcdiff_streaming_decoder_.get()) {
+ if (!vcdiff_streaming_decoder_->FinishDecoding())
+ decoding_status_ = DECODING_ERROR;
+ }
+ // TODO(jar): Use DHISTOGRAM when we turn sdch on by default.
+ if (decoding_status_ == DECODING_ERROR) {
+ HISTOGRAM_COUNTS(L"Sdch.Decoding Error bytes read", source_bytes_);
+ HISTOGRAM_COUNTS(L"Sdch.Decoding Error bytes output", output_bytes_);
+ } else {
+ if (decoding_status_ == DECODING_IN_PROGRESS) {
+ HISTOGRAM_COUNTS(L"Sdch.Bytes read", source_bytes_);
+ HISTOGRAM_COUNTS(L"Sdch.Bytes output", output_bytes_);
+ }
+ }
+ if (dictionary_)
+ dictionary_->Release();
+}
+
+bool SdchFilter::InitDecoding() {
+ if (decoding_status_ != DECODING_UNINITIALIZED)
+ return false;
+
+ // Initialize decoder only after we have a dictionary in hand.
+ decoding_status_ = WAITING_FOR_DICTIONARY_SELECTION;
+ return true;
+}
+
+Filter::FilterStatus SdchFilter::ReadFilteredData(char* dest_buffer,
+ int* dest_len) {
+ int available_space = *dest_len;
+ *dest_len = 0; // Nothing output yet.
+
+ if (!dest_buffer || available_space <= 0)
+ return FILTER_ERROR;
+
+ char* dest_buffer_end = dest_buffer + available_space;
+
+ if (WAITING_FOR_DICTIONARY_SELECTION == decoding_status_) {
+ FilterStatus status = InitializeDictionary();
+ if (DECODING_IN_PROGRESS != decoding_status_) {
+ DCHECK(status == FILTER_ERROR || status == FILTER_NEED_MORE_DATA);
+ return status;
+ }
+ }
+
+ if (decoding_status_ != DECODING_IN_PROGRESS) {
+ decoding_status_ = DECODING_ERROR;
+ return FILTER_ERROR;
+ }
+
+
+ int amount = OutputBufferExcess(dest_buffer, available_space);
+ *dest_len += amount;
+ dest_buffer += amount;
+ available_space -= amount;
+ DCHECK(available_space >= 0);
+
+ if (available_space <= 0)
+ return FILTER_OK;
+ DCHECK(dest_buffer_excess_.empty());
+
+ if (!next_stream_data_ || stream_data_len_ <= 0)
+ return FILTER_NEED_MORE_DATA;
+
+ bool ret = vcdiff_streaming_decoder_->DecodeChunk(
+ next_stream_data_, stream_data_len_, &dest_buffer_excess_);
+ // Assume all data was used in decoding.
+ next_stream_data_ = NULL;
+ source_bytes_ += stream_data_len_;
+ stream_data_len_ = 0;
+ output_bytes_ += dest_buffer_excess_.size();
+ if (!ret) {
+ vcdiff_streaming_decoder_.reset(NULL); // Don't call it again.
+ decoding_status_ = DECODING_ERROR;
+ return FILTER_ERROR;
+ }
+
+ amount = OutputBufferExcess(dest_buffer, available_space);
+ *dest_len += amount;
+ dest_buffer += amount;
+ if (0 == available_space && !dest_buffer_excess_.empty())
+ return FILTER_OK;
+ return FILTER_NEED_MORE_DATA;
+}
+
+Filter::FilterStatus SdchFilter::InitializeDictionary() {
+ const size_t kServerIdLength = 9; // Dictionary hash plus null from server.
+ size_t bytes_needed = kServerIdLength - dictionary_hash_.size();
+ DCHECK(bytes_needed > 0);
+ if (!next_stream_data_)
+ return FILTER_NEED_MORE_DATA;
+ if (static_cast<size_t>(stream_data_len_) < bytes_needed) {
+ dictionary_hash_.append(next_stream_data_, stream_data_len_);
+ next_stream_data_ = NULL;
+ stream_data_len_ = 0;
+ return FILTER_NEED_MORE_DATA;
+ }
+ dictionary_hash_.append(next_stream_data_, bytes_needed);
+ DCHECK(kServerIdLength == dictionary_hash_.size());
+ stream_data_len_ -= bytes_needed;
+ DCHECK(0 <= stream_data_len_);
+ if (stream_data_len_ > 0)
+ next_stream_data_ += bytes_needed;
+ else
+ next_stream_data_ = NULL;
+
+ if ('\0' != dictionary_hash_[kServerIdLength - 1] ||
+ (kServerIdLength - 1) != strlen(dictionary_hash_.data())) {
+ decoding_status_ = DECODING_ERROR;
+ return FILTER_ERROR; // No dictionary hash.
+ }
+ dictionary_hash_.erase(kServerIdLength - 1);
+
+ DCHECK(!dictionary_);
+ SdchManager::Global()->GetVcdiffDictionary(dictionary_hash_, url(),
+ &dictionary_);
+ if (!dictionary_) {
+ decoding_status_ = DECODING_ERROR;
+ return FILTER_ERROR;
+ }
+ dictionary_->AddRef();
+ vcdiff_streaming_decoder_.reset(new open_vcdiff::VCDiffStreamingDecoder);
+ vcdiff_streaming_decoder_->StartDecoding(dictionary_->text().data(),
+ dictionary_->text().size());
+ decoding_status_ = DECODING_IN_PROGRESS;
+ return FILTER_OK;
+}
+
+int SdchFilter::OutputBufferExcess(char* const dest_buffer,
+ size_t available_space) {
+ if (dest_buffer_excess_.empty())
+ return 0;
+ DCHECK(dest_buffer_excess_.size() > dest_buffer_excess_index_);
+ size_t amount = std::min(available_space,
+ dest_buffer_excess_.size() - dest_buffer_excess_index_);
+ memcpy(dest_buffer, dest_buffer_excess_.data() + dest_buffer_excess_index_,
+ amount);
+ dest_buffer_excess_index_ += amount;
+ if (dest_buffer_excess_.size() <= dest_buffer_excess_index_) {
+ DCHECK(dest_buffer_excess_.size() == dest_buffer_excess_index_);
+ dest_buffer_excess_.clear();
+ dest_buffer_excess_index_ = 0;
+ }
+ return amount;
+}
diff --git a/net/base/sdch_filter.h b/net/base/sdch_filter.h
new file mode 100644
index 0000000..a13bd38
--- /dev/null
+++ b/net/base/sdch_filter.h
@@ -0,0 +1,95 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// SdchFilter applies open_vcdiff content decoding to a datastream.
+// This decoding uses a pre-cached dictionary of text fragments to decode
+// (expand) the stream back to its original contents.
+//
+// This SdchFilter internally uses open_vcdiff/vcdec library to do decoding.
+//
+// SdchFilter is also a subclass of Filter. See the latter's header file
+// filter.h for sample usage.
+
+#ifndef NET_BASE_SDCH_FILTER_H_
+#define NET_BASE_SDCH_FILTER_H_
+
+#include <string>
+
+#include "base/scoped_ptr.h"
+#include "net/base/filter.h"
+#include "net/base/sdch_manager.h"
+
+class SafeOutputStringInterface;
+
+namespace open_vcdiff {
+ class VCDiffStreamingDecoder;
+}
+
+class SdchFilter : public Filter {
+ public:
+ SdchFilter();
+
+ virtual ~SdchFilter();
+
+ // Initializes filter decoding mode and internal control blocks.
+ bool InitDecoding();
+
+ // Decode the pre-filter data and writes the output into |dest_buffer|
+ // The function returns FilterStatus. See filter.h for its description.
+ //
+ // Upon entry, *dest_len is the total size (in number of chars) of the
+ // destination buffer. Upon exit, *dest_len is the actual number of chars
+ // written into the destination buffer.
+ virtual FilterStatus ReadFilteredData(char* dest_buffer, int* dest_len);
+
+ private:
+ // Internal status. Once we enter an error state, we stop processing data.
+ enum DecodingStatus {
+ DECODING_UNINITIALIZED,
+ WAITING_FOR_DICTIONARY_SELECTION,
+ DECODING_IN_PROGRESS,
+ DECODING_ERROR
+ };
+
+ // Identify the suggested dictionary, and initialize underlying decompressor.
+ Filter::FilterStatus InitializeDictionary();
+
+ // Move data that was internally buffered (after decompression) to the
+ // specified dest_buffer.
+ int OutputBufferExcess(char* const dest_buffer, size_t available_space);
+
+ // Tracks the status of decoding.
+ // This variable is initialized by InitDecoding and updated only by
+ // ReadFilteredData.
+ DecodingStatus decoding_status_;
+
+ // The underlying decoder that processes data.
+ // This data structure is initialized by InitDecoding and updated in
+ // ReadFilteredData.
+ scoped_ptr<open_vcdiff::VCDiffStreamingDecoder> vcdiff_streaming_decoder_;
+
+ // In case we need to assemble the hash piecemeal, we have a place to store
+ // a part of the hash until we "get all 8 bytes."
+ std::string dictionary_hash_;
+
+ // We hold an in-memory copy of the dictionary during the entire decoding.
+ // The char* data is embedded in a RefCounted dictionary_.
+ SdchManager::Dictionary* dictionary_;
+
+ // The decoder may demand a larger output buffer than the target of
+ // ReadFilteredData so we buffer the excess output between calls.
+ std::string dest_buffer_excess_;
+ // To avoid moving strings around too much, we save the index into
+ // dest_buffer_excess_ that has the next byte to output.
+ size_t dest_buffer_excess_index_;
+
+ // To get stats on activities, we keep track of source and target bytes.
+ // Visit about:histograms/Sdch to see histogram data.
+ size_t source_bytes_;
+ size_t output_bytes_;
+
+ DISALLOW_COPY_AND_ASSIGN(SdchFilter);
+};
+
+#endif // NET_BASE_SDCH_FILTER_H_
diff --git a/net/base/sdch_filter_unitest.cc b/net/base/sdch_filter_unitest.cc
new file mode 100644
index 0000000..c7bbb50
--- /dev/null
+++ b/net/base/sdch_filter_unitest.cc
@@ -0,0 +1,441 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "base/logging.h"
+#include "base/scoped_ptr.h"
+#include "net/base/filter.h"
+#include "net/base/sdch_filter.h"
+#include "net/url_request/url_request_http_job.cc"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/zlib/zlib.h"
+
+// Provide sample data and compression results with a sample VCDIFF dictionary.
+// Note an SDCH dictionary has extra meta-data before the VCDIFF text.
+const char kTtestVcdiffDictionary[] = "DictionaryFor"
+ "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n";
+// Pre-compression test data.
+const char kTestData[] = "TestData "
+ "SdchCompression1SdchCompression2SdchCompression3SdchCompression\n";
+// Note SDCH compressed data will include a reference to the SDCH dictionary.
+const char kCompressedTestData[] =
+ "\326\303\304\0\0\001M\0\022I\0\t\003\001TestData \n\023\100\r";
+
+namespace {
+
+class SdchFilterTest : public testing::Test {
+ protected:
+ SdchFilterTest()
+ : test_vcdiff_dictionary_(kTtestVcdiffDictionary,
+ sizeof(kTtestVcdiffDictionary) - 1),
+ expanded_(kTestData, sizeof(kTestData) - 1),
+ compressed_test_data_(kCompressedTestData,
+ sizeof(kCompressedTestData) - 1),
+ sdch_manager_(new SdchManager) {
+ }
+
+ const std::string test_vcdiff_dictionary_;
+ const std::string compressed_test_data_;
+ const std::string expanded_; // Desired final, decompressed data.
+
+ scoped_ptr<SdchManager> sdch_manager_; // A singleton database.
+};
+
+
+TEST_F(SdchFilterTest, Hashing) {
+ std::string client_hash, server_hash;
+ std::string dictionary("test contents");
+ SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
+
+ EXPECT_EQ(client_hash, "lMQBjS3P");
+ EXPECT_EQ(server_hash, "MyciMVll");
+}
+
+
+//------------------------------------------------------------------------------
+// Provide a generic helper function for trying to filter data.
+// This function repeatedly calls the filter to process data, until the entire
+// source is consumed. The return value from the filter is appended to output.
+// This allows us to vary input and output block sizes in order to test for edge
+// effects (boundary effects?) during the filtering process.
+// This function provides data to the filter in blocks of no-more-than the
+// specified input_block_length. It allows the filter to fill no more than
+// output_buffer_length in any one call to proccess (a.k.a., Read) data, and
+// concatenates all these little output blocks into the singular output string.
+static bool FilterTestData(const std::string& source,
+ size_t input_block_length,
+ const size_t output_buffer_length,
+ Filter* filter, std::string* output) {
+ CHECK(input_block_length > 0);
+ Filter::FilterStatus status(Filter::FILTER_NEED_MORE_DATA);
+ size_t source_index = 0;
+ scoped_array<char> output_buffer(new char[output_buffer_length]);
+ size_t input_amount = std::min(input_block_length,
+ static_cast<size_t>(filter->stream_buffer_size()));
+
+ do {
+ int copy_amount = std::min(input_amount, source.size() - source_index);
+ if (copy_amount > 0 && status == Filter::FILTER_NEED_MORE_DATA) {
+ memcpy(filter->stream_buffer(), source.data() + source_index,
+ copy_amount);
+ filter->FlushStreamBuffer(copy_amount);
+ source_index += copy_amount;
+ }
+ int buffer_length = output_buffer_length;
+ status = filter->ReadData(output_buffer.get(), &buffer_length);
+ output->append(output_buffer.get(), buffer_length);
+ if (status == Filter::FILTER_ERROR)
+ return false;
+ if (copy_amount == 0 && buffer_length == 0)
+ return true;
+ } while (1);
+}
+//------------------------------------------------------------------------------
+
+
+TEST_F(SdchFilterTest, BasicBadDicitonary) {
+ SdchManager::enable_sdch_support("");
+
+ std::vector<std::string> filters;
+ filters.push_back("sdch");
+ int kInputBufferSize(30);
+ char output_buffer[20];
+ size_t kOutputBufferSize(20);
+ scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime",
+ kInputBufferSize));
+ filter->SetURL(GURL("http://ignore.com"));
+
+
+ // With no input data, try to read output.
+ int output_bytes_or_buffer_size = sizeof(output_buffer);
+ Filter::FilterStatus status = filter->ReadData(output_buffer,
+ &output_bytes_or_buffer_size);
+
+ EXPECT_EQ(0, output_bytes_or_buffer_size);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
+
+ // Supply bogus data (which doesnt't yet specify a full dictionary hash).
+ // Dictionary hash is 8 characters followed by a null.
+ std::string dictionary_hash_prefix("123");
+
+ char* input_buffer = filter->stream_buffer();
+ int input_buffer_size = filter->stream_buffer_size();
+ EXPECT_EQ(kInputBufferSize, input_buffer_size);
+
+ EXPECT_LT(static_cast<int>(dictionary_hash_prefix.size()),
+ input_buffer_size);
+ memcpy(input_buffer, dictionary_hash_prefix.data(),
+ dictionary_hash_prefix.size());
+ filter->FlushStreamBuffer(dictionary_hash_prefix.size());
+
+ // With less than a dictionary specifier, try to read output.
+ output_bytes_or_buffer_size = sizeof(output_buffer);
+ status = filter->ReadData(output_buffer, &output_bytes_or_buffer_size);
+
+ EXPECT_EQ(0, output_bytes_or_buffer_size);
+ EXPECT_EQ(Filter::FILTER_NEED_MORE_DATA, status);
+
+ // Provide enough data to complete *a* hash, but it is bogus, and not in our
+ // list of dictionaries, so the filter should error out immediately.
+ std::string dictionary_hash_postfix("4abcd\0", 6);
+
+ CHECK(dictionary_hash_postfix.size() <
+ static_cast<size_t>(input_buffer_size));
+ memcpy(input_buffer, dictionary_hash_postfix.data(),
+ dictionary_hash_postfix.size());
+ filter->FlushStreamBuffer(dictionary_hash_postfix.size());
+
+ // With a non-existant dictionary specifier, try to read output.
+ output_bytes_or_buffer_size = sizeof(output_buffer);
+ status = filter->ReadData(output_buffer, &output_bytes_or_buffer_size);
+
+ EXPECT_EQ(0, output_bytes_or_buffer_size);
+ EXPECT_EQ(Filter::FILTER_ERROR, status);
+}
+
+
+TEST_F(SdchFilterTest, BasicDictionary) {
+ SdchManager::enable_sdch_support("");
+
+ const std::string kSampleDomain = "sdchtest.com";
+
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ std::string dictionary("Domain: ");
+ dictionary.append(kSampleDomain);
+ dictionary.append("\n\n");
+ dictionary.append(test_vcdiff_dictionary_);
+ std::string client_hash;
+ std::string server_hash;
+ SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ bool status = sdch_manager_->AddSdchDictionary(dictionary, url);
+ EXPECT_TRUE(status);
+
+ // Check we can't add it twice.
+ status = sdch_manager_->AddSdchDictionary(dictionary, url);
+ EXPECT_FALSE(status); // Already loaded.
+
+ // Build compressed data that refers to our dictionary.
+ std::string compressed(server_hash);
+ compressed.append("\0", 1);
+ compressed.append(compressed_test_data_);
+
+ std::vector<std::string> filters;
+ filters.push_back("sdch");
+
+ // First try with a large buffer (larger than test input, or compressed data).
+ int kInputBufferSize(100);
+ scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime",
+ kInputBufferSize));
+ filter->SetURL(url);
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ status = FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output);
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(output == expanded_);
+
+ // Now try with really small buffers (size 1) to check for edge effects.
+ filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ filter->SetURL(url);
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ status = FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output);
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(output == expanded_);
+
+ // Now try with content arriving from the "wrong" domain.
+ // This tests CanSet() in the sdch_manager_->
+ filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ filter->SetURL(GURL("http://www.wrongdomain.com"));
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ status = FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output);
+ EXPECT_FALSE(status); // Couldn't decode.
+ EXPECT_TRUE(output == ""); // No output written.
+
+
+ // Now check that path restrictions on dictionary are being enforced.
+
+ // Create a dictionary with a path restriction, by prefixing old dictionary.
+ const std::string path("/special_path/bin");
+ std::string dictionary_with_path("Path: " + path + "\n");
+ dictionary_with_path.append(dictionary);
+ std::string pathed_client_hash;
+ std::string pathed_server_hash;
+ SdchManager::GenerateHash(dictionary_with_path,
+ &pathed_client_hash, &pathed_server_hash);
+ status = sdch_manager_->AddSdchDictionary(dictionary_with_path, url);
+ EXPECT_TRUE(status);
+
+ // Build compressed data that refers to our dictionary
+ std::string compressed_for_path(pathed_server_hash);
+ compressed_for_path.append("\0", 1);
+ compressed_for_path.append(compressed_test_data_);
+
+ // Test decode the path data, arriving from a valid path.
+ filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ filter->SetURL(GURL(url_string + path));
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ status = FilterTestData(compressed_for_path, feed_block_size,
+ output_block_size, filter.get(), &output);
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(output == expanded_);
+
+ // Test decode the path data, arriving from a invalid path.
+ filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ filter->SetURL(GURL(url_string));
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ status = FilterTestData(compressed_for_path, feed_block_size,
+ output_block_size, filter.get(), &output);
+ EXPECT_FALSE(status); // Couldn't decode.
+ EXPECT_TRUE(output == ""); // No output written.
+
+
+ // Create a dictionary with a port restriction, by prefixing old dictionary.
+ const std::string port("502");
+ std::string dictionary_with_port("Port: " + port + "\n");
+ dictionary_with_port.append("Port: 80\n"); // Add default port.
+ dictionary_with_port.append(dictionary);
+ std::string ported_client_hash;
+ std::string ported_server_hash;
+ SdchManager::GenerateHash(dictionary_with_port,
+ &ported_client_hash, &ported_server_hash);
+ status = sdch_manager_->AddSdchDictionary(dictionary_with_port,
+ GURL(url_string + ":" + port));
+ EXPECT_TRUE(status);
+
+ // Build compressed data that refers to our dictionary
+ std::string compressed_for_port(ported_server_hash);
+ compressed_for_port.append("\0", 1);
+ compressed_for_port.append(compressed_test_data_);
+
+ // Test decode the port data, arriving from a valid port.
+ filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ filter->SetURL(GURL(url_string + ":" + port));
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ status = FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output);
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(output == expanded_);
+
+ // Test decode the port data, arriving from a valid (default) port.
+ filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ filter->SetURL(GURL(url_string)); // Default port.
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ status = FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output);
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(output == expanded_);
+
+ // Test decode the port data, arriving from a invalid port.
+ filter.reset((Filter::Factory(filters, "missing-mime", kInputBufferSize)));
+ filter->SetURL(GURL(url_string + ":" + port + "1"));
+
+ feed_block_size = 100;
+ output_block_size = 100;
+ output.clear();
+ status = FilterTestData(compressed_for_port, feed_block_size,
+ output_block_size, filter.get(), &output);
+ EXPECT_FALSE(status); // Couldn't decode.
+ EXPECT_TRUE(output == ""); // No output written.
+}
+
+
+// Test that filters can be cascaded (chained) so that the output of one filter
+// is processed by the next one. This is most critical for SDCH, which is
+// routinely followed by gzip (during encoding). The filter we'll test for will
+// do the gzip decoding first, and then decode the SDCH content.
+TEST_F(SdchFilterTest, FilterChaining) {
+ const std::string kSampleDomain = "sdchtest.com";
+
+ // Construct a valid SDCH dictionary from a VCDIFF dictionary.
+ std::string dictionary("Domain: ");
+ dictionary.append(kSampleDomain);
+ dictionary.append("\n\n");
+ dictionary.append(test_vcdiff_dictionary_);
+ std::string client_hash;
+ std::string server_hash;
+ SdchManager::GenerateHash(dictionary, &client_hash, &server_hash);
+ std::string url_string = "http://" + kSampleDomain;
+
+ GURL url(url_string);
+ bool status = sdch_manager_->AddSdchDictionary(dictionary, url);
+ EXPECT_TRUE(status);
+
+ // Check we can't add it twice.
+ status = sdch_manager_->AddSdchDictionary(dictionary, url);
+ EXPECT_FALSE(status); // Already loaded.
+
+ // Build compressed sdch encoded data that refers to our dictionary.
+ std::string sdch_compressed(server_hash);
+ sdch_compressed.append("\0", 1);
+ sdch_compressed.append(compressed_test_data_);
+
+ // Use Gzip to compress the sdch sdch_compressed data.
+ z_stream zlib_stream;
+ memset(&zlib_stream, 0, sizeof(zlib_stream));
+ int code;
+
+ // Initialize zlib
+ code = deflateInit2(&zlib_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED,
+ -MAX_WBITS,
+ 8, // DEF_MEM_LEVEL
+ Z_DEFAULT_STRATEGY);
+
+ CHECK(code == Z_OK);
+
+ // Fill in zlib control block
+ zlib_stream.next_in = bit_cast<Bytef*>(sdch_compressed.data());
+ zlib_stream.avail_in = sdch_compressed.size();
+
+ // Assume we can compress into similar buffer (add 100 bytes to be sure).
+ size_t gzip_compressed_length = zlib_stream.avail_in + 100;
+ scoped_array<char> gzip_compressed(new char[gzip_compressed_length]);
+ zlib_stream.next_out = bit_cast<Bytef*>(gzip_compressed.get());
+ zlib_stream.avail_out = gzip_compressed_length;
+
+ // The GZIP header (see RFC 1952):
+ // +---+---+---+---+---+---+---+---+---+---+
+ // |ID1|ID2|CM |FLG| MTIME |XFL|OS |
+ // +---+---+---+---+---+---+---+---+---+---+
+ // ID1 \037
+ // ID2 \213
+ // CM \010 (compression method == DEFLATE)
+ // FLG \000 (special flags that we do not support)
+ // MTIME Unix format modification time (0 means not available)
+ // XFL 2-4? DEFLATE flags
+ // OS ???? Operating system indicator (255 means unknown)
+ //
+ // Header value we generate:
+ const char kGZipHeader[] = { '\037', '\213', '\010', '\000', '\000',
+ '\000', '\000', '\000', '\002', '\377' };
+ CHECK(zlib_stream.avail_out > sizeof(kGZipHeader));
+ memcpy(zlib_stream.next_out, kGZipHeader, sizeof(kGZipHeader));
+ zlib_stream.next_out += sizeof(kGZipHeader);
+ zlib_stream.avail_out -= sizeof(kGZipHeader);
+
+ // Do deflate
+ code = MOZ_Z_deflate(&zlib_stream, Z_FINISH);
+ gzip_compressed_length -= zlib_stream.avail_out;
+ std::string compressed(gzip_compressed.get(), gzip_compressed_length);
+
+ // Construct a chained filter.
+ std::vector<std::string> filters;
+ filters.push_back("sdch");
+ filters.push_back("gzip");
+
+ // First try with a large buffer (larger than test input, or compressed data).
+ int kInputBufferSize(100);
+ scoped_ptr<Filter> filter(Filter::Factory(filters, "missing-mime",
+ kInputBufferSize));
+ filter->SetURL(url);
+
+ size_t feed_block_size = 100;
+ size_t output_block_size = 100;
+ std::string output;
+ status = FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output);
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(output == expanded_);
+
+ // Next try with a tiny buffer to cover edge effects.
+ filter.reset(Filter::Factory(filters, "missing-mime", kInputBufferSize));
+ filter->SetURL(url);
+
+ feed_block_size = 1;
+ output_block_size = 1;
+ output.clear();
+ status = FilterTestData(compressed, feed_block_size, output_block_size,
+ filter.get(), &output);
+ EXPECT_TRUE(status);
+ EXPECT_TRUE(output == expanded_);
+}
+
+}; // namespace anonymous
diff --git a/net/base/sdch_manager.cc b/net/base/sdch_manager.cc
new file mode 100644
index 0000000..6c8bce3
--- /dev/null
+++ b/net/base/sdch_manager.cc
@@ -0,0 +1,336 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/histogram.h"
+#include "base/logging.h"
+#include "base/sha2.h"
+#include "base/string_util.h"
+#include "net/base/base64.h"
+#include "net/base/registry_controlled_domain.h"
+#include "net/base/sdch_manager.h"
+#include "net/url_request/url_request_http_job.h"
+
+
+//------------------------------------------------------------------------------
+// static
+SdchManager* SdchManager::global_;
+
+// static
+SdchManager* SdchManager::Global() {
+ return global_;
+}
+
+//------------------------------------------------------------------------------
+SdchManager::SdchManager() : sdch_enabled_(false) {
+ DCHECK(!global_);
+ global_ = this;
+}
+
+SdchManager::~SdchManager() {
+ DCHECK(global_ == this);
+ while (!dictionaries_.empty()) {
+ DictionaryMap::iterator it = dictionaries_.begin();
+ it->second->Release();
+ dictionaries_.erase(it->first);
+ }
+ global_ = NULL;
+}
+
+const bool SdchManager::IsInSupportedDomain(const GURL& url) const {
+ return sdch_enabled_ &&
+ (supported_domain_.empty() ||
+ url.DomainIs(supported_domain_.data(), supported_domain_.size()));
+}
+
+void SdchManager::FetchDictionary(const GURL& referring_url,
+ const GURL& dictionary_url) {
+ /* The user agent may retrieve a dictionary from the dictionary URL if all of
+ the following are true:
+ 1 The dictionary URL host name matches the referrer URL host name
+ 2 The dictionary URL host name domain matches the parent domain of the
+ referrer URL host name
+ 3 The parent domain of the referrer URL host name is not a top level
+ domain
+ 4 The dictionary URL is not an HTTPS URL.
+ */
+ // Item (1) above implies item (2). Spec should be updated.
+ // I take "host name match" to be "is identical to"
+ if (referring_url.host() != dictionary_url.host())
+ return;
+ if (referring_url.SchemeIs("https"))
+ return;
+ if (fetcher_.get())
+ fetcher_->Schedule(dictionary_url);
+}
+
+bool SdchManager::AddSdchDictionary(const std::string& dictionary_text,
+ const GURL& dictionary_url) {
+ std::string client_hash;
+ std::string server_hash;
+ GenerateHash(dictionary_text, &client_hash, &server_hash);
+ if (dictionaries_.find(server_hash) != dictionaries_.end())
+ return false; // Already loaded.
+
+ std::string domain, path;
+ std::set<int> ports;
+ Time expiration;
+
+ size_t header_end = dictionary_text.find("\n\n");
+ if (std::string::npos == header_end)
+ return false; // Missing header.
+ size_t line_start = 0; // Start of line being parsed.
+ while (1) {
+ size_t line_end = dictionary_text.find('\n', line_start);
+ DCHECK(std::string::npos != line_end);
+ DCHECK(line_end <= header_end);
+
+ size_t colon_index = dictionary_text.find(':', line_start);
+ if (std::string::npos == colon_index)
+ return false; // Illegal line missing a colon.
+
+ if (colon_index > line_end)
+ break;
+
+ size_t value_start = dictionary_text.find_first_not_of(" \t",
+ colon_index + 1);
+ if (std::string::npos != value_start) {
+ if (value_start >= line_end)
+ break;
+ std::string name(dictionary_text, line_start, colon_index - line_start);
+ std::string value(dictionary_text, value_start, line_end - value_start);
+ name = StringToLowerASCII(name);
+ if (name == "domain") {
+ domain = value;
+ } else if (name == "path") {
+ path = value;
+ } else if (name == "format-version") {
+ if (value != "1.0")
+ return false;
+ } else if (name == "max-age") {
+ expiration = Time::Now() + TimeDelta::FromSeconds(StringToInt64(value));
+ } else if (name == "port") {
+ int port = StringToInt(value);
+ if (port >= 0)
+ ports.insert(port);
+ }
+ }
+
+ if (line_end >= header_end)
+ break;
+ line_start = line_end + 1;
+ }
+
+ if (!Dictionary::CanSet(domain, path, ports, dictionary_url))
+ return false;
+
+ DHISTOGRAM_COUNTS(L"Sdch.Dictionary size loaded", dictionary_text.size());
+ DLOG(INFO) << "Loaded dictionary with client hash " << client_hash <<
+ " and server hash " << server_hash;
+ Dictionary* dictionary =
+ new Dictionary(dictionary_text, header_end + 2, client_hash,
+ dictionary_url, domain, path, expiration, ports);
+ dictionary->AddRef();
+ dictionaries_[server_hash] = dictionary;
+ return true;
+}
+
+void SdchManager::GetVcdiffDictionary(const std::string& server_hash,
+ const GURL& referring_url, Dictionary** dictionary) {
+ *dictionary = NULL;
+ DictionaryMap::iterator it = dictionaries_.find(server_hash);
+ if (it == dictionaries_.end())
+ return;
+ Dictionary* matching_dictionary = it->second;
+ if (!matching_dictionary->CanUse(referring_url))
+ return;
+ *dictionary = matching_dictionary;
+}
+
+// TODO(jar): If we have evictions from the dictionaries_, then we need to
+// change this interface to return a list of reference counted Dictionary
+// instances that can be used if/when a server specifies one.
+void SdchManager::GetAvailDictionaryList(const GURL& target_url,
+ std::string* list) {
+ for (DictionaryMap::iterator it = dictionaries_.begin();
+ it != dictionaries_.end(); ++it) {
+ if (!it->second->CanAdvertise(target_url))
+ continue;
+ if (!list->empty())
+ list->append(",");
+ list->append(it->second->client_hash());
+ }
+}
+
+SdchManager::Dictionary::Dictionary(const std::string& dictionary_text,
+ size_t offset, const std::string& client_hash, const GURL& gurl,
+ const std::string& domain, const std::string& path, const Time& expiration,
+ const std::set<int> ports)
+ : text_(dictionary_text, offset),
+ client_hash_(client_hash),
+ url_(gurl),
+ domain_(domain),
+ path_(path),
+ expiration_(expiration),
+ ports_(ports) {
+}
+
+// static
+void SdchManager::GenerateHash(const std::string& dictionary_text,
+ std::string* client_hash, std::string* server_hash) {
+ char binary_hash[32];
+ base::SHA256HashString(dictionary_text, binary_hash, sizeof(binary_hash));
+
+ std::string first_48_bits(&binary_hash[0], 6);
+ std::string second_48_bits(&binary_hash[6], 6);
+ UrlSafeBase64Encode(first_48_bits, client_hash);
+ UrlSafeBase64Encode(second_48_bits, server_hash);
+
+ DCHECK(server_hash->length() == 8);
+ DCHECK(client_hash->length() == 8);
+}
+
+// static
+void SdchManager::UrlSafeBase64Encode(const std::string& input,
+ std::string* output) {
+ // Since this is only done during a dictionary load, and hashes are only 8
+ // characters, we just do the simple fixup, rather than rewriting the encoder.
+ net::Base64Encode(input, output);
+ for (size_t i = 0; i < output->size(); ++i) {
+ switch (output->data()[i]) {
+ case '+':
+ (*output)[i] = '-';
+ continue;
+ case '/':
+ (*output)[i] = '_';
+ continue;
+ default:
+ continue;
+ }
+ }
+}
+
+//------------------------------------------------------------------------------
+// Security functions restricting loads and use of dictionaries.
+
+// static
+int SdchManager::Dictionary::GetPortIncludingDefault(const GURL& url) {
+ std::string port(url.port());
+ if (port.length())
+ return StringToInt(port);
+ if (url.scheme() == "http")
+ return 80; // Default port value.
+ // TODO(jar): If sdch supports other schemes, then write a general function
+ // or surface functionality hidden in url_cannon_stdurl.cc into url_canon.h.
+ return -1;
+}
+
+// static
+bool SdchManager::Dictionary::CanSet(const std::string& domain,
+ const std::string& path,
+ const std::set<int> ports,
+ const GURL& dictionary_url) {
+ if (!SdchManager::Global()->IsInSupportedDomain(dictionary_url))
+ return false;
+ /*
+ A dictionary is invalid and must not be stored if any of the following are
+ true:
+ 1. The dictionary has no Domain attribute.
+ 2. The effective host name that derives from the referer URL host name does
+ not domain-match the Domain attribute.
+ 3. The Domain attribute is a top level domain.
+ 4. The referer URL host is a host domain name (not IP address) and has the
+ form HD, where D is the value of the Domain attribute, and H is a string
+ that contains one or more dots.
+ 5. If the dictionary has a Port attribute and the referer URL's port was not
+ in the list.
+ */
+ if (domain.empty())
+ return false; // Domain is required.
+ if (0 ==
+ net::RegistryControlledDomainService::GetDomainAndRegistry(domain).size())
+ return false; // domain was a TLD.
+ if (!Dictionary::DomainMatch(dictionary_url, domain))
+ return false;
+
+ // TODO(jar): Enforce item 4 above.
+
+ if (!ports.empty()
+ && 0 == ports.count(GetPortIncludingDefault(dictionary_url)))
+ return false;
+ return true;
+}
+
+// static
+bool SdchManager::Dictionary::CanUse(const GURL referring_url) {
+ if (!SdchManager::Global()->IsInSupportedDomain(referring_url))
+ return false;
+ /*
+ 1. The request URL's host name domain-matches the Domain attribute of the
+ dictionary.
+ 2. If the dictionary has a Port attribute, the request port is one of the
+ ports listed in the Port attribute.
+ 3. The request URL path-matches the path attribute of the dictionary.
+ 4. The request is not an HTTPS request.
+*/
+ if (!DomainMatch(referring_url, domain_))
+ return false;
+ if (!ports_.empty()
+ && 0 == ports_.count(GetPortIncludingDefault(referring_url)))
+ return false;
+ if (path_.size() && !PathMatch(referring_url.path(), path_))
+ return false;
+ if (referring_url.SchemeIsSecure())
+ return false;
+ return true;
+}
+
+bool SdchManager::Dictionary::CanAdvertise(const GURL& target_url) {
+ if (!SdchManager::Global()->IsInSupportedDomain(target_url))
+ return false;
+ /* The specific rules of when a dictionary should be advertised in an
+ Avail-Dictionary header are modeled after the rules for cookie scoping. The
+ terms "domain-match" and "pathmatch" are defined in RFC 2965 [6]. A
+ dictionary may be advertised in the Avail-Dictionaries header exactly when
+ all of the following are true:
+ 1. The server's effective host name domain-matches the Domain attribute of
+ the dictionary.
+ 2. If the dictionary has a Port attribute, the request port is one of the
+ ports listed in the Port attribute.
+ 3. The request URI path-matches the path header of the dictionary.
+ 4. The request is not an HTTPS request.
+ */
+ if (!DomainMatch(target_url, domain_))
+ return false;
+ if (!ports_.empty() && 0 == ports_.count(GetPortIncludingDefault(target_url)))
+ return false;
+ if (path_.size() && !PathMatch(target_url.path(), path_))
+ return false;
+ if (target_url.SchemeIsSecure())
+ return false;
+ return true;
+}
+
+bool SdchManager::Dictionary::PathMatch(const std::string& path,
+ const std::string& restriction) {
+ /* Must be either:
+ 1. P2 is equal to P1
+ 2. P2 is a prefix of P1 and either the final character in P2 is "/" or the
+ character following P2 in P1 is "/".
+ */
+ if (path == restriction)
+ return true;
+ size_t prefix_length = restriction.size();
+ if (prefix_length > path.size())
+ return false; // Can't be a prefix.
+ if (0 != restriction.compare(0, prefix_length, path))
+ return false;
+ return restriction[prefix_length - 1] == '/' || path[prefix_length] == '/';
+}
+
+// static
+bool SdchManager::Dictionary::DomainMatch(const GURL& gurl,
+ const std::string& restriction) {
+ // TODO(jar): This is not precisely a domain match definition.
+ return gurl.DomainIs(restriction.data(), restriction.size());
+}
diff --git a/net/base/sdch_manager.h b/net/base/sdch_manager.h
new file mode 100644
index 0000000..998b0a3
--- /dev/null
+++ b/net/base/sdch_manager.h
@@ -0,0 +1,202 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Provides global database of differential decompression dictionaries for the
+// SDCH filter (processes sdch enconded content).
+
+// Exactly one instance of SdchManager is built, and all references are made
+// into that collection.
+//
+// The SdchManager maintains a collection of memory resident dictionaries. It
+// can find a dictionary (based on a server specification of a hash), store a
+// dictionary, and make judgements about what URLs can use, set, etc. a
+// dictionary.
+
+// These dictionaries are acquired over the net, and include a header
+// (containing metadata) as well as a VCDIFF dictionary (for use by a VCDIFF
+// module) to decompress data.
+
+#ifndef NET_BASE_SDCH_MANAGER_H_
+#define NET_BASE_SDCH_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/ref_counted.h"
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+
+
+//------------------------------------------------------------------------------
+// Create a public interface to help us load SDCH dictionaries.
+// The SdchManager class allows registration to support this interface.
+// A browser may register a fetcher that is used by the dictionary managers to
+// get data from a specified URL. This allows us to use very high level browser
+// functionality in this base (when the functionaity can be provided).
+class SdchFetcher {
+ public:
+ SdchFetcher() {}
+ virtual ~SdchFetcher() {}
+
+ // The Schedule() method is called when there is a need to get a dictionary
+ // from a server. The callee is responsible for getting that dictionary_text,
+ // and then calling back to AddSdchDictionary() to the SdchManager instance.
+ virtual void Schedule(const GURL& dictionary_url) = 0;
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SdchFetcher);
+};
+//------------------------------------------------------------------------------
+
+class SdchManager {
+ public:
+ // There is one instance of |Dictionary| for each memory-cached SDCH
+ // dictionary.
+ class Dictionary : public base::RefCounted<Dictionary> {
+ public:
+ // Sdch filters can get our text to use in decoding compressed data.
+ const std::string& text() const { return text_; }
+
+ private:
+ friend SdchManager; // Only manager can struct an instance.
+
+ // Construct a vc-diff usable dictionary from the dictionary_text starting
+ // at the given offset. The supplied client_hash should be used to
+ // advertise the dictionary's availability relative to the suppplied URL.
+ Dictionary(const std::string& dictionary_text, size_t offset,
+ const std::string& client_hash, const GURL& url,
+ const std::string& domain, const std::string& path,
+ const Time& expiration, const std::set<int> ports);
+
+ const GURL& url() const { return url_; }
+ const std::string& client_hash() const { return client_hash_; }
+
+ // For a given URL, get the actual or default port.
+ static int GetPortIncludingDefault(const GURL& url);
+
+ // Security method to check if we can advertise this dictionary for use
+ // if the |target_url| returns SDCH compressed data.
+ bool CanAdvertise(const GURL& target_url);
+
+ // Security methods to check if we can establish a new dictionary with the
+ // given data, that arrived in response to get of dictionary_url.
+ static bool CanSet(const std::string& domain, const std::string& path,
+ const std::set<int> ports, const GURL& dictionary_url);
+
+ // Security method to check if we can use a dictionary to decompress a
+ // target that arrived with a reference to this dictionary.
+ bool CanUse(const GURL referring_url);
+
+ // Compare paths to see if they "match" for dictionary use.
+ static bool PathMatch(const std::string& path,
+ const std::string& restriction);
+
+ // Compare domains to see if the "match" for dictionary use.
+ static bool DomainMatch(const GURL& url, const std::string& restriction);
+
+ // Each dictionary payload consists of several headers, followed by the text
+ // of the dictionary. The following are the known headers.
+ std::string domain_attribute_;
+ std::set<int> ports_;
+
+ // The actual text of the dictionary.
+ std::string text_;
+
+ // Part of the hash of text_ that the client uses to advertise the fact that
+ // it has a specific dictionary pre-cached.
+ std::string client_hash_;
+
+ // The GURL that arrived with the text_ in a URL request to specify where
+ // this dictionary may be used.
+ const GURL url_;
+
+ // Metadate "headers" in before dictionary text contained the following:
+ const std::string domain_;
+ const std::string path_;
+ const Time expiration_; // Implied by max-age.
+ const std::set<int> ports;
+
+ DISALLOW_COPY_AND_ASSIGN(Dictionary);
+ };
+
+ SdchManager();
+ ~SdchManager();
+
+ // Provide access to the single instance of this class.
+ static SdchManager* Global();
+
+ // Register a fetcher that this class can use to obtain dictionaries.
+ void set_sdch_fetcher(SdchFetcher* fetcher) { fetcher_.reset(fetcher); }
+
+ // If called with an empty string, advertise and support sdch on all domains.
+ // If called with a specific string, advertise and support only the specified
+ // domain.
+ static void enable_sdch_support(const std::string& domain) {
+ // We presume that there is a SDCH manager instance.
+ global_->supported_domain_ = domain;
+ global_->sdch_enabled_ = true;
+ }
+
+ const bool IsInSupportedDomain(const GURL& url) const;
+
+ // Schedule the URL fetching to load a dictionary. This will generally return
+ // long before the dictionary is actually loaded and added.
+ // After the implied task does completes, the dictionary will have been
+ // cached in memory.
+ void FetchDictionary(const GURL& referring_url, const GURL& dictionary_url);
+
+ // Add an SDCH dictionary to our list of availible dictionaries. This addition
+ // will fail (return false) if addition is illegal (data in the dictionary is
+ // not acceptable from the dictionary_url; dictionary already added, etc.).
+ bool AddSdchDictionary(const std::string& dictionary_text,
+ const GURL& dictionary_url);
+
+ // Find the vcdiff dictionary (the body of the sdch dictionary that appears
+ // after the meta-data headers like Domain:...) with the given |server_hash|
+ // to use to decompreses data that arrived as SDCH encoded content. Check to
+ // be sure the returned |dictionary| can be used for decoding content supplied
+ // in response to a request for |referring_url|.
+ // Caller is responsible for AddRef()ing the dictionary, and Release()ing it
+ // when done.
+ // Return null in |dictionary| if there is no matching legal dictionary.
+ void GetVcdiffDictionary(const std::string& server_hash, const GURL& referring_url,
+ Dictionary** dictionary);
+
+ // Get list of available (pre-cached) dictionaries that we have already loaded
+ // into memory. The list is a comma separated list of (client) hashes per
+ // the SDCH spec.
+ void GetAvailDictionaryList(const GURL& target_url, std::string* list);
+
+ // Construct the pair of hashes for client and server to identify an SDCH
+ // dictionary. This is only made public to facilitate unit testing, but is
+ // otherwise private
+ static void GenerateHash(const std::string& dictionary_text,
+ std::string* client_hash, std::string* server_hash);
+
+ private:
+ // A map of dictionaries info indexed by the hash that the server provides.
+ typedef std::map<std::string, Dictionary*> DictionaryMap;
+
+ // The one global instance of that holds all the data.
+ static SdchManager* global_;
+
+ // A simple implementatino of a RFC 3548 "URL safe" base64 encoder.
+ static void UrlSafeBase64Encode(const std::string& input,
+ std::string* output);
+ DictionaryMap dictionaries_;
+
+ // An instance that can fetch a dictionary given a URL.
+ scoped_ptr<SdchFetcher> fetcher_;
+
+ // Support SDCH compression, by advertising in headers.
+ bool sdch_enabled_;
+
+ // Empty string means all domains. Non-empty means support only the given
+ // domain is supported.
+ std::string supported_domain_;
+
+ DISALLOW_COPY_AND_ASSIGN(SdchManager);
+};
+
+#endif // NET_BASE_SDCH_MANAGER_H_
diff --git a/net/build/net.vcproj b/net/build/net.vcproj
index d4e779b..77b21f1 100644
--- a/net/build/net.vcproj
+++ b/net/build/net.vcproj
@@ -21,7 +21,7 @@
<Configuration
Name="Debug|Win32"
ConfigurationType="4"
- InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\debug.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;$(SolutionDir)..\sdch\using_sdch.vsprops"
>
<Tool
Name="VCPreBuildEventTool"
@@ -78,7 +78,7 @@
<Configuration
Name="Release|Win32"
ConfigurationType="4"
- InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops"
+ InheritedPropertySheets="$(SolutionDir)..\build\common.vsprops;$(SolutionDir)..\build\release.vsprops;$(SolutionDir)..\third_party\icu38\build\using_icu.vsprops;$(SolutionDir)..\third_party\zlib\using_zlib.vsprops;$(SolutionDir)..\sdch\using_sdch.vsprops"
>
<Tool
Name="VCPreBuildEventTool"
@@ -167,6 +167,14 @@
<File
RelativePath="..\base\bzip2_filter.cc"
>
+ <FileConfiguration
+ Name="Debug|Win32"
+ >
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalIncludeDirectories=""
+ />
+ </FileConfiguration>
</File>
<File
RelativePath="..\base\bzip2_filter.h"
@@ -357,11 +365,11 @@
>
</File>
<File
- RelativePath="..\base\net_util_win.cc"
+ RelativePath="..\base\net_util.h"
>
</File>
<File
- RelativePath="..\base\net_util.h"
+ RelativePath="..\base\net_util_win.cc"
>
</File>
<File
@@ -405,6 +413,22 @@
>
</File>
<File
+ RelativePath="..\base\sdch_filter.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\sdch_filter.h"
+ >
+ </File>
+ <File
+ RelativePath="..\base\sdch_manager.cc"
+ >
+ </File>
+ <File
+ RelativePath="..\base\sdch_manager.h"
+ >
+ </File>
+ <File
RelativePath="..\base\socket.h"
>
</File>
diff --git a/net/build/net_unittests.vcproj b/net/build/net_unittests.vcproj
index 9d0cd3c..479c34b 100644
--- a/net/build/net_unittests.vcproj
+++ b/net/build/net_unittests.vcproj
@@ -343,6 +343,10 @@
>
</File>
<File
+ RelativePath="..\base\sdch_filter_unitest.cc"
+ >
+ </File>
+ <File
RelativePath="..\base\ssl_client_socket_unittest.cc"
>
</File>
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index 8316e73..094acd8 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -5,12 +5,15 @@
#include "net/url_request/url_request_http_job.h"
#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/file_version_info.h"
#include "base/message_loop.h"
#include "base/string_util.h"
#include "net/base/cookie_monster.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/base/net_util.h"
+#include "net/base/sdch_manager.h"
#include "net/http/http_response_info.h"
#include "net/http/http_transaction.h"
#include "net/http/http_transaction_factory.h"
@@ -159,15 +162,20 @@ int URLRequestHttpJob::GetResponseCode() {
return response_info_->headers->response_code();
}
-bool URLRequestHttpJob::GetContentEncoding(std::string* encoding_type) {
+bool URLRequestHttpJob::GetContentEncodings(
+ std::vector<std::string>* encoding_types) {
DCHECK(transaction_);
if (!response_info_)
return false;
- // TODO(darin): what if there are multiple content encodings?
- return response_info_->headers->EnumerateHeader(NULL, "Content-Encoding",
- encoding_type);
+ std::string encoding_type;
+ void* iter = NULL;
+ while (response_info_->headers->EnumerateHeader(&iter, "Content-Encoding",
+ &encoding_type)) {
+ encoding_types->push_back(encoding_type);
+ }
+ return !encoding_types->empty();
}
bool URLRequestHttpJob::IsRedirectResponse(GURL* location,
@@ -413,6 +421,23 @@ void URLRequestHttpJob::NotifyHeadersComplete() {
}
}
+ // Get list of SDCH dictionary requests, and schedule them to be loaded.
+ if (SdchManager::Global()->IsInSupportedDomain(request_->url())) {
+ static const std::string name = "Get-Dictionary";
+ std::string url_text;
+ void* iter = NULL;
+ // TODO(jar): We need to not fetch dictionaries the first time they are
+ // seen, but rather wait until we can justify their usefulness.
+ // For now, we will only fetch the first dictionary, which will at least
+ // require multiple suggestions before we get additional ones for this site.
+ // Eventually we should wait until a dictionary is requested several times
+ // before we even download it (so that we don't waste memory or bandwidth).
+ if (response_info_->headers->EnumerateHeader(&iter, name, &url_text)) {
+ GURL dictionary_url = request_->url().Resolve(url_text);
+ SdchManager::Global()->FetchDictionary(request_->url(), dictionary_url);
+ }
+ }
+
URLRequestJob::NotifyHeadersComplete();
}
@@ -476,8 +501,32 @@ void URLRequestHttpJob::AddExtraHeaders() {
context->accept_charset() + "\r\n";
}
+ if (!SdchManager::Global()->IsInSupportedDomain(request_->url())) {
+ // Tell the server what compression formats we support (other than SDCH).
+ request_info_.extra_headers += "Accept-Encoding: gzip,deflate,bzip2\r\n";
+ return;
+ }
+
+ // Supply SDCH related headers, as well as accepting that encoding.
+
+ // TODO(jar): See if it is worth optimizing away these bytes when the URL is
+ // probably an img or such. (and SDCH encoding is not likely).
+ std::string avail_dictionaries;
+ SdchManager::Global()->GetAvailDictionaryList(request_->url(),
+ &avail_dictionaries);
+ if (!avail_dictionaries.empty())
+ request_info_.extra_headers += "Avail-Dictionary: "
+ + avail_dictionaries + "\r\n";
+
+ scoped_ptr<FileVersionInfo> file_version_info(
+ FileVersionInfo::CreateFileVersionInfoForCurrentModule());
+ request_info_.extra_headers += "X-SDCH: Chrome ";
+ request_info_.extra_headers +=
+ WideToASCII(file_version_info->product_version());
+ request_info_.extra_headers += "\r\n";
+
// Tell the server what compression formats we support.
- request_info_.extra_headers += "Accept-Encoding: gzip,deflate,bzip2\r\n";
+ request_info_.extra_headers += "Accept-Encoding: gzip,deflate,bzip2,sdch\r\n";
}
void URLRequestHttpJob::FetchResponseCookies() {
diff --git a/net/url_request/url_request_http_job.h b/net/url_request/url_request_http_job.h
index 9312e35..908db2c 100644
--- a/net/url_request/url_request_http_job.h
+++ b/net/url_request/url_request_http_job.h
@@ -2,8 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H__
-#define NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H__
+#ifndef NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
+
+#include <string>
+#include <vector>
#include "net/base/completion_callback.h"
#include "net/http/http_request_info.h"
@@ -24,7 +27,7 @@ class URLRequestHttpJob : public URLRequestJob {
virtual ~URLRequestHttpJob();
protected:
- URLRequestHttpJob(URLRequest* request);
+ explicit URLRequestHttpJob(URLRequest* request);
// URLRequestJob methods:
virtual void SetUpload(net::UploadData* upload);
@@ -38,7 +41,7 @@ class URLRequestHttpJob : public URLRequestJob {
virtual void GetResponseInfo(net::HttpResponseInfo* info);
virtual bool GetResponseCookies(std::vector<std::string>* cookies);
virtual int GetResponseCode();
- virtual bool GetContentEncoding(std::string* encoding_type);
+ virtual bool GetContentEncodings(std::vector<std::string>* encoding_type);
virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
virtual bool IsSafeRedirect(const GURL& location);
virtual bool NeedsAuth();
@@ -77,12 +80,11 @@ class URLRequestHttpJob : public URLRequestJob {
bool read_in_progress_;
- // Keep a reference to the url request context to be sure it's not
- // deleted before us.
+ // Keep a reference to the url request context to be sure it's not deleted
+ // before us.
scoped_refptr<URLRequestContext> context_;
- DISALLOW_EVIL_CONSTRUCTORS(URLRequestHttpJob);
+ DISALLOW_COPY_AND_ASSIGN(URLRequestHttpJob);
};
-#endif // NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H__
-
+#endif // NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
diff --git a/net/url_request/url_request_job.cc b/net/url_request/url_request_job.cc
index b0517ba..0501286 100644
--- a/net/url_request/url_request_job.cc
+++ b/net/url_request/url_request_job.cc
@@ -47,11 +47,13 @@ void URLRequestJob::DetachRequest() {
}
void URLRequestJob::SetupFilter() {
- std::string encoding_type;
- if (GetContentEncoding(&encoding_type)) {
+ std::vector<std::string> encoding_types;
+ if (GetContentEncodings(&encoding_types)) {
std::string mime_type;
GetMimeType(&mime_type);
- filter_.reset(Filter::Factory(encoding_type, mime_type, kFilterBufSize));
+ filter_.reset(Filter::Factory(encoding_types, mime_type, kFilterBufSize));
+ if (filter_.get())
+ filter_->SetURL(request_->url());
}
}
@@ -174,7 +176,7 @@ bool URLRequestJob::ReadFilteredData(int *bytes_read) {
// Get filtered data
int filtered_data_len = read_buffer_len_;
Filter::FilterStatus status;
- status = filter_->ReadFilteredData(read_buffer_, &filtered_data_len);
+ status = filter_->ReadData(read_buffer_, &filtered_data_len);
switch (status) {
case Filter::FILTER_DONE: {
*bytes_read = filtered_data_len;
diff --git a/net/url_request/url_request_job.h b/net/url_request/url_request_job.h
index b70bbc7..1e4a089 100644
--- a/net/url_request/url_request_job.h
+++ b/net/url_request/url_request_job.h
@@ -5,6 +5,7 @@
#ifndef NET_URL_REQUEST_URL_REQUEST_JOB_H_
#define NET_URL_REQUEST_URL_REQUEST_JOB_H_
+#include <string>
#include <vector>
#include "base/basictypes.h"
@@ -28,7 +29,7 @@ class URLRequestJobMetrics;
// UrlRequestFileJob.
class URLRequestJob : public base::RefCountedThreadSafe<URLRequestJob> {
public:
- URLRequestJob(URLRequest* request);
+ explicit URLRequestJob(URLRequest* request);
virtual ~URLRequestJob();
// Returns the request that owns this job. THIS POINTER MAY BE NULL if the
@@ -44,7 +45,8 @@ class URLRequestJob : public base::RefCountedThreadSafe<URLRequestJob> {
// Sets extra request headers for Job types that support request headers.
virtual void SetExtraRequestHeaders(const std::string& headers) { }
- // If any error occurs while starting the Job, NotifyStartError should be called.
+ // If any error occurs while starting the Job, NotifyStartError should be
+ // called.
// This helps ensure that all errors follow more similar notification code
// paths, which should simplify testing.
virtual void Start() = 0;
@@ -108,10 +110,18 @@ class URLRequestJob : public base::RefCountedThreadSafe<URLRequestJob> {
// Returns the HTTP response code for the request.
virtual int GetResponseCode() { return -1; }
- // Called to fetch the encoding type for this request. Only makes sense for
+ // Called to fetch the encoding types for this request. Only makes sense for
// some types of requests. Returns true on success. Calling this on a request
// that doesn't have or specify an encoding type will return false.
- virtual bool GetContentEncoding(std::string* encoding_type) { return false; }
+ // Returns a array of strings showing the sequential encodings used on the
+ // content. For example, types[0] = "sdch" and types[1] = gzip, means the
+ // content was first encoded by sdch, and then encoded by gzip. To decode,
+ // a series of filters must be applied in the reverse order (in the above
+ // example, ungzip first, and then sdch expand).
+ // TODO(jar): Cleaner API would return an array of enums.
+ virtual bool GetContentEncodings(std::vector<std::string>* encoding_types) {
+ return false;
+ }
// Called to setup stream filter for this request. An example of filter is
// content encoding/decoding.