1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/extensions/extension_protocols.h"
9 #include "base/base64.h"
10 #include "base/compiler_specific.h"
11 #include "base/file_util.h"
12 #include "base/files/file_path.h"
13 #include "base/format_macros.h"
14 #include "base/logging.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/path_service.h"
18 #include "base/sha1.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/threading/sequenced_worker_pool.h"
24 #include "base/threading/thread_restrictions.h"
25 #include "build/build_config.h"
26 #include "chrome/browser/extensions/extension_info_map.h"
27 #include "chrome/browser/extensions/extension_renderer_state.h"
28 #include "chrome/browser/extensions/image_loader.h"
29 #include "chrome/common/chrome_paths.h"
30 #include "chrome/common/extensions/background_info.h"
31 #include "chrome/common/extensions/csp_handler.h"
32 #include "chrome/common/extensions/extension.h"
33 #include "chrome/common/extensions/extension_file_util.h"
34 #include "chrome/common/extensions/incognito_handler.h"
35 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
36 #include "chrome/common/extensions/manifest_handlers/shared_module_info.h"
37 #include "chrome/common/extensions/manifest_url_handler.h"
38 #include "chrome/common/extensions/web_accessible_resources_handler.h"
39 #include "chrome/common/extensions/webview_handler.h"
40 #include "chrome/common/url_constants.h"
41 #include "content/public/browser/browser_thread.h"
42 #include "content/public/browser/resource_request_info.h"
43 #include "extensions/common/constants.h"
44 #include "extensions/common/extension_resource.h"
45 #include "grit/component_extension_resources_map.h"
46 #include "net/base/mime_util.h"
47 #include "net/base/net_errors.h"
48 #include "net/http/http_request_headers.h"
49 #include "net/http/http_response_headers.h"
50 #include "net/http/http_response_info.h"
51 #include "net/url_request/url_request_error_job.h"
52 #include "net/url_request/url_request_file_job.h"
53 #include "net/url_request/url_request_simple_job.h"
54 #include "ui/base/resource/resource_bundle.h"
55 #include "url/url_util.h"
57 using content::ResourceRequestInfo;
58 using extensions::Extension;
59 using extensions::SharedModuleInfo;
63 net::HttpResponseHeaders* BuildHttpHeaders(
64 const std::string& content_security_policy, bool send_cors_header,
65 const base::Time& last_modified_time) {
66 std::string raw_headers;
67 raw_headers.append("HTTP/1.1 200 OK");
68 if (!content_security_policy.empty()) {
69 raw_headers.append(1, '\0');
70 raw_headers.append("Content-Security-Policy: ");
71 raw_headers.append(content_security_policy);
74 if (send_cors_header) {
75 raw_headers.append(1, '\0');
76 raw_headers.append("Access-Control-Allow-Origin: *");
79 if (!last_modified_time.is_null()) {
80 // Hash the time and make an etag to avoid exposing the exact
81 // user installation time of the extension.
82 std::string hash = base::StringPrintf("%" PRId64,
83 last_modified_time.ToInternalValue());
84 hash = base::SHA1HashString(hash);
86 if (base::Base64Encode(hash, &etag)) {
87 raw_headers.append(1, '\0');
88 raw_headers.append("ETag: \"");
89 raw_headers.append(etag);
90 raw_headers.append("\"");
91 // Also force revalidation.
92 raw_headers.append(1, '\0');
93 raw_headers.append("cache-control: no-cache");
97 raw_headers.append(2, '\0');
98 return new net::HttpResponseHeaders(raw_headers);
101 void ReadMimeTypeFromFile(const base::FilePath& filename,
102 std::string* mime_type,
104 *result = net::GetMimeTypeFromFile(filename, mime_type);
107 void GetLastModifiedTime(const base::FilePath& filename,
108 base::Time* last_modified_time) {
109 if (base::PathExists(filename)) {
110 base::PlatformFileInfo info;
111 if (file_util::GetFileInfo(filename, &info))
112 *last_modified_time = info.last_modified;
116 class URLRequestResourceBundleJob : public net::URLRequestSimpleJob {
118 URLRequestResourceBundleJob(net::URLRequest* request,
119 net::NetworkDelegate* network_delegate,
120 const base::FilePath& filename,
122 const std::string& content_security_policy,
123 bool send_cors_header)
124 : net::URLRequestSimpleJob(request, network_delegate),
126 resource_id_(resource_id),
127 weak_factory_(this) {
128 // Leave cache headers out of resource bundle requests.
129 response_info_.headers = BuildHttpHeaders(content_security_policy,
134 // Overridden from URLRequestSimpleJob:
135 virtual int GetData(std::string* mime_type,
136 std::string* charset,
138 const net::CompletionCallback& callback) const OVERRIDE {
139 const ResourceBundle& rb = ResourceBundle::GetSharedInstance();
140 *data = rb.GetRawDataResource(resource_id_).as_string();
142 // Add the Content-Length header now that we know the resource length.
143 response_info_.headers->AddHeader(base::StringPrintf(
144 "%s: %s", net::HttpRequestHeaders::kContentLength,
145 base::UintToString(data->size()).c_str()));
147 std::string* read_mime_type = new std::string;
148 bool* read_result = new bool;
149 bool posted = content::BrowserThread::PostBlockingPoolTaskAndReply(
151 base::Bind(&ReadMimeTypeFromFile, filename_,
152 base::Unretained(read_mime_type),
153 base::Unretained(read_result)),
154 base::Bind(&URLRequestResourceBundleJob::OnMimeTypeRead,
155 weak_factory_.GetWeakPtr(),
156 mime_type, charset, data,
157 base::Owned(read_mime_type),
158 base::Owned(read_result),
162 return net::ERR_IO_PENDING;
165 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
166 *info = response_info_;
170 virtual ~URLRequestResourceBundleJob() { }
172 void OnMimeTypeRead(std::string* out_mime_type,
173 std::string* charset,
175 std::string* read_mime_type,
177 const net::CompletionCallback& callback) {
178 *out_mime_type = *read_mime_type;
179 if (StartsWithASCII(*read_mime_type, "text/", false)) {
180 // All of our HTML files should be UTF-8 and for other resource types
181 // (like images), charset doesn't matter.
182 DCHECK(IsStringUTF8(*data));
185 int result = *read_result? net::OK: net::ERR_INVALID_URL;
186 callback.Run(result);
189 // We need the filename of the resource to determine the mime type.
190 base::FilePath filename_;
192 // The resource bundle id to load.
195 net::HttpResponseInfo response_info_;
197 mutable base::WeakPtrFactory<URLRequestResourceBundleJob> weak_factory_;
200 class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob {
202 GeneratedBackgroundPageJob(net::URLRequest* request,
203 net::NetworkDelegate* network_delegate,
204 const scoped_refptr<const Extension> extension,
205 const std::string& content_security_policy)
206 : net::URLRequestSimpleJob(request, network_delegate),
207 extension_(extension) {
208 const bool send_cors_headers = false;
209 // Leave cache headers out of generated background page jobs.
210 response_info_.headers = BuildHttpHeaders(content_security_policy,
215 // Overridden from URLRequestSimpleJob:
216 virtual int GetData(std::string* mime_type,
217 std::string* charset,
219 const net::CompletionCallback& callback) const OVERRIDE {
220 *mime_type = "text/html";
223 *data = "<!DOCTYPE html>\n<body>\n";
224 const std::vector<std::string>& background_scripts =
225 extensions::BackgroundInfo::GetBackgroundScripts(extension_.get());
226 for (size_t i = 0; i < background_scripts.size(); ++i) {
227 *data += "<script src=\"";
228 *data += background_scripts[i];
229 *data += "\"></script>\n";
235 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
236 *info = response_info_;
240 virtual ~GeneratedBackgroundPageJob() {}
242 scoped_refptr<const Extension> extension_;
243 net::HttpResponseInfo response_info_;
246 void ReadResourceFilePathAndLastModifiedTime(
247 const extensions::ExtensionResource& resource,
248 base::FilePath* file_path,
249 base::Time* last_modified_time) {
250 *file_path = resource.GetFilePath();
251 GetLastModifiedTime(*file_path, last_modified_time);
254 class URLRequestExtensionJob : public net::URLRequestFileJob {
256 URLRequestExtensionJob(net::URLRequest* request,
257 net::NetworkDelegate* network_delegate,
258 const std::string& extension_id,
259 const base::FilePath& directory_path,
260 const base::FilePath& relative_path,
261 const std::string& content_security_policy,
262 bool send_cors_header)
263 : net::URLRequestFileJob(
264 request, network_delegate, base::FilePath(),
265 content::BrowserThread::GetBlockingPool()->
266 GetTaskRunnerWithShutdownBehavior(
267 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)),
268 // TODO(tc): Move all of these files into resources.pak so we don't break
269 // when updating on Linux.
270 resource_(extension_id, directory_path, relative_path),
271 content_security_policy_(content_security_policy),
272 send_cors_header_(send_cors_header),
273 weak_factory_(this) {
276 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE {
277 *info = response_info_;
280 virtual void Start() OVERRIDE {
281 base::FilePath* read_file_path = new base::FilePath;
282 base::Time* last_modified_time = new base::Time();
283 bool posted = content::BrowserThread::PostBlockingPoolTaskAndReply(
285 base::Bind(&ReadResourceFilePathAndLastModifiedTime, resource_,
286 base::Unretained(read_file_path),
287 base::Unretained(last_modified_time)),
288 base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead,
289 weak_factory_.GetWeakPtr(),
290 base::Owned(read_file_path),
291 base::Owned(last_modified_time)));
296 virtual ~URLRequestExtensionJob() {}
298 void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path,
299 base::Time* last_modified_time) {
300 file_path_ = *read_file_path;
301 response_info_.headers = BuildHttpHeaders(
302 content_security_policy_,
304 *last_modified_time);
305 URLRequestFileJob::Start();
308 net::HttpResponseInfo response_info_;
309 extensions::ExtensionResource resource_;
310 std::string content_security_policy_;
311 bool send_cors_header_;
312 base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_;
315 bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info,
316 const std::string& extension_id,
317 ExtensionInfoMap* extension_info_map) {
318 if (!extension_info_map->IsIncognitoEnabled(extension_id))
321 // Only allow incognito toplevel navigations to extension resources in
322 // split mode. In spanning mode, the extension must run in a single process,
323 // and an incognito tab prevents that.
324 if (info->GetResourceType() == ResourceType::MAIN_FRAME) {
325 const Extension* extension =
326 extension_info_map->extensions().GetByID(extension_id);
327 return extension && extensions::IncognitoInfo::IsSplitMode(extension);
333 // Returns true if an chrome-extension:// resource should be allowed to load.
334 // TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we
335 // first need to find a way to get CanLoadInIncognito state into the renderers.
336 bool AllowExtensionResourceLoad(net::URLRequest* request,
338 const Extension* extension,
339 ExtensionInfoMap* extension_info_map) {
340 const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request);
342 // We have seen crashes where info is NULL: crbug.com/52374.
344 LOG(ERROR) << "Allowing load of " << request->url().spec()
345 << "from unknown origin. Could not find user data for "
350 if (is_incognito && !ExtensionCanLoadInIncognito(info, request->url().host(),
351 extension_info_map)) {
355 // The following checks are meant to replicate similar set of checks in the
356 // renderer process, performed by ResourceRequestPolicy::CanRequestResource.
357 // These are not exactly equivalent, because we don't have the same bits of
358 // information. The two checks need to be kept in sync as much as possible, as
359 // an exploited renderer can bypass the checks in ResourceRequestPolicy.
361 // Check if the extension for which this request is made is indeed loaded in
362 // the process sending the request. If not, we need to explicitly check if
363 // the resource is explicitly accessible or fits in a set of exception cases.
364 // Note: This allows a case where two extensions execute in the same renderer
365 // process to request each other's resources. We can't do a more precise
366 // check, since the renderer can lie about which extension has made the
368 if (extension_info_map->process_map().Contains(
369 request->url().host(), info->GetChildID())) {
373 // Extensions with webview: allow loading certain resources by guest renderers
374 // with privileged partition IDs as specified in the manifest file.
375 ExtensionRendererState* renderer_state =
376 ExtensionRendererState::GetInstance();
377 ExtensionRendererState::WebViewInfo webview_info;
378 bool is_guest = renderer_state->GetWebViewInfo(info->GetChildID(),
381 std::string resource_path = request->url().path();
382 if (is_guest && webview_info.allow_chrome_extension_urls &&
383 extensions::WebviewInfo::IsResourceWebviewAccessible(
384 extension, webview_info.partition_id, resource_path)) {
388 // If the request is for navigations outside of webviews, then it should be
389 // allowed. The navigation logic in CrossSiteResourceHandler will properly
390 // transfer the navigation to a privileged process before it commits.
391 if (ResourceType::IsFrame(info->GetResourceType()) && !is_guest)
394 if (!content::PageTransitionIsWebTriggerable(info->GetPageTransition()))
397 // The following checks require that we have an actual extension object. If we
398 // don't have it, allow the request handling to continue with the rest of the
403 // Disallow loading of packaged resources for hosted apps. We don't allow
404 // hybrid hosted/packaged apps. The one exception is access to icons, since
405 // some extensions want to be able to do things like create their own
407 std::string resource_root_relative_path =
408 request->url().path().empty() ? std::string()
409 : request->url().path().substr(1);
410 if (extension->is_hosted_app() &&
411 !extensions::IconsInfo::GetIcons(extension)
412 .ContainsPath(resource_root_relative_path)) {
413 LOG(ERROR) << "Denying load of " << request->url().spec() << " from "
418 // Extensions with web_accessible_resources: allow loading by regular
419 // renderers. Since not all subresources are required to be listed in a v2
420 // manifest, we must allow all loads if there are any web accessible
421 // resources. See http://crbug.com/179127.
422 if (extension->manifest_version() < 2 ||
423 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
428 // If there aren't any explicitly marked web accessible resources, the
429 // load should be allowed only if it is by DevTools. A close approximation is
430 // checking if the extension contains a DevTools page.
431 if (extensions::ManifestURL::GetDevToolsPage(extension).is_empty())
437 // Returns true if the given URL references an icon in the given extension.
438 bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) {
439 DCHECK(url.SchemeIs(extensions::kExtensionScheme));
444 std::string path = url.path();
445 DCHECK_EQ(url.host(), extension->id());
446 DCHECK(path.length() > 0 && path[0] == '/');
447 path = path.substr(1);
448 return extensions::IconsInfo::GetIcons(extension).ContainsPath(path);
451 class ExtensionProtocolHandler
452 : public net::URLRequestJobFactory::ProtocolHandler {
454 ExtensionProtocolHandler(bool is_incognito,
455 ExtensionInfoMap* extension_info_map)
456 : is_incognito_(is_incognito),
457 extension_info_map_(extension_info_map) {}
459 virtual ~ExtensionProtocolHandler() {}
461 virtual net::URLRequestJob* MaybeCreateJob(
462 net::URLRequest* request,
463 net::NetworkDelegate* network_delegate) const OVERRIDE;
466 const bool is_incognito_;
467 ExtensionInfoMap* const extension_info_map_;
468 DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler);
471 // Creates URLRequestJobs for extension:// URLs.
473 ExtensionProtocolHandler::MaybeCreateJob(
474 net::URLRequest* request, net::NetworkDelegate* network_delegate) const {
475 // chrome-extension://extension-id/resource/path.js
476 std::string extension_id = request->url().host();
477 const Extension* extension =
478 extension_info_map_->extensions().GetByID(extension_id);
480 // TODO(mpcomplete): better error code.
481 if (!AllowExtensionResourceLoad(
482 request, is_incognito_, extension, extension_info_map_)) {
483 return new net::URLRequestErrorJob(
484 request, network_delegate, net::ERR_ADDRESS_UNREACHABLE);
487 base::FilePath directory_path;
489 directory_path = extension->path();
490 if (directory_path.value().empty()) {
491 const Extension* disabled_extension =
492 extension_info_map_->disabled_extensions().GetByID(extension_id);
493 if (URLIsForExtensionIcon(request->url(), disabled_extension))
494 directory_path = disabled_extension->path();
495 if (directory_path.value().empty()) {
496 LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id;
501 std::string content_security_policy;
502 bool send_cors_header = false;
504 std::string resource_path = request->url().path();
505 content_security_policy =
506 extensions::CSPInfo::GetResourceContentSecurityPolicy(extension,
508 if ((extension->manifest_version() >= 2 ||
509 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
511 extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
512 extension, resource_path))
513 send_cors_header = true;
516 std::string path = request->url().path();
517 if (path.size() > 1 &&
518 path.substr(1) == extensions::kGeneratedBackgroundPageFilename) {
519 return new GeneratedBackgroundPageJob(
520 request, network_delegate, extension, content_security_policy);
523 base::FilePath resources_path;
524 base::FilePath relative_path;
525 // Try to load extension resources from chrome resource file if
526 // directory_path is a descendant of resources_path. resources_path
527 // corresponds to src/chrome/browser/resources in source tree.
528 if (PathService::Get(chrome::DIR_RESOURCES, &resources_path) &&
529 // Since component extension resources are included in
530 // component_extension_resources.pak file in resources_path, calculate
531 // extension relative path against resources_path.
532 resources_path.AppendRelativePath(directory_path, &relative_path)) {
533 base::FilePath request_path =
534 extension_file_util::ExtensionURLToRelativeFilePath(request->url());
536 if (extensions::ImageLoader::IsComponentExtensionResource(
537 directory_path, request_path, &resource_id)) {
538 relative_path = relative_path.Append(request_path);
539 relative_path = relative_path.NormalizePathSeparators();
540 return new URLRequestResourceBundleJob(
545 content_security_policy,
551 extension_file_util::ExtensionURLToRelativeFilePath(request->url());
553 if (SharedModuleInfo::IsImportedPath(path)) {
554 std::string new_extension_id;
555 std::string new_relative_path;
556 SharedModuleInfo::ParseImportedPath(path, &new_extension_id,
558 const Extension* new_extension =
559 extension_info_map_->extensions().GetByID(new_extension_id);
561 bool first_party_in_import = false;
562 // NB: This first_party_for_cookies call is not for security, it is only
563 // used so an exported extension can limit the visible surface to the
564 // extension that imports it, more or less constituting its API.
565 const std::string& first_party_path =
566 request->first_party_for_cookies().path();
567 if (SharedModuleInfo::IsImportedPath(first_party_path)) {
568 std::string first_party_id;
570 SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id,
572 if (first_party_id == new_extension_id) {
573 first_party_in_import = true;
577 if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) &&
579 (first_party_in_import ||
580 SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) {
581 directory_path = new_extension->path();
582 extension_id = new_extension_id;
583 relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path);
589 return new URLRequestExtensionJob(request,
594 content_security_policy,
600 net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler(
602 ExtensionInfoMap* extension_info_map) {
603 return new ExtensionProtocolHandler(is_incognito, extension_info_map);