diff options
author | cira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-11 00:20:09 +0000 |
---|---|---|
committer | cira@chromium.org <cira@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-02-11 00:20:09 +0000 |
commit | 1e07f78cf624f3ab8f14a21d80c8999595da21ea (patch) | |
tree | 41002a66bbe994cf56155696d309fbbc159c2d02 | |
parent | 9393b71775c7ea64004df0461d0564361bddd553 (diff) | |
download | chromium_src-1e07f78cf624f3ab8f14a21d80c8999595da21ea.zip chromium_src-1e07f78cf624f3ab8f14a21d80c8999595da21ea.tar.gz chromium_src-1e07f78cf624f3ab8f14a21d80c8999595da21ea.tar.bz2 |
Replace __MSG_some_name__ template within extension css files with localized messages.
We avoid replacing messages within html and js extension files for security reasons. Also, developers can already localize messages in html/js using chrome.i18n.getMessage calls.
TEST=Localize extension, try body{direction: __MSG_@@bidi_reversed_dir__;} in popup.css, while using non-rtl locale. Text should be alligned to the right (as if we were using rtl locale).
BUG=26144
Review URL: http://codereview.chromium.org/570007
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@38717 0039d316-1c4b-4281-b951-d872f2087c98
17 files changed, 732 insertions, 50 deletions
diff --git a/chrome/browser/renderer_host/resource_dispatcher_host.cc b/chrome/browser/renderer_host/resource_dispatcher_host.cc index 4885423..9a245c7 100644 --- a/chrome/browser/renderer_host/resource_dispatcher_host.cc +++ b/chrome/browser/renderer_host/resource_dispatcher_host.cc @@ -51,6 +51,7 @@ #include "chrome/browser/ssl/ssl_manager.h" #include "chrome/browser/worker_host/worker_service.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension_l10n_util.h" #include "chrome/common/notification_service.h" #include "chrome/common/render_messages.h" #include "net/base/auth.h" @@ -465,6 +466,8 @@ void ResourceDispatcherHost::BeginRequest( ResourceType::IsFrame(request_data.resource_type), // allow_download request_data.host_renderer_id, request_data.host_render_view_id); + extension_l10n_util::ApplyMessageFilterPolicy( + request_data.url, request_data.resource_type, extra_info); SetRequestInfo(request, extra_info); // Request takes ownership. chrome_browser_net::SetOriginProcessUniqueIDForRequest( request_data.origin_child_id, request); diff --git a/chrome/browser/renderer_host/resource_message_filter.cc b/chrome/browser/renderer_host/resource_message_filter.cc index 7cf9b0e..6f427c3 100644 --- a/chrome/browser/renderer_host/resource_message_filter.cc +++ b/chrome/browser/renderer_host/resource_message_filter.cc @@ -1343,14 +1343,17 @@ void ResourceMessageFilter::OnGetExtensionMessageBundleOnFileThread( IPC::Message* reply_msg) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); - std::string error; - scoped_ptr<ExtensionMessageBundle> bundle( - extension_file_util::LoadExtensionMessageBundle( - extension_path, default_locale, &error)); - std::map<std::string, std::string> dictionary_map; - if (bundle.get()) - dictionary_map = *bundle->dictionary(); + if (!default_locale.empty()) { + // Touch disk only if extension is localized. + std::string error; + scoped_ptr<ExtensionMessageBundle> bundle( + extension_file_util::LoadExtensionMessageBundle( + extension_path, default_locale, &error)); + + if (bundle.get()) + dictionary_map = *bundle->dictionary(); + } ViewHostMsg_GetExtensionMessageBundle::WriteReplyParams( reply_msg, dictionary_map); diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index 9391b6d..59097a4 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -153,6 +153,8 @@ 'common/extensions/extension_l10n_util.h', 'common/extensions/extension_message_bundle.cc', 'common/extensions/extension_message_bundle.h', + 'common/extensions/extension_message_filter_peer.cc', + 'common/extensions/extension_message_filter_peer.h', 'common/extensions/extension_resource.cc', 'common/extensions/extension_resource.h', 'common/extensions/extension_unpacker.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 01c3a9a..950e04b 100755 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -840,6 +840,7 @@ 'common/extensions/extension_action_unittest.cc', 'common/extensions/extension_l10n_util_unittest.cc', 'common/extensions/extension_message_bundle_unittest.cc', + 'common/extensions/extension_message_filter_peer_unittest.cc', 'common/extensions/extension_unpacker_unittest.cc', 'common/extensions/update_manifest_unittest.cc', 'common/extensions/url_pattern_unittest.cc', diff --git a/chrome/common/extensions/extension_l10n_util.cc b/chrome/common/extensions/extension_l10n_util.cc index dcc8cc5..2fd3f92 100644 --- a/chrome/common/extensions/extension_l10n_util.cc +++ b/chrome/common/extensions/extension_l10n_util.cc @@ -15,10 +15,12 @@ #include "base/values.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/extensions/extension_file_util.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_message_bundle.h" #include "chrome/common/json_value_serializer.h" +#include "chrome/common/url_constants.h" #include "unicode/uloc.h" namespace errors = extension_manifest_errors; @@ -294,4 +296,15 @@ ExtensionMessageBundle* LoadMessageCatalogs( return ExtensionMessageBundle::Create(catalogs, error); } +void ApplyMessageFilterPolicy(const GURL& url, + const ResourceType::Type& resource_type, + ResourceDispatcherHostRequestInfo* request_info) { + // Apply filter only to chrome extension css files that don't have + // security filter already set. + if (url.SchemeIs(chrome::kExtensionScheme) && + request_info->filter_policy() == FilterPolicy::DONT_FILTER && + resource_type == ResourceType::STYLESHEET) + request_info->set_filter_policy(FilterPolicy::FILTER_EXTENSION_MESSAGES); +} + } // namespace extension_l10n_util diff --git a/chrome/common/extensions/extension_l10n_util.h b/chrome/common/extensions/extension_l10n_util.h index 60733d1..a63bfdb 100644 --- a/chrome/common/extensions/extension_l10n_util.h +++ b/chrome/common/extensions/extension_l10n_util.h @@ -11,10 +11,14 @@ #include <string> #include <vector> +#include "webkit/glue/resource_type.h" + class DictionaryValue; class Extension; class ExtensionMessageBundle; class FilePath; +class GURL; +class ResourceDispatcherHostRequestInfo; struct ExtensionInfo; namespace extension_l10n_util { @@ -91,6 +95,12 @@ ExtensionMessageBundle* LoadMessageCatalogs( const std::set<std::string>& valid_locales, std::string* error); +// Applies FilterPolicy::FILTER_EXTENSION_MESSAGES to all text/css requests +// that have "chrome-extension://" scheme. +void ApplyMessageFilterPolicy(const GURL& url, + const ResourceType::Type& resource_type, + ResourceDispatcherHostRequestInfo* request_info); + } // namespace extension_l10n_util #endif // CHROME_COMMON_EXTENSIONS_EXTENSION_L10N_UTIL_H_ diff --git a/chrome/common/extensions/extension_l10n_util_unittest.cc b/chrome/common/extensions/extension_l10n_util_unittest.cc index 93b16d3..278a4ed 100644 --- a/chrome/common/extensions/extension_l10n_util_unittest.cc +++ b/chrome/common/extensions/extension_l10n_util_unittest.cc @@ -10,12 +10,15 @@ #include "base/scoped_ptr.h" #include "base/scoped_temp_dir.h" #include "base/values.h" +#include "chrome/browser/renderer_host/resource_dispatcher_host_request_info.h" +#include "chrome/browser/renderer_host/resource_handler.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_l10n_util.h" #include "chrome/common/extensions/extension_message_bundle.h" #include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/resource_type.h" namespace errors = extension_manifest_errors; namespace keys = extension_manifest_keys; @@ -379,4 +382,97 @@ TEST(ExtensionL10nUtil, ShouldRelocalizeManifestDifferentCurrentLocale) { EXPECT_TRUE(extension_l10n_util::ShouldRelocalizeManifest(info)); } +class DummyResourceHandler : public ResourceHandler { + public: + DummyResourceHandler() {} + + bool OnRequestRedirected(int request_id, const GURL& url, + ResourceResponse* response, bool* defer) { + return true; + } + + bool OnResponseStarted(int request_id, ResourceResponse* response) { + return true; + } + + bool OnWillRead( + int request_id, net::IOBuffer** buf, int* buf_size, int min_size) { + return true; + } + + bool OnReadCompleted(int request_id, int* bytes_read) { return true; } + + bool OnResponseCompleted( + int request_id, const URLRequestStatus& status, const std::string& info) { + return true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(DummyResourceHandler); +}; + +class ApplyMessageFilterPolicyTest : public testing::Test { + protected: + void SetUp() { + url_.reset(new GURL( + "chrome-extension://behllobkkfkfnphdnhnkndlbkcpglgmj/popup.html")); + resource_type_ = ResourceType::STYLESHEET; + request_info_.reset(CreateNewResourceRequestInfo()); + } + + ResourceDispatcherHostRequestInfo* CreateNewResourceRequestInfo() { + return new ResourceDispatcherHostRequestInfo( + new DummyResourceHandler(), ChildProcessInfo::RENDER_PROCESS, 0, 0, 0, + "not important", "not important", + ResourceType::STYLESHEET, 0U, false, false, -1, -1); + } + + scoped_ptr<GURL> url_; + ResourceType::Type resource_type_; + scoped_ptr<ResourceDispatcherHostRequestInfo> request_info_; +}; + +TEST_F(ApplyMessageFilterPolicyTest, WrongScheme) { + url_.reset(new GURL("html://behllobkkfkfnphdnhnkndlbkcpglgmj/popup.html")); + extension_l10n_util::ApplyMessageFilterPolicy( + *url_, resource_type_, request_info_.get()); + + EXPECT_EQ(FilterPolicy::DONT_FILTER, request_info_->filter_policy()); +} + +TEST_F(ApplyMessageFilterPolicyTest, GoodScheme) { + extension_l10n_util::ApplyMessageFilterPolicy( + *url_, resource_type_, request_info_.get()); + + EXPECT_EQ(FilterPolicy::FILTER_EXTENSION_MESSAGES, + request_info_->filter_policy()); +} + +TEST_F(ApplyMessageFilterPolicyTest, GoodSchemeWithSecurityFilter) { + request_info_->set_filter_policy(FilterPolicy::FILTER_ALL_EXCEPT_IMAGES); + extension_l10n_util::ApplyMessageFilterPolicy( + *url_, resource_type_, request_info_.get()); + + EXPECT_EQ(FilterPolicy::FILTER_ALL_EXCEPT_IMAGES, + request_info_->filter_policy()); +} + +TEST_F(ApplyMessageFilterPolicyTest, GoodSchemeWrongResourceType) { + resource_type_ = ResourceType::MAIN_FRAME; + extension_l10n_util::ApplyMessageFilterPolicy( + *url_, resource_type_, request_info_.get()); + + EXPECT_EQ(FilterPolicy::DONT_FILTER, request_info_->filter_policy()); +} + +TEST_F(ApplyMessageFilterPolicyTest, WrongSchemeResourceAndFilter) { + url_.reset(new GURL("html://behllobkkfkfnphdnhnkndlbkcpglgmj/popup.html")); + resource_type_ = ResourceType::MEDIA; + request_info_->set_filter_policy(FilterPolicy::FILTER_ALL); + extension_l10n_util::ApplyMessageFilterPolicy( + *url_, resource_type_, request_info_.get()); + + EXPECT_EQ(FilterPolicy::FILTER_ALL, request_info_->filter_policy()); +} + } // namespace diff --git a/chrome/common/extensions/extension_message_bundle.cc b/chrome/common/extensions/extension_message_bundle.cc index dd2f4c0..0256914 100644 --- a/chrome/common/extensions/extension_message_bundle.cc +++ b/chrome/common/extensions/extension_message_bundle.cc @@ -11,6 +11,7 @@ #include "base/hash_tables.h" #include "base/linked_ptr.h" #include "base/scoped_ptr.h" +#include "base/singleton.h" #include "base/stl_util-inl.h" #include "base/string_util.h" #include "base/values.h" @@ -209,7 +210,13 @@ bool ExtensionMessageBundle::ReplacePlaceholders( bool ExtensionMessageBundle::ReplaceMessages(std::string* text, std::string* error) const { - return ReplaceVariables(dictionary_, kMessageBegin, kMessageEnd, text, error); + return ReplaceMessagesWithExternalDictionary(dictionary_, text, error); +} + +// static +bool ExtensionMessageBundle::ReplaceMessagesWithExternalDictionary( + const SubstitutionMap& dictionary, std::string* text, std::string* error) { + return ReplaceVariables(dictionary, kMessageBegin, kMessageEnd, text, error); } // static @@ -283,3 +290,22 @@ std::string ExtensionMessageBundle::GetL10nMessage( return ""; } + +/////////////////////////////////////////////////////////////////////////////// +// +// Renderer helper functions. +// +/////////////////////////////////////////////////////////////////////////////// + +ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() { + return &Singleton<ExtensionToMessagesMap>()->messages_map; +} + +L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id) { + ExtensionToL10nMessagesMap::iterator it = + Singleton<ExtensionToMessagesMap>()->messages_map.find(extension_id); + if (it != Singleton<ExtensionToMessagesMap>()->messages_map.end()) + return &(it->second); + + return NULL; +} diff --git a/chrome/common/extensions/extension_message_bundle.h b/chrome/common/extensions/extension_message_bundle.h index eb6eaa4..6e59fe3 100644 --- a/chrome/common/extensions/extension_message_bundle.h +++ b/chrome/common/extensions/extension_message_bundle.h @@ -71,6 +71,9 @@ class ExtensionMessageBundle { // Returns false if there is a message in text that's not defined in the // dictionary. bool ReplaceMessages(std::string* text, std::string* error) const; + // Static version that accepts dictionary. + static bool ReplaceMessagesWithExternalDictionary( + const SubstitutionMap& dictionary, std::string* text, std::string* error); // Replaces each occurance of variable placeholder with its value. // I.e. replaces __MSG_name__ with value from the catalog with the key "name". @@ -145,4 +148,28 @@ class ExtensionMessageBundle { SubstitutionMap dictionary_; }; +/////////////////////////////////////////////////////////////////////////////// +// +// Renderer helper typedefs and functions. +// +/////////////////////////////////////////////////////////////////////////////// + +// A map of message name to message. +typedef std::map<std::string, std::string> L10nMessagesMap; + +// A map of extension ID to l10n message map. +typedef std::map<std::string, L10nMessagesMap > ExtensionToL10nMessagesMap; + +// Unique class for Singleton. +struct ExtensionToMessagesMap { + // Maps extension ID to message map. + ExtensionToL10nMessagesMap messages_map; +}; + +// Returns the extension_id to messages map. +ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap(); + +// Returns message map that matches given extension_id, or NULL. +L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id); + #endif // CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGE_BUNDLE_H_ diff --git a/chrome/common/extensions/extension_message_bundle_unittest.cc b/chrome/common/extensions/extension_message_bundle_unittest.cc index f4d1f77..94ffd37 100644 --- a/chrome/common/extensions/extension_message_bundle_unittest.cc +++ b/chrome/common/extensions/extension_message_bundle_unittest.cc @@ -390,3 +390,36 @@ TEST(ExtensionMessageBundle, ReplaceMessagesInText) { EXPECT_EQ(test_cases[i].result, text); } } + +/////////////////////////////////////////////////////////////////////////////// +// +// Renderer helper functions test. +// +/////////////////////////////////////////////////////////////////////////////// + +TEST(GetExtensionToL10nMessagesMapTest, ReturnsTheSameObject) { + ExtensionToL10nMessagesMap* map1 = GetExtensionToL10nMessagesMap(); + ASSERT_TRUE(NULL != map1); + + ExtensionToL10nMessagesMap* map2 = GetExtensionToL10nMessagesMap(); + ASSERT_EQ(map1, map2); +} + +TEST(GetExtensionToL10nMessagesMapTest, ReturnsNullForUnknownExtensionId) { + const std::string extension_id("some_unique_12334212314234_id"); + L10nMessagesMap* map = GetL10nMessagesMap(extension_id); + EXPECT_TRUE(NULL == map); +} + +TEST(GetExtensionToL10nMessagesMapTest, ReturnsMapForKnownExtensionId) { + const std::string extension_id("some_unique_121212121212121_id"); + // Store a map for given id. + L10nMessagesMap messages; + messages.insert(std::make_pair("message_name", "message_value")); + (*GetExtensionToL10nMessagesMap())[extension_id] = messages; + + L10nMessagesMap* map = GetL10nMessagesMap(extension_id); + ASSERT_TRUE(NULL != map); + EXPECT_EQ(1U, map->size()); + EXPECT_EQ("message_value", (*map)["message_name"]); +} diff --git a/chrome/common/extensions/extension_message_filter_peer.cc b/chrome/common/extensions/extension_message_filter_peer.cc new file mode 100644 index 0000000..1c0c1d0 --- /dev/null +++ b/chrome/common/extensions/extension_message_filter_peer.cc @@ -0,0 +1,130 @@ +// Copyright (c) 2010 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/common/extensions/extension_message_filter_peer.h" + +#include "app/l10n_util.h" +#include "base/string_util.h" +#include "chrome/common/extensions/extension_message_bundle.h" +#include "chrome/common/render_messages.h" +#include "grit/generated_resources.h" +#include "grit/renderer_resources.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "webkit/glue/webkit_glue.h" + +ExtensionMessageFilterPeer::ExtensionMessageFilterPeer( + webkit_glue::ResourceLoaderBridge::Peer* peer, + IPC::Message::Sender* message_sender, + const GURL& request_url) + : original_peer_(peer), + message_sender_(message_sender), + request_url_(request_url) { +} + +ExtensionMessageFilterPeer::~ExtensionMessageFilterPeer() { +} + +// static +ExtensionMessageFilterPeer* +ExtensionMessageFilterPeer::CreateExtensionMessageFilterPeer( + webkit_glue::ResourceLoaderBridge::Peer* peer, + IPC::Message::Sender* message_sender, + const std::string& mime_type, + FilterPolicy::Type filter_policy, + const GURL& request_url) { + if (filter_policy != FilterPolicy::FILTER_EXTENSION_MESSAGES) + return NULL; + + if (StartsWithASCII(mime_type, "text/css", false)) + return new ExtensionMessageFilterPeer(peer, message_sender, request_url); + + // Return NULL if content is not text/css or it doesn't belong to extension + // scheme. + return NULL; +} + +void ExtensionMessageFilterPeer::OnUploadProgress( + uint64 position, uint64 size) { + NOTREACHED(); +} + +bool ExtensionMessageFilterPeer::OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies) { + NOTREACHED(); + return false; +} + +void ExtensionMessageFilterPeer::OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered) { + response_info_ = info; +} + +void ExtensionMessageFilterPeer::OnReceivedData(const char* data, int len) { + data_.append(data, len); +} + +void ExtensionMessageFilterPeer::OnCompletedRequest( + const URLRequestStatus& status, const std::string& security_info) { + // Make sure we delete ourselves at the end of this call. + scoped_ptr<ExtensionMessageFilterPeer> this_deleter(this); + + // Give sub-classes a chance at altering the data. + if (status.status() != URLRequestStatus::SUCCESS) { + // We failed to load the resource. + original_peer_->OnReceivedResponse(response_info_, true); + URLRequestStatus status(URLRequestStatus::CANCELED, net::ERR_ABORTED); + original_peer_->OnCompletedRequest(status, security_info); + return; + } + + ReplaceMessages(); + + original_peer_->OnReceivedResponse(response_info_, true); + if (!data_.empty()) + original_peer_->OnReceivedData(data_.data(), + static_cast<int>(data_.size())); + original_peer_->OnCompletedRequest(status, security_info); +} + +GURL ExtensionMessageFilterPeer::GetURLForDebugging() const { + return original_peer_->GetURLForDebugging(); +} + +void ExtensionMessageFilterPeer::ReplaceMessages() { + if (!message_sender_ || data_.empty()) + return; + + if (!request_url_.is_valid()) + return; + + std::string extension_id = request_url_.host(); + L10nMessagesMap* l10n_messages = GetL10nMessagesMap(extension_id); + if (!l10n_messages) { + L10nMessagesMap messages; + message_sender_->Send(new ViewHostMsg_GetExtensionMessageBundle( + extension_id, &messages)); + + // Save messages we got, even if they are empty, so we don't have to + // ask again. + ExtensionToL10nMessagesMap& l10n_messages_map = + *GetExtensionToL10nMessagesMap(); + l10n_messages_map[extension_id] = messages; + + l10n_messages = GetL10nMessagesMap(extension_id); + } + + if (l10n_messages->empty()) + return; + + std::string error; + if (ExtensionMessageBundle::ReplaceMessagesWithExternalDictionary( + *l10n_messages, &data_, &error)) { + data_.resize(data_.size()); + } +} diff --git a/chrome/common/extensions/extension_message_filter_peer.h b/chrome/common/extensions/extension_message_filter_peer.h new file mode 100644 index 0000000..137ab36 --- /dev/null +++ b/chrome/common/extensions/extension_message_filter_peer.h @@ -0,0 +1,80 @@ +// Copyright (c) 2010 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. + + +#ifndef CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGE_FILTER_PEER_H_ +#define CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGE_FILTER_PEER_H_ + +#include <string> + +#include "chrome/common/filter_policy.h" +#include "ipc/ipc_message.h" +#include "webkit/glue/resource_loader_bridge.h" + +// The ExtensionMessageFilterPeer is a proxy to a +// webkit_glue::ResourceLoaderBridge::Peer instance. It is used to pre-process +// extension resources (such as css files). +// Call the factory method CreateExtensionMessageFilterPeer() to obtain an +// instance of ExtensionMessageFilterPeer based on the original Peer. +class ExtensionMessageFilterPeer + : public webkit_glue::ResourceLoaderBridge::Peer { + public: + virtual ~ExtensionMessageFilterPeer(); + + static ExtensionMessageFilterPeer* CreateExtensionMessageFilterPeer( + webkit_glue::ResourceLoaderBridge::Peer* peer, + IPC::Message::Sender* message_sender, + const std::string& mime_type, + FilterPolicy::Type filter_policy, + const GURL& request_url); + + // ResourceLoaderBridge::Peer methods. + virtual void OnUploadProgress(uint64 position, uint64 size); + virtual bool OnReceivedRedirect( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies); + virtual void OnReceivedResponse( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered); + virtual void OnReceivedData(const char* data, int len); + virtual void OnCompletedRequest(const URLRequestStatus& status, + const std::string& security_info); + virtual GURL GetURLForDebugging() const; + + private: + friend class ExtensionMessageFilterPeerTest; + + // Use CreateExtensionMessageFilterPeer to create an instance. + ExtensionMessageFilterPeer( + webkit_glue::ResourceLoaderBridge::Peer* peer, + IPC::Message::Sender* message_sender, + const GURL& request_url); + + // Loads message catalogs, and replaces all __MSG_some_name__ templates within + // loaded file. + void ReplaceMessages(); + + // Original peer that handles the request once we are done processing data_. + webkit_glue::ResourceLoaderBridge::Peer* original_peer_; + + // We just pass though the response info. This holds the copy of the original. + webkit_glue::ResourceLoaderBridge::ResponseInfo response_info_; + + // Sends ViewHostMsg_GetExtensionMessageBundle message to the browser to fetch + // message catalog. + IPC::Message::Sender* message_sender_; + + // Buffer for incoming data. We wait until OnCompletedRequest before using it. + std::string data_; + + // Original request URL. + GURL request_url_; + + private: + DISALLOW_COPY_AND_ASSIGN(ExtensionMessageFilterPeer); +}; + +#endif // CHROME_COMMON_EXTENSIONS_EXTENSION_MESSAGE_FILTER_PEER_H_ diff --git a/chrome/common/extensions/extension_message_filter_peer_unittest.cc b/chrome/common/extensions/extension_message_filter_peer_unittest.cc new file mode 100644 index 0000000..f0a73d7 --- /dev/null +++ b/chrome/common/extensions/extension_message_filter_peer_unittest.cc @@ -0,0 +1,260 @@ +// Copyright (c) 2010 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 <map> +#include <string> + +#include "base/scoped_ptr.h" +#include "chrome/common/extensions/extension_message_bundle.h" +#include "chrome/common/extensions/extension_message_filter_peer.h" +#include "chrome/common/filter_policy.h" +#include "ipc/ipc_message.h" +#include "net/base/net_errors.h" +#include "net/url_request/url_request_status.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "webkit/glue/resource_loader_bridge.h" + +using testing::_; +using testing::StrEq; +using testing::Return; + +static const char* const kExtensionUrl_1 = + "chrome-extension://some_id/popup.css"; + +static const char* const kExtensionUrl_2 = + "chrome-extension://some_id2/popup.css"; + +static const char* const kExtensionUrl_3 = + "chrome-extension://some_id3/popup.css"; + +class MockIpcMessageSender : public IPC::Message::Sender { + public: + MockIpcMessageSender() {} + virtual ~MockIpcMessageSender() {} + + MOCK_METHOD1(Send, bool(IPC::Message* message)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockIpcMessageSender); +}; + +class MockResourceLoaderBridgePeer + : public webkit_glue::ResourceLoaderBridge::Peer { + public: + MockResourceLoaderBridgePeer() {} + virtual ~MockResourceLoaderBridgePeer() {} + + MOCK_METHOD2(OnUploadProgress, void(uint64 position, uint64 size)); + MOCK_METHOD4(OnReceivedRedirect, bool( + const GURL& new_url, + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool* has_new_first_party_for_cookies, + GURL* new_first_party_for_cookies)); + MOCK_METHOD2(OnReceivedResponse, void( + const webkit_glue::ResourceLoaderBridge::ResponseInfo& info, + bool content_filtered)); + MOCK_METHOD2(OnReceivedData, void(const char* data, int len)); + MOCK_METHOD2(OnCompletedRequest, void( + const URLRequestStatus& status, const std::string& security_info)); + MOCK_CONST_METHOD0(GetURLForDebugging, GURL()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockResourceLoaderBridgePeer); +}; + +class ExtensionMessageFilterPeerTest : public testing::Test { + protected: + virtual void SetUp() { + sender_.reset(new MockIpcMessageSender()); + original_peer_.reset(new MockResourceLoaderBridgePeer()); + filter_peer_.reset( + ExtensionMessageFilterPeer::CreateExtensionMessageFilterPeer( + original_peer_.get(), sender_.get(), "text/css", + FilterPolicy::FILTER_EXTENSION_MESSAGES, GURL(kExtensionUrl_1))); + } + + ExtensionMessageFilterPeer* CreateExtensionMessageFilterPeer( + const std::string& mime_type, + FilterPolicy::Type filter_policy, + const GURL& request_url) { + return ExtensionMessageFilterPeer::CreateExtensionMessageFilterPeer( + original_peer_.get(), sender_.get(), + mime_type, filter_policy, request_url); + } + + std::string GetData(ExtensionMessageFilterPeer* filter_peer) { + EXPECT_TRUE(NULL != filter_peer); + return filter_peer->data_; + } + + void SetData(ExtensionMessageFilterPeer* filter_peer, + const std::string& data) { + EXPECT_TRUE(NULL != filter_peer); + filter_peer->data_ = data; + } + + scoped_ptr<MockIpcMessageSender> sender_; + scoped_ptr<MockResourceLoaderBridgePeer> original_peer_; + scoped_ptr<ExtensionMessageFilterPeer> filter_peer_; +}; + +TEST_F(ExtensionMessageFilterPeerTest, CreateWithWrongFilterPolicy) { + filter_peer_.reset(CreateExtensionMessageFilterPeer( + "text/css", FilterPolicy::DONT_FILTER, GURL(kExtensionUrl_1))); + EXPECT_TRUE(NULL == filter_peer_.get()); +} + +TEST_F(ExtensionMessageFilterPeerTest, CreateWithWrongMimeType) { + filter_peer_.reset(CreateExtensionMessageFilterPeer( + "text/html", + FilterPolicy::FILTER_EXTENSION_MESSAGES, + GURL(kExtensionUrl_1))); + EXPECT_TRUE(NULL == filter_peer_.get()); +} + +TEST_F(ExtensionMessageFilterPeerTest, CreateWithValidInput) { + EXPECT_TRUE(NULL != filter_peer_.get()); +} + +TEST_F(ExtensionMessageFilterPeerTest, OnReceivedData) { + EXPECT_TRUE(GetData(filter_peer_.get()).empty()); + + const std::string data_chunk("12345"); + filter_peer_->OnReceivedData(data_chunk.c_str(), data_chunk.length()); + + EXPECT_EQ(data_chunk, GetData(filter_peer_.get())); + + filter_peer_->OnReceivedData(data_chunk.c_str(), data_chunk.length()); + EXPECT_EQ(data_chunk + data_chunk, GetData(filter_peer_.get())); +} + +MATCHER_P(IsURLRequestEqual, status, "") { return arg.status() == status; } + +TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestBadURLRequestStatus) { + // It will self-delete once it exits OnCompletedRequest. + ExtensionMessageFilterPeer* filter_peer = filter_peer_.release(); + + EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true)); + EXPECT_CALL(*original_peer_, OnCompletedRequest( + IsURLRequestEqual(URLRequestStatus::CANCELED), "")); + + URLRequestStatus status; + status.set_status(URLRequestStatus::FAILED); + filter_peer->OnCompletedRequest(status, ""); +} + +TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestEmptyData) { + // It will self-delete once it exits OnCompletedRequest. + ExtensionMessageFilterPeer* filter_peer = filter_peer_.release(); + + EXPECT_CALL(*original_peer_, GetURLForDebugging()).Times(0); + EXPECT_CALL(*original_peer_, OnReceivedData(_, _)).Times(0); + EXPECT_CALL(*sender_, Send(_)).Times(0); + + EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true)); + EXPECT_CALL(*original_peer_, OnCompletedRequest( + IsURLRequestEqual(URLRequestStatus::SUCCESS), "")); + + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + filter_peer->OnCompletedRequest(status, ""); +} + +TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestNoCatalogs) { + // It will self-delete once it exits OnCompletedRequest. + ExtensionMessageFilterPeer* filter_peer = filter_peer_.release(); + + SetData(filter_peer, "some text"); + + EXPECT_CALL(*original_peer_, GetURLForDebugging()).Times(0); + EXPECT_CALL(*sender_, Send(_)); + + std::string data = GetData(filter_peer); + EXPECT_CALL(*original_peer_, + OnReceivedData(StrEq(data.data()), data.length())).Times(2); + + EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true)).Times(2); + EXPECT_CALL(*original_peer_, OnCompletedRequest( + IsURLRequestEqual(URLRequestStatus::SUCCESS), "")).Times(2); + + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + filter_peer->OnCompletedRequest(status, ""); + + // Test if Send gets called again (it shouldn't be) when first call returned + // an empty dictionary. + filter_peer = CreateExtensionMessageFilterPeer( + "text/css", + FilterPolicy::FILTER_EXTENSION_MESSAGES, + GURL(kExtensionUrl_1)); + SetData(filter_peer, "some text"); + filter_peer->OnCompletedRequest(status, ""); +} + +TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestWithCatalogs) { + // It will self-delete once it exits OnCompletedRequest. + ExtensionMessageFilterPeer* filter_peer = CreateExtensionMessageFilterPeer( + "text/css", + FilterPolicy::FILTER_EXTENSION_MESSAGES, + GURL(kExtensionUrl_2)); + + L10nMessagesMap messages; + messages.insert(std::make_pair("text", "new text")); + ExtensionToL10nMessagesMap& l10n_messages_map = + *GetExtensionToL10nMessagesMap(); + l10n_messages_map["some_id2"] = messages; + + SetData(filter_peer, "some __MSG_text__"); + + EXPECT_CALL(*original_peer_, GetURLForDebugging()).Times(0); + // We already have messages in memory, Send will be skipped. + EXPECT_CALL(*sender_, Send(_)).Times(0); + + // __MSG_text__ gets replaced with "new text". + std::string data("some new text"); + EXPECT_CALL(*original_peer_, + OnReceivedData(StrEq(data.data()), data.length())); + + EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true)); + EXPECT_CALL(*original_peer_, OnCompletedRequest( + IsURLRequestEqual(URLRequestStatus::SUCCESS), "")); + + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + filter_peer->OnCompletedRequest(status, ""); +} + +TEST_F(ExtensionMessageFilterPeerTest, OnCompletedRequestReplaceMessagesFails) { + // It will self-delete once it exits OnCompletedRequest. + ExtensionMessageFilterPeer* filter_peer = CreateExtensionMessageFilterPeer( + "text/css", + FilterPolicy::FILTER_EXTENSION_MESSAGES, + GURL(kExtensionUrl_3)); + + L10nMessagesMap messages; + messages.insert(std::make_pair("text", "new text")); + ExtensionToL10nMessagesMap& l10n_messages_map = + *GetExtensionToL10nMessagesMap(); + l10n_messages_map["some_id3"] = messages; + + std::string message("some __MSG_missing_message__"); + SetData(filter_peer, message); + + EXPECT_CALL(*original_peer_, GetURLForDebugging()).Times(0); + // We already have messages in memory, Send will be skipped. + EXPECT_CALL(*sender_, Send(_)).Times(0); + + // __MSG_missing_message__ is missing, so message stays the same. + EXPECT_CALL(*original_peer_, + OnReceivedData(StrEq(message.data()), message.length())); + + EXPECT_CALL(*original_peer_, OnReceivedResponse(_, true)); + EXPECT_CALL(*original_peer_, OnCompletedRequest( + IsURLRequestEqual(URLRequestStatus::SUCCESS), "")); + + URLRequestStatus status; + status.set_status(URLRequestStatus::SUCCESS); + filter_peer->OnCompletedRequest(status, ""); +} diff --git a/chrome/common/filter_policy.h b/chrome/common/filter_policy.h index efea7c1..51ba1e4 100644 --- a/chrome/common/filter_policy.h +++ b/chrome/common/filter_policy.h @@ -11,12 +11,17 @@ // can decide to filter it. The filtering is done in the renderer. This class // enumerates the different policy that can be used for the filtering. It is // passed along with resource response messages. +// It can be used for content post-processing, like message replacement within +// extension css files. class FilterPolicy { public: enum Type { // Pass all types of resources through unmodified. DONT_FILTER = 0, + // Post-process extension css files. + FILTER_EXTENSION_MESSAGES, + // Block all types of resources, except images. For images, modify them to // indicate that they have been filtered. // TODO(abarth): This is a misleading name for this enum value. We should diff --git a/chrome/common/resource_dispatcher.cc b/chrome/common/resource_dispatcher.cc index 8eb74a5..7cbb2a8 100644 --- a/chrome/common/resource_dispatcher.cc +++ b/chrome/common/resource_dispatcher.cc @@ -12,6 +12,7 @@ #include "base/message_loop.h" #include "base/shared_memory.h" #include "base/string_util.h" +#include "chrome/common/extensions/extension_message_filter_peer.h" #include "chrome/common/render_messages.h" #include "chrome/common/security_filter_peer.h" #include "net/base/net_errors.h" @@ -179,7 +180,8 @@ bool IPCResourceLoaderBridge::Start(Peer* peer) { peer_ = peer; // generate the request ID, and append it to the message - request_id_ = dispatcher_->AddPendingRequest(peer_, request_.resource_type); + request_id_ = dispatcher_->AddPendingRequest( + peer_, request_.resource_type, request_.url); return dispatcher_->message_sender()->Send( new ViewHostMsg_RequestResource(routing_id_, request_id_, request_)); @@ -330,18 +332,28 @@ void ResourceDispatcher::OnReceivedResponse( PendingRequestInfo& request_info = it->second; request_info.filter_policy = response_head.filter_policy; webkit_glue::ResourceLoaderBridge::Peer* peer = request_info.peer; - if (request_info.filter_policy != FilterPolicy::DONT_FILTER) { + webkit_glue::ResourceLoaderBridge::Peer* new_peer = NULL; + if (request_info.filter_policy == FilterPolicy::FILTER_EXTENSION_MESSAGES) { + new_peer = ExtensionMessageFilterPeer::CreateExtensionMessageFilterPeer( + peer, + message_sender(), + response_head.mime_type, + request_info.filter_policy, + request_info.url); + } else if (request_info.filter_policy != FilterPolicy::DONT_FILTER) { // TODO(jcampan): really pass the loader bridge. - webkit_glue::ResourceLoaderBridge::Peer* new_peer = - SecurityFilterPeer::CreateSecurityFilterPeer( - NULL, peer, - request_info.resource_type, response_head.mime_type, - request_info.filter_policy, - net::ERR_INSECURE_RESPONSE); - if (new_peer) { - request_info.peer = new_peer; - peer = new_peer; - } + new_peer = SecurityFilterPeer::CreateSecurityFilterPeer( + NULL, + peer, + request_info.resource_type, + response_head.mime_type, + request_info.filter_policy, + net::ERR_INSECURE_RESPONSE); + } + + if (new_peer) { + request_info.peer = new_peer; + peer = new_peer; } RESOURCE_LOG("Dispatching response for " << @@ -452,10 +464,12 @@ void ResourceDispatcher::OnRequestComplete(int request_id, int ResourceDispatcher::AddPendingRequest( webkit_glue::ResourceLoaderBridge::Peer* callback, - ResourceType::Type resource_type) { + ResourceType::Type resource_type, + const GURL& request_url) { // Compute a unique request_id for this renderer process. int id = MakeRequestID(); - pending_requests_[id] = PendingRequestInfo(callback, resource_type); + pending_requests_[id] = + PendingRequestInfo(callback, resource_type, request_url); return id; } diff --git a/chrome/common/resource_dispatcher.h b/chrome/common/resource_dispatcher.h index 1d3f25a..92234f3 100644 --- a/chrome/common/resource_dispatcher.h +++ b/chrome/common/resource_dispatcher.h @@ -42,7 +42,8 @@ class ResourceDispatcher { // Adds a request from the pending_requests_ list, returning the new // requests' ID int AddPendingRequest(webkit_glue::ResourceLoaderBridge::Peer* callback, - ResourceType::Type resource_type); + ResourceType::Type resource_type, + const GURL& request_url); // Removes a request from the pending_requests_ list, returning true if the // request was found and removed. @@ -65,12 +66,14 @@ class ResourceDispatcher { struct PendingRequestInfo { PendingRequestInfo() { } PendingRequestInfo(webkit_glue::ResourceLoaderBridge::Peer* peer, - ResourceType::Type resource_type) + ResourceType::Type resource_type, + const GURL& request_url) : peer(peer), resource_type(resource_type), filter_policy(FilterPolicy::DONT_FILTER), is_deferred(false), - is_cancelled(false) { + is_cancelled(false), + url(request_url) { } ~PendingRequestInfo() { } webkit_glue::ResourceLoaderBridge::Peer* peer; @@ -79,6 +82,7 @@ class ResourceDispatcher { MessageQueue deferred_message_queue; bool is_deferred; bool is_cancelled; + GURL url; }; typedef base::hash_map<int, PendingRequestInfo> PendingRequestList; diff --git a/chrome/renderer/extensions/renderer_extension_bindings.cc b/chrome/renderer/extensions/renderer_extension_bindings.cc index 54231b6..2f50143 100644 --- a/chrome/renderer/extensions/renderer_extension_bindings.cc +++ b/chrome/renderer/extensions/renderer_extension_bindings.cc @@ -35,12 +35,6 @@ using bindings_utils::ExtensionBase; namespace { -// A map of message name to message. -typedef std::map<std::string, std::string> L10nMessagesMap; - -// A map of extension ID to l10n message map. -typedef std::map<std::string, L10nMessagesMap > ExtensionToL10nMessagesMap; - struct ExtensionData { struct PortData { int ref_count; // how many contexts have a handle to this port @@ -48,8 +42,6 @@ struct ExtensionData { PortData() : ref_count(0), disconnected(false) {} }; std::map<int, PortData> ports; // port ID -> data - // Maps extension ID to message map. - ExtensionToL10nMessagesMap extension_l10n_messages_map_; }; static bool HasPortData(int port_id) { @@ -65,21 +57,6 @@ static void ClearPortData(int port_id) { Singleton<ExtensionData>::get()->ports.erase(port_id); } -static ExtensionToL10nMessagesMap* GetExtensionToL10nMessagesMap() { - return &Singleton<ExtensionData>()->extension_l10n_messages_map_; -} - -static L10nMessagesMap* GetL10nMessagesMap(const std::string extension_id) { - ExtensionToL10nMessagesMap::iterator it = - Singleton<ExtensionData>()->extension_l10n_messages_map_.find( - extension_id); - if (it != Singleton<ExtensionData>()->extension_l10n_messages_map_.end()) { - return &(it->second); - } else { - return NULL; - } -} - const char kPortClosedError[] = "Attempting to use a disconnected port object"; const char* kExtensionDeps[] = { EventBindings::kName }; @@ -230,8 +207,6 @@ class ExtensionImpl : public ExtensionBase { l10n_messages_map[extension_id] = messages; l10n_messages = GetL10nMessagesMap(extension_id); - if (!l10n_messages) - return v8::Undefined(); } std::string message_name = *v8::String::AsciiValue(args[0]); |