diff options
author | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-11 06:28:15 +0000 |
---|---|---|
committer | aa@chromium.org <aa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-11 06:28:15 +0000 |
commit | c7ad50f409be00bda164b2f60c29e2733eed1c94 (patch) | |
tree | aed183fbb1b944e6ef18c87e77cb49370ace834b | |
parent | 7e922f33d88905196067d901a14910a62c48ac21 (diff) | |
download | chromium_src-c7ad50f409be00bda164b2f60c29e2733eed1c94.zip chromium_src-c7ad50f409be00bda164b2f60c29e2733eed1c94.tar.gz chromium_src-c7ad50f409be00bda164b2f60c29e2733eed1c94.tar.bz2 |
Don't allow updating tabs to javascript URLs without host
permissions to that tab.
Cleaned up a few things along the way:
- added a GetExtension() method to
ExtensionFunctionDispatcher and ExtensionFunction since it
was used in more than one place.
- Removed first param from chrome.test.failCallback() since
it wasn't used anywhere.
- Added a convenience CanAccessHost() method to Extension,
since it seems likely to be commonly used.
- Refactored setup of mock host resolver in browsertest,
since the way it was, you could only customize it at the
testsuite level, not the test level.
Review URL: http://codereview.chromium.org/199074
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@25971 0039d316-1c4b-4281-b951-d872f2087c98
20 files changed, 166 insertions, 54 deletions
diff --git a/chrome/browser/extensions/extension_function.h b/chrome/browser/extensions/extension_function.h index 02bab68..d763696 100644 --- a/chrome/browser/extensions/extension_function.h +++ b/chrome/browser/extensions/extension_function.h @@ -66,6 +66,15 @@ class ExtensionFunction : public base::RefCounted<ExtensionFunction> { virtual void Run() = 0; protected: + // Gets the extension that called this function. This can return NULL for + // async functions. + Extension* GetExtension() { + if (dispatcher()) + return dispatcher()->GetExtension(); + else + return NULL; + } + // The peer to the dispatcher that will service this extension function call. scoped_refptr<ExtensionFunctionDispatcher::Peer> peer_; diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc index 276dd16..36f4192 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.cc +++ b/chrome/browser/extensions/extension_function_dispatcher.cc @@ -251,6 +251,16 @@ ExtensionHost* ExtensionFunctionDispatcher::GetExtensionHost() { return delegate_->GetExtensionHost(); } +Extension* ExtensionFunctionDispatcher::GetExtension() { + ExtensionsService* service = profile()->GetExtensionsService(); + DCHECK(service); + + Extension* extension = service->GetExtensionById(extension_id()); + DCHECK(extension); + + return extension; +} + void ExtensionFunctionDispatcher::HandleRequest(const std::string& name, const std::string& args, int request_id, diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h index 7a47b3f..492c431e 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.h +++ b/chrome/browser/extensions/extension_function_dispatcher.h @@ -13,6 +13,7 @@ #include "googleurl/src/gurl.h" class Browser; +class Extension; class ExtensionFunction; class ExtensionHost; class Profile; @@ -74,6 +75,10 @@ class ExtensionFunctionDispatcher { // tab hosted extension pages, this will return NULL. ExtensionHost* GetExtensionHost(); + // Gets the extension the function is being invoked by. This should not ever + // return NULL. + Extension* GetExtension(); + // Handle a malformed message. Possibly the result of an attack, so kill // the renderer. void HandleBadMessage(ExtensionFunction* api); diff --git a/chrome/browser/extensions/extension_javascript_url_apitest.cc b/chrome/browser/extensions/extension_javascript_url_apitest.cc new file mode 100644 index 0000000..87e4789 --- /dev/null +++ b/chrome/browser/extensions/extension_javascript_url_apitest.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2009 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/extensions/extension_apitest.h" + +IN_PROC_BROWSER_TEST_F(ExtensionApiTest, JavaScriptURLPermissions) { + host_resolver()->AddRule("a.com", "127.0.0.1"); + host_resolver()->AddRule("b.com", "127.0.0.1"); + StartHTTPServer(); + + ASSERT_TRUE(RunExtensionTest("javascript_url_permissions")) << message_; +} diff --git a/chrome/browser/extensions/extension_tabs_module.cc b/chrome/browser/extensions/extension_tabs_module.cc index e79c161..01788ea 100644 --- a/chrome/browser/extensions/extension_tabs_module.cc +++ b/chrome/browser/extensions/extension_tabs_module.cc @@ -21,6 +21,7 @@ #include "chrome/browser/window_sizer.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_error_utils.h" +#include "chrome/common/url_constants.h" #include "net/base/base64.h" #include "skia/ext/image_operations.h" #include "skia/ext/platform_canvas.h" @@ -44,10 +45,6 @@ static bool GetTabById(int tab_id, Profile* profile, Browser** browser, TabContents** contents, int* tab_index, std::string* error_message); -// Construct an absolute path from a relative path. -static GURL AbsolutePath(Profile* profile, const std::string& extension_id, - const std::string& relative_url); - int ExtensionTabUtil::GetWindowId(const Browser* browser) { return browser->session_id().id(); } @@ -471,7 +468,7 @@ bool CreateTabFunction::RunImpl() { url.reset(new GURL(url_string)); if (!url->is_valid()) { // The path as passed in is not valid. Try converting to absolute path. - *url = AbsolutePath(profile(), extension_id(), url_string); + *url = GetExtension()->GetResourceURL(url_string); if (!url->is_valid()) { error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url_string); @@ -558,7 +555,7 @@ bool UpdateTabFunction::RunImpl() { if (!new_gurl.is_valid()) { // The path as passed in is not valid. Try converting to absolute path. - new_gurl = AbsolutePath(profile(), extension_id(), url); + new_gurl = GetExtension()->GetResourceURL(url); if (!new_gurl.is_valid()) { error_ = ExtensionErrorUtils::FormatErrorMessage(keys::kInvalidUrlError, url); @@ -566,8 +563,26 @@ bool UpdateTabFunction::RunImpl() { } } + // JavaScript URLs can do the same kinds of things as cross-origin XHR, so + // we need to check host permissions before allowing them. + if (new_gurl.SchemeIs(chrome::kJavaScriptScheme)) { + if (!GetExtension()->CanAccessHost(contents->GetURL())) { + error_ = ExtensionErrorUtils::FormatErrorMessage( + keys::kCannotAccessPageError, contents->GetURL().spec()); + return false; + } + + // TODO(aa): How does controller queue URLs? Is there any chance that this + // JavaScript URL will end up applying to something other than + // controller->GetURL()? + } + controller.LoadURL(new_gurl, GURL(), PageTransition::LINK); - DCHECK_EQ(new_gurl.spec(), contents->GetURL().spec()); + + // The URL of a tab contents never actually changes to a JavaScript URL, so + // this check only makes sense in other cases. + if (!new_gurl.SchemeIs(chrome::kJavaScriptScheme)) + DCHECK_EQ(new_gurl.spec(), contents->GetURL().spec()); } bool selected = false; @@ -828,13 +843,6 @@ static Browser* GetBrowserInProfileWithId(Profile* profile, return NULL; } -static GURL AbsolutePath(Profile* profile, const std::string& extension_id, - const std::string& relative_url) { - ExtensionsService* service = profile->GetExtensionsService(); - Extension* extension = service->GetExtensionById(extension_id); - return Extension::GetResourceURL(extension->url(), relative_url); -} - static bool GetTabById(int tab_id, Profile* profile, Browser** browser, TabStripModel** tab_strip, TabContents** contents, diff --git a/chrome/browser/extensions/extension_tabs_module_constants.cc b/chrome/browser/extensions/extension_tabs_module_constants.cc index da73ec6..a143e44 100644 --- a/chrome/browser/extensions/extension_tabs_module_constants.cc +++ b/chrome/browser/extensions/extension_tabs_module_constants.cc @@ -42,6 +42,8 @@ const char kInvalidUrlError[] = "Invalid url: \"*\"."; const char kInternalVisibleTabCaptureError[] = "Internal error while trying to capture visible region of the current tab"; const char kNotImplementedError[] = "This call is not yet implemented"; +const char kCannotAccessPageError[] = "Cannot access contents of url \"*\". " + "Extension manifest must request permission to access this host."; const char kGetWindowFunction[] = "windows.get"; const char kGetCurrentWindowFunction[] = "windows.getCurrent"; diff --git a/chrome/browser/extensions/extension_tabs_module_constants.h b/chrome/browser/extensions/extension_tabs_module_constants.h index e6e73a0..574389a 100644 --- a/chrome/browser/extensions/extension_tabs_module_constants.h +++ b/chrome/browser/extensions/extension_tabs_module_constants.h @@ -47,6 +47,7 @@ extern const char kNoSelectedTabError[]; extern const char kInvalidUrlError[]; extern const char kInternalVisibleTabCaptureError[]; extern const char kNotImplementedError[]; +extern const char kCannotAccessPageError[]; // Function names, Windows API. extern const char kGetWindowFunction[]; diff --git a/chrome/browser/search_engines/template_url_scraper_unittest.cc b/chrome/browser/search_engines/template_url_scraper_unittest.cc index a93790f..32173d0 100644 --- a/chrome/browser/search_engines/template_url_scraper_unittest.cc +++ b/chrome/browser/search_engines/template_url_scraper_unittest.cc @@ -20,13 +20,6 @@ class TemplateURLScraperTest : public InProcessBrowserTest { TemplateURLScraperTest() { } - protected: - virtual void ConfigureHostResolverProc(net::RuleBasedHostResolverProc* proc) { - InProcessBrowserTest::ConfigureHostResolverProc(proc); - // We use foo.com in our tests. - proc->AddRule("*.foo.com", "localhost"); - } - private: DISALLOW_COPY_AND_ASSIGN(TemplateURLScraperTest); }; @@ -61,6 +54,8 @@ class TemplateURLModelLoader : public NotificationObserver { /* IN_PROC_BROWSER_TEST_F(TemplateURLScraperTest, ScrapeWithOnSubmit) { + host_resolver()->AddRule("*.foo.com", "localhost"); + TemplateURLModel* template_urls = browser()->profile()->GetTemplateURLModel(); TemplateURLModelLoader loader(template_urls); diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index c8a1cde..782a764 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -62,6 +62,7 @@ 'browser/extensions/extension_apitest.cc', 'browser/extensions/extension_apitest.h', 'browser/extensions/extension_bookmarks_apitest.cc', + 'browser/extensions/extension_javascript_url_apitest.cc', 'browser/extensions/extension_browsertest.cc', 'browser/extensions/extension_browsertest.h', 'browser/extensions/extension_browsertests_misc.cc', @@ -90,6 +91,7 @@ 'browser/extensions/extension_apitest.cc', 'browser/extensions/extension_apitest.h', 'browser/extensions/extension_bookmarks_apitest.cc', + 'browser/extensions/extension_javascript_url_apitest.cc', 'browser/extensions/extension_browsertest.cc', 'browser/extensions/extension_browsertest.h', 'browser/extensions/extension_browsertests_misc.cc', diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index df59fa1..2566d41 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -982,6 +982,10 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_id, return false; } + // The path component is not used for host permissions, so we force it to + // match all paths. + pattern.set_path("/*"); + host_permissions_.push_back(pattern); } } @@ -1087,6 +1091,16 @@ FilePath Extension::GetIconPath(Icons icon) { return GetResourcePath(iter->second); } +bool Extension::CanAccessHost(const GURL& url) const { + for (HostPermissions::const_iterator host = host_permissions_.begin(); + host != host_permissions_.end(); ++host) { + if (host->MatchesUrl(url)) + return true; + } + + return false; +} + const std::set<std::string> Extension::GetEffectiveHostPermissions() const { std::set<std::string> effective_hosts; diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index e8cdd10..5116bd3 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -199,12 +199,16 @@ class Extension { const std::vector<PluginInfo>& plugins() const { return plugins_; } const GURL& background_url() const { return background_url_; } const std::vector<ToolstripInfo>& toolstrips() const { return toolstrips_; } - const HostPermissions& host_permissions() const { - return host_permissions_; - } const std::vector<std::string>& api_permissions() const { return api_permissions_; } + const HostPermissions& host_permissions() const { + return host_permissions_; + } + + // Returns true if the extension has permission to access the host for the + // specified URL. + bool CanAccessHost(const GURL& url) const; // Returns the set of hosts that the extension effectively has access to. This // is used in the permissions UI and is a combination of the hosts accessible diff --git a/chrome/common/extensions/url_pattern.h b/chrome/common/extensions/url_pattern.h index f69cd7a..d952216 100644 --- a/chrome/common/extensions/url_pattern.h +++ b/chrome/common/extensions/url_pattern.h @@ -98,6 +98,10 @@ class URLPattern { // Gets the path the pattern matches with the leading slash. This can have // embedded asterisks which are interpreted using glob rules. std::string path() const { return path_; } + void set_path(const std::string& path) { + path_ = path; + path_escaped_ = ""; + } private: // Returns true if |test| matches our host. diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd index 81f924f..d55f1a2 100644 --- a/chrome/renderer/renderer_resources.grd +++ b/chrome/renderer/renderer_resources.grd @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- This comment is only here because changes to resources are not picked up -without changes to the corresponding grd file. ek1 --> +without changes to the corresponding grd file. aa1 --> <grit latest_public_release="0" current_release="1"> <outputs> <output filename="grit/renderer_resources.h" type="rc_header"> diff --git a/chrome/renderer/resources/extension_apitest.js b/chrome/renderer/resources/extension_apitest.js index 9e4ed5e..7b2d583 100644 --- a/chrome/renderer/resources/extension_apitest.js +++ b/chrome/renderer/resources/extension_apitest.js @@ -151,8 +151,12 @@ var chrome = chrome || {}; // Wrapper for generating test functions, that takes care of calling // assertNoLastError() and (optionally) succeed() for you. chrome.test.callback = function(func, expectedError) { - chrome.test.assertEq(typeof(func), 'function'); + if (func) { + chrome.test.assertEq(typeof(func), 'function'); + } + callbackAdded(); + return function() { if (expectedError == null) { chrome.test.assertNoLastError(); @@ -160,7 +164,11 @@ var chrome = chrome || {}; chrome.test.assertEq(typeof(expectedError), 'string'); chrome.test.assertEq(expectedError, chrome.extension.lastError.message); } - safeFunctionApply(func, arguments); + + if (func) { + safeFunctionApply(func, arguments); + } + callbackCompleted(); }; }; @@ -179,8 +187,8 @@ var chrome = chrome || {}; return chrome.test.callback(func); }; - chrome.test.callbackFail = function(func, expectedError) { - return chrome.test.callback(func, expectedError); + chrome.test.callbackFail = function(expectedError) { + return chrome.test.callback(null, expectedError); }; // TODO(erikkay) This is deprecated and should be removed. diff --git a/chrome/test/data/extensions/api_test/README.txt b/chrome/test/data/extensions/api_test/README.txt index 7e1bc05..7f43d07 100755 --- a/chrome/test/data/extensions/api_test/README.txt +++ b/chrome/test/data/extensions/api_test/README.txt @@ -43,7 +43,7 @@ chrome.test.runTests([ chrome.bookmarks.get("1", chrome.test.callbackPass(function(results) {
chrome.test.assertTrue(compareNode(results[0], expected[0].children[0]));
}));
- chrome.bookmarks.get("42", chrome.test.callbackFail(function(){}, "Can't find bookmark for id."));
+ chrome.bookmarks.get("42", chrome.test.callbackFail("Can't find bookmark for id."));
},
function getArray() {
@@ -59,8 +59,9 @@ chrome.test.runTests([ // compareNode and compareTrees are helper functions that the bookmarks test
// uses for convenience. They're not really relevant to the framework itself.
-Note that chrome.test.callbackFail takes a second argument which is the error
-message that it expects to get when the callback fails (chrome.extension.lastError.message).
+Note that chrome.test.callbackFail takes an argument which is the error message
+that it expects to get when the callback fails
+(chrome.extension.lastError.message).
Here's what the output of this test might look like:
[==========] Running 1 test from 1 test case.
diff --git a/chrome/test/data/extensions/api_test/bookmarks/test.js b/chrome/test/data/extensions/api_test/bookmarks/test.js index 6c1fa1b..7f0a97a 100644 --- a/chrome/test/data/extensions/api_test/bookmarks/test.js +++ b/chrome/test/data/extensions/api_test/bookmarks/test.js @@ -79,7 +79,7 @@ chrome.test.runTests([ chrome.bookmarks.get("1", pass(function(results) { chrome.test.assertTrue(compareNode(results[0], expected[0].children[0])); })); - chrome.bookmarks.get("42", fail(function(){}, "Can't find bookmark for id.")); + chrome.bookmarks.get("42", fail("Can't find bookmark for id.")); }, function getArray() { diff --git a/chrome/test/data/extensions/api_test/javascript_url_permissions/manifest.json b/chrome/test/data/extensions/api_test/javascript_url_permissions/manifest.json new file mode 100644 index 0000000..34fc5c5 --- /dev/null +++ b/chrome/test/data/extensions/api_test/javascript_url_permissions/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "javascript url permissions test", + "version": "0.1", + "description": "Tests that navigating a tab to a javascript URLs only works if the extension has access to that tabs host.", + "background_page": "test.html", + "permissions": ["tabs", "http://b.com/"] +} diff --git a/chrome/test/data/extensions/api_test/javascript_url_permissions/test.html b/chrome/test/data/extensions/api_test/javascript_url_permissions/test.html new file mode 100644 index 0000000..2295dd2 --- /dev/null +++ b/chrome/test/data/extensions/api_test/javascript_url_permissions/test.html @@ -0,0 +1,29 @@ +<script> +var javaScriptURL = "javascript:void(document.body.bgColor='red')"; + +chrome.tabs.create({ url: "http://a.com:1337/files/extensions/test_file.html" }, + function(tab) { + var firstTabId = tab.id; + + chrome.tabs.create( + { url: "http://b.com:1337/files/extensions/test_file.html" }, + function(tab) { + var secondTabId = tab.id; + + chrome.test.runTests([ + function javaScriptURLShouldFail() { + chrome.tabs.update(firstTabId, {url: javaScriptURL}, + chrome.test.callbackFail('Cannot access contents of url ' + + '"http://a.com:1337/files/extensions/test_file.html". ' + + 'Extension manifest must request permission to access this ' + + 'host.')); + }, + + function javaScriptURLShouldSucceed() { + chrome.tabs.update(secondTabId, {url: javaScriptURL}, + chrome.test.callbackPass()); + } + ]); + }); +}); +</script> diff --git a/chrome/test/in_process_browser_test.cc b/chrome/test/in_process_browser_test.cc index af63adb..d4c26ee 100644 --- a/chrome/test/in_process_browser_test.cc +++ b/chrome/test/in_process_browser_test.cc @@ -30,7 +30,6 @@ #include "chrome/common/notification_type.h" #include "chrome/test/testing_browser_process.h" #include "chrome/test/ui_test_utils.h" -#include "net/base/mock_host_resolver.h" #include "sandbox/src/dep.h" extern int BrowserMain(const MainFunctionParams&); @@ -129,11 +128,18 @@ void InProcessBrowserTest::SetUp() { params.ui_task = NewRunnableMethod(this, &InProcessBrowserTest::RunTestOnMainThreadLoop); - scoped_refptr<net::RuleBasedHostResolverProc> host_resolver_proc( - new net::RuleBasedHostResolverProc(NULL)); - ConfigureHostResolverProc(host_resolver_proc); + host_resolver_ = new net::RuleBasedHostResolverProc(NULL); + + // Something inside the browser does this lookup implicitly. Make it fail + // to avoid external dependency. It won't break the tests. + host_resolver_->AddSimulatedFailure("*.google.com"); + + // See http://en.wikipedia.org/wiki/Web_Proxy_Autodiscovery_Protocol + // We don't want the test code to use it. + host_resolver_->AddSimulatedFailure("wpad"); + net::ScopedDefaultHostResolverProc scoped_host_resolver_proc( - host_resolver_proc); + host_resolver_.get()); BrowserMain(params); } @@ -235,17 +241,6 @@ void InProcessBrowserTest::RunTestOnMainThreadLoop() { http_server_ = NULL; } -void InProcessBrowserTest::ConfigureHostResolverProc( - net::RuleBasedHostResolverProc* host_resolver_proc) { - // Something inside the browser does this lookup implicitly. Make it fail - // to avoid external dependency. It won't break the tests. - host_resolver_proc->AddSimulatedFailure("*.google.com"); - - // See http://en.wikipedia.org/wiki/Web_Proxy_Autodiscovery_Protocol - // We don't want the test code to use it. - host_resolver_proc->AddSimulatedFailure("wpad"); -} - void InProcessBrowserTest::TimedOut() { DCHECK(MessageLoopForUI::current()->IsNested()); diff --git a/chrome/test/in_process_browser_test.h b/chrome/test/in_process_browser_test.h index ac7139c..053dce1 100644 --- a/chrome/test/in_process_browser_test.h +++ b/chrome/test/in_process_browser_test.h @@ -5,6 +5,7 @@ #ifndef CHROME_TEST_IN_PROCESS_BROWSER_TEST_H_ #define CHROME_TEST_IN_PROCESS_BROWSER_TEST_H_ +#include "net/base/mock_host_resolver.h" #include "net/url_request/url_request_unittest.h" #include "testing/gtest/include/gtest/gtest.h" @@ -70,11 +71,6 @@ class InProcessBrowserTest : public testing::Test { // main thread before the browser is torn down. virtual void CleanUpOnMainThread() {} - // Allows subclasses to configure the host resolver procedure. By default - // this blocks requests to google.com as Chrome pings that on startup and we - // don't want to do that during testing. - virtual void ConfigureHostResolverProc(net::RuleBasedHostResolverProc* proc); - // Invoked when a test is not finishing in a timely manner. void TimedOut(); @@ -90,6 +86,12 @@ class InProcessBrowserTest : public testing::Test { // This is invoked from Setup. virtual Browser* CreateBrowser(Profile* profile); + // Returns the host resolver being used for the tests. Subclasses might want + // to configure it inside tests. + net::RuleBasedHostResolverProc* host_resolver() { + return host_resolver_.get(); + } + // Sets some test states (see below for comments). Call this in your test // constructor. void set_show_window(bool show) { show_window_ = show; } @@ -128,6 +130,9 @@ class InProcessBrowserTest : public testing::Test { // Initial timeout value in ms. int initial_timeout_; + // Host resolver to use during the test. + scoped_refptr<net::RuleBasedHostResolverProc> host_resolver_; + DISALLOW_COPY_AND_ASSIGN(InProcessBrowserTest); }; |