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. 4 5#include "chrome/browser/extensions/extension_protocols.h" 6 7#include <algorithm> 8 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/extensions/web_accessible_resources_handler.h" 34#include "chrome/common/extensions/webview_handler.h" 35#include "chrome/common/url_constants.h" 36#include "content/public/browser/browser_thread.h" 37#include "content/public/browser/resource_request_info.h" 38#include "extensions/browser/info_map.h" 39#include "extensions/common/constants.h" 40#include "extensions/common/extension.h" 41#include "extensions/common/extension_resource.h" 42#include "extensions/common/file_util.h" 43#include "extensions/common/manifest_handlers/background_info.h" 44#include "extensions/common/manifest_handlers/csp_info.h" 45#include "extensions/common/manifest_handlers/incognito_info.h" 46#include "extensions/common/manifest_handlers/shared_module_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" 58 59using content::BrowserThread; 60using content::ResourceRequestInfo; 61using extensions::Extension; 62using extensions::SharedModuleInfo; 63 64namespace { 65 66net::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); 75 } 76 77 if (send_cors_header) { 78 raw_headers.append(1, '\0'); 79 raw_headers.append("Access-Control-Allow-Origin: *"); 80 } 81 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); 88 std::string etag; 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"); 97 } 98 99 raw_headers.append(2, '\0'); 100 return new net::HttpResponseHeaders(raw_headers); 101} 102 103class URLRequestResourceBundleJob : public net::URLRequestSimpleJob { 104 public: 105 URLRequestResourceBundleJob(net::URLRequest* request, 106 net::NetworkDelegate* network_delegate, 107 const base::FilePath& filename, 108 int resource_id, 109 const std::string& content_security_policy, 110 bool send_cors_header) 111 : net::URLRequestSimpleJob(request, network_delegate), 112 filename_(filename), 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, 117 send_cors_header, 118 base::Time()); 119 } 120 121 // Overridden from URLRequestSimpleJob: 122 virtual int GetData(std::string* mime_type, 123 std::string* charset, 124 std::string* data, 125 const net::CompletionCallback& callback) const OVERRIDE { 126 const ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 127 *data = rb.GetRawDataResource(resource_id_).as_string(); 128 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())); 133 134 std::string* read_mime_type = new std::string; 135 bool posted = base::PostTaskAndReplyWithResult( 136 BrowserThread::GetBlockingPool(), 137 FROM_HERE, 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), 144 callback)); 145 DCHECK(posted); 146 147 return net::ERR_IO_PENDING; 148 } 149 150 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 151 *info = response_info_; 152 } 153 154 private: 155 virtual ~URLRequestResourceBundleJob() { } 156 157 void OnMimeTypeRead(std::string* out_mime_type, 158 std::string* charset, 159 std::string* data, 160 std::string* read_mime_type, 161 const net::CompletionCallback& callback, 162 bool read_result) { 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)); 168 *charset = "utf-8"; 169 } 170 int result = read_result ? net::OK : net::ERR_INVALID_URL; 171 callback.Run(result); 172 } 173 174 // We need the filename of the resource to determine the mime type. 175 base::FilePath filename_; 176 177 // The resource bundle id to load. 178 int resource_id_; 179 180 net::HttpResponseInfo response_info_; 181 182 mutable base::WeakPtrFactory<URLRequestResourceBundleJob> weak_factory_; 183}; 184 185class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob { 186 public: 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, 196 send_cors_headers, 197 base::Time()); 198 } 199 200 // Overridden from URLRequestSimpleJob: 201 virtual int GetData(std::string* mime_type, 202 std::string* charset, 203 std::string* data, 204 const net::CompletionCallback& callback) const OVERRIDE { 205 *mime_type = "text/html"; 206 *charset = "utf-8"; 207 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"; 215 } 216 217 return net::OK; 218 } 219 220 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 221 *info = response_info_; 222 } 223 224 private: 225 virtual ~GeneratedBackgroundPageJob() {} 226 227 scoped_refptr<const Extension> extension_; 228 net::HttpResponseInfo response_info_; 229}; 230 231base::Time GetFileLastModifiedTime(const base::FilePath& filename) { 232 if (base::PathExists(filename)) { 233 base::PlatformFileInfo info; 234 if (base::GetFileInfo(filename, &info)) 235 return info.last_modified; 236 } 237 return base::Time(); 238} 239 240base::Time GetFileCreationTime(const base::FilePath& filename) { 241 if (base::PathExists(filename)) { 242 base::PlatformFileInfo info; 243 if (base::GetFileInfo(filename, &info)) 244 return info.creation_time; 245 } 246 return base::Time(); 247} 248 249void 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", 265 delta_seconds, 266 0, 267 base::TimeDelta::FromDays(30).InSeconds(), 268 50); 269 } else { 270 UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta", 271 -delta_seconds, 272 1, 273 base::TimeDelta::FromDays(30).InSeconds(), 274 50); 275 } 276} 277 278class URLRequestExtensionJob : public net::URLRequestFileJob { 279 public: 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 : net::URLRequestFileJob( 288 request, network_delegate, base::FilePath(), 289 BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( 290 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)), 291 directory_path_(directory_path), 292 // TODO(tc): Move all of these files into resources.pak so we don't break 293 // when updating on Linux. 294 resource_(extension_id, directory_path, relative_path), 295 content_security_policy_(content_security_policy), 296 send_cors_header_(send_cors_header), 297 weak_factory_(this) { 298 } 299 300 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 301 *info = response_info_; 302 } 303 304 virtual void Start() OVERRIDE { 305 base::FilePath* read_file_path = new base::FilePath; 306 base::Time* last_modified_time = new base::Time(); 307 bool posted = BrowserThread::PostBlockingPoolTaskAndReply( 308 FROM_HERE, 309 base::Bind(&ReadResourceFilePathAndLastModifiedTime, resource_, 310 directory_path_, 311 base::Unretained(read_file_path), 312 base::Unretained(last_modified_time)), 313 base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead, 314 weak_factory_.GetWeakPtr(), 315 base::Owned(read_file_path), 316 base::Owned(last_modified_time))); 317 DCHECK(posted); 318 } 319 320 private: 321 virtual ~URLRequestExtensionJob() {} 322 323 void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path, 324 base::Time* last_modified_time) { 325 file_path_ = *read_file_path; 326 response_info_.headers = BuildHttpHeaders( 327 content_security_policy_, 328 send_cors_header_, 329 *last_modified_time); 330 URLRequestFileJob::Start(); 331 } 332 333 net::HttpResponseInfo response_info_; 334 base::FilePath directory_path_; 335 extensions::ExtensionResource resource_; 336 std::string content_security_policy_; 337 bool send_cors_header_; 338 base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_; 339}; 340 341bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info, 342 const std::string& extension_id, 343 extensions::InfoMap* extension_info_map) { 344 if (!extension_info_map->IsIncognitoEnabled(extension_id)) 345 return false; 346 347 // Only allow incognito toplevel navigations to extension resources in 348 // split mode. In spanning mode, the extension must run in a single process, 349 // and an incognito tab prevents that. 350 if (info->GetResourceType() == ResourceType::MAIN_FRAME) { 351 const Extension* extension = 352 extension_info_map->extensions().GetByID(extension_id); 353 return extension && extensions::IncognitoInfo::IsSplitMode(extension); 354 } 355 356 return true; 357} 358 359// Returns true if an chrome-extension:// resource should be allowed to load. 360// TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we 361// first need to find a way to get CanLoadInIncognito state into the renderers. 362bool AllowExtensionResourceLoad(net::URLRequest* request, 363 bool is_incognito, 364 const Extension* extension, 365 extensions::InfoMap* extension_info_map) { 366 const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); 367 368 // We have seen crashes where info is NULL: crbug.com/52374. 369 if (!info) { 370 LOG(ERROR) << "Allowing load of " << request->url().spec() 371 << "from unknown origin. Could not find user data for " 372 << "request."; 373 return true; 374 } 375 376 if (is_incognito && !ExtensionCanLoadInIncognito(info, request->url().host(), 377 extension_info_map)) { 378 return false; 379 } 380 381 // The following checks are meant to replicate similar set of checks in the 382 // renderer process, performed by ResourceRequestPolicy::CanRequestResource. 383 // These are not exactly equivalent, because we don't have the same bits of 384 // information. The two checks need to be kept in sync as much as possible, as 385 // an exploited renderer can bypass the checks in ResourceRequestPolicy. 386 387 // Check if the extension for which this request is made is indeed loaded in 388 // the process sending the request. If not, we need to explicitly check if 389 // the resource is explicitly accessible or fits in a set of exception cases. 390 // Note: This allows a case where two extensions execute in the same renderer 391 // process to request each other's resources. We can't do a more precise 392 // check, since the renderer can lie about which extension has made the 393 // request. 394 if (extension_info_map->process_map().Contains( 395 request->url().host(), info->GetChildID())) { 396 return true; 397 } 398 399 // Extensions with webview: allow loading certain resources by guest renderers 400 // with privileged partition IDs as specified in the manifest file. 401 ExtensionRendererState* renderer_state = 402 ExtensionRendererState::GetInstance(); 403 ExtensionRendererState::WebViewInfo webview_info; 404 bool is_guest = renderer_state->GetWebViewInfo(info->GetChildID(), 405 info->GetRouteID(), 406 &webview_info); 407 std::string resource_path = request->url().path(); 408 if (is_guest && webview_info.allow_chrome_extension_urls && 409 extensions::WebviewInfo::IsResourceWebviewAccessible( 410 extension, webview_info.partition_id, resource_path)) { 411 return true; 412 } 413 414 // If the request is for navigations outside of webviews, then it should be 415 // allowed. The navigation logic in CrossSiteResourceHandler will properly 416 // transfer the navigation to a privileged process before it commits. 417 if (ResourceType::IsFrame(info->GetResourceType()) && !is_guest) 418 return true; 419 420 if (!content::PageTransitionIsWebTriggerable(info->GetPageTransition())) 421 return false; 422 423 // The following checks require that we have an actual extension object. If we 424 // don't have it, allow the request handling to continue with the rest of the 425 // checks. 426 if (!extension) 427 return true; 428 429 // Disallow loading of packaged resources for hosted apps. We don't allow 430 // hybrid hosted/packaged apps. The one exception is access to icons, since 431 // some extensions want to be able to do things like create their own 432 // launchers. 433 std::string resource_root_relative_path = 434 request->url().path().empty() ? std::string() 435 : request->url().path().substr(1); 436 if (extension->is_hosted_app() && 437 !extensions::IconsInfo::GetIcons(extension) 438 .ContainsPath(resource_root_relative_path)) { 439 LOG(ERROR) << "Denying load of " << request->url().spec() << " from " 440 << "hosted app."; 441 return false; 442 } 443 444 // Extensions with web_accessible_resources: allow loading by regular 445 // renderers. Since not all subresources are required to be listed in a v2 446 // manifest, we must allow all loads if there are any web accessible 447 // resources. See http://crbug.com/179127. 448 if (extension->manifest_version() < 2 || 449 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources( 450 extension)) { 451 return true; 452 } 453 454 // If there aren't any explicitly marked web accessible resources, the 455 // load should be allowed only if it is by DevTools. A close approximation is 456 // checking if the extension contains a DevTools page. 457 if (extensions::ManifestURL::GetDevToolsPage(extension).is_empty()) 458 return false; 459 460 return true; 461} 462 463// Returns true if the given URL references an icon in the given extension. 464bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) { 465 DCHECK(url.SchemeIs(extensions::kExtensionScheme)); 466 467 if (!extension) 468 return false; 469 470 std::string path = url.path(); 471 DCHECK_EQ(url.host(), extension->id()); 472 DCHECK(path.length() > 0 && path[0] == '/'); 473 path = path.substr(1); 474 return extensions::IconsInfo::GetIcons(extension).ContainsPath(path); 475} 476 477class ExtensionProtocolHandler 478 : public net::URLRequestJobFactory::ProtocolHandler { 479 public: 480 ExtensionProtocolHandler(bool is_incognito, 481 extensions::InfoMap* extension_info_map) 482 : is_incognito_(is_incognito), extension_info_map_(extension_info_map) {} 483 484 virtual ~ExtensionProtocolHandler() {} 485 486 virtual net::URLRequestJob* MaybeCreateJob( 487 net::URLRequest* request, 488 net::NetworkDelegate* network_delegate) const OVERRIDE; 489 490 private: 491 const bool is_incognito_; 492 extensions::InfoMap* const extension_info_map_; 493 DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler); 494}; 495 496// Creates URLRequestJobs for extension:// URLs. 497net::URLRequestJob* 498ExtensionProtocolHandler::MaybeCreateJob( 499 net::URLRequest* request, net::NetworkDelegate* network_delegate) const { 500 // chrome-extension://extension-id/resource/path.js 501 std::string extension_id = request->url().host(); 502 const Extension* extension = 503 extension_info_map_->extensions().GetByID(extension_id); 504 505 // TODO(mpcomplete): better error code. 506 if (!AllowExtensionResourceLoad( 507 request, is_incognito_, extension, extension_info_map_)) { 508 return new net::URLRequestErrorJob( 509 request, network_delegate, net::ERR_ADDRESS_UNREACHABLE); 510 } 511 512 base::FilePath directory_path; 513 if (extension) 514 directory_path = extension->path(); 515 if (directory_path.value().empty()) { 516 const Extension* disabled_extension = 517 extension_info_map_->disabled_extensions().GetByID(extension_id); 518 if (URLIsForExtensionIcon(request->url(), disabled_extension)) 519 directory_path = disabled_extension->path(); 520 if (directory_path.value().empty()) { 521 LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id; 522 return NULL; 523 } 524 } 525 526 std::string content_security_policy; 527 bool send_cors_header = false; 528 if (extension) { 529 std::string resource_path = request->url().path(); 530 content_security_policy = 531 extensions::CSPInfo::GetResourceContentSecurityPolicy(extension, 532 resource_path); 533 if ((extension->manifest_version() >= 2 || 534 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources( 535 extension)) && 536 extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible( 537 extension, resource_path)) 538 send_cors_header = true; 539 } 540 541 std::string path = request->url().path(); 542 if (path.size() > 1 && 543 path.substr(1) == extensions::kGeneratedBackgroundPageFilename) { 544 return new GeneratedBackgroundPageJob( 545 request, network_delegate, extension, content_security_policy); 546 } 547 548 base::FilePath resources_path; 549 base::FilePath relative_path; 550 // Try to load extension resources from chrome resource file if 551 // directory_path is a descendant of resources_path. resources_path 552 // corresponds to src/chrome/browser/resources in source tree. 553 if (PathService::Get(chrome::DIR_RESOURCES, &resources_path) && 554 // Since component extension resources are included in 555 // component_extension_resources.pak file in resources_path, calculate 556 // extension relative path against resources_path. 557 resources_path.AppendRelativePath(directory_path, &relative_path)) { 558 base::FilePath request_path = 559 extensions::file_util::ExtensionURLToRelativeFilePath(request->url()); 560 int resource_id; 561 if (extensions::ImageLoader::IsComponentExtensionResource( 562 directory_path, request_path, &resource_id)) { 563 relative_path = relative_path.Append(request_path); 564 relative_path = relative_path.NormalizePathSeparators(); 565 return new URLRequestResourceBundleJob( 566 request, 567 network_delegate, 568 relative_path, 569 resource_id, 570 content_security_policy, 571 send_cors_header); 572 } 573 } 574 575 relative_path = 576 extensions::file_util::ExtensionURLToRelativeFilePath(request->url()); 577 578 if (SharedModuleInfo::IsImportedPath(path)) { 579 std::string new_extension_id; 580 std::string new_relative_path; 581 SharedModuleInfo::ParseImportedPath(path, &new_extension_id, 582 &new_relative_path); 583 const Extension* new_extension = 584 extension_info_map_->extensions().GetByID(new_extension_id); 585 586 bool first_party_in_import = false; 587 // NB: This first_party_for_cookies call is not for security, it is only 588 // used so an exported extension can limit the visible surface to the 589 // extension that imports it, more or less constituting its API. 590 const std::string& first_party_path = 591 request->first_party_for_cookies().path(); 592 if (SharedModuleInfo::IsImportedPath(first_party_path)) { 593 std::string first_party_id; 594 std::string dummy; 595 SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id, 596 &dummy); 597 if (first_party_id == new_extension_id) { 598 first_party_in_import = true; 599 } 600 } 601 602 if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) && 603 new_extension && 604 (first_party_in_import || 605 SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) { 606 directory_path = new_extension->path(); 607 extension_id = new_extension_id; 608 relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path); 609 } else { 610 return NULL; 611 } 612 } 613 614 return new URLRequestExtensionJob(request, 615 network_delegate, 616 extension_id, 617 directory_path, 618 relative_path, 619 content_security_policy, 620 send_cors_header); 621} 622 623} // namespace 624 625net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler( 626 bool is_incognito, 627 extensions::InfoMap* extension_info_map) { 628 return new ExtensionProtocolHandler(is_incognito, extension_info_map); 629} 630