diff options
-rw-r--r-- | chrome/browser/browser.vcproj | 8 | ||||
-rw-r--r-- | chrome/browser/browser_main.cc | 11 | ||||
-rw-r--r-- | chrome/browser/net/sdch_dictionary_fetcher.cc | 45 | ||||
-rw-r--r-- | chrome/browser/net/sdch_dictionary_fetcher.h | 66 | ||||
-rw-r--r-- | chrome/chrome.sln | 16 | ||||
-rw-r--r-- | chrome/chrome_kjs.sln | 15 | ||||
-rw-r--r-- | chrome/common/chrome_switches.cc | 7 | ||||
-rw-r--r-- | chrome/common/chrome_switches.h | 2 | ||||
-rw-r--r-- | net/base/bzip2_filter_unittest.cc | 62 | ||||
-rw-r--r-- | net/base/filter.cc | 80 | ||||
-rw-r--r-- | net/base/filter.h | 72 | ||||
-rw-r--r-- | net/base/gzip_filter_unittest.cc | 61 | ||||
-rw-r--r-- | net/base/sdch_filter.cc | 170 | ||||
-rw-r--r-- | net/base/sdch_filter.h | 95 | ||||
-rw-r--r-- | net/base/sdch_filter_unitest.cc | 441 | ||||
-rw-r--r-- | net/base/sdch_manager.cc | 336 | ||||
-rw-r--r-- | net/base/sdch_manager.h | 202 | ||||
-rw-r--r-- | net/build/net.vcproj | 32 | ||||
-rw-r--r-- | net/build/net_unittests.vcproj | 4 | ||||
-rw-r--r-- | net/url_request/url_request_http_job.cc | 59 | ||||
-rw-r--r-- | net/url_request/url_request_http_job.h | 20 | ||||
-rw-r--r-- | net/url_request/url_request_job.cc | 10 | ||||
-rw-r--r-- | net/url_request/url_request_job.h | 18 |
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. |