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/path_service.h" 18 #include "base/sha1.h" 19 #include "base/strings/string_util.h" 20 #include "base/strings/stringprintf.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "base/threading/thread_restrictions.h" 23 #include "base/threading/worker_pool.h" 24 #include "build/build_config.h" 25 #include "chrome/browser/extensions/extension_info_map.h" 26 #include "chrome/browser/extensions/image_loader.h" 27 #include "chrome/common/chrome_paths.h" 28 #include "chrome/common/extensions/background_info.h" 29 #include "chrome/common/extensions/csp_handler.h" 30 #include "chrome/common/extensions/extension.h" 31 #include "chrome/common/extensions/extension_file_util.h" 32 #include "chrome/common/extensions/incognito_handler.h" 33 #include "chrome/common/extensions/manifest_handlers/icons_handler.h" 34 #include "chrome/common/extensions/manifest_handlers/shared_module_info.h" 35 #include "chrome/common/extensions/manifest_url_handler.h" 36 #include "chrome/common/extensions/web_accessible_resources_handler.h" 37 #include "chrome/common/url_constants.h" 38 #include "content/public/browser/resource_request_info.h" 39 #include "extensions/common/constants.h" 40 #include "extensions/common/extension_resource.h" 41 #include "grit/component_extension_resources_map.h" 42 #include "net/base/mime_util.h" 43 #include "net/base/net_errors.h" 44 #include "net/http/http_response_headers.h" 45 #include "net/http/http_response_info.h" 46 #include "net/url_request/url_request_error_job.h" 47 #include "net/url_request/url_request_file_job.h" 48 #include "net/url_request/url_request_simple_job.h" 49 #include "ui/base/resource/resource_bundle.h" 50 #include "url/url_util.h" 51 52 using content::ResourceRequestInfo; 53 using extensions::Extension; 54 using extensions::SharedModuleInfo; 55 56 namespace { 57 58 net::HttpResponseHeaders* BuildHttpHeaders( 59 const std::string& content_security_policy, bool send_cors_header, 60 const base::Time& last_modified_time) { 61 std::string raw_headers; 62 raw_headers.append("HTTP/1.1 200 OK"); 63 if (!content_security_policy.empty()) { 64 raw_headers.append(1, '\0'); 65 raw_headers.append("Content-Security-Policy: "); 66 raw_headers.append(content_security_policy); 67 } 68 69 if (send_cors_header) { 70 raw_headers.append(1, '\0'); 71 raw_headers.append("Access-Control-Allow-Origin: *"); 72 } 73 74 if (!last_modified_time.is_null()) { 75 // Hash the time and make an etag to avoid exposing the exact 76 // user installation time of the extension. 77 std::string hash = base::StringPrintf("%" PRId64, 78 last_modified_time.ToInternalValue()); 79 hash = base::SHA1HashString(hash); 80 std::string etag; 81 if (base::Base64Encode(hash, &etag)) { 82 raw_headers.append(1, '\0'); 83 raw_headers.append("ETag: \""); 84 raw_headers.append(etag); 85 raw_headers.append("\""); 86 // Also force revalidation. 87 raw_headers.append(1, '\0'); 88 raw_headers.append("cache-control: no-cache"); 89 } 90 } 91 92 raw_headers.append(2, '\0'); 93 return new net::HttpResponseHeaders(raw_headers); 94 } 95 96 void ReadMimeTypeFromFile(const base::FilePath& filename, 97 std::string* mime_type, 98 bool* result) { 99 *result = net::GetMimeTypeFromFile(filename, mime_type); 100 } 101 102 void GetLastModifiedTime(const base::FilePath& filename, 103 base::Time* last_modified_time) { 104 if (base::PathExists(filename)) { 105 base::PlatformFileInfo info; 106 if (file_util::GetFileInfo(filename, &info)) 107 *last_modified_time = info.last_modified; 108 } 109 } 110 111 class URLRequestResourceBundleJob : public net::URLRequestSimpleJob { 112 public: 113 URLRequestResourceBundleJob(net::URLRequest* request, 114 net::NetworkDelegate* network_delegate, 115 const base::FilePath& filename, 116 int resource_id, 117 const std::string& content_security_policy, 118 bool send_cors_header) 119 : net::URLRequestSimpleJob(request, network_delegate), 120 filename_(filename), 121 resource_id_(resource_id), 122 weak_factory_(this) { 123 // Leave cache headers out of resource bundle requests. 124 response_info_.headers = BuildHttpHeaders(content_security_policy, 125 send_cors_header, 126 base::Time()); 127 } 128 129 // Overridden from URLRequestSimpleJob: 130 virtual int GetData(std::string* mime_type, 131 std::string* charset, 132 std::string* data, 133 const net::CompletionCallback& callback) const OVERRIDE { 134 const ResourceBundle& rb = ResourceBundle::GetSharedInstance(); 135 *data = rb.GetRawDataResource(resource_id_).as_string(); 136 137 std::string* read_mime_type = new std::string; 138 bool* read_result = new bool; 139 bool posted = base::WorkerPool::PostTaskAndReply( 140 FROM_HERE, 141 base::Bind(&ReadMimeTypeFromFile, filename_, 142 base::Unretained(read_mime_type), 143 base::Unretained(read_result)), 144 base::Bind(&URLRequestResourceBundleJob::OnMimeTypeRead, 145 weak_factory_.GetWeakPtr(), 146 mime_type, charset, data, 147 base::Owned(read_mime_type), 148 base::Owned(read_result), 149 callback), 150 true /* task is slow */); 151 DCHECK(posted); 152 153 return net::ERR_IO_PENDING; 154 } 155 156 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 157 *info = response_info_; 158 } 159 160 private: 161 virtual ~URLRequestResourceBundleJob() { } 162 163 void OnMimeTypeRead(std::string* out_mime_type, 164 std::string* charset, 165 std::string* data, 166 std::string* read_mime_type, 167 bool* read_result, 168 const net::CompletionCallback& callback) { 169 *out_mime_type = *read_mime_type; 170 if (StartsWithASCII(*read_mime_type, "text/", false)) { 171 // All of our HTML files should be UTF-8 and for other resource types 172 // (like images), charset doesn't matter. 173 DCHECK(IsStringUTF8(*data)); 174 *charset = "utf-8"; 175 } 176 int result = *read_result? net::OK: net::ERR_INVALID_URL; 177 callback.Run(result); 178 } 179 180 // We need the filename of the resource to determine the mime type. 181 base::FilePath filename_; 182 183 // The resource bundle id to load. 184 int resource_id_; 185 186 net::HttpResponseInfo response_info_; 187 188 mutable base::WeakPtrFactory<URLRequestResourceBundleJob> weak_factory_; 189 }; 190 191 class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob { 192 public: 193 GeneratedBackgroundPageJob(net::URLRequest* request, 194 net::NetworkDelegate* network_delegate, 195 const scoped_refptr<const Extension> extension, 196 const std::string& content_security_policy) 197 : net::URLRequestSimpleJob(request, network_delegate), 198 extension_(extension) { 199 const bool send_cors_headers = false; 200 // Leave cache headers out of generated background page jobs. 201 response_info_.headers = BuildHttpHeaders(content_security_policy, 202 send_cors_headers, 203 base::Time()); 204 } 205 206 // Overridden from URLRequestSimpleJob: 207 virtual int GetData(std::string* mime_type, 208 std::string* charset, 209 std::string* data, 210 const net::CompletionCallback& callback) const OVERRIDE { 211 *mime_type = "text/html"; 212 *charset = "utf-8"; 213 214 *data = "<!DOCTYPE html>\n<body>\n"; 215 const std::vector<std::string>& background_scripts = 216 extensions::BackgroundInfo::GetBackgroundScripts(extension_.get()); 217 for (size_t i = 0; i < background_scripts.size(); ++i) { 218 *data += "<script src=\""; 219 *data += background_scripts[i]; 220 *data += "\"></script>\n"; 221 } 222 223 return net::OK; 224 } 225 226 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 227 *info = response_info_; 228 } 229 230 private: 231 virtual ~GeneratedBackgroundPageJob() {} 232 233 scoped_refptr<const Extension> extension_; 234 net::HttpResponseInfo response_info_; 235 }; 236 237 void ReadResourceFilePathAndLastModifiedTime( 238 const extensions::ExtensionResource& resource, 239 base::FilePath* file_path, 240 base::Time* last_modified_time) { 241 *file_path = resource.GetFilePath(); 242 GetLastModifiedTime(*file_path, last_modified_time); 243 } 244 245 class URLRequestExtensionJob : public net::URLRequestFileJob { 246 public: 247 URLRequestExtensionJob(net::URLRequest* request, 248 net::NetworkDelegate* network_delegate, 249 const std::string& extension_id, 250 const base::FilePath& directory_path, 251 const base::FilePath& relative_path, 252 const std::string& content_security_policy, 253 bool send_cors_header) 254 : net::URLRequestFileJob(request, network_delegate, base::FilePath()), 255 // TODO(tc): Move all of these files into resources.pak so we don't break 256 // when updating on Linux. 257 resource_(extension_id, directory_path, relative_path), 258 content_security_policy_(content_security_policy), 259 send_cors_header_(send_cors_header), 260 weak_factory_(this) { 261 } 262 263 virtual void GetResponseInfo(net::HttpResponseInfo* info) OVERRIDE { 264 *info = response_info_; 265 } 266 267 virtual void Start() OVERRIDE { 268 base::FilePath* read_file_path = new base::FilePath; 269 base::Time* last_modified_time = new base::Time(); 270 bool posted = base::WorkerPool::PostTaskAndReply( 271 FROM_HERE, 272 base::Bind(&ReadResourceFilePathAndLastModifiedTime, resource_, 273 base::Unretained(read_file_path), 274 base::Unretained(last_modified_time)), 275 base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead, 276 weak_factory_.GetWeakPtr(), 277 base::Owned(read_file_path), 278 base::Owned(last_modified_time)), 279 true /* task is slow */); 280 DCHECK(posted); 281 } 282 283 private: 284 virtual ~URLRequestExtensionJob() {} 285 286 void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path, 287 base::Time* last_modified_time) { 288 file_path_ = *read_file_path; 289 response_info_.headers = BuildHttpHeaders( 290 content_security_policy_, 291 send_cors_header_, 292 *last_modified_time); 293 URLRequestFileJob::Start(); 294 } 295 296 net::HttpResponseInfo response_info_; 297 extensions::ExtensionResource resource_; 298 std::string content_security_policy_; 299 bool send_cors_header_; 300 base::WeakPtrFactory<URLRequestExtensionJob> weak_factory_; 301 }; 302 303 bool ExtensionCanLoadInIncognito(const ResourceRequestInfo* info, 304 const std::string& extension_id, 305 ExtensionInfoMap* extension_info_map) { 306 if (!extension_info_map->IsIncognitoEnabled(extension_id)) 307 return false; 308 309 // Only allow incognito toplevel navigations to extension resources in 310 // split mode. In spanning mode, the extension must run in a single process, 311 // and an incognito tab prevents that. 312 if (info->GetResourceType() == ResourceType::MAIN_FRAME) { 313 const Extension* extension = 314 extension_info_map->extensions().GetByID(extension_id); 315 return extension && extensions::IncognitoInfo::IsSplitMode(extension); 316 } 317 318 return true; 319 } 320 321 // Returns true if an chrome-extension:// resource should be allowed to load. 322 // TODO(aa): This should be moved into ExtensionResourceRequestPolicy, but we 323 // first need to find a way to get CanLoadInIncognito state into the renderers. 324 bool AllowExtensionResourceLoad(net::URLRequest* request, 325 bool is_incognito, 326 const Extension* extension, 327 ExtensionInfoMap* extension_info_map) { 328 const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); 329 330 // We have seen crashes where info is NULL: crbug.com/52374. 331 if (!info) { 332 LOG(ERROR) << "Allowing load of " << request->url().spec() 333 << "from unknown origin. Could not find user data for " 334 << "request."; 335 return true; 336 } 337 338 if (is_incognito && !ExtensionCanLoadInIncognito(info, request->url().host(), 339 extension_info_map)) { 340 return false; 341 } 342 343 // The following checks are meant to replicate similar set of checks in the 344 // renderer process, performed by ResourceRequestPolicy::CanRequestResource. 345 // These are not exactly equivalent, because we don't have the same bits of 346 // information. The two checks need to be kept in sync as much as possible, as 347 // an exploited renderer can bypass the checks in ResourceRequestPolicy. 348 349 // Check if the extension for which this request is made is indeed loaded in 350 // the process sending the request. If not, we need to explicitly check if 351 // the resource is explicitly accessible or fits in a set of exception cases. 352 // Note: This allows a case where two extensions execute in the same renderer 353 // process to request each other's resources. We can't do a more precise 354 // check, since the renderer can lie about which extension has made the 355 // request. 356 if (extension_info_map->process_map().Contains( 357 request->url().host(), info->GetChildID())) { 358 return true; 359 } 360 361 if (!content::PageTransitionIsWebTriggerable(info->GetPageTransition())) 362 return false; 363 364 // The following checks require that we have an actual extension object. If we 365 // don't have it, allow the request handling to continue with the rest of the 366 // checks. 367 if (!extension) 368 return true; 369 370 // Disallow loading of packaged resources for hosted apps. We don't allow 371 // hybrid hosted/packaged apps. The one exception is access to icons, since 372 // some extensions want to be able to do things like create their own 373 // launchers. 374 std::string resource_root_relative_path = 375 request->url().path().empty() ? std::string() 376 : request->url().path().substr(1); 377 if (extension->is_hosted_app() && 378 !extensions::IconsInfo::GetIcons(extension) 379 .ContainsPath(resource_root_relative_path)) { 380 LOG(ERROR) << "Denying load of " << request->url().spec() << " from " 381 << "hosted app."; 382 return false; 383 } 384 385 // Extensions with web_accessible_resources: allow loading by regular 386 // renderers. Since not all subresources are required to be listed in a v2 387 // manifest, we must allow all loads if there are any web accessible 388 // resources. See http://crbug.com/179127. 389 if (extension->manifest_version() < 2 || 390 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources( 391 extension)) { 392 return true; 393 } 394 395 // If there aren't any explicitly marked web accessible resources, the 396 // load should be allowed only if it is by DevTools. A close approximation is 397 // checking if the extension contains a DevTools page. 398 if (extensions::ManifestURL::GetDevToolsPage(extension).is_empty()) 399 return false; 400 401 return true; 402 } 403 404 // Returns true if the given URL references an icon in the given extension. 405 bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) { 406 DCHECK(url.SchemeIs(extensions::kExtensionScheme)); 407 408 if (!extension) 409 return false; 410 411 std::string path = url.path(); 412 DCHECK_EQ(url.host(), extension->id()); 413 DCHECK(path.length() > 0 && path[0] == '/'); 414 path = path.substr(1); 415 return extensions::IconsInfo::GetIcons(extension).ContainsPath(path); 416 } 417 418 class ExtensionProtocolHandler 419 : public net::URLRequestJobFactory::ProtocolHandler { 420 public: 421 ExtensionProtocolHandler(bool is_incognito, 422 ExtensionInfoMap* extension_info_map) 423 : is_incognito_(is_incognito), 424 extension_info_map_(extension_info_map) {} 425 426 virtual ~ExtensionProtocolHandler() {} 427 428 virtual net::URLRequestJob* MaybeCreateJob( 429 net::URLRequest* request, 430 net::NetworkDelegate* network_delegate) const OVERRIDE; 431 432 private: 433 const bool is_incognito_; 434 ExtensionInfoMap* const extension_info_map_; 435 DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler); 436 }; 437 438 // Creates URLRequestJobs for extension:// URLs. 439 net::URLRequestJob* 440 ExtensionProtocolHandler::MaybeCreateJob( 441 net::URLRequest* request, net::NetworkDelegate* network_delegate) const { 442 // chrome-extension://extension-id/resource/path.js 443 std::string extension_id = request->url().host(); 444 const Extension* extension = 445 extension_info_map_->extensions().GetByID(extension_id); 446 447 // TODO(mpcomplete): better error code. 448 if (!AllowExtensionResourceLoad( 449 request, is_incognito_, extension, extension_info_map_)) { 450 return new net::URLRequestErrorJob( 451 request, network_delegate, net::ERR_ADDRESS_UNREACHABLE); 452 } 453 454 base::FilePath directory_path; 455 if (extension) 456 directory_path = extension->path(); 457 if (directory_path.value().empty()) { 458 const Extension* disabled_extension = 459 extension_info_map_->disabled_extensions().GetByID(extension_id); 460 if (URLIsForExtensionIcon(request->url(), disabled_extension)) 461 directory_path = disabled_extension->path(); 462 if (directory_path.value().empty()) { 463 LOG(WARNING) << "Failed to GetPathForExtension: " << extension_id; 464 return NULL; 465 } 466 } 467 468 std::string content_security_policy; 469 bool send_cors_header = false; 470 if (extension) { 471 std::string resource_path = request->url().path(); 472 content_security_policy = 473 extensions::CSPInfo::GetResourceContentSecurityPolicy(extension, 474 resource_path); 475 if ((extension->manifest_version() >= 2 || 476 extensions::WebAccessibleResourcesInfo::HasWebAccessibleResources( 477 extension)) && 478 extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible( 479 extension, resource_path)) 480 send_cors_header = true; 481 } 482 483 std::string path = request->url().path(); 484 if (path.size() > 1 && 485 path.substr(1) == extensions::kGeneratedBackgroundPageFilename) { 486 return new GeneratedBackgroundPageJob( 487 request, network_delegate, extension, content_security_policy); 488 } 489 490 base::FilePath resources_path; 491 base::FilePath relative_path; 492 // Try to load extension resources from chrome resource file if 493 // directory_path is a descendant of resources_path. resources_path 494 // corresponds to src/chrome/browser/resources in source tree. 495 if (PathService::Get(chrome::DIR_RESOURCES, &resources_path) && 496 // Since component extension resources are included in 497 // component_extension_resources.pak file in resources_path, calculate 498 // extension relative path against resources_path. 499 resources_path.AppendRelativePath(directory_path, &relative_path)) { 500 base::FilePath request_path = 501 extension_file_util::ExtensionURLToRelativeFilePath(request->url()); 502 int resource_id; 503 if (extensions::ImageLoader::IsComponentExtensionResource( 504 directory_path, request_path, &resource_id)) { 505 relative_path = relative_path.Append(request_path); 506 relative_path = relative_path.NormalizePathSeparators(); 507 return new URLRequestResourceBundleJob( 508 request, 509 network_delegate, 510 relative_path, 511 resource_id, 512 content_security_policy, 513 send_cors_header); 514 } 515 } 516 517 relative_path = 518 extension_file_util::ExtensionURLToRelativeFilePath(request->url()); 519 520 if (SharedModuleInfo::IsImportedPath(path)) { 521 std::string new_extension_id; 522 std::string new_relative_path; 523 SharedModuleInfo::ParseImportedPath(path, &new_extension_id, 524 &new_relative_path); 525 const Extension* new_extension = 526 extension_info_map_->extensions().GetByID(new_extension_id); 527 528 bool first_party_in_import = false; 529 // NB: This first_party_for_cookies call is not for security, it is only 530 // used so an exported extension can limit the visible surface to the 531 // extension that imports it, more or less constituting its API. 532 const std::string& first_party_path = 533 request->first_party_for_cookies().path(); 534 if (SharedModuleInfo::IsImportedPath(first_party_path)) { 535 std::string first_party_id; 536 std::string dummy; 537 SharedModuleInfo::ParseImportedPath(first_party_path, &first_party_id, 538 &dummy); 539 if (first_party_id == new_extension_id) { 540 first_party_in_import = true; 541 } 542 } 543 544 if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) && 545 new_extension && 546 (first_party_in_import || 547 SharedModuleInfo::IsExportAllowed(new_extension, new_relative_path))) { 548 directory_path = new_extension->path(); 549 extension_id = new_extension_id; 550 relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path); 551 } else { 552 return NULL; 553 } 554 } 555 556 return new URLRequestExtensionJob(request, 557 network_delegate, 558 extension_id, 559 directory_path, 560 relative_path, 561 content_security_policy, 562 send_cors_header); 563 } 564 565 } // namespace 566 567 net::URLRequestJobFactory::ProtocolHandler* CreateExtensionProtocolHandler( 568 bool is_incognito, 569 ExtensionInfoMap* extension_info_map) { 570 return new ExtensionProtocolHandler(is_incognito, extension_info_map); 571 } 572