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/metrics/histogram.h"
18 #include "base/path_service.h"
19 #include "base/sha1.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/threading/sequenced_worker_pool.h"
25 #include "base/threading/thread_restrictions.h"
26 #include "base/timer/elapsed_timer.h"
27 #include "build/build_config.h"
28 #include "chrome/browser/extensions/extension_renderer_state.h"
29 #include "chrome/browser/extensions/image_loader.h"
30 #include "chrome/common/chrome_paths.h"
31 #include "chrome/common/extensions/manifest_handlers/icons_handler.h"
32 #include "chrome/common/extensions/manifest_url_handler.h"
33 #include "chrome/common/url_constants.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/resource_request_info.h"
36 #include "extensions/browser/info_map.h"
37 #include "extensions/common/constants.h"
38 #include "extensions/common/extension.h"
39 #include "extensions/common/extension_resource.h"
40 #include "extensions/common/file_util.h"
41 #include "extensions/common/manifest_handlers/background_info.h"
42 #include "extensions/common/manifest_handlers/csp_info.h"
43 #include "extensions/common/manifest_handlers/incognito_info.h"
44 #include "extensions/common/manifest_handlers/shared_module_info.h"
45 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
46 #include "extensions/common/manifest_handlers/webview_info.h"
47 #include "grit/component_extension_resources_map.h"
48 #include "net/base/mime_util.h"
49 #include "net/base/net_errors.h"
50 #include "net/http/http_request_headers.h"
51 #include "net/http/http_response_headers.h"
52 #include "net/http/http_response_info.h"
53 #include "net/url_request/url_request_error_job.h"
54 #include "net/url_request/url_request_file_job.h"
55 #include "net/url_request/url_request_simple_job.h"
56 #include "ui/base/resource/resource_bundle.h"
57 #include "url/url_util.h"
59 using content::BrowserThread
;
60 using content::ResourceRequestInfo
;
61 using extensions::Extension
;
62 using extensions::SharedModuleInfo
;
66 net::HttpResponseHeaders
* BuildHttpHeaders(
67 const std::string
& content_security_policy
, bool send_cors_header
,
68 const base::Time
& last_modified_time
) {
69 std::string raw_headers
;
70 raw_headers
.append("HTTP/1.1 200 OK");
71 if (!content_security_policy
.empty()) {
72 raw_headers
.append(1, '\0');
73 raw_headers
.append("Content-Security-Policy: ");
74 raw_headers
.append(content_security_policy
);
77 if (send_cors_header
) {
78 raw_headers
.append(1, '\0');
79 raw_headers
.append("Access-Control-Allow-Origin: *");
82 if (!last_modified_time
.is_null()) {
83 // Hash the time and make an etag to avoid exposing the exact
84 // user installation time of the extension.
85 std::string hash
= base::StringPrintf("%" PRId64
,
86 last_modified_time
.ToInternalValue());
87 hash
= base::SHA1HashString(hash
);
89 base::Base64Encode(hash
, &etag
);
90 raw_headers
.append(1, '\0');
91 raw_headers
.append("ETag: \"");
92 raw_headers
.append(etag
);
93 raw_headers
.append("\"");
94 // Also force revalidation.
95 raw_headers
.append(1, '\0');
96 raw_headers
.append("cache-control: no-cache");
99 raw_headers
.append(2, '\0');
100 return new net::HttpResponseHeaders(raw_headers
);
103 class URLRequestResourceBundleJob
: public net::URLRequestSimpleJob
{
105 URLRequestResourceBundleJob(net::URLRequest
* request
,
106 net::NetworkDelegate
* network_delegate
,
107 const base::FilePath
& filename
,
109 const std::string
& content_security_policy
,
110 bool send_cors_header
)
111 : net::URLRequestSimpleJob(request
, network_delegate
),
113 resource_id_(resource_id
),
114 weak_factory_(this) {
115 // Leave cache headers out of resource bundle requests.
116 response_info_
.headers
= BuildHttpHeaders(content_security_policy
,
121 // Overridden from URLRequestSimpleJob:
122 virtual int GetData(std::string
* mime_type
,
123 std::string
* charset
,
125 const net::CompletionCallback
& callback
) const OVERRIDE
{
126 const ResourceBundle
& rb
= ResourceBundle::GetSharedInstance();
127 *data
= rb
.GetRawDataResource(resource_id_
).as_string();
129 // Add the Content-Length header now that we know the resource length.
130 response_info_
.headers
->AddHeader(base::StringPrintf(
131 "%s: %s", net::HttpRequestHeaders::kContentLength
,
132 base::UintToString(data
->size()).c_str()));
134 std::string
* read_mime_type
= new std::string
;
135 bool posted
= base::PostTaskAndReplyWithResult(
136 BrowserThread::GetBlockingPool(),
138 base::Bind(&net::GetMimeTypeFromFile
, filename_
,
139 base::Unretained(read_mime_type
)),
140 base::Bind(&URLRequestResourceBundleJob::OnMimeTypeRead
,
141 weak_factory_
.GetWeakPtr(),
142 mime_type
, charset
, data
,
143 base::Owned(read_mime_type
),
147 return net::ERR_IO_PENDING
;
150 virtual void GetResponseInfo(net::HttpResponseInfo
* info
) OVERRIDE
{
151 *info
= response_info_
;
155 virtual ~URLRequestResourceBundleJob() { }
157 void OnMimeTypeRead(std::string
* out_mime_type
,
158 std::string
* charset
,
160 std::string
* read_mime_type
,
161 const net::CompletionCallback
& callback
,
163 *out_mime_type
= *read_mime_type
;
164 if (StartsWithASCII(*read_mime_type
, "text/", false)) {
165 // All of our HTML files should be UTF-8 and for other resource types
166 // (like images), charset doesn't matter.
167 DCHECK(IsStringUTF8(*data
));
170 int result
= read_result
? net::OK
: net::ERR_INVALID_URL
;
171 callback
.Run(result
);
174 // We need the filename of the resource to determine the mime type.
175 base::FilePath filename_
;
177 // The resource bundle id to load.
180 net::HttpResponseInfo response_info_
;
182 mutable base::WeakPtrFactory
<URLRequestResourceBundleJob
> weak_factory_
;
185 class GeneratedBackgroundPageJob
: public net::URLRequestSimpleJob
{
187 GeneratedBackgroundPageJob(net::URLRequest
* request
,
188 net::NetworkDelegate
* network_delegate
,
189 const scoped_refptr
<const Extension
> extension
,
190 const std::string
& content_security_policy
)
191 : net::URLRequestSimpleJob(request
, network_delegate
),
192 extension_(extension
) {
193 const bool send_cors_headers
= false;
194 // Leave cache headers out of generated background page jobs.
195 response_info_
.headers
= BuildHttpHeaders(content_security_policy
,
200 // Overridden from URLRequestSimpleJob:
201 virtual int GetData(std::string
* mime_type
,
202 std::string
* charset
,
204 const net::CompletionCallback
& callback
) const OVERRIDE
{
205 *mime_type
= "text/html";
208 *data
= "<!DOCTYPE html>\n<body>\n";
209 const std::vector
<std::string
>& background_scripts
=
210 extensions::BackgroundInfo::GetBackgroundScripts(extension_
.get());
211 for (size_t i
= 0; i
< background_scripts
.size(); ++i
) {
212 *data
+= "<script src=\"";
213 *data
+= background_scripts
[i
];
214 *data
+= "\"></script>\n";
220 virtual void GetResponseInfo(net::HttpResponseInfo
* info
) OVERRIDE
{
221 *info
= response_info_
;
225 virtual ~GeneratedBackgroundPageJob() {}
227 scoped_refptr
<const Extension
> extension_
;
228 net::HttpResponseInfo response_info_
;
231 base::Time
GetFileLastModifiedTime(const base::FilePath
& filename
) {
232 if (base::PathExists(filename
)) {
233 base::File::Info info
;
234 if (base::GetFileInfo(filename
, &info
))
235 return info
.last_modified
;
240 base::Time
GetFileCreationTime(const base::FilePath
& filename
) {
241 if (base::PathExists(filename
)) {
242 base::File::Info info
;
243 if (base::GetFileInfo(filename
, &info
))
244 return info
.creation_time
;
249 void ReadResourceFilePathAndLastModifiedTime(
250 const extensions::ExtensionResource
& resource
,
251 const base::FilePath
& directory
,
252 base::FilePath
* file_path
,
253 base::Time
* last_modified_time
) {
254 *file_path
= resource
.GetFilePath();
255 *last_modified_time
= GetFileLastModifiedTime(*file_path
);
256 // While we're here, log the delta between extension directory
257 // creation time and the resource's last modification time.
258 base::ElapsedTimer query_timer
;
259 base::Time dir_creation_time
= GetFileCreationTime(directory
);
260 UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency",
261 query_timer
.Elapsed());
262 int64 delta_seconds
= (*last_modified_time
- dir_creation_time
).InSeconds();
263 if (delta_seconds
>= 0) {
264 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta",
267 base::TimeDelta::FromDays(30).InSeconds(),
270 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta",
273 base::TimeDelta::FromDays(30).InSeconds(),
278 class URLRequestExtensionJob
: public net::URLRequestFileJob
{
280 URLRequestExtensionJob(net::URLRequest
* request
,
281 net::NetworkDelegate
* network_delegate
,
282 const std::string
& extension_id
,
283 const base::FilePath
& directory_path
,
284 const base::FilePath
& relative_path
,
285 const std::string
& content_security_policy
,
286 bool send_cors_header
,
287 bool follow_symlinks_anywhere
)
288 : net::URLRequestFileJob(
289 request
, network_delegate
, base::FilePath(),
290 BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
291 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
)),
292 directory_path_(directory_path
),
293 // TODO(tc): Move all of these files into resources.pak so we don't break
294 // when updating on Linux.
295 resource_(extension_id
, directory_path
, relative_path
),
296 content_security_policy_(content_security_policy
),
297 send_cors_header_(send_cors_header
),
298 weak_factory_(this) {
299 if (follow_symlinks_anywhere
) {
300 resource_
.set_follow_symlinks_anywhere();
304 virtual void GetResponseInfo(net::HttpResponseInfo
* info
) OVERRIDE
{
305 *info
= response_info_
;
308 virtual void Start() OVERRIDE
{
309 base::FilePath
* read_file_path
= new base::FilePath
;
310 base::Time
* last_modified_time
= new base::Time();
311 bool posted
= BrowserThread::PostBlockingPoolTaskAndReply(
313 base::Bind(&ReadResourceFilePathAndLastModifiedTime
, resource_
,
315 base::Unretained(read_file_path
),
316 base::Unretained(last_modified_time
)),
317 base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead
,
318 weak_factory_
.GetWeakPtr(),
319 base::Owned(read_file_path
),
320 base::Owned(last_modified_time
)));
325 virtual ~URLRequestExtensionJob() {}
327 void OnFilePathAndLastModifiedTimeRead(base::FilePath
* read_file_path
,
328 base::Time
* last_modified_time
) {
329 file_path_
= *read_file_path
;
330 response_info_
.headers
= BuildHttpHeaders(
331 content_security_policy_
,
333 *last_modified_time
);
334 URLRequestFileJob::Start();
337 net::HttpResponseInfo response_info_
;
338 base::FilePath directory_path_
;
339 extensions::ExtensionResource resource_
;
340 std::string content_security_policy_
;
341 bool send_cors_header_
;
342 base::WeakPtrFactory
<URLRequestExtensionJob
> weak_factory_
;
345 bool ExtensionCanLoadInIncognito(const ResourceRequestInfo
* info
,
346 const std::string
& extension_id
,
347 extensions::InfoMap
* extension_info_map
) {
348 if (!extension_info_map
->IsIncognitoEnabled(extension_id
))
351 // Only allow incognito toplevel navigations to extension resources in
352 // split mode. In spanning mode, the extension must run in a single process,
353 // and an incognito tab prevents that.
354 if (info
->GetResourceType() == ResourceType::MAIN_FRAME
) {
355 const Extension
* extension
=
356 extension_info_map
->extensions().GetByID(extension_id
);
357 return extension
&& extensions::IncognitoInfo::IsSplitMode(extension
);
363 // Returns true if an chrome-extension:// resource should be allowed to load.
364 // TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we
365 // first need to find a way to get CanLoadInIncognito state into the renderers.
366 bool AllowExtensionResourceLoad(net::URLRequest
* request
,
368 const Extension
* extension
,
369 extensions::InfoMap
* extension_info_map
) {
370 const ResourceRequestInfo
* info
= ResourceRequestInfo::ForRequest(request
);
372 // We have seen crashes where info is NULL: crbug.com/52374.
374 LOG(ERROR
) << "Allowing load of " << request
->url().spec()
375 << "from unknown origin. Could not find user data for "
380 if (is_incognito
&& !ExtensionCanLoadInIncognito(info
, request
->url().host(),
381 extension_info_map
)) {
385 // The following checks are meant to replicate similar set of checks in the
386 // renderer process, performed by ResourceRequestPolicy::CanRequestResource.
387 // These are not exactly equivalent, because we don't have the same bits of
388 // information. The two checks need to be kept in sync as much as possible, as
389 // an exploited renderer can bypass the checks in ResourceRequestPolicy.
391 // Check if the extension for which this request is made is indeed loaded in
392 // the process sending the request. If not, we need to explicitly check if
393 // the resource is explicitly accessible or fits in a set of exception cases.
394 // Note: This allows a case where two extensions execute in the same renderer
395 // process to request each other's resources. We can't do a more precise
396 // check, since the renderer can lie about which extension has made the
398 if (extension_info_map
->process_map().Contains(
399 request
->url().host(), info
->GetChildID())) {
403 // Extensions with webview: allow loading certain resources by guest renderers
404 // with privileged partition IDs as specified in the manifest file.
405 ExtensionRendererState
* renderer_state
=
406 ExtensionRendererState::GetInstance();
407 ExtensionRendererState::WebViewInfo webview_info
;
408 bool is_guest
= renderer_state
->GetWebViewInfo(info
->GetChildID(),
411 std::string resource_path
= request
->url().path();
412 if (is_guest
&& webview_info
.allow_chrome_extension_urls
&&
413 extensions::WebviewInfo::IsResourceWebviewAccessible(
414 extension
, webview_info
.partition_id
, resource_path
)) {
418 // If the request is for navigations outside of webviews, then it should be
419 // allowed. The navigation logic in CrossSiteResourceHandler will properly
420 // transfer the navigation to a privileged process before it commits.
421 if (ResourceType::IsFrame(info
->GetResourceType()) && !is_guest
)
424 if (!content::PageTransitionIsWebTriggerable(info
->GetPageTransition()))
427 // The following checks require that we have an actual extension object. If we
428 // don't have it, allow the request handling to continue with the rest of the
433 // Disallow loading of packaged resources for hosted apps. We don't allow
434 // hybrid hosted/packaged apps. The one exception is access to icons, since
435 // some extensions want to be able to do things like create their own
437 std::string resource_root_relative_path
=
438 request
->url().path().empty() ? std::string()
439 : request
->url().path().substr(1);
440 if (extension
->is_hosted_app() &&
441 !extensions::IconsInfo::GetIcons(extension
)
442 .ContainsPath(resource_root_relative_path
)) {
443 LOG(ERROR
) << "Denying load of " << request
->url().spec() << " from "
448 // Extensions with web_accessible_resources: allow loading by regular
449 // renderers. Since not all subresources are required to be listed in a v2
450 // manifest, we must allow all loads if there are any web accessible
451 // resources. See http://crbug.com/179127.
452 if (extension
->manifest_version() < 2 ||
453 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
458 // If there aren't any explicitly marked web accessible resources, the
459 // load should be allowed only if it is by DevTools. A close approximation is
460 // checking if the extension contains a DevTools page.
461 if (extensions::ManifestURL::GetDevToolsPage(extension
).is_empty())
467 // Returns true if the given URL references an icon in the given extension.
468 bool URLIsForExtensionIcon(const GURL
& url
, const Extension
* extension
) {
469 DCHECK(url
.SchemeIs(extensions::kExtensionScheme
));
474 std::string path
= url
.path();
475 DCHECK_EQ(url
.host(), extension
->id());
476 DCHECK(path
.length() > 0 && path
[0] == '/');
477 path
= path
.substr(1);
478 return extensions::IconsInfo::GetIcons(extension
).ContainsPath(path
);
481 class ExtensionProtocolHandler
482 : public net::URLRequestJobFactory::ProtocolHandler
{
484 ExtensionProtocolHandler(bool is_incognito
,
485 extensions::InfoMap
* extension_info_map
)
486 : is_incognito_(is_incognito
), extension_info_map_(extension_info_map
) {}
488 virtual ~ExtensionProtocolHandler() {}
490 virtual net::URLRequestJob
* MaybeCreateJob(
491 net::URLRequest
* request
,
492 net::NetworkDelegate
* network_delegate
) const OVERRIDE
;
495 const bool is_incognito_
;
496 extensions::InfoMap
* const extension_info_map_
;
497 DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler
);
500 // Creates URLRequestJobs for extension:// URLs.
502 ExtensionProtocolHandler::MaybeCreateJob(
503 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) const {
504 // chrome-extension://extension-id/resource/path.js
505 std::string extension_id
= request
->url().host();
506 const Extension
* extension
=
507 extension_info_map_
->extensions().GetByID(extension_id
);
509 // TODO(mpcomplete): better error code.
510 if (!AllowExtensionResourceLoad(
511 request
, is_incognito_
, extension
, extension_info_map_
)) {
512 return new net::URLRequestErrorJob(
513 request
, network_delegate
, net::ERR_ADDRESS_UNREACHABLE
);
516 base::FilePath directory_path
;
518 directory_path
= extension
->path();
519 if (directory_path
.value().empty()) {
520 const Extension
* disabled_extension
=
521 extension_info_map_
->disabled_extensions().GetByID(extension_id
);
522 if (URLIsForExtensionIcon(request
->url(), disabled_extension
))
523 directory_path
= disabled_extension
->path();
524 if (directory_path
.value().empty()) {
525 LOG(WARNING
) << "Failed to GetPathForExtension: " << extension_id
;
530 std::string content_security_policy
;
531 bool send_cors_header
= false;
532 bool follow_symlinks_anywhere
= false;
534 std::string resource_path
= request
->url().path();
535 content_security_policy
=
536 extensions::CSPInfo::GetResourceContentSecurityPolicy(extension
,
538 if ((extension
->manifest_version() >= 2 ||
539 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
541 extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
542 extension
, resource_path
))
543 send_cors_header
= true;
545 follow_symlinks_anywhere
=
546 (extension
->creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE
)
550 std::string path
= request
->url().path();
551 if (path
.size() > 1 &&
552 path
.substr(1) == extensions::kGeneratedBackgroundPageFilename
) {
553 return new GeneratedBackgroundPageJob(
554 request
, network_delegate
, extension
, content_security_policy
);
557 base::FilePath resources_path
;
558 base::FilePath relative_path
;
559 // Try to load extension resources from chrome resource file if
560 // directory_path is a descendant of resources_path. resources_path
561 // corresponds to src/chrome/browser/resources in source tree.
562 if (PathService::Get(chrome::DIR_RESOURCES
, &resources_path
) &&
563 // Since component extension resources are included in
564 // component_extension_resources.pak file in resources_path, calculate
565 // extension relative path against resources_path.
566 resources_path
.AppendRelativePath(directory_path
, &relative_path
)) {
567 base::FilePath request_path
=
568 extensions::file_util::ExtensionURLToRelativeFilePath(request
->url());
570 if (extensions::ImageLoader::IsComponentExtensionResource(
571 directory_path
, request_path
, &resource_id
)) {
572 relative_path
= relative_path
.Append(request_path
);
573 relative_path
= relative_path
.NormalizePathSeparators();
574 return new URLRequestResourceBundleJob(
579 content_security_policy
,
585 extensions::file_util::ExtensionURLToRelativeFilePath(request
->url());
587 if (SharedModuleInfo::IsImportedPath(path
)) {
588 std::string new_extension_id
;
589 std::string new_relative_path
;
590 SharedModuleInfo::ParseImportedPath(path
, &new_extension_id
,
592 const Extension
* new_extension
=
593 extension_info_map_
->extensions().GetByID(new_extension_id
);
595 bool first_party_in_import
= false;
596 // NB: This first_party_for_cookies call is not for security, it is only
597 // used so an exported extension can limit the visible surface to the
598 // extension that imports it, more or less constituting its API.
599 const std::string
& first_party_path
=
600 request
->first_party_for_cookies().path();
601 if (SharedModuleInfo::IsImportedPath(first_party_path
)) {
602 std::string first_party_id
;
604 SharedModuleInfo::ParseImportedPath(first_party_path
, &first_party_id
,
606 if (first_party_id
== new_extension_id
) {
607 first_party_in_import
= true;
611 if (SharedModuleInfo::ImportsExtensionById(extension
, new_extension_id
) &&
613 (first_party_in_import
||
614 SharedModuleInfo::IsExportAllowed(new_extension
, new_relative_path
))) {
615 directory_path
= new_extension
->path();
616 extension_id
= new_extension_id
;
617 relative_path
= base::FilePath::FromUTF8Unsafe(new_relative_path
);
623 return new URLRequestExtensionJob(request
,
628 content_security_policy
,
630 follow_symlinks_anywhere
);
635 net::URLRequestJobFactory::ProtocolHandler
* CreateExtensionProtocolHandler(
637 extensions::InfoMap
* extension_info_map
) {
638 return new ExtensionProtocolHandler(is_incognito
, extension_info_map
);