extension_protocols.cc revision 010d83a9304c5a91596085d917d248abff47903a
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. 4 5#include "extensions/browser/extension_protocols.h" 6 7#include <algorithm> 8#include <string> 9#include <vector> 10 11#include "base/base64.h" 12#include "base/compiler_specific.h" 13#include "base/file_util.h" 14#include "base/files/file_path.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/sha1.h" 24#include "base/strings/string_number_conversions.h" 25#include "base/strings/string_util.h" 26#include "base/strings/stringprintf.h" 27#include "base/strings/utf_string_conversions.h" 28#include "base/threading/sequenced_worker_pool.h" 29#include "base/threading/thread_restrictions.h" 30#include "base/timer/elapsed_timer.h" 31#include "build/build_config.h" 32#include "content/public/browser/browser_thread.h" 33#include "content/public/browser/resource_request_info.h" 34#include "crypto/secure_hash.h" 35#include "crypto/sha2.h" 36#include "extensions/browser/content_verifier.h" 37#include "extensions/browser/content_verify_job.h" 38#include "extensions/browser/extensions_browser_client.h" 39#include "extensions/browser/info_map.h" 40#include "extensions/common/constants.h" 41#include "extensions/common/extension.h" 42#include "extensions/common/extension_resource.h" 43#include "extensions/common/file_util.h" 44#include "extensions/common/manifest_handlers/background_info.h" 45#include "extensions/common/manifest_handlers/csp_info.h" 46#include "extensions/common/manifest_handlers/icons_handler.h" 47#include "extensions/common/manifest_handlers/incognito_info.h" 48#include "extensions/common/manifest_handlers/shared_module_info.h" 49#include "extensions/common/manifest_handlers/web_accessible_resources_info.h" 50#include "net/base/io_buffer.h" 51#include "net/base/net_errors.h" 52#include "net/http/http_request_headers.h" 53#include "net/http/http_response_headers.h" 54#include "net/http/http_response_info.h" 55#include "net/url_request/url_request_error_job.h" 56#include "net/url_request/url_request_file_job.h" 57#include "net/url_request/url_request_simple_job.h" 58#include "url/url_util.h" 59 60using content::BrowserThread; 61using content::ResourceRequestInfo; 62using extensions::Extension; 63using extensions::SharedModuleInfo; 64 65namespace extensions { 66namespace { 67 68class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob { 69 public: 70 GeneratedBackgroundPageJob(net::URLRequest* request, 71 net::NetworkDelegate* network_delegate, 72 const scoped_refptr<const Extension> extension, 73 const std::string& content_security_policy) 74 : net::URLRequestSimpleJob(request, network_delegate), 75 extension_(extension) { 76 const bool send_cors_headers = false; 77 // Leave cache headers out of generated background page jobs. 78 response_info_.headers = BuildHttpHeaders(content_security_policy, 79 send_cors_headers, 80 base::Time()); 81 } 82 83 // Overridden from URLRequestSimpleJob: 84 virtual int GetData(std::string* mime_type, 85 std::string* charset, 86 std::string* data, 87 const net::CompletionCallback& callback) const OVERRIDE { 88 *mime_type = "text/html"; 89 *charset = "utf-8"; 90 91 *data = "<!DOCTYPE html>\n<body>\n"; 92 const std::vector<std::string>& background_scripts = 93 extensions::BackgroundInfo::GetBackgroundScripts(extension_.get()); 94 for (size_t i = 0; i < background_scripts.size(); ++i) { 95 *data += "<script src=\""; 96 *data += background_scripts[i]; 97 *data += "\"></script>\n"; 98 } 99 100 return net::OK; 101 } 102 103 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 104 *info = response_info_; 105 } 106 107 private: 108 virtual ~GeneratedBackgroundPageJob() {} 109 110 scoped_refptr<const Extension> extension_; 111 net::HttpResponseInfo response_info_; 112}; 113 114base::Time GetFileLastModifiedTime(const base::FilePath& filename) { 115 if (base::PathExists(filename)) { 116 base::File::Info info; 117 if (base::GetFileInfo(filename, &info)) 118 return info.last_modified; 119 } 120 return base::Time(); 121} 122 123base::Time GetFileCreationTime(const base::FilePath& filename) { 124 if (base::PathExists(filename)) { 125 base::File::Info info; 126 if (base::GetFileInfo(filename, &info)) 127 return info.creation_time; 128 } 129 return base::Time(); 130} 131 132void ReadResourceFilePathAndLastModifiedTime( 133 const extensions::ExtensionResource& resource, 134 const base::FilePath& directory, 135 base::FilePath* file_path, 136 base::Time* last_modified_time) { 137 *file_path = resource.GetFilePath(); 138 *last_modified_time = GetFileLastModifiedTime(*file_path); 139 // While we're here, log the delta between extension directory 140 // creation time and the resource's last modification time. 141 base::ElapsedTimer query_timer; 142 base::Time dir_creation_time = GetFileCreationTime(directory); 143 UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency", 144 query_timer.Elapsed()); 145 int64 delta_seconds = (*last_modified_time - dir_creation_time).InSeconds(); 146 if (delta_seconds >= 0) { 147 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta", 148 delta_seconds, 149 0, 150 base::TimeDelta::FromDays(30).InSeconds(), 151 50); 152 } else { 153 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta", 154 -delta_seconds, 155 1, 156 base::TimeDelta::FromDays(30).InSeconds(), 157 50); 158 } 159} 160 161class URLRequestExtensionJob : public net::URLRequestFileJob { 162 public: 163 URLRequestExtensionJob(net::URLRequest* request, 164 net::NetworkDelegate* network_delegate, 165 const std::string& extension_id, 166 const base::FilePath& directory_path, 167 const base::FilePath& relative_path, 168 const std::string& content_security_policy, 169 bool send_cors_header, 170 bool follow_symlinks_anywhere, 171 ContentVerifyJob* verify_job) 172 : net::URLRequestFileJob( 173 request, 174 network_delegate, 175 base::FilePath(), 176 BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( 177 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), 178 verify_job_(verify_job), 179 seek_position_(0), 180 bytes_read_(0), 181 directory_path_(directory_path), 182 // TODO(tc): Move all of these files into resources.pak so we don't 183 // break when updating on Linux. 184 resource_(extension_id, directory_path, relative_path), 185 content_security_policy_(content_security_policy), 186 send_cors_header_(send_cors_header), 187 weak_factory_(this) { 188 if (follow_symlinks_anywhere) { 189 resource_.set_follow_symlinks_anywhere(); 190 } 191 } 192 193 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 194 *info = response_info_; 195 } 196 197 virtual void Start() OVERRIDE { 198 base::FilePath* read_file_path = new base::FilePath; 199 base::Time* last_modified_time = new base::Time(); 200 bool posted = BrowserThread::PostBlockingPoolTaskAndReply( 201 FROM_HERE, 202 base::Bind(&ReadResourceFilePathAndLastModifiedTime, 203 resource_, 204 directory_path_, 205 base::Unretained(read_file_path), 206 base::Unretained(last_modified_time)), 207 base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead, 208 weak_factory_.GetWeakPtr(), 209 base::Owned(read_file_path), 210 base::Owned(last_modified_time))); 211 DCHECK(posted); 212 } 213 214 virtual void SetExtraRequestHeaders( 215 const net::HttpRequestHeaders& headers) OVERRIDE { 216 // TODO(asargent) - we'll need to add proper support for range headers. 217 // crbug.com/369895. 218 std::string range_header; 219 if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { 220 if (verify_job_) 221 verify_job_ = NULL; 222 } 223 URLRequestFileJob::SetExtraRequestHeaders(headers); 224 } 225 226 virtual void OnSeekComplete(int64 result) OVERRIDE { 227 DCHECK_EQ(seek_position_, 0); 228 seek_position_ = result; 229 // TODO(asargent) - we'll need to add proper support for range headers. 230 // crbug.com/369895. 231 if (result > 0 && verify_job_) 232 verify_job_ = NULL; 233 } 234 235 virtual void OnReadComplete(net::IOBuffer* buffer, int result) OVERRIDE { 236 if (result >= 0) 237 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.OnReadCompleteResult", result); 238 else 239 UMA_HISTOGRAM_SPARSE_SLOWLY("ExtensionUrlRequest.OnReadCompleteError", 240 -result); 241 if (result > 0) { 242 bytes_read_ += result; 243 if (verify_job_) { 244 verify_job_->BytesRead(result, buffer->data()); 245 if (!remaining_bytes()) 246 verify_job_->DoneReading(); 247 } 248 } 249 } 250 251 private: 252 virtual ~URLRequestExtensionJob() { 253 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.TotalKbRead", bytes_read_ / 1024); 254 UMA_HISTOGRAM_COUNTS("ExtensionUrlRequest.SeekPosition", seek_position_); 255 } 256 257 void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path, 258 base::Time* last_modified_time) { 259 file_path_ = *read_file_path; 260 response_info_.headers = BuildHttpHeaders( 261 content_security_policy_, 262 send_cors_header_, 263 *last_modified_time); 264 URLRequestFileJob::Start(); 265 } 266 267 scoped_refptr<ContentVerifyJob> verify_job_; 268 269 // The position we seeked to in the file. 270 int64 seek_position_; 271 272 // The number of bytes of content we read from the file. 273 int bytes_read_; 274 275 net::HttpResponseInfo response_info_; 276 base::FilePath directory_path_; 277 extensions::ExtensionResource resource_; 278 std::string content_security_policy_; 279 bool send_cors_header_; 280 base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_; 281}; 282 283bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info, 284 const std::string& extension_id, 285 extensions::InfoMap* extension_info_map) { 286 if (!extension_info_map->IsIncognitoEnabled(extension_id)) 287 return false; 288 289 // Only allow incognito toplevel navigations to extension resources in 290 // split mode. In spanning mode, the extension must run in a single process, 291 // and an incognito tab prevents that. 292 if (info->GetResourceType() == ResourceType::MAIN_FRAME) { 293 const Extension* extension = 294 extension_info_map->extensions().GetByID(extension_id); 295 return extension && extensions::IncognitoInfo::IsSplitMode(extension); 296 } 297 298 return true; 299} 300 301// Returns true if an chrome-extension:// resource should be allowed to load. 302// Pass true for |is_incognito| only for incognito profiles and not Chrome OS 303// guest mode profiles. 304// TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we 305// first need to find a way to get CanLoadInIncognito state into the renderers. 306bool AllowExtensionResourceLoad(net::URLRequest* request, 307 bool is_incognito, 308 const Extension* extension, 309 extensions::InfoMap* extension_info_map) { 310 const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); 311 312 // We have seen crashes where info is NULL: crbug.com/52374. 313 if (!info) { 314 LOG(ERROR) << "Allowing load of " << request->url().spec() 315 << "from unknown origin. Could not find user data for " 316 << "request."; 317 return true; 318 } 319 320 if (is_incognito && !ExtensionCanLoadInIncognito( 321 info, request->url().host(), extension_info_map)) { 322 return false; 323 } 324 325 // The following checks are meant to replicate similar set of checks in the 326 // renderer process, performed by ResourceRequestPolicy::CanRequestResource. 327 // These are not exactly equivalent, because we don't have the same bits of 328 // information. The two checks need to be kept in sync as much as possible, as 329 // an exploited renderer can bypass the checks in ResourceRequestPolicy. 330 331 // Check if the extension for which this request is made is indeed loaded in 332 // the process sending the request. If not, we need to explicitly check if 333 // the resource is explicitly accessible or fits in a set of exception cases. 334 // Note: This allows a case where two extensions execute in the same renderer 335 // process to request each other's resources. We can't do a more precise 336 // check, since the renderer can lie about which extension has made the 337 // request. 338 if (extension_info_map->process_map().Contains( 339 request->url().host(), info->GetChildID())) { 340 return true; 341 } 342 343 // Allow the extension module embedder to grant permission for loads. 344 if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad( 345 request, is_incognito, extension, extension_info_map)) { 346 return true; 347 } 348 349 // No special exceptions for cross-process loading. Block the load. 350 return false; 351} 352 353// Returns true if the given URL references an icon in the given extension. 354bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) { 355 DCHECK(url.SchemeIs(extensions::kExtensionScheme)); 356 357 if (!extension) 358 return false; 359 360 std::string path = url.path(); 361 DCHECK_EQ(url.host(), extension->id()); 362 DCHECK(path.length() > 0 && path[0] == '/'); 363 path = path.substr(1); 364 return extensions::IconsInfo::GetIcons(extension).ContainsPath(path); 365} 366 367class ExtensionProtocolHandler 368 : public net::URLRequestJobFactory::ProtocolHandler { 369 public: 370 ExtensionProtocolHandler(bool is_incognito, 371 extensions::InfoMap* extension_info_map) 372 : is_incognito_(is_incognito), extension_info_map_(extension_info_map) {} 373 374 virtual ~ExtensionProtocolHandler() {} 375 376 virtual net::URLRequestJob* MaybeCreateJob( 377 net::URLRequest* request, 378 net::NetworkDelegate* network_delegate) const OVERRIDE; 379 380 private: 381 const bool is_incognito_; 382 extensions::InfoMap* const extension_info_map_; 383 DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler); 384}; 385 386// Creates URLRequestJobs for extension:// URLs. 387net::URLRequestJob* 388ExtensionProtocolHandler::MaybeCreateJob( 389 net::URLRequest* request, net::NetworkDelegate* network_delegate) const { 390 // chrome-extension://extension-id/resource/path.js 391 std::string extension_id = request->url().host(); 392 const Extension* extension = 393 extension_info_map_->extensions().GetByID(extension_id); 394 395 // TODO(mpcomplete): better error code. 396 if (!AllowExtensionResourceLoad( 397 request, is_incognito_, extension, extension_info_map_)) { 398 return new net::URLRequestErrorJob( 399 request, network_delegate, net::ERR_ADDRESS_UNREACHABLE); 400 } 401 402 // If this is a disabled extension only allow the icon to load. 403 base::FilePath directory_path; 404 if (extension) 405 directory_path = extension->path(); 406 if (directory_path.value().empty()) { 407 const Extension* disabled_extension = 408 extension_info_map_->disabled_extensions().GetByID(extension_id); 409 if (URLIsForExtensionIcon(request->url(), disabled_extension)) 410 directory_path = disabled_extension->path(); 411 if (directory_path.value().empty()) { 412 LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id; 413 return NULL; 414 } 415 } 416 417 // Set up content security policy. 418 std::string content_security_policy; 419 bool send_cors_header = false; 420 bool follow_symlinks_anywhere = false; 421 422 if (extension) { 423 std::string resource_path = request->url().path(); 424 425 // Use default CSP for <webview>. 426 if (!ExtensionsBrowserClient::Get()->IsWebViewRequest(request)) { 427 content_security_policy = 428 extensions::CSPInfo::GetResourceContentSecurityPolicy(extension, 429 resource_path); 430 } 431 432 if ((extension->manifest_version() >= 2 || 433 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources( 434 extension)) && 435 extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible( 436 extension, resource_path)) { 437 send_cors_header = true; 438 } 439 440 follow_symlinks_anywhere = 441 (extension->creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE) 442 != 0; 443 } 444 445 // Create a job for a generated background page. 446 std::string path = request->url().path(); 447 if (path.size() > 1 && 448 path.substr(1) == extensions::kGeneratedBackgroundPageFilename) { 449 return new GeneratedBackgroundPageJob( 450 request, network_delegate, extension, content_security_policy); 451 } 452 453 // Component extension resources may be part of the embedder's resource files, 454 // for example component_extension_resources.pak in Chrome. 455 net::URLRequestJob* resource_bundle_job = 456 extensions::ExtensionsBrowserClient::Get() 457 ->MaybeCreateResourceBundleRequestJob(request, 458 network_delegate, 459 directory_path, 460 content_security_policy, 461 send_cors_header); 462 if (resource_bundle_job) 463 return resource_bundle_job; 464 465 base::FilePath relative_path = 466 extensions::file_util::ExtensionURLToRelativeFilePath(request->url()); 467 468 // Handle shared resources (extension A loading resources out of extension B). 469 if (SharedModuleInfo::IsImportedPath(path)) { 470 std::string new_extension_id; 471 std::string new_relative_path; 472 SharedModuleInfo::ParseImportedPath(path, &new_extension_id, 473 &new_relative_path); 474 const Extension* new_extension = 475 extension_info_map_->extensions().GetByID(new_extension_id); 476 477 bool first_party_in_import = false; 478 // NB: This first_party_for_cookies call is not for security, it is only 479 // used so an exported extension can limit the visible surface to the 480 // extension that imports it, more or less constituting its API. 481 const std::string& first_party_path = 482 request->first_party_for_cookies().path(); 483 if (SharedModuleInfo::IsImportedPath(first_party_path)) { 484 std::string first_party_id; 485 std::string dummy; 486 SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id, 487 &dummy); 488 if (first_party_id == new_extension_id) { 489 first_party_in_import = true; 490 } 491 } 492 493 if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) && 494 new_extension && 495 (first_party_in_import || 496 SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) { 497 directory_path = new_extension->path(); 498 extension_id = new_extension_id; 499 relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path); 500 } else { 501 return NULL; 502 } 503 } 504 ContentVerifyJob* verify_job = NULL; 505 ContentVerifier* verifier = extension_info_map_->content_verifier(); 506 if (verifier) { 507 verify_job = 508 verifier->CreateJobFor(extension_id, directory_path, relative_path); 509 if (verify_job) 510 verify_job->Start(); 511 } 512 513 return new URLRequestExtensionJob(request, 514 network_delegate, 515 extension_id, 516 directory_path, 517 relative_path, 518 content_security_policy, 519 send_cors_header, 520 follow_symlinks_anywhere, 521 verify_job); 522} 523 524} // namespace 525 526net::HttpResponseHeaders* BuildHttpHeaders( 527 const std::string& content_security_policy, 528 bool send_cors_header, 529 const base::Time& last_modified_time) { 530 std::string raw_headers; 531 raw_headers.append("HTTP/1.1 200 OK"); 532 if (!content_security_policy.empty()) { 533 raw_headers.append(1, '\0'); 534 raw_headers.append("Content-Security-Policy: "); 535 raw_headers.append(content_security_policy); 536 } 537 538 if (send_cors_header) { 539 raw_headers.append(1, '\0'); 540 raw_headers.append("Access-Control-Allow-Origin: *"); 541 } 542 543 if (!last_modified_time.is_null()) { 544 // Hash the time and make an etag to avoid exposing the exact 545 // user installation time of the extension. 546 std::string hash = 547 base::StringPrintf("%" PRId64, last_modified_time.ToInternalValue()); 548 hash = base::SHA1HashString(hash); 549 std::string etag; 550 base::Base64Encode(hash, &etag); 551 raw_headers.append(1, '\0'); 552 raw_headers.append("ETag: \""); 553 raw_headers.append(etag); 554 raw_headers.append("\""); 555 // Also force revalidation. 556 raw_headers.append(1, '\0'); 557 raw_headers.append("cache-control: no-cache"); 558 } 559 560 raw_headers.append(2, '\0'); 561 return new net::HttpResponseHeaders(raw_headers); 562} 563 564net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler( 565 bool is_incognito, 566 extensions::InfoMap* extension_info_map) { 567 return new ExtensionProtocolHandler(is_incognito, extension_info_map); 568} 569 570} // namespace extensions 571