1 // Copyright 2014 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 "extensions/browser/extension_protocols.h"
11 #include "base/base64.h"
12 #include "base/compiler_specific.h"
13 #include "base/files/file_path.h"
14 #include "base/files/file_util.h"
15 #include "base/format_macros.h"
16 #include "base/logging.h"
17 #include "base/memory/weak_ptr.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/metrics/field_trial.h"
20 #include "base/metrics/histogram.h"
21 #include "base/metrics/sparse_histogram.h"
22 #include "base/path_service.h"
23 #include "base/profiler/scoped_tracker.h"
24 #include "base/sha1.h"
25 #include "base/strings/string_number_conversions.h"
26 #include "base/strings/string_util.h"
27 #include "base/strings/stringprintf.h"
28 #include "base/strings/utf_string_conversions.h"
29 #include "base/threading/sequenced_worker_pool.h"
30 #include "base/threading/thread_restrictions.h"
31 #include "base/timer/elapsed_timer.h"
32 #include "build/build_config.h"
33 #include "content/public/browser/browser_thread.h"
34 #include "content/public/browser/resource_request_info.h"
35 #include "crypto/secure_hash.h"
36 #include "crypto/sha2.h"
37 #include "extensions/browser/content_verifier.h"
38 #include "extensions/browser/content_verify_job.h"
39 #include "extensions/browser/extensions_browser_client.h"
40 #include "extensions/browser/info_map.h"
41 #include "extensions/browser/url_request_util.h"
42 #include "extensions/common/constants.h"
43 #include "extensions/common/extension.h"
44 #include "extensions/common/extension_resource.h"
45 #include "extensions/common/file_util.h"
46 #include "extensions/common/manifest_handlers/background_info.h"
47 #include "extensions/common/manifest_handlers/csp_info.h"
48 #include "extensions/common/manifest_handlers/icons_handler.h"
49 #include "extensions/common/manifest_handlers/incognito_info.h"
50 #include "extensions/common/manifest_handlers/shared_module_info.h"
51 #include "extensions/common/manifest_handlers/web_accessible_resources_info.h"
52 #include "net/base/io_buffer.h"
53 #include "net/base/net_errors.h"
54 #include "net/http/http_request_headers.h"
55 #include "net/http/http_response_headers.h"
56 #include "net/http/http_response_info.h"
57 #include "net/url_request/url_request_error_job.h"
58 #include "net/url_request/url_request_file_job.h"
59 #include "net/url_request/url_request_simple_job.h"
60 #include "url/url_util.h"
62 using content::BrowserThread
;
63 using content::ResourceRequestInfo
;
64 using content::ResourceType
;
65 using extensions::Extension
;
66 using extensions::SharedModuleInfo
;
68 namespace extensions
{
71 class GeneratedBackgroundPageJob
: public net::URLRequestSimpleJob
{
73 GeneratedBackgroundPageJob(net::URLRequest
* request
,
74 net::NetworkDelegate
* network_delegate
,
75 const scoped_refptr
<const Extension
> extension
,
76 const std::string
& content_security_policy
)
77 : net::URLRequestSimpleJob(request
, network_delegate
),
78 extension_(extension
) {
79 const bool send_cors_headers
= false;
80 // Leave cache headers out of generated background page jobs.
81 response_info_
.headers
= BuildHttpHeaders(content_security_policy
,
86 // Overridden from URLRequestSimpleJob:
87 int GetData(std::string
* mime_type
,
90 const net::CompletionCallback
& callback
) const override
{
91 // TODO(vadimt): Remove ScopedTracker below once crbug.com/422489 is fixed.
92 tracked_objects::ScopedTracker
tracking_profile(
93 FROM_HERE_WITH_EXPLICIT_FUNCTION(
94 "422489 GeneratedBackgroundPageJob::GetData"));
96 *mime_type
= "text/html";
99 *data
= "<!DOCTYPE html>\n<body>\n";
100 const std::vector
<std::string
>& background_scripts
=
101 extensions::BackgroundInfo::GetBackgroundScripts(extension_
.get());
102 for (size_t i
= 0; i
< background_scripts
.size(); ++i
) {
103 *data
+= "<script src=\"";
104 *data
+= background_scripts
[i
];
105 *data
+= "\"></script>\n";
111 void GetResponseInfo(net::HttpResponseInfo
* info
) override
{
112 *info
= response_info_
;
116 ~GeneratedBackgroundPageJob() override
{}
118 scoped_refptr
<const Extension
> extension_
;
119 net::HttpResponseInfo response_info_
;
122 base::Time
GetFileLastModifiedTime(const base::FilePath
& filename
) {
123 if (base::PathExists(filename
)) {
124 base::File::Info info
;
125 if (base::GetFileInfo(filename
, &info
))
126 return info
.last_modified
;
131 base::Time
GetFileCreationTime(const base::FilePath
& filename
) {
132 if (base::PathExists(filename
)) {
133 base::File::Info info
;
134 if (base::GetFileInfo(filename
, &info
))
135 return info
.creation_time
;
140 void ReadResourceFilePathAndLastModifiedTime(
141 const extensions::ExtensionResource
& resource
,
142 const base::FilePath
& directory
,
143 base::FilePath
* file_path
,
144 base::Time
* last_modified_time
) {
145 *file_path
= resource
.GetFilePath();
146 *last_modified_time
= GetFileLastModifiedTime(*file_path
);
147 // While we're here, log the delta between extension directory
148 // creation time and the resource's last modification time.
149 base::ElapsedTimer query_timer
;
150 base::Time dir_creation_time
= GetFileCreationTime(directory
);
151 UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency",
152 query_timer
.Elapsed());
153 int64 delta_seconds
= (*last_modified_time
- dir_creation_time
).InSeconds();
154 if (delta_seconds
>= 0) {
155 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta",
158 base::TimeDelta::FromDays(30).InSeconds(),
161 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta",
164 base::TimeDelta::FromDays(30).InSeconds(),
169 class URLRequestExtensionJob
: public net::URLRequestFileJob
{
171 URLRequestExtensionJob(net::URLRequest
* request
,
172 net::NetworkDelegate
* network_delegate
,
173 const std::string
& extension_id
,
174 const base::FilePath
& directory_path
,
175 const base::FilePath
& relative_path
,
176 const std::string
& content_security_policy
,
177 bool send_cors_header
,
178 bool follow_symlinks_anywhere
,
179 ContentVerifyJob
* verify_job
)
180 : net::URLRequestFileJob(
184 BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior(
185 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN
)),
186 verify_job_(verify_job
),
189 directory_path_(directory_path
),
190 // TODO(tc): Move all of these files into resources.pak so we don't
191 // break when updating on Linux.
192 resource_(extension_id
, directory_path
, relative_path
),
193 content_security_policy_(content_security_policy
),
194 send_cors_header_(send_cors_header
),
195 weak_factory_(this) {
196 if (follow_symlinks_anywhere
) {
197 resource_
.set_follow_symlinks_anywhere();
201 void GetResponseInfo(net::HttpResponseInfo
* info
) override
{
202 *info
= response_info_
;
205 void Start() override
{
206 request_timer_
.reset(new base::ElapsedTimer());
207 base::FilePath
* read_file_path
= new base::FilePath
;
208 base::Time
* last_modified_time
= new base::Time();
209 bool posted
= BrowserThread::PostBlockingPoolTaskAndReply(
211 base::Bind(&ReadResourceFilePathAndLastModifiedTime
,
214 base::Unretained(read_file_path
),
215 base::Unretained(last_modified_time
)),
216 base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead
,
217 weak_factory_
.GetWeakPtr(),
218 base::Owned(read_file_path
),
219 base::Owned(last_modified_time
)));
223 bool IsRedirectResponse(GURL
* location
, int* http_status_code
) override
{
227 void SetExtraRequestHeaders(const net::HttpRequestHeaders
& headers
) override
{
228 // TODO(asargent) - we'll need to add proper support for range headers.
230 std::string range_header
;
231 if (headers
.GetHeader(net::HttpRequestHeaders::kRange
, &range_header
)) {
232 if (verify_job_
.get())
235 URLRequestFileJob::SetExtraRequestHeaders(headers
);
238 void OnSeekComplete(int64 result
) override
{
239 DCHECK_EQ(seek_position_
, 0);
240 seek_position_
= result
;
241 // TODO(asargent) - we'll need to add proper support for range headers.
243 if (result
> 0 && verify_job_
.get())
247 void OnReadComplete(net::IOBuffer
* buffer
, int result
) override
{
249 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.OnReadCompleteResult", result
);
251 UMA_HISTOGRAM_SPARSE_SLOWLY("ExtensionUrlRequest.OnReadCompleteError",
254 bytes_read_
+= result
;
255 if (verify_job_
.get()) {
256 verify_job_
->BytesRead(result
, buffer
->data());
257 if (!remaining_bytes())
258 verify_job_
->DoneReading();
264 ~URLRequestExtensionJob() override
{
265 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.TotalKbRead", bytes_read_
/ 1024);
266 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.SeekPosition", seek_position_
);
267 if (request_timer_
.get())
268 UMA_HISTOGRAM_TIMES("ExtensionUrlRequest.Latency",
269 request_timer_
->Elapsed());
272 void OnFilePathAndLastModifiedTimeRead(base::FilePath
* read_file_path
,
273 base::Time
* last_modified_time
) {
274 file_path_
= *read_file_path
;
275 response_info_
.headers
= BuildHttpHeaders(
276 content_security_policy_
,
278 *last_modified_time
);
279 URLRequestFileJob::Start();
282 scoped_refptr
<ContentVerifyJob
> verify_job_
;
284 scoped_ptr
<base::ElapsedTimer
> request_timer_
;
286 // The position we seeked to in the file.
287 int64 seek_position_
;
289 // The number of bytes of content we read from the file.
292 net::HttpResponseInfo response_info_
;
293 base::FilePath directory_path_
;
294 extensions::ExtensionResource resource_
;
295 std::string content_security_policy_
;
296 bool send_cors_header_
;
297 base::WeakPtrFactory
<URLRequestExtensionJob
> weak_factory_
;
300 bool ExtensionCanLoadInIncognito(const ResourceRequestInfo
* info
,
301 const std::string
& extension_id
,
302 extensions::InfoMap
* extension_info_map
) {
303 if (!extension_info_map
->IsIncognitoEnabled(extension_id
))
306 // Only allow incognito toplevel navigations to extension resources in
307 // split mode. In spanning mode, the extension must run in a single process,
308 // and an incognito tab prevents that.
309 if (info
->GetResourceType() == content::RESOURCE_TYPE_MAIN_FRAME
) {
310 const Extension
* extension
=
311 extension_info_map
->extensions().GetByID(extension_id
);
312 return extension
&& extensions::IncognitoInfo::IsSplitMode(extension
);
318 // Returns true if an chrome-extension:// resource should be allowed to load.
319 // Pass true for |is_incognito| only for incognito profiles and not Chrome OS
320 // guest mode profiles.
321 // TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we
322 // first need to find a way to get CanLoadInIncognito state into the renderers.
323 bool AllowExtensionResourceLoad(net::URLRequest
* request
,
325 const Extension
* extension
,
326 extensions::InfoMap
* extension_info_map
) {
327 const ResourceRequestInfo
* info
= ResourceRequestInfo::ForRequest(request
);
329 // We have seen crashes where info is NULL: crbug.com/52374.
331 LOG(ERROR
) << "Allowing load of " << request
->url().spec()
332 << "from unknown origin. Could not find user data for "
337 if (is_incognito
&& !ExtensionCanLoadInIncognito(
338 info
, request
->url().host(), extension_info_map
)) {
342 // The following checks are meant to replicate similar set of checks in the
343 // renderer process, performed by ResourceRequestPolicy::CanRequestResource.
344 // These are not exactly equivalent, because we don't have the same bits of
345 // information. The two checks need to be kept in sync as much as possible, as
346 // an exploited renderer can bypass the checks in ResourceRequestPolicy.
348 // Check if the extension for which this request is made is indeed loaded in
349 // the process sending the request. If not, we need to explicitly check if
350 // the resource is explicitly accessible or fits in a set of exception cases.
351 // Note: This allows a case where two extensions execute in the same renderer
352 // process to request each other's resources. We can't do a more precise
353 // check, since the renderer can lie about which extension has made the
355 if (extension_info_map
->process_map().Contains(
356 request
->url().host(), info
->GetChildID())) {
360 // Allow the extension module embedder to grant permission for loads.
361 if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad(
362 request
, is_incognito
, extension
, extension_info_map
)) {
366 // No special exceptions for cross-process loading. Block the load.
370 // Returns true if the given URL references an icon in the given extension.
371 bool URLIsForExtensionIcon(const GURL
& url
, const Extension
* extension
) {
372 DCHECK(url
.SchemeIs(extensions::kExtensionScheme
));
377 std::string path
= url
.path();
378 DCHECK_EQ(url
.host(), extension
->id());
379 DCHECK(path
.length() > 0 && path
[0] == '/');
380 path
= path
.substr(1);
381 return extensions::IconsInfo::GetIcons(extension
).ContainsPath(path
);
384 class ExtensionProtocolHandler
385 : public net::URLRequestJobFactory::ProtocolHandler
{
387 ExtensionProtocolHandler(bool is_incognito
,
388 extensions::InfoMap
* extension_info_map
)
389 : is_incognito_(is_incognito
), extension_info_map_(extension_info_map
) {}
391 ~ExtensionProtocolHandler() override
{}
393 net::URLRequestJob
* MaybeCreateJob(
394 net::URLRequest
* request
,
395 net::NetworkDelegate
* network_delegate
) const override
;
398 const bool is_incognito_
;
399 extensions::InfoMap
* const extension_info_map_
;
400 DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler
);
403 // Creates URLRequestJobs for extension:// URLs.
405 ExtensionProtocolHandler::MaybeCreateJob(
406 net::URLRequest
* request
, net::NetworkDelegate
* network_delegate
) const {
407 // chrome-extension://extension-id/resource/path.js
408 std::string extension_id
= request
->url().host();
409 const Extension
* extension
=
410 extension_info_map_
->extensions().GetByID(extension_id
);
412 // TODO(mpcomplete): better error code.
413 if (!AllowExtensionResourceLoad(
414 request
, is_incognito_
, extension
, extension_info_map_
)) {
415 return new net::URLRequestErrorJob(
416 request
, network_delegate
, net::ERR_ADDRESS_UNREACHABLE
);
419 // If this is a disabled extension only allow the icon to load.
420 base::FilePath directory_path
;
422 directory_path
= extension
->path();
423 if (directory_path
.value().empty()) {
424 const Extension
* disabled_extension
=
425 extension_info_map_
->disabled_extensions().GetByID(extension_id
);
426 if (URLIsForExtensionIcon(request
->url(), disabled_extension
))
427 directory_path
= disabled_extension
->path();
428 if (directory_path
.value().empty()) {
429 LOG(WARNING
) << "Failed to GetPathForExtension: " << extension_id
;
434 // Set up content security policy.
435 std::string content_security_policy
;
436 bool send_cors_header
= false;
437 bool follow_symlinks_anywhere
= false;
440 std::string resource_path
= request
->url().path();
442 // Use default CSP for <webview>.
443 if (!url_request_util::IsWebViewRequest(request
)) {
444 content_security_policy
=
445 extensions::CSPInfo::GetResourceContentSecurityPolicy(extension
,
449 if ((extension
->manifest_version() >= 2 ||
450 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources(
452 extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible(
453 extension
, resource_path
)) {
454 send_cors_header
= true;
457 follow_symlinks_anywhere
=
458 (extension
->creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE
)
462 // Create a job for a generated background page.
463 std::string path
= request
->url().path();
464 if (path
.size() > 1 &&
465 path
.substr(1) == extensions::kGeneratedBackgroundPageFilename
) {
466 return new GeneratedBackgroundPageJob(
467 request
, network_delegate
, extension
, content_security_policy
);
470 // Component extension resources may be part of the embedder's resource files,
471 // for example component_extension_resources.pak in Chrome.
472 net::URLRequestJob
* resource_bundle_job
=
473 extensions::ExtensionsBrowserClient::Get()
474 ->MaybeCreateResourceBundleRequestJob(request
,
477 content_security_policy
,
479 if (resource_bundle_job
)
480 return resource_bundle_job
;
482 base::FilePath relative_path
=
483 extensions::file_util::ExtensionURLToRelativeFilePath(request
->url());
485 // Handle shared resources (extension A loading resources out of extension B).
486 if (SharedModuleInfo::IsImportedPath(path
)) {
487 std::string new_extension_id
;
488 std::string new_relative_path
;
489 SharedModuleInfo::ParseImportedPath(path
, &new_extension_id
,
491 const Extension
* new_extension
=
492 extension_info_map_
->extensions().GetByID(new_extension_id
);
494 if (SharedModuleInfo::ImportsExtensionById(extension
, new_extension_id
) &&
496 directory_path
= new_extension
->path();
497 extension_id
= new_extension_id
;
498 relative_path
= base::FilePath::FromUTF8Unsafe(new_relative_path
);
503 ContentVerifyJob
* verify_job
= NULL
;
504 ContentVerifier
* verifier
= extension_info_map_
->content_verifier();
507 verifier
->CreateJobFor(extension_id
, directory_path
, relative_path
);
512 return new URLRequestExtensionJob(request
,
517 content_security_policy
,
519 follow_symlinks_anywhere
,
525 net::HttpResponseHeaders
* BuildHttpHeaders(
526 const std::string
& content_security_policy
,
527 bool send_cors_header
,
528 const base::Time
& last_modified_time
) {
529 std::string raw_headers
;
530 raw_headers
.append("HTTP/1.1 200 OK");
531 if (!content_security_policy
.empty()) {
532 raw_headers
.append(1, '\0');
533 raw_headers
.append("Content-Security-Policy: ");
534 raw_headers
.append(content_security_policy
);
537 if (send_cors_header
) {
538 raw_headers
.append(1, '\0');
539 raw_headers
.append("Access-Control-Allow-Origin: *");
542 if (!last_modified_time
.is_null()) {
543 // Hash the time and make an etag to avoid exposing the exact
544 // user installation time of the extension.
546 base::StringPrintf("%" PRId64
, last_modified_time
.ToInternalValue());
547 hash
= base::SHA1HashString(hash
);
549 base::Base64Encode(hash
, &etag
);
550 raw_headers
.append(1, '\0');
551 raw_headers
.append("ETag: \"");
552 raw_headers
.append(etag
);
553 raw_headers
.append("\"");
554 // Also force revalidation.
555 raw_headers
.append(1, '\0');
556 raw_headers
.append("cache-control: no-cache");
559 raw_headers
.append(2, '\0');
560 return new net::HttpResponseHeaders(raw_headers
);
563 net::URLRequestJobFactory::ProtocolHandler
* CreateExtensionProtocolHandler(
565 extensions::InfoMap
* extension_info_map
) {
566 return new ExtensionProtocolHandler(is_incognito
, extension_info_map
);
569 } // namespace extensions