// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/browser/extension_protocols.h" #include #include #include #include #include #include #include #include "base/base64.h" #include "base/bind.h" #include "base/compiler_specific.h" #include "base/feature_list.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/format_macros.h" #include "base/hash/sha1.h" #include "base/logging.h" #include "base/macros.h" #include "base/memory/weak_ptr.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/metrics/user_metrics.h" #include "base/no_destructor.h" #include "base/numerics/safe_conversions.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/task/thread_pool.h" #include "base/threading/thread_restrictions.h" #include "base/timer/elapsed_timer.h" #include "build/build_config.h" #include "components/keyed_service/content/browser_context_keyed_service_shutdown_notifier_factory.h" #include "components/keyed_service/core/keyed_service_shutdown_notifier.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/file_url_loader.h" #include "content/public/browser/navigation_ui_data.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "content/public/common/child_process_host.h" #include "crypto/secure_hash.h" #include "crypto/sha2.h" #include "extensions/browser/content_verifier.h" #include "extensions/browser/content_verify_job.h" #include "extensions/browser/extension_navigation_ui_data.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry_factory.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/extension_util.h" #include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/guest_view/web_view/web_view_guest.h" #include "extensions/browser/guest_view/web_view/web_view_renderer_state.h" #include "extensions/browser/info_map.h" #include "extensions/browser/process_map.h" #include "extensions/browser/process_map_factory.h" #include "extensions/browser/url_request_util.h" #include "extensions/common/extension.h" #include "extensions/common/extension_resource.h" #include "extensions/common/file_util.h" #include "extensions/common/identifiability_metrics.h" #include "extensions/common/manifest_handlers/background_info.h" #include "extensions/common/manifest_handlers/cross_origin_isolation_info.h" #include "extensions/common/manifest_handlers/csp_info.h" #include "extensions/common/manifest_handlers/icons_handler.h" #include "extensions/common/manifest_handlers/incognito_info.h" #include "extensions/common/manifest_handlers/shared_module_info.h" #include "extensions/common/manifest_handlers/web_accessible_resources_info.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" #include "net/base/filename_util.h" #include "net/base/io_buffer.h" #include "net/base/mime_util.h" #include "net/base/net_errors.h" #include "net/http/http_request_headers.h" #include "net/http/http_response_headers.h" #include "net/http/http_response_info.h" #include "services/metrics/public/cpp/ukm_source_id.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/self_deleting_url_loader_factory.h" #include "services/network/public/cpp/url_loader_completion_status.h" #include "services/network/public/mojom/early_hints.mojom.h" #include "services/network/public/mojom/fetch_api.mojom.h" #include "services/network/public/mojom/url_response_head.mojom.h" #include "third_party/blink/public/common/features.h" #include "third_party/blink/public/common/loader/resource_type_util.h" #include "url/origin.h" #include "url/url_util.h" using content::BrowserContext; using extensions::Extension; using extensions::SharedModuleInfo; namespace extensions { namespace { ExtensionProtocolTestHandler* g_test_handler = nullptr; // This is used to collect some metrics of load results, by wrapping the actual // URLLoaderClient and observing success or failure. // // This approach is taken because loading can happen via things like // content::CreateFileURLLoaderBypassingSecurityChecks(), and // LoadResourceFromResourceBundle and it avoids having to modify all those // places for a temporary study. class ResultRecordingClient : public network::mojom::URLLoaderClient { public: ResultRecordingClient( const GURL& url, ukm::SourceIdObj ukm_source_id, mojo::PendingRemote real_client) : url_(url), ukm_source_id_(ukm_source_id), real_client_(std::move(real_client)) {} void OnReceiveEarlyHints(network::mojom::EarlyHintsPtr early_hints) override { real_client_->OnReceiveEarlyHints(std::move(early_hints)); } void OnReceiveResponse( network::mojom::URLResponseHeadPtr response_head) override { real_client_->OnReceiveResponse(std::move(response_head)); } void OnReceiveRedirect( const net::RedirectInfo& redirect_info, network::mojom::URLResponseHeadPtr response_head) override { real_client_->OnReceiveRedirect(redirect_info, std::move(response_head)); } void OnUploadProgress(int64_t current_position, int64_t total_size, OnUploadProgressCallback ack_callback) override { real_client_->OnUploadProgress(current_position, total_size, std::move(ack_callback)); } void OnReceiveCachedMetadata(mojo_base::BigBuffer data) override { real_client_->OnReceiveCachedMetadata(std::move(data)); } void OnTransferSizeUpdated(int32_t transfer_size_diff) override { real_client_->OnTransferSizeUpdated(transfer_size_diff); } void OnStartLoadingResponseBody( mojo::ScopedDataPipeConsumerHandle body) override { real_client_->OnStartLoadingResponseBody(std::move(body)); } void OnComplete(const network::URLLoaderCompletionStatus& status) override { RecordExtensionResourceAccessResult( ukm_source_id_, url_, status.error_code == net::OK ? ExtensionResourceAccessResult::kSuccess : ExtensionResourceAccessResult::kFailure); real_client_->OnComplete(status); } private: GURL url_; ukm::SourceIdObj ukm_source_id_; mojo::Remote real_client_; }; mojo::PendingRemote WrapWithMetricsIfNeeded( const GURL& url, ukm::SourceIdObj ukm_source_id, mojo::PendingRemote in_client) { if (ukm_source_id == ukm::kInvalidSourceIdObj) return in_client; mojo::PendingRemote proxy_client_remote; auto proxy_client = std::make_unique( url, ukm_source_id, std::move(in_client)); mojo::MakeSelfOwnedReceiver( std::move(proxy_client), proxy_client_remote.InitWithNewPipeAndPassReceiver()); return proxy_client_remote; } void GenerateBackgroundPageContents(const Extension* extension, std::string* mime_type, std::string* charset, std::string* data) { *mime_type = "text/html"; *charset = "utf-8"; *data = "\n\n"; for (const auto& script : BackgroundInfo::GetBackgroundScripts(extension)) { *data += "\n"; } } base::Time GetFileLastModifiedTime(const base::FilePath& filename) { if (base::PathExists(filename)) { base::File::Info info; if (base::GetFileInfo(filename, &info)) return info.last_modified; } return base::Time(); } base::Time GetFileCreationTime(const base::FilePath& filename) { if (base::PathExists(filename)) { base::File::Info info; if (base::GetFileInfo(filename, &info)) return info.creation_time; } return base::Time(); } void ReadResourceFilePathAndLastModifiedTime( const extensions::ExtensionResource& resource, const base::FilePath& directory, base::FilePath* file_path, base::Time* last_modified_time) { // NOTE: ExtensionResource::GetFilePath() must be called on a sequence which // tolerates blocking operations. *file_path = resource.GetFilePath(); *last_modified_time = GetFileLastModifiedTime(*file_path); base::Time dir_creation_time = GetFileCreationTime(directory); int64_t delta_seconds = (*last_modified_time - dir_creation_time).InSeconds(); if (delta_seconds >= 0) { UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedDelta", delta_seconds, 1, base::TimeDelta::FromDays(30).InSeconds(), 50); } else { UMA_HISTOGRAM_CUSTOM_COUNTS("Extensions.ResourceLastModifiedNegativeDelta", -delta_seconds, 1, base::TimeDelta::FromDays(30).InSeconds(), 50); } } bool ExtensionCanLoadInIncognito(bool is_main_frame, const Extension* extension, bool extension_enabled_in_incognito) { if (!extension || !extension_enabled_in_incognito) return false; if (!is_main_frame || extension->is_login_screen_extension()) return true; // Only allow incognito toplevel navigations to extension resources in // split mode. In spanning mode, the extension must run in a single process, // and an incognito tab prevents that. return IncognitoInfo::IsSplitMode(extension); } // Returns true if an chrome-extension:// resource should be allowed to load. // Pass true for |is_incognito| only for incognito profiles and not Chrome OS // guest mode profiles. // // Called on the UI thread. bool AllowExtensionResourceLoad(const network::ResourceRequest& request, network::mojom::RequestDestination destination, ui::PageTransition page_transition, int child_id, bool is_incognito, const Extension* extension, bool extension_enabled_in_incognito, const ExtensionSet& extensions, const ProcessMap& process_map) { const bool is_main_frame = destination == network::mojom::RequestDestination::kDocument; if (is_incognito && !ExtensionCanLoadInIncognito(is_main_frame, extension, extension_enabled_in_incognito)) { return false; } // The following checks are meant to replicate similar set of checks in the // renderer process, performed by ResourceRequestPolicy::CanRequestResource. // These are not exactly equivalent, because we don't have the same bits of // information. The two checks need to be kept in sync as much as possible, as // an exploited renderer can bypass the checks in ResourceRequestPolicy. // Check if the extension for which this request is made is indeed loaded in // the process sending the request. If not, we need to explicitly check if // the resource is explicitly accessible or fits in a set of exception cases. // Note: This allows a case where two extensions execute in the same renderer // process to request each other's resources. We can't do a more precise // check, since the renderer can lie about which extension has made the // request. if (process_map.Contains(request.url.host(), child_id)) return true; // Frame navigations to extensions have already been checked in // the ExtensionNavigationThrottle. // Dedicated Worker (with PlzDedicatedWorker) and Shared Worker main scripts // can be loaded with extension URLs in browser process. // Service Worker and the imported scripts can be loaded with extension URLs // in browser process when PlzServiceWorker is enabled or during update check. if (child_id == content::ChildProcessHost::kInvalidUniqueID && (blink::IsRequestDestinationFrame(destination) || (base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker) && destination == network::mojom::RequestDestination::kWorker) || destination == network::mojom::RequestDestination::kSharedWorker || destination == network::mojom::RequestDestination::kScript || destination == network::mojom::RequestDestination::kServiceWorker)) { return true; } // Allow the extension module embedder to grant permission for loads. if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad( request, destination, page_transition, child_id, is_incognito, extension, extensions, process_map)) { return true; } // No special exceptions for cross-process loading. Block the load. return false; } // Returns true if the given URL references an icon in the given extension. bool URLIsForExtensionIcon(const GURL& url, const Extension* extension) { DCHECK(url.SchemeIs(extensions::kExtensionScheme)); if (!extension) return false; DCHECK_EQ(url.host(), extension->id()); base::StringPiece path = url.path_piece(); DCHECK(path.length() > 0 && path[0] == '/'); base::StringPiece path_without_slash = path.substr(1); return IconsInfo::GetIcons(extension).ContainsPath(path_without_slash); } // Retrieves the path corresponding to an extension on disk. Returns |true| on // success and populates |*path|; otherwise returns |false|. bool GetDirectoryForExtensionURL(const GURL& url, const std::string& extension_id, const Extension* extension, const ExtensionSet& disabled_extensions, base::FilePath* out_path) { base::FilePath path; if (extension) path = extension->path(); const Extension* disabled_extension = disabled_extensions.GetByID(extension_id); if (path.empty()) { // For disabled extensions, we only resolve the directory path to service // extension icon URL requests. if (URLIsForExtensionIcon(url, disabled_extension)) path = disabled_extension->path(); } if (!path.empty()) { *out_path = path; return true; } DLOG_IF(WARNING, !disabled_extension) << "Failed to get directory for extension " << extension_id; return false; } void GetSecurityPolicyForURL(const network::ResourceRequest& request, const Extension& extension, bool is_web_view_request, std::string* content_security_policy, const std::string** cross_origin_embedder_policy, const std::string** cross_origin_opener_policy, bool* send_cors_header, bool* follow_symlinks_anywhere) { std::string resource_path = request.url.path(); // Use default CSP for . if (!is_web_view_request) { *content_security_policy = CSPInfo::GetResourceContentSecurityPolicy(&extension, resource_path); } *cross_origin_embedder_policy = CrossOriginIsolationHeader::GetCrossOriginEmbedderPolicy(extension); *cross_origin_opener_policy = CrossOriginIsolationHeader::GetCrossOriginOpenerPolicy(extension); if (WebAccessibleResourcesInfo::IsResourceWebAccessible( &extension, resource_path, request.request_initiator)) { *send_cors_header = true; } *follow_symlinks_anywhere = (extension.creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE) != 0; } bool IsBackgroundPageURL(const GURL& url) { base::StringPiece path_piece = url.path_piece(); return path_piece.size() > 1 && path_piece.substr(1) == kGeneratedBackgroundPageFilename; } scoped_refptr BuildHttpHeaders( const std::string& content_security_policy, const std::string* cross_origin_embedder_policy, const std::string* cross_origin_opener_policy, bool send_cors_header, bool include_allow_service_worker_header) { std::string raw_headers; raw_headers.append("HTTP/1.1 200 OK"); if (!content_security_policy.empty()) { raw_headers.append(1, '\0'); raw_headers.append("Content-Security-Policy: "); raw_headers.append(content_security_policy); } if (cross_origin_embedder_policy) { raw_headers.append(1, '\0'); raw_headers.append("Cross-Origin-Embedder-Policy: "); raw_headers.append(*cross_origin_embedder_policy); } if (cross_origin_opener_policy) { raw_headers.append(1, '\0'); raw_headers.append("Cross-Origin-Opener-Policy: "); raw_headers.append(*cross_origin_opener_policy); } if (send_cors_header) { raw_headers.append(1, '\0'); raw_headers.append("Access-Control-Allow-Origin: *"); raw_headers.append(1, '\0'); raw_headers.append("Cross-Origin-Resource-Policy: cross-origin"); } if (include_allow_service_worker_header) { raw_headers.append(1, '\0'); raw_headers.append("Service-Worker-Allowed: /"); } raw_headers.append(2, '\0'); return base::MakeRefCounted(raw_headers); } void AddCacheHeaders(net::HttpResponseHeaders& headers, base::Time last_modified_time) { if (last_modified_time.is_null()) return; // Hash the time and make an etag to avoid exposing the exact // user installation time of the extension. std::string hash = base::StringPrintf("%" PRId64, last_modified_time.ToInternalValue()); hash = base::SHA1HashString(hash); std::string etag; base::Base64Encode(hash, &etag); etag = "\"" + etag + "\""; headers.SetHeader("ETag", etag); // Also force revalidation. headers.SetHeader("cache-control", "no-cache"); } class FileLoaderObserver : public content::FileURLLoaderObserver { public: explicit FileLoaderObserver(scoped_refptr verify_job) : verify_job_(std::move(verify_job)) {} ~FileLoaderObserver() override { base::AutoLock auto_lock(lock_); UMA_HISTOGRAM_COUNTS_1M("ExtensionUrlRequest.TotalKbRead", bytes_read_ / 1024); UMA_HISTOGRAM_COUNTS_1M("ExtensionUrlRequest.SeekPosition", seek_position_); if (request_timer_.get()) UMA_HISTOGRAM_TIMES("ExtensionUrlRequest.Latency", request_timer_->Elapsed()); } void OnStart() override { base::AutoLock auto_lock(lock_); request_timer_ = std::make_unique(); } void OnSeekComplete(int64_t result) override { DCHECK_EQ(seek_position_, 0); base::AutoLock auto_lock(lock_); seek_position_ = result; // TODO(asargent) - we'll need to add proper support for range headers. // crbug.com/369895. const bool is_seek_contiguous = result == bytes_read_; if (result > 0 && verify_job_.get() && !is_seek_contiguous) verify_job_ = nullptr; } void OnRead(base::span buffer, mojo::DataPipeProducer::DataSource::ReadResult* result) override { DCHECK(result); { base::AutoLock auto_lock(lock_); bytes_read_ += result->bytes_read; if (verify_job_) { // Note: We still pass the data to |verify_job_|, even if there was a // read error, because some errors are ignorable. See // ContentVerifyJob::BytesRead() for more details. verify_job_->Read(static_cast(buffer.data()), result->bytes_read, result->result); } } } void OnDone() override { base::AutoLock auto_lock(lock_); if (verify_job_.get()) verify_job_->Done(); } private: int64_t bytes_read_ = 0; int64_t seek_position_ = 0; std::unique_ptr request_timer_; scoped_refptr verify_job_; // To synchronize access to all members. base::Lock lock_; DISALLOW_COPY_AND_ASSIGN(FileLoaderObserver); }; class ExtensionURLLoaderFactory : public network::SelfDeletingURLLoaderFactory { public: static mojo::PendingRemote Create( content::BrowserContext* browser_context, ukm::SourceIdObj ukm_source_id, bool is_web_view_request, int render_process_id) { DCHECK(browser_context); mojo::PendingRemote pending_remote; // Return an unbound |pending_remote| if the |browser_context| has already // started shutting down. if (browser_context->ShutdownStarted()) return pending_remote; // Manages its own lifetime. new ExtensionURLLoaderFactory( browser_context, ukm_source_id, is_web_view_request, render_process_id, pending_remote.InitWithNewPipeAndPassReceiver()); return pending_remote; } static void EnsureShutdownNotifierFactoryBuilt() { BrowserContextShutdownNotifierFactory::GetInstance(); } private: // Constructs ExtensionURLLoaderFactory bound to the |factory_receiver|. // // The factory is self-owned - it will delete itself once there are no more // receivers (including the receiver associated with the returned // mojo::PendingRemote and the receivers bound by the Clone method). See also // the network::SelfDeletingURLLoaderFactory::OnDisconnect method. ExtensionURLLoaderFactory( content::BrowserContext* browser_context, ukm::SourceIdObj ukm_source_id, bool is_web_view_request, int render_process_id, mojo::PendingReceiver factory_receiver) : network::SelfDeletingURLLoaderFactory(std::move(factory_receiver)), browser_context_(browser_context), is_web_view_request_(is_web_view_request), ukm_source_id_(ukm_source_id), render_process_id_(render_process_id) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); extension_info_map_ = extensions::ExtensionSystem::Get(browser_context_)->info_map(); // base::Unretained is safe below, because lifetime of // |browser_context_shutdown_subscription_| guarantees that // OnBrowserContextDestroyed won't be called after |this| is destroyed. browser_context_shutdown_subscription_ = BrowserContextShutdownNotifierFactory::GetInstance() ->Get(browser_context) ->Subscribe(base::BindRepeating( &ExtensionURLLoaderFactory::OnBrowserContextDestroyed, base::Unretained(this))); } ~ExtensionURLLoaderFactory() override = default; // network::mojom::URLLoaderFactory: void CreateLoaderAndStart( mojo::PendingReceiver loader, int32_t request_id, uint32_t options, const network::ResourceRequest& request, mojo::PendingRemote client, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) override { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (browser_context_->ShutdownStarted()) { mojo::Remote(std::move(client)) ->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED)); return; } client = WrapWithMetricsIfNeeded(request.url, ukm_source_id_, std::move(client)); const std::string extension_id = request.url.host(); ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); scoped_refptr extension = registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING); const ExtensionSet& enabled_extensions = registry->enabled_extensions(); const ProcessMap* process_map = ProcessMap::Get(browser_context_); bool incognito_enabled = extensions::util::IsIncognitoEnabled(extension_id, browser_context_); if (!AllowExtensionResourceLoad( request, request.destination, static_cast(request.transition_type), render_process_id_, browser_context_->IsOffTheRecord(), extension.get(), incognito_enabled, enabled_extensions, *process_map)) { mojo::Remote(std::move(client)) ->OnComplete( network::URLLoaderCompletionStatus(net::ERR_BLOCKED_BY_CLIENT)); return; } base::FilePath directory_path; if (!GetDirectoryForExtensionURL(request.url, extension_id, extension.get(), registry->disabled_extensions(), &directory_path)) { mojo::Remote(std::move(client)) ->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED)); return; } LoadExtension(std::move(loader), request, std::move(client), extension, std::move(directory_path)); } void LoadExtension( mojo::PendingReceiver loader, const network::ResourceRequest& request, mojo::PendingRemote client, scoped_refptr extension, base::FilePath directory_path) { std::string content_security_policy; const std::string* cross_origin_embedder_policy = nullptr; const std::string* cross_origin_opener_policy = nullptr; bool send_cors_header = false; bool follow_symlinks_anywhere = false; bool include_allow_service_worker_header = false; if (extension) { GetSecurityPolicyForURL( request, *extension, is_web_view_request_, &content_security_policy, &cross_origin_embedder_policy, &cross_origin_opener_policy, &send_cors_header, &follow_symlinks_anywhere); if (BackgroundInfo::IsServiceWorkerBased(extension.get())) { include_allow_service_worker_header = request.destination == network::mojom::RequestDestination::kServiceWorker && request.url == extension->GetResourceURL( BackgroundInfo::GetBackgroundServiceWorkerScript( extension.get())); } } if (IsBackgroundPageURL(request.url)) { // Handle background page requests immediately with a simple generated // chunk of HTML. // Leave cache headers out of generated background page jobs. auto head = network::mojom::URLResponseHead::New(); head->headers = BuildHttpHeaders( content_security_policy, cross_origin_embedder_policy, cross_origin_opener_policy, false /* send_cors_headers */, include_allow_service_worker_header); std::string contents; GenerateBackgroundPageContents(extension.get(), &head->mime_type, &head->charset, &contents); mojo::Remote client_remote( std::move(client)); uint32_t size = base::saturated_cast(contents.size()); mojo::ScopedDataPipeProducerHandle producer_handle; mojo::ScopedDataPipeConsumerHandle consumer_handle; if (mojo::CreateDataPipe(size, producer_handle, consumer_handle) != MOJO_RESULT_OK) { client_remote->OnComplete( network::URLLoaderCompletionStatus(net::ERR_FAILED)); } MojoResult result = producer_handle->WriteData(contents.data(), &size, MOJO_WRITE_DATA_FLAG_NONE); if (result != MOJO_RESULT_OK || size < contents.size()) { client_remote->OnComplete( network::URLLoaderCompletionStatus(net::ERR_FAILED)); return; } client_remote->OnReceiveResponse(std::move(head)); client_remote->OnStartLoadingResponseBody(std::move(consumer_handle)); client_remote->OnComplete(network::URLLoaderCompletionStatus(net::OK)); return; } auto headers = BuildHttpHeaders(content_security_policy, cross_origin_embedder_policy, cross_origin_opener_policy, send_cors_header, include_allow_service_worker_header); // Component extension resources may be part of the embedder's resource // files, for example component_extension_resources.pak in Chrome. int resource_id = 0; const base::FilePath bundle_resource_path = ExtensionsBrowserClient::Get()->GetBundleResourcePath( request, directory_path, &resource_id); if (!bundle_resource_path.empty()) { ExtensionsBrowserClient::Get()->LoadResourceFromResourceBundle( request, std::move(loader), bundle_resource_path, resource_id, std::move(headers), std::move(client)); return; } base::FilePath relative_path = file_util::ExtensionURLToRelativeFilePath(request.url); // Do not allow requests for resources in the _metadata folder, since any // files there are internal implementation details that should not be // considered part of the extension. if (base::FilePath(kMetadataFolder).IsParent(relative_path)) { mojo::Remote(std::move(client)) ->OnComplete( network::URLLoaderCompletionStatus(net::ERR_FILE_NOT_FOUND)); return; } // Handle shared resources (extension A loading resources out of extension // B). std::string extension_id = extension->id(); std::string path = request.url.path(); if (SharedModuleInfo::IsImportedPath(path)) { std::string new_extension_id; std::string new_relative_path; SharedModuleInfo::ParseImportedPath(path, &new_extension_id, &new_relative_path); ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context_); const Extension* new_extension = registry->enabled_extensions().GetByID(new_extension_id); if (SharedModuleInfo::ImportsExtensionById(extension.get(), new_extension_id) && new_extension) { directory_path = new_extension->path(); extension_id = new_extension_id; relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path); } else { mojo::Remote(std::move(client)) ->OnComplete( network::URLLoaderCompletionStatus(net::ERR_BLOCKED_BY_CLIENT)); return; } } if (g_test_handler) g_test_handler->Run(&directory_path, &relative_path); extensions::ExtensionResource resource(extension_id, directory_path, relative_path); if (follow_symlinks_anywhere) resource.set_follow_symlinks_anywhere(); base::FilePath* read_file_path = new base::FilePath; base::Time* last_modified_time = new base::Time(); scoped_refptr content_verifier = extension_info_map_->content_verifier(); base::ThreadPool::PostTaskAndReply( FROM_HERE, {base::MayBlock()}, base::BindOnce(&ReadResourceFilePathAndLastModifiedTime, resource, directory_path, base::Unretained(read_file_path), base::Unretained(last_modified_time)), base::BindOnce( &OnFilePathAndLastModifiedTimeRead, base::Owned(read_file_path), base::Owned(last_modified_time), request, std::move(loader), std::move(client), std::move(content_verifier), resource, std::move(headers))); } static void OnFilePathAndLastModifiedTimeRead( const base::FilePath* read_file_path, const base::Time* last_modified_time, network::ResourceRequest request, mojo::PendingReceiver loader, mojo::PendingRemote client, scoped_refptr content_verifier, const extensions::ExtensionResource& resource, scoped_refptr headers) { request.url = net::FilePathToFileURL(*read_file_path); AddCacheHeaders(*headers, *last_modified_time); content::GetIOThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce(&StartVerifyJob, std::move(request), std::move(loader), std::move(client), std::move(content_verifier), resource, std::move(headers))); } static void StartVerifyJob( network::ResourceRequest request, mojo::PendingReceiver loader, mojo::PendingRemote client, scoped_refptr content_verifier, const ExtensionResource& resource, scoped_refptr response_headers) { scoped_refptr verify_job; if (content_verifier) { verify_job = content_verifier->CreateAndStartJobFor( resource.extension_id(), resource.extension_root(), resource.relative_path()); } content::CreateFileURLLoaderBypassingSecurityChecks( request, std::move(loader), std::move(client), std::make_unique(std::move(verify_job)), /* allow_directory_listing */ false, std::move(response_headers)); } void OnBrowserContextDestroyed() { // When |browser_context_| gets destroyed, |this| factory is not able to // serve any more requests. DisconnectReceiversAndDestroy(); } class BrowserContextShutdownNotifierFactory : public BrowserContextKeyedServiceShutdownNotifierFactory { public: static BrowserContextShutdownNotifierFactory* GetInstance() { static base::NoDestructor s_factory; return s_factory.get(); } // No copying. BrowserContextShutdownNotifierFactory( const BrowserContextShutdownNotifierFactory&) = delete; BrowserContextShutdownNotifierFactory& operator=( const BrowserContextShutdownNotifierFactory&) = delete; private: friend class base::NoDestructor; BrowserContextShutdownNotifierFactory() : BrowserContextKeyedServiceShutdownNotifierFactory( "ExtensionURLLoaderFactory::" "BrowserContextShutdownNotifierFactory") { DependsOn(ExtensionRegistryFactory::GetInstance()); DependsOn(ProcessMapFactory::GetInstance()); } }; content::BrowserContext* browser_context_; bool is_web_view_request_; ukm::SourceIdObj ukm_source_id_; // We store the ID and get RenderProcessHost each time it's needed. This is to // avoid holding on to stale pointers if we get requests past the lifetime of // the objects. const int render_process_id_; scoped_refptr extension_info_map_; base::CallbackListSubscription browser_context_shutdown_subscription_; DISALLOW_COPY_AND_ASSIGN(ExtensionURLLoaderFactory); }; } // namespace void SetExtensionProtocolTestHandler(ExtensionProtocolTestHandler* handler) { g_test_handler = handler; } mojo::PendingRemote CreateExtensionNavigationURLLoaderFactory( content::BrowserContext* browser_context, ukm::SourceIdObj ukm_source_id, bool is_web_view_request) { return ExtensionURLLoaderFactory::Create( browser_context, ukm_source_id, is_web_view_request, content::ChildProcessHost::kInvalidUniqueID); } mojo::PendingRemote CreateExtensionWorkerMainResourceURLLoaderFactory( content::BrowserContext* browser_context) { return ExtensionURLLoaderFactory::Create( browser_context, ukm::kInvalidSourceIdObj, /*is_web_view_request=*/false, content::ChildProcessHost::kInvalidUniqueID); } mojo::PendingRemote CreateExtensionServiceWorkerScriptURLLoaderFactory( content::BrowserContext* browser_context) { return ExtensionURLLoaderFactory::Create( browser_context, ukm::kInvalidSourceIdObj, /*is_web_view_request=*/false, content::ChildProcessHost::kInvalidUniqueID); } mojo::PendingRemote CreateExtensionURLLoaderFactory(int render_process_id, int render_frame_id) { content::RenderProcessHost* process_host = content::RenderProcessHost::FromID(render_process_id); content::BrowserContext* browser_context = process_host->GetBrowserContext(); bool is_web_view_request = WebViewGuest::FromFrameID(render_process_id, render_frame_id) != nullptr; content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(render_process_id, render_frame_id); ukm::SourceIdObj ukm_source_id = ukm::kInvalidSourceIdObj; if (rfh) ukm_source_id = ukm::SourceIdObj::FromInt64(rfh->GetPageUkmSourceId()); return ExtensionURLLoaderFactory::Create( browser_context, ukm_source_id, is_web_view_request, render_process_id); } void EnsureExtensionURLLoaderFactoryShutdownNotifierFactoryBuilt() { ExtensionURLLoaderFactory::EnsureShutdownNotifierFactoryBuilt(); } } // namespace extensions