summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authorjamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-11 08:29:01 +0000
committerjamescook@chromium.org <jamescook@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-04-11 08:29:01 +0000
commit1791e6c97d8c12bf93c2812f9e4e658dba99a93d (patch)
tree784fb047aff898d4202f0f6574dba165b910aed1 /extensions
parent9cebf3e5a7d645ab0e4388d6d24e7c809abf7c2d (diff)
downloadchromium_src-1791e6c97d8c12bf93c2812f9e4e658dba99a93d.zip
chromium_src-1791e6c97d8c12bf93c2812f9e4e658dba99a93d.tar.gz
chromium_src-1791e6c97d8c12bf93c2812f9e4e658dba99a93d.tar.bz2
Toro: Move ExtensionProtocolHandler to //extensions
This breaks Chrome dependencies in ExtensionProtocolHandler and moves //chrome/browser/extensions/extension_protocol.cc into //extensions/browser so it can be used in app_shell. * Extract URLRequestResourceBundleJob to Chrome as it is used only to load component extension resources from Chrome's PAK files. * Replace Profile::ProfileType usage with is_incognito to break the Profile dependency. * Delegate out the decision to allow cross-renderer extension resource loads because this is primarily used for Chrome-specific extension features * Eliminate chrome-extension-resource:// handling in app_shell (it allows sharing common extension data in the <chrome-install>/resources/extension/ directory, which app_shell doesn't need). BUG=361373 TEST=browser_tests *Extension* and PlatformApp*, unit_tests ExtensionProtocol*, app_shell loads calculator TBR=sky@chromium.org for mechanical header file move/refactor across chrome/browser Review URL: https://codereview.chromium.org/229733002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@263198 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'extensions')
-rw-r--r--extensions/browser/extension_protocols.cc488
-rw-r--r--extensions/browser/extension_protocols.h40
-rw-r--r--extensions/browser/extension_protocols_unittest.cc340
-rw-r--r--extensions/browser/extensions_browser_client.h28
-rw-r--r--extensions/browser/test_extensions_browser_client.cc18
-rw-r--r--extensions/browser/test_extensions_browser_client.h11
-rw-r--r--extensions/extensions.gyp2
7 files changed, 927 insertions, 0 deletions
diff --git a/extensions/browser/extension_protocols.cc b/extensions/browser/extension_protocols.cc
new file mode 100644
index 0000000..681a85f
--- /dev/null
+++ b/extensions/browser/extension_protocols.cc
@@ -0,0 +1,488 @@
+// Copyright 2014 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 "extensions/browser/extension_protocols.h"
+
+#include <algorithm>
+
+#include "base/base64.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/path_service.h"
+#include "base/sha1.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/timer/elapsed_timer.h"
+#include "build/build_config.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/resource_request_info.h"
+#include "extensions/browser/extensions_browser_client.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_resource.h"
+#include "extensions/common/file_util.h"
+#include "extensions/common/manifest_handlers/background_info.h"
+#include "extensions/common/manifest_handlers/csp_info.h"
+#include "extensions/common/manifest_handlers/icons_handler.h"
+#include "extensions/common/manifest_handlers/incognito_info.h"
+#include "extensions/common/manifest_handlers/shared_module_info.h"
+#include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_file_job.h"
+#include "net/url_request/url_request_simple_job.h"
+#include "url/url_util.h"
+
+using content::BrowserThread;
+using content::ResourceRequestInfo;
+using extensions::Extension;
+using extensions::SharedModuleInfo;
+
+namespace extensions {
+namespace {
+
+class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob {
+ public:
+ GeneratedBackgroundPageJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const scoped_refptr<const Extension> extension,
+ const std::string& content_security_policy)
+ : net::URLRequestSimpleJob(request, network_delegate),
+ extension_(extension) {
+ const bool send_cors_headers = false;
+ // Leave cache headers out of generated background page jobs.
+ response_info_.headers = BuildHttpHeaders(content_security_policy,
+ send_cors_headers,
+ base::Time());
+ }
+
+ // Overridden from URLRequestSimpleJob:
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const net::CompletionCallback& callback) const OVERRIDE {
+ *mime_type = "text/html";
+ *charset = "utf-8";
+
+ *data = "<!DOCTYPE html>\n<body>\n";
+ const std::vector<std::string>& background_scripts =
+ extensions::BackgroundInfo::GetBackgroundScripts(extension_.get());
+ for (size_t i = 0; i < background_scripts.size(); ++i) {
+ *data += "<script src=\"";
+ *data += background_scripts[i];
+ *data += "\"></script>\n";
+ }
+
+ return net::OK;
+ }
+
+ virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
+ *info = response_info_;
+ }
+
+ private:
+ virtual ~GeneratedBackgroundPageJob() {}
+
+ scoped_refptr<const Extension> extension_;
+ net::HttpResponseInfo response_info_;
+};
+
+base::Time GetFileLastModifiedTime(const base::FilePath& filename) {
+ if (base::PathExists(filename)) {
+ base::File::Info info;
+ if (base::GetFileInfo(filename, &info))
+ return info.last_modified;
+ }
+ return base::Time();
+}
+
+base::Time GetFileCreationTime(const base::FilePath& filename) {
+ if (base::PathExists(filename)) {
+ base::File::Info info;
+ if (base::GetFileInfo(filename, &info))
+ return info.creation_time;
+ }
+ return base::Time();
+}
+
+void ReadResourceFilePathAndLastModifiedTime(
+ const extensions::ExtensionResource& resource,
+ const base::FilePath& directory,
+ base::FilePath* file_path,
+ base::Time* last_modified_time) {
+ *file_path = resource.GetFilePath();
+ *last_modified_time = GetFileLastModifiedTime(*file_path);
+ // While we're here, log the delta between extension directory
+ // creation time and the resource's last modification time.
+ base::ElapsedTimer query_timer;
+ base::Time dir_creation_time = GetFileCreationTime(directory);
+ UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency",
+ query_timer.Elapsed());
+ int64 delta_seconds = (*last_modified_time - dir_creation_time).InSeconds();
+ if (delta_seconds >= 0) {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta",
+ delta_seconds,
+ 0,
+ base::TimeDelta::FromDays(30).InSeconds(),
+ 50);
+ } else {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta",
+ -delta_seconds,
+ 1,
+ base::TimeDelta::FromDays(30).InSeconds(),
+ 50);
+ }
+}
+
+class URLRequestExtensionJob : public net::URLRequestFileJob {
+ public:
+ URLRequestExtensionJob(net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& extension_id,
+ const base::FilePath& directory_path,
+ const base::FilePath& relative_path,
+ const std::string& content_security_policy,
+ bool send_cors_header,
+ bool follow_symlinks_anywhere)
+ : net::URLRequestFileJob(
+ request, network_delegate, base::FilePath(),
+ BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
+ base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
+ directory_path_(directory_path),
+ // TODO(tc): Move all of these files into resources.pak so we don't break
+ // when updating on Linux.
+ resource_(extension_id, directory_path, relative_path),
+ content_security_policy_(content_security_policy),
+ send_cors_header_(send_cors_header),
+ weak_factory_(this) {
+ if (follow_symlinks_anywhere) {
+ resource_.set_follow_symlinks_anywhere();
+ }
+ }
+
+ virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
+ *info = response_info_;
+ }
+
+ virtual void Start() OVERRIDE {
+ base::FilePath* read_file_path = new base::FilePath;
+ base::Time* last_modified_time = new base::Time();
+ bool posted = BrowserThread::PostBlockingPoolTaskAndReply(
+ FROM_HERE,
+ base::Bind(&ReadResourceFilePathAndLastModifiedTime, resource_,
+ directory_path_,
+ base::Unretained(read_file_path),
+ base::Unretained(last_modified_time)),
+ base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead,
+ weak_factory_.GetWeakPtr(),
+ base::Owned(read_file_path),
+ base::Owned(last_modified_time)));
+ DCHECK(posted);
+ }
+
+ private:
+ virtual ~URLRequestExtensionJob() {}
+
+ void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path,
+ base::Time* last_modified_time) {
+ file_path_ = *read_file_path;
+ response_info_.headers = BuildHttpHeaders(
+ content_security_policy_,
+ send_cors_header_,
+ *last_modified_time);
+ URLRequestFileJob::Start();
+ }
+
+ net::HttpResponseInfo response_info_;
+ base::FilePath directory_path_;
+ extensions::ExtensionResource resource_;
+ std::string content_security_policy_;
+ bool send_cors_header_;
+ base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_;
+};
+
+bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info,
+ const std::string& extension_id,
+ extensions::InfoMap* extension_info_map) {
+ if (!extension_info_map->IsIncognitoEnabled(extension_id))
+ return false;
+
+ // Only allow incognito toplevel navigations to extension resources in
+ // split mode. In spanning mode, the extension must run in a single process,
+ // and an incognito tab prevents that.
+ if (info->GetResourceType() == ResourceType::MAIN_FRAME) {
+ const Extension* extension =
+ extension_info_map->extensions().GetByID(extension_id);
+ return extension && extensions::IncognitoInfo::IsSplitMode(extension);
+ }
+
+ return true;
+}
+
+// Returns true if an chrome-extension:// resource should be allowed to load.
+// Pass true for |is_incognito| only for incognito profiles and not Chrome OS
+// guest mode profiles.
+// TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we
+// first need to find a way to get CanLoadInIncognito state into the renderers.
+bool AllowExtensionResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ extensions::InfoMap* extension_info_map) {
+ const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
+
+ // We have seen crashes where info is NULL: crbug.com/52374.
+ if (!info) {
+ LOG(ERROR) << "Allowing load of " << request->url().spec()
+ << "from unknown origin. Could not find user data for "
+ << "request.";
+ return true;
+ }
+
+ if (is_incognito && !ExtensionCanLoadInIncognito(
+ info, request->url().host(), extension_info_map)) {
+ return false;
+ }
+
+ // The following checks are meant to replicate similar set of checks in the
+ // renderer process, performed by ResourceRequestPolicy::CanRequestResource.
+ // These are not exactly equivalent, because we don't have the same bits of
+ // information. The two checks need to be kept in sync as much as possible, as
+ // an exploited renderer can bypass the checks in ResourceRequestPolicy.
+
+ // Check if the extension for which this request is made is indeed loaded in
+ // the process sending the request. If not, we need to explicitly check if
+ // the resource is explicitly accessible or fits in a set of exception cases.
+ // Note: This allows a case where two extensions execute in the same renderer
+ // process to request each other's resources. We can't do a more precise
+ // check, since the renderer can lie about which extension has made the
+ // request.
+ if (extension_info_map->process_map().Contains(
+ request->url().host(), info->GetChildID())) {
+ return true;
+ }
+
+ // Allow the extension module embedder to grant permission for loads.
+ if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad(
+ request, is_incognito, extension, extension_info_map)) {
+ return true;
+ }
+
+ // No special exceptions for cross-process loading. Block the load.
+ return false;
+}
+
+// Returns true if the given URL references an icon in the given extension.
+bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) {
+ DCHECK(url.SchemeIs(extensions::kExtensionScheme));
+
+ if (!extension)
+ return false;
+
+ std::string path = url.path();
+ DCHECK_EQ(url.host(), extension->id());
+ DCHECK(path.length() > 0 && path[0] == '/');
+ path = path.substr(1);
+ return extensions::IconsInfo::GetIcons(extension).ContainsPath(path);
+}
+
+class ExtensionProtocolHandler
+ : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ ExtensionProtocolHandler(bool is_incognito,
+ extensions::InfoMap* extension_info_map)
+ : is_incognito_(is_incognito), extension_info_map_(extension_info_map) {}
+
+ virtual ~ExtensionProtocolHandler() {}
+
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ const bool is_incognito_;
+ extensions::InfoMap* const extension_info_map_;
+ DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler);
+};
+
+// Creates URLRequestJobs for extension:// URLs.
+net::URLRequestJob*
+ExtensionProtocolHandler::MaybeCreateJob(
+ net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
+ // chrome-extension://extension-id/resource/path.js
+ std::string extension_id = request->url().host();
+ const Extension* extension =
+ extension_info_map_->extensions().GetByID(extension_id);
+
+ // TODO(mpcomplete): better error code.
+ if (!AllowExtensionResourceLoad(
+ request, is_incognito_, extension, extension_info_map_)) {
+ return new net::URLRequestErrorJob(
+ request, network_delegate, net::ERR_ADDRESS_UNREACHABLE);
+ }
+
+ // If this is a disabled extension only allow the icon to load.
+ base::FilePath directory_path;
+ if (extension)
+ directory_path = extension->path();
+ if (directory_path.value().empty()) {
+ const Extension* disabled_extension =
+ extension_info_map_->disabled_extensions().GetByID(extension_id);
+ if (URLIsForExtensionIcon(request->url(), disabled_extension))
+ directory_path = disabled_extension->path();
+ if (directory_path.value().empty()) {
+ LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id;
+ return NULL;
+ }
+ }
+
+ // Set up content security policy.
+ std::string content_security_policy;
+ bool send_cors_header = false;
+ bool follow_symlinks_anywhere = false;
+ if (extension) {
+ std::string resource_path = request->url().path();
+ content_security_policy =
+ extensions::CSPInfo::GetResourceContentSecurityPolicy(extension,
+ resource_path);
+ if ((extension->manifest_version() >= 2 ||
+ extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
+ extension)) &&
+ extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
+ extension, resource_path))
+ send_cors_header = true;
+
+ follow_symlinks_anywhere =
+ (extension->creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE)
+ != 0;
+ }
+
+ // Create a job for a generated background page.
+ std::string path = request->url().path();
+ if (path.size() > 1 &&
+ path.substr(1) == extensions::kGeneratedBackgroundPageFilename) {
+ return new GeneratedBackgroundPageJob(
+ request, network_delegate, extension, content_security_policy);
+ }
+
+ // Component extension resources may be part of the embedder's resource files,
+ // for example component_extension_resources.pak in Chrome.
+ net::URLRequestJob* resource_bundle_job =
+ extensions::ExtensionsBrowserClient::Get()
+ ->MaybeCreateResourceBundleRequestJob(request,
+ network_delegate,
+ directory_path,
+ content_security_policy,
+ send_cors_header);
+ if (resource_bundle_job)
+ return resource_bundle_job;
+
+ base::FilePath relative_path =
+ extensions::file_util::ExtensionURLToRelativeFilePath(request->url());
+
+ // Handle shared resources (extension A loading resources out of extension B).
+ if (SharedModuleInfo::IsImportedPath(path)) {
+ std::string new_extension_id;
+ std::string new_relative_path;
+ SharedModuleInfo::ParseImportedPath(path, &new_extension_id,
+ &new_relative_path);
+ const Extension* new_extension =
+ extension_info_map_->extensions().GetByID(new_extension_id);
+
+ bool first_party_in_import = false;
+ // NB: This first_party_for_cookies call is not for security, it is only
+ // used so an exported extension can limit the visible surface to the
+ // extension that imports it, more or less constituting its API.
+ const std::string& first_party_path =
+ request->first_party_for_cookies().path();
+ if (SharedModuleInfo::IsImportedPath(first_party_path)) {
+ std::string first_party_id;
+ std::string dummy;
+ SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id,
+ &dummy);
+ if (first_party_id == new_extension_id) {
+ first_party_in_import = true;
+ }
+ }
+
+ if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) &&
+ new_extension &&
+ (first_party_in_import ||
+ SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) {
+ directory_path = new_extension->path();
+ extension_id = new_extension_id;
+ relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path);
+ } else {
+ return NULL;
+ }
+ }
+
+ return new URLRequestExtensionJob(request,
+ network_delegate,
+ extension_id,
+ directory_path,
+ relative_path,
+ content_security_policy,
+ send_cors_header,
+ follow_symlinks_anywhere);
+}
+
+} // namespace
+
+net::HttpResponseHeaders* BuildHttpHeaders(
+ const std::string& content_security_policy,
+ bool send_cors_header,
+ const base::Time& last_modified_time) {
+ std::string raw_headers;
+ raw_headers.append("HTTP/1.1 200 OK");
+ if (!content_security_policy.empty()) {
+ raw_headers.append(1, '\0');
+ raw_headers.append("Content-Security-Policy: ");
+ raw_headers.append(content_security_policy);
+ }
+
+ if (send_cors_header) {
+ raw_headers.append(1, '\0');
+ raw_headers.append("Access-Control-Allow-Origin: *");
+ }
+
+ if (!last_modified_time.is_null()) {
+ // Hash the time and make an etag to avoid exposing the exact
+ // user installation time of the extension.
+ std::string hash =
+ base::StringPrintf("%" PRId64, last_modified_time.ToInternalValue());
+ hash = base::SHA1HashString(hash);
+ std::string etag;
+ base::Base64Encode(hash, &etag);
+ raw_headers.append(1, '\0');
+ raw_headers.append("ETag: \"");
+ raw_headers.append(etag);
+ raw_headers.append("\"");
+ // Also force revalidation.
+ raw_headers.append(1, '\0');
+ raw_headers.append("cache-control: no-cache");
+ }
+
+ raw_headers.append(2, '\0');
+ return new net::HttpResponseHeaders(raw_headers);
+}
+
+net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler(
+ bool is_incognito,
+ extensions::InfoMap* extension_info_map) {
+ return new ExtensionProtocolHandler(is_incognito, extension_info_map);
+}
+
+} // namespace extensions
diff --git a/extensions/browser/extension_protocols.h b/extensions/browser/extension_protocols.h
new file mode 100644
index 0000000..2260374
--- /dev/null
+++ b/extensions/browser/extension_protocols.h
@@ -0,0 +1,40 @@
+// Copyright 2014 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 EXTENSIONS_BROWSER_EXTENSION_PROTOCOLS_H_
+#define EXTENSIONS_BROWSER_EXTENSION_PROTOCOLS_H_
+
+#include <string>
+
+#include "net/url_request/url_request_job_factory.h"
+
+namespace base {
+class Time;
+}
+
+namespace net {
+class HttpResponseHeaders;
+}
+
+namespace extensions {
+
+class InfoMap;
+
+// Builds HTTP headers for an extension request. Hashes the time to avoid
+// exposing the exact user installation time of the extension.
+net::HttpResponseHeaders* BuildHttpHeaders(
+ const std::string& content_security_policy,
+ bool send_cors_header,
+ const base::Time& last_modified_time);
+
+// Creates the handlers for the chrome-extension:// scheme. Pass true for
+// |is_incognito| only for incognito profiles and not for Chrome OS guest mode
+// profiles.
+net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler(
+ bool is_incognito,
+ extensions::InfoMap* extension_info_map);
+
+} // namespace extensions
+
+#endif // EXTENSIONS_BROWSER_EXTENSION_PROTOCOLS_H_
diff --git a/extensions/browser/extension_protocols_unittest.cc b/extensions/browser/extension_protocols_unittest.cc
new file mode 100644
index 0000000..0aa097b
--- /dev/null
+++ b/extensions/browser/extension_protocols_unittest.cc
@@ -0,0 +1,340 @@
+// Copyright 2014 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 <string>
+
+#include "base/file_util.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "chrome/common/chrome_paths.h"
+//#include "chrome/common/url_constants.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/test/mock_resource_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "extensions/browser/extension_protocols.h"
+#include "extensions/browser/info_map.h"
+#include "extensions/common/constants.h"
+#include "extensions/common/extension.h"
+#include "net/base/request_priority.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "net/url_request/url_request_status.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+scoped_refptr<Extension> CreateTestExtension(const std::string& name,
+ bool incognito_split_mode) {
+ base::DictionaryValue manifest;
+ manifest.SetString("name", name);
+ manifest.SetString("version", "1");
+ manifest.SetInteger("manifest_version", 2);
+ manifest.SetString("incognito", incognito_split_mode ? "split" : "spanning");
+
+ base::FilePath path;
+ EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("extensions").AppendASCII("response_headers");
+
+ std::string error;
+ scoped_refptr<Extension> extension(
+ Extension::Create(path, Manifest::INTERNAL, manifest,
+ Extension::NO_FLAGS, &error));
+ EXPECT_TRUE(extension.get()) << error;
+ return extension;
+}
+
+scoped_refptr<Extension> CreateWebStoreExtension() {
+ base::DictionaryValue manifest;
+ manifest.SetString("name", "WebStore");
+ manifest.SetString("version", "1");
+ manifest.SetString("icons.16", "webstore_icon_16.png");
+
+ base::FilePath path;
+ EXPECT_TRUE(PathService::Get(chrome::DIR_RESOURCES, &path));
+ path = path.AppendASCII("web_store");
+
+ std::string error;
+ scoped_refptr<Extension> extension(
+ Extension::Create(path, Manifest::COMPONENT, manifest,
+ Extension::NO_FLAGS, &error));
+ EXPECT_TRUE(extension.get()) << error;
+ return extension;
+}
+
+scoped_refptr<Extension> CreateTestResponseHeaderExtension() {
+ base::DictionaryValue manifest;
+ manifest.SetString("name", "An extension with web-accessible resources");
+ manifest.SetString("version", "2");
+
+ base::ListValue* web_accessible_list = new base::ListValue();
+ web_accessible_list->AppendString("test.dat");
+ manifest.Set("web_accessible_resources", web_accessible_list);
+
+ base::FilePath path;
+ EXPECT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
+ path = path.AppendASCII("extensions").AppendASCII("response_headers");
+
+ std::string error;
+ scoped_refptr<Extension> extension(
+ Extension::Create(path, Manifest::UNPACKED, manifest,
+ Extension::NO_FLAGS, &error));
+ EXPECT_TRUE(extension.get()) << error;
+ return extension;
+}
+
+class ExtensionProtocolTest : public testing::Test {
+ public:
+ ExtensionProtocolTest()
+ : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
+ old_factory_(NULL),
+ resource_context_(&test_url_request_context_) {}
+
+ virtual void SetUp() OVERRIDE {
+ testing::Test::SetUp();
+ extension_info_map_ = new InfoMap();
+ net::URLRequestContext* request_context =
+ resource_context_.GetRequestContext();
+ old_factory_ = request_context->job_factory();
+ }
+
+ virtual void TearDown() {
+ net::URLRequestContext* request_context =
+ resource_context_.GetRequestContext();
+ request_context->set_job_factory(old_factory_);
+ }
+
+ void SetProtocolHandler(bool is_incognito) {
+ net::URLRequestContext* request_context =
+ resource_context_.GetRequestContext();
+ job_factory_.SetProtocolHandler(
+ kExtensionScheme,
+ CreateExtensionProtocolHandler(is_incognito,
+ extension_info_map_.get()));
+ request_context->set_job_factory(&job_factory_);
+ }
+
+ void StartRequest(net::URLRequest* request,
+ ResourceType::Type resource_type) {
+ content::ResourceRequestInfo::AllocateForTesting(request,
+ resource_type,
+ &resource_context_,
+ -1,
+ -1,
+ -1,
+ false);
+ request->Start();
+ base::MessageLoop::current()->Run();
+ }
+
+ protected:
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_refptr<InfoMap> extension_info_map_;
+ net::URLRequestJobFactoryImpl job_factory_;
+ const net::URLRequestJobFactory* old_factory_;
+ net::TestDelegate test_delegate_;
+ net::TestURLRequestContext test_url_request_context_;
+ content::MockResourceContext resource_context_;
+};
+
+// Tests that making a chrome-extension request in an incognito context is
+// only allowed under the right circumstances (if the extension is allowed
+// in incognito, and it's either a non-main-frame request or a split-mode
+// extension).
+TEST_F(ExtensionProtocolTest, IncognitoRequest) {
+ // Register an incognito extension protocol handler.
+ SetProtocolHandler(true);
+
+ struct TestCase {
+ // Inputs.
+ std::string name;
+ bool incognito_split_mode;
+ bool incognito_enabled;
+
+ // Expected results.
+ bool should_allow_main_frame_load;
+ bool should_allow_sub_frame_load;
+ } cases[] = {
+ {"spanning disabled", false, false, false, false},
+ {"split disabled", true, false, false, false},
+ {"spanning enabled", false, true, false, true},
+ {"split enabled", true, true, true, true},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ scoped_refptr<Extension> extension =
+ CreateTestExtension(cases[i].name, cases[i].incognito_split_mode);
+ extension_info_map_->AddExtension(
+ extension.get(), base::Time::Now(), cases[i].incognito_enabled, false);
+
+ // First test a main frame request.
+ {
+ // It doesn't matter that the resource doesn't exist. If the resource
+ // is blocked, we should see ADDRESS_UNREACHABLE. Otherwise, the request
+ // should just fail because the file doesn't exist.
+ net::URLRequest request(extension->GetResourceURL("404.html"),
+ net::DEFAULT_PRIORITY,
+ &test_delegate_,
+ resource_context_.GetRequestContext());
+ StartRequest(&request, ResourceType::MAIN_FRAME);
+ EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
+
+ if (cases[i].should_allow_main_frame_load) {
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request.status().error()) <<
+ cases[i].name;
+ } else {
+ EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE, request.status().error()) <<
+ cases[i].name;
+ }
+ }
+
+ // Now do a subframe request.
+ {
+ net::URLRequest request(extension->GetResourceURL("404.html"),
+ net::DEFAULT_PRIORITY,
+ &test_delegate_,
+ resource_context_.GetRequestContext());
+ StartRequest(&request, ResourceType::SUB_FRAME);
+ EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
+
+ if (cases[i].should_allow_sub_frame_load) {
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, request.status().error()) <<
+ cases[i].name;
+ } else {
+ EXPECT_EQ(net::ERR_ADDRESS_UNREACHABLE, request.status().error()) <<
+ cases[i].name;
+ }
+ }
+ }
+}
+
+void CheckForContentLengthHeader(net::URLRequest* request) {
+ std::string content_length;
+ request->GetResponseHeaderByName(net::HttpRequestHeaders::kContentLength,
+ &content_length);
+ EXPECT_FALSE(content_length.empty());
+ int length_value = 0;
+ EXPECT_TRUE(base::StringToInt(content_length, &length_value));
+ EXPECT_GT(length_value, 0);
+}
+
+// Tests getting a resource for a component extension works correctly, both when
+// the extension is enabled and when it is disabled.
+TEST_F(ExtensionProtocolTest, ComponentResourceRequest) {
+ // Register a non-incognito extension protocol handler.
+ SetProtocolHandler(false);
+
+ scoped_refptr<Extension> extension = CreateWebStoreExtension();
+ extension_info_map_->AddExtension(extension.get(),
+ base::Time::Now(),
+ false,
+ false);
+
+ // First test it with the extension enabled.
+ {
+ net::URLRequest request(extension->GetResourceURL("webstore_icon_16.png"),
+ net::DEFAULT_PRIORITY,
+ &test_delegate_,
+ resource_context_.GetRequestContext());
+ StartRequest(&request, ResourceType::MEDIA);
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
+ CheckForContentLengthHeader(&request);
+ }
+
+ // And then test it with the extension disabled.
+ extension_info_map_->RemoveExtension(extension->id(),
+ UnloadedExtensionInfo::REASON_DISABLE);
+ {
+ net::URLRequest request(extension->GetResourceURL("webstore_icon_16.png"),
+ net::DEFAULT_PRIORITY,
+ &test_delegate_,
+ resource_context_.GetRequestContext());
+ StartRequest(&request, ResourceType::MEDIA);
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
+ CheckForContentLengthHeader(&request);
+ }
+}
+
+// Tests that a URL request for resource from an extension returns a few
+// expected response headers.
+TEST_F(ExtensionProtocolTest, ResourceRequestResponseHeaders) {
+ // Register a non-incognito extension protocol handler.
+ SetProtocolHandler(false);
+
+ scoped_refptr<Extension> extension = CreateTestResponseHeaderExtension();
+ extension_info_map_->AddExtension(extension.get(),
+ base::Time::Now(),
+ false,
+ false);
+
+ {
+ net::URLRequest request(extension->GetResourceURL("test.dat"),
+ net::DEFAULT_PRIORITY,
+ &test_delegate_,
+ resource_context_.GetRequestContext());
+ StartRequest(&request, ResourceType::MEDIA);
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
+
+ // Check that cache-related headers are set.
+ std::string etag;
+ request.GetResponseHeaderByName("ETag", &etag);
+ EXPECT_TRUE(StartsWithASCII(etag, "\"", false));
+ EXPECT_TRUE(EndsWith(etag, "\"", false));
+
+ std::string revalidation_header;
+ request.GetResponseHeaderByName("cache-control", &revalidation_header);
+ EXPECT_EQ("no-cache", revalidation_header);
+
+ // We set test.dat as web-accessible, so it should have a CORS header.
+ std::string access_control;
+ request.GetResponseHeaderByName("Access-Control-Allow-Origin",
+ &access_control);
+ EXPECT_EQ("*", access_control);
+ }
+}
+
+// Tests that a URL request for main frame or subframe from an extension
+// succeeds, but subresources fail. See http://crbug.com/312269.
+TEST_F(ExtensionProtocolTest, AllowFrameRequests) {
+ // Register a non-incognito extension protocol handler.
+ SetProtocolHandler(false);
+
+ scoped_refptr<Extension> extension = CreateTestExtension("foo", false);
+ extension_info_map_->AddExtension(extension.get(),
+ base::Time::Now(),
+ false,
+ false);
+
+ // All MAIN_FRAME and SUB_FRAME requests should succeed.
+ {
+ net::URLRequest request(extension->GetResourceURL("test.dat"),
+ net::DEFAULT_PRIORITY,
+ &test_delegate_,
+ resource_context_.GetRequestContext());
+ StartRequest(&request, ResourceType::MAIN_FRAME);
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
+ }
+ {
+ net::URLRequest request(extension->GetResourceURL("test.dat"),
+ net::DEFAULT_PRIORITY,
+ &test_delegate_,
+ resource_context_.GetRequestContext());
+ StartRequest(&request, ResourceType::SUB_FRAME);
+ EXPECT_EQ(net::URLRequestStatus::SUCCESS, request.status().status());
+ }
+
+ // And subresource types, such as media, should fail.
+ {
+ net::URLRequest request(extension->GetResourceURL("test.dat"),
+ net::DEFAULT_PRIORITY,
+ &test_delegate_,
+ resource_context_.GetRequestContext());
+ StartRequest(&request, ResourceType::MEDIA);
+ EXPECT_EQ(net::URLRequestStatus::FAILED, request.status().status());
+ }
+}
+
+} // namespace extensions
diff --git a/extensions/browser/extensions_browser_client.h b/extensions/browser/extensions_browser_client.h
index c7542a7..d7c02d9 100644
--- a/extensions/browser/extensions_browser_client.h
+++ b/extensions/browser/extensions_browser_client.h
@@ -16,6 +16,7 @@ class PrefService;
namespace base {
class CommandLine;
+class FilePath;
}
namespace content {
@@ -23,6 +24,12 @@ class BrowserContext;
class WebContents;
}
+namespace net {
+class NetworkDelegate;
+class URLRequest;
+class URLRequestJob;
+}
+
namespace extensions {
class ApiActivityMonitor;
@@ -32,6 +39,7 @@ class ExtensionHostDelegate;
class ExtensionPrefsObserver;
class ExtensionSystem;
class ExtensionSystemProvider;
+class InfoMap;
// Interface to allow the extensions module to make browser-process-specific
// queries of the embedder. Should be Set() once in the browser process.
@@ -88,6 +96,26 @@ class ExtensionsBrowserClient {
const extensions::Extension* extension,
content::BrowserContext* context) const = 0;
+ // Returns an URLRequestJob to load an extension resource from the embedder's
+ // resource bundle (.pak) files. Returns NULL if the request is not for a
+ // resource bundle resource or if the embedder does not support this feature.
+ // Used for component extensions. Called on the IO thread.
+ virtual net::URLRequestJob* MaybeCreateResourceBundleRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& directory_path,
+ const std::string& content_security_policy,
+ bool send_cors_header) = 0;
+
+ // Returns true if the embedder wants to allow a chrome-extension:// resource
+ // request coming from renderer A to access a resource in an extension running
+ // in renderer B. For example, Chrome overrides this to provide support for
+ // webview and dev tools. Called on the IO thread.
+ virtual bool AllowCrossRendererResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map) = 0;
+
// Returns the PrefService associated with |context|.
virtual PrefService* GetPrefServiceForContext(
content::BrowserContext* context) = 0;
diff --git a/extensions/browser/test_extensions_browser_client.cc b/extensions/browser/test_extensions_browser_client.cc
index f1d3cc9..132ac19 100644
--- a/extensions/browser/test_extensions_browser_client.cc
+++ b/extensions/browser/test_extensions_browser_client.cc
@@ -83,6 +83,24 @@ bool TestExtensionsBrowserClient::CanExtensionCrossIncognito(
return false;
}
+net::URLRequestJob*
+TestExtensionsBrowserClient::MaybeCreateResourceBundleRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& directory_path,
+ const std::string& content_security_policy,
+ bool send_cors_header) {
+ return NULL;
+}
+
+bool TestExtensionsBrowserClient::AllowCrossRendererResourceLoad(
+ net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map) {
+ return false;
+}
+
PrefService* TestExtensionsBrowserClient::GetPrefServiceForContext(
BrowserContext* context) {
return NULL;
diff --git a/extensions/browser/test_extensions_browser_client.h b/extensions/browser/test_extensions_browser_client.h
index 4db3270..ee3a625 100644
--- a/extensions/browser/test_extensions_browser_client.h
+++ b/extensions/browser/test_extensions_browser_client.h
@@ -42,6 +42,17 @@ class TestExtensionsBrowserClient : public ExtensionsBrowserClient {
virtual bool CanExtensionCrossIncognito(
const extensions::Extension* extension,
content::BrowserContext* context) const OVERRIDE;
+ virtual net::URLRequestJob* MaybeCreateResourceBundleRequestJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const base::FilePath& directory_path,
+ const std::string& content_security_policy,
+ bool send_cors_header) OVERRIDE;
+ virtual bool AllowCrossRendererResourceLoad(net::URLRequest* request,
+ bool is_incognito,
+ const Extension* extension,
+ InfoMap* extension_info_map)
+ OVERRIDE;
virtual PrefService* GetPrefServiceForContext(
content::BrowserContext* context) OVERRIDE;
virtual void GetEarlyExtensionPrefsObservers(
diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp
index 4227cc0..bf4a61d 100644
--- a/extensions/extensions.gyp
+++ b/extensions/extensions.gyp
@@ -327,6 +327,8 @@
'browser/extension_prefs_factory.h',
'browser/extension_prefs_observer.h',
'browser/extension_prefs_scope.h',
+ 'browser/extension_protocols.cc',
+ 'browser/extension_protocols.h',
'browser/extension_registry.cc',
'browser/extension_registry.h',
'browser/extension_registry_factory.cc',