// 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/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/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/post_task.h" #include "base/threading/thread_restrictions.h" #include "base/timer/elapsed_timer.h" #include "build/build_config.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/browser/resource_request_info.h" #include "content/public/common/resource_type.h" #include "crypto/secure_hash.h" #include "crypto/sha2.h" #include "extensions/browser/component_extension_resource_manager.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_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/url_request_util.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" #include "extensions/common/extension_resource.h" #include "extensions/common/file_util.h" #include "extensions/common/manifest_handlers/background_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/binding_set.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 "net/url_request/url_request_error_job.h" #include "net/url_request/url_request_file_job.h" #include "net/url_request/url_request_simple_job.h" #include "services/network/public/cpp/url_loader_completion_status.h" #include "url/url_util.h" using content::BrowserContext; using content::ResourceRequestInfo; using extensions::Extension; using extensions::SharedModuleInfo; namespace extensions { namespace { ExtensionProtocolTestHandler* g_test_handler = nullptr; 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"; } } class GeneratedBackgroundPageJob : public net::URLRequestSimpleJob { public: GeneratedBackgroundPageJob(net::URLRequest* request, net::NetworkDelegate* network_delegate, const scoped_refptr extension, const std::string& content_security_policy) : net::URLRequestSimpleJob(request, network_delegate), extension_(extension) { const bool send_cors_headers = false; // Leave cache headers out of generated background page jobs. response_info_.headers = BuildHttpHeaders(content_security_policy, send_cors_headers, base::Time()); } // Overridden from URLRequestSimpleJob: int GetData(std::string* mime_type, std::string* charset, std::string* data, net::CompletionOnceCallback callback) const override { GenerateBackgroundPageContents(extension_.get(), mime_type, charset, data); return net::OK; } // base::PowerObserver override: void OnSuspend() override { // Unlike URLRequestJob, don't suspend active requests here. Requests for // generated background pages need not be suspended when the system // suspends. This is not needed for URLRequestExtensionJob since it inherits // from URLRequestFileJob, which has the same behavior. } void GetResponseInfo(net::HttpResponseInfo* info) override { *info = response_info_; } private: ~GeneratedBackgroundPageJob() override {} scoped_refptr extension_; net::HttpResponseInfo response_info_; }; 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); // While we're here, log the delta between extension directory // creation time and the resource's last modification time. base::ElapsedTimer query_timer; base::Time dir_creation_time = GetFileCreationTime(directory); UMA_HISTOGRAM_TIMES("Extensions.ResourceDirectoryTimestampQueryLatency", query_timer.Elapsed()); 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); } } class URLRequestExtensionJob : public net::URLRequestFileJob { public: URLRequestExtensionJob(net::URLRequest* request, net::NetworkDelegate* network_delegate, const std::string& extension_id, const base::FilePath& directory_path, const base::FilePath& relative_path, const std::string& content_security_policy, bool send_cors_header, bool follow_symlinks_anywhere, scoped_refptr verify_job) : net::URLRequestFileJob( request, network_delegate, base::FilePath(), base::CreateTaskRunnerWithTraits( {base::MayBlock(), base::TaskPriority::USER_VISIBLE, base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN})), verify_job_(std::move(verify_job)), seek_position_(0), bytes_read_(0), directory_path_(directory_path), // TODO(tc): Move all of these files into resources.pak so we don't // break when updating on Linux. resource_(extension_id, directory_path, relative_path), content_security_policy_(content_security_policy), send_cors_header_(send_cors_header), weak_factory_(this) { if (follow_symlinks_anywhere) { resource_.set_follow_symlinks_anywhere(); } } void GetResponseInfo(net::HttpResponseInfo* info) override { *info = response_info_; } void Start() override { request_timer_.reset(new base::ElapsedTimer()); base::FilePath* read_file_path = new base::FilePath; base::Time* last_modified_time = new base::Time(); // Inherit task priority from the calling context. base::PostTaskWithTraitsAndReply( FROM_HERE, {base::MayBlock()}, base::Bind(&ReadResourceFilePathAndLastModifiedTime, resource_, directory_path_, base::Unretained(read_file_path), base::Unretained(last_modified_time)), base::Bind(&URLRequestExtensionJob::OnFilePathAndLastModifiedTimeRead, weak_factory_.GetWeakPtr(), base::Owned(read_file_path), base::Owned(last_modified_time))); } bool IsRedirectResponse(GURL* location, int* http_status_code, bool* insecure_scheme_was_upgraded) override { return false; } void SetExtraRequestHeaders(const net::HttpRequestHeaders& headers) override { // TODO(asargent) - we'll need to add proper support for range headers. // crbug.com/369895. std::string range_header; if (headers.GetHeader(net::HttpRequestHeaders::kRange, &range_header)) { if (verify_job_.get()) verify_job_ = NULL; } URLRequestFileJob::SetExtraRequestHeaders(headers); } void OnOpenComplete(int result) override { if (result < 0) { // This can happen when the file is unreadable (which can happen during // corruption or third-party interaction). We need to be sure to inform // the verification job that we've finished reading so that it can // proceed; see crbug.com/703888. if (verify_job_.get()) { std::string tmp; verify_job_->BytesRead(0, base::data(tmp)); verify_job_->DoneReading(); } } } void OnSeekComplete(int64_t result) override { DCHECK_EQ(seek_position_, 0); seek_position_ = result; // TODO(asargent) - we'll need to add proper support for range headers. // crbug.com/369895. if (result > 0 && verify_job_.get()) verify_job_ = NULL; } void OnReadComplete(net::IOBuffer* buffer, int result) override { if (result >= 0) UMA_HISTOGRAM_COUNTS_1M("ExtensionUrlRequest.OnReadCompleteResult", result); else base::UmaHistogramSparse("ExtensionUrlRequest.OnReadCompleteError", -result); if (result > 0) { bytes_read_ += result; if (verify_job_.get()) verify_job_->BytesRead(result, buffer->data()); } } void DoneReading() override { URLRequestFileJob::DoneReading(); if (verify_job_.get()) verify_job_->DoneReading(); } private: ~URLRequestExtensionJob() override { 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()); } bool CanAccessFile(const base::FilePath& original_path, const base::FilePath& absolute_path) override { // The access checks for the file are performed before the job is // created, so we should know that this is safe. return true; } void OnFilePathAndLastModifiedTimeRead(base::FilePath* read_file_path, base::Time* last_modified_time) { file_path_ = *read_file_path; response_info_.headers = BuildHttpHeaders( content_security_policy_, send_cors_header_, *last_modified_time); // Set the mime type for the request. std::string mime_type; bool found_mime_type = GetMimeType(&mime_type); if (found_mime_type) response_info_.headers->AddHeader("Content-Type: " + mime_type); URLRequestFileJob::Start(); } bool GetMimeType(std::string* mime_type) const override { base::FilePath::StringType file_extension = file_path_.Extension(); if (file_extension.empty()) return false; // We use GetWellKnownMimeTypeFromExtension() to ensure that configurations // that may have been set by other programs on a user's machine don't affect // the mime type returned (in particular, JS should always be // application/javascript). See https://crbug.com/797712. Using an accurate // mime type is necessary at least for modules, which enforce strict mime // type requirements. return net::GetWellKnownMimeTypeFromExtension( file_extension.substr(1), // Trim leading '.' mime_type); } scoped_refptr verify_job_; std::unique_ptr request_timer_; // The position we seeked to in the file. int64_t seek_position_; // The number of bytes of content we read from the file. int bytes_read_; net::HttpResponseInfo response_info_; base::FilePath directory_path_; extensions::ExtensionResource resource_; std::string content_security_policy_; bool send_cors_header_; base::WeakPtrFactory weak_factory_; }; 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) 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. // // May be called on the IO thread (non-Network Service path) or the UI thread // (Network Service path). bool AllowExtensionResourceLoad(const GURL& url, content::ResourceType resource_type, 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 = resource_type == content::ResourceType::kMainFrame; 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(url.host(), child_id)) return true; // Frame navigations to extensions have already been checked in // the ExtensionNavigationThrottle. if (child_id == -1 && content::IsResourceTypeFrame(resource_type)) return true; // Allow the extension module embedder to grant permission for loads. if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad( url, resource_type, 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; } bool IsWebViewRequest(net::URLRequest* request) { content::ResourceRequestInfo* info = content::ResourceRequestInfo::ForRequest(request); // |info| can be null sometimes: http://crbug.com/370070. if (!info) return false; if (WebViewRendererState::GetInstance()->IsGuest(info->GetChildID())) return true; // GetChildId() is -1 with PlzNavigate for navigation requests, so also try // the ExtensionNavigationUIData data. const ExtensionNavigationUIData* data = ExtensionsBrowserClient::Get()->GetExtensionNavigationUIData(request); return data && data->is_web_view(); } void GetSecurityPolicyForURL(const GURL& url, const Extension* extension, bool is_web_view_request, std::string* content_security_policy, bool* send_cors_header, bool* follow_symlinks_anywhere) { std::string resource_path = url.path(); // Use default CSP for . if (!is_web_view_request) { *content_security_policy = extensions::CSPInfo::GetResourceContentSecurityPolicy(extension, resource_path); } if (extensions::WebAccessibleResourcesInfo::IsResourceWebAccessible( extension, resource_path)) { *send_cors_header = true; } *follow_symlinks_anywhere = (extension->creation_flags() & Extension::FOLLOW_SYMLINKS_ANYWHERE) != 0; } bool IsBackgroundPageURL(const GURL& url) { std::string path = url.path(); return path.size() > 1 && path.substr(1) == kGeneratedBackgroundPageFilename; } class ExtensionProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { public: ExtensionProtocolHandler(bool is_incognito, extensions::InfoMap* extension_info_map) : is_incognito_(is_incognito), extension_info_map_(extension_info_map) {} ~ExtensionProtocolHandler() override {} net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const override; bool IsSafeRedirectTarget(const GURL& location) const override; private: const bool is_incognito_; extensions::InfoMap* const extension_info_map_; DISALLOW_COPY_AND_ASSIGN(ExtensionProtocolHandler); }; // Creates URLRequestJobs for extension:// URLs. net::URLRequestJob* ExtensionProtocolHandler::MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const { // chrome-extension://extension-id/resource/path.js std::string extension_id = request->url().host(); const Extension* extension = extension_info_map_->extensions().GetByID(extension_id); ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request); const bool enabled_in_incognito = extension_info_map_->IsIncognitoEnabled(extension_id); // We have seen crashes where info is NULL: crbug.com/52374. if (!info) { // SeviceWorker net requests created through ServiceWorkerWriteToCacheJob // do not have ResourceRequestInfo associated with them. So skip logging // spurious errors below. // TODO(falken): Either consider attaching ResourceRequestInfo to these or // finish refactoring ServiceWorkerWriteToCacheJob so that it doesn't spawn // a new URLRequest. if (!ResourceRequestInfo::OriginatedFromServiceWorker(request)) { LOG(ERROR) << "Allowing load of " << request->url().spec() << "from unknown origin. Could not find user data for " << "request."; } } else if (!AllowExtensionResourceLoad( request->url(), info->GetResourceType(), info->GetPageTransition(), info->GetChildID(), is_incognito_, extension, enabled_in_incognito, extension_info_map_->extensions(), extension_info_map_->process_map())) { return new net::URLRequestErrorJob(request, network_delegate, net::ERR_BLOCKED_BY_CLIENT); } base::FilePath directory_path; if (!GetDirectoryForExtensionURL(request->url(), extension_id, extension, extension_info_map_->disabled_extensions(), &directory_path)) { return nullptr; } // Set up content security policy. std::string content_security_policy; bool send_cors_header = false; bool follow_symlinks_anywhere = false; if (extension) { GetSecurityPolicyForURL(request->url(), extension, IsWebViewRequest(request), &content_security_policy, &send_cors_header, &follow_symlinks_anywhere); } // Create a job for a generated background page. if (IsBackgroundPageURL(request->url())) { return new GeneratedBackgroundPageJob( request, network_delegate, extension, content_security_policy); } // Component extension resources may be part of the embedder's resource files, // for example component_extension_resources.pak in Chrome. net::URLRequestJob* resource_bundle_job = extensions::ExtensionsBrowserClient::Get() ->MaybeCreateResourceBundleRequestJob(request, network_delegate, directory_path, content_security_policy, send_cors_header); if (resource_bundle_job) return resource_bundle_job; base::FilePath relative_path = extensions::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)) return nullptr; // Handle shared resources (extension A loading resources out of extension B). 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); const Extension* new_extension = extension_info_map_->extensions().GetByID(new_extension_id); if (SharedModuleInfo::ImportsExtensionById(extension, new_extension_id) && new_extension) { directory_path = new_extension->path(); extension_id = new_extension_id; relative_path = base::FilePath::FromUTF8Unsafe(new_relative_path); } else { return NULL; } } if (g_test_handler) g_test_handler->Run(&directory_path, &relative_path); scoped_refptr verify_job; ContentVerifier* verifier = extension_info_map_->content_verifier(); if (verifier) { verify_job = verifier->CreateJobFor(extension_id, directory_path, relative_path); if (verify_job) verify_job->Start(verifier); } return new URLRequestExtensionJob( request, network_delegate, extension_id, directory_path, relative_path, content_security_policy, send_cors_header, follow_symlinks_anywhere, std::move(verify_job)); } bool ExtensionProtocolHandler::IsSafeRedirectTarget( const GURL& location) const { // Redirects originate from http/https origins, which are subject to // webaccessible resources restrictions. Redirects can also be triggered via // the extensions webrequest API, which is also limited to webaccessible // resources. std::string extension_id = location.host(); const Extension* extension = extension_info_map_->extensions().GetByID(extension_id); if (!extension) return false; return WebAccessibleResourcesInfo::IsResourceWebAccessible(extension, location.path()); } 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_.reset(new base::ElapsedTimer()); } 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. if (result > 0 && verify_job_.get()) verify_job_ = nullptr; } void OnBytesRead(const void* data, size_t num_bytes_read, base::File::Error read_result) override { if (read_result == base::File::FILE_OK) { UMA_HISTOGRAM_COUNTS_1M("ExtensionUrlRequest.OnReadCompleteResult", read_result); base::AutoLock auto_lock(lock_); bytes_read_ += num_bytes_read; if (verify_job_.get()) verify_job_->BytesRead(num_bytes_read, static_cast(data)); } else { net::Error net_error = net::FileErrorToNetError(read_result); base::UmaHistogramSparse("ExtensionUrlRequest.OnReadCompleteError", net_error); } } void OnDoneReading() override { base::AutoLock auto_lock(lock_); if (verify_job_.get()) verify_job_->DoneReading(); } 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::mojom::URLLoaderFactory { public: ExtensionURLLoaderFactory(int render_process_id, int render_frame_id) : render_process_id_(render_process_id) { content::RenderProcessHost* process_host = content::RenderProcessHost::FromID(render_process_id); browser_context_ = process_host->GetBrowserContext(); is_web_view_request_ = WebViewGuest::FromFrameID( render_process_id_, render_frame_id) != nullptr; Init(); } ExtensionURLLoaderFactory(content::BrowserContext* browser_context, bool is_web_view_request) : browser_context_(browser_context), is_web_view_request_(is_web_view_request), render_process_id_(-1) { Init(); } void Init() { extension_info_map_ = extensions::ExtensionSystem::Get(browser_context_)->info_map(); DCHECK_CURRENTLY_ON(content::BrowserThread::UI); } ~ExtensionURLLoaderFactory() override = default; // network::mojom::URLLoaderFactory: void CreateLoaderAndStart(network::mojom::URLLoaderRequest loader, int32_t routing_id, int32_t request_id, uint32_t options, const network::ResourceRequest& request, network::mojom::URLLoaderClientPtr client, const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) override { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 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.url, static_cast(request.resource_type), static_cast(request.transition_type), render_process_id_, browser_context_->IsOffTheRecord(), extension.get(), incognito_enabled, enabled_extensions, *process_map)) { 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)) { client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED)); return; } LoadExtension(std::move(loader), request, std::move(client), extension, std::move(directory_path)); } void Clone(network::mojom::URLLoaderFactoryRequest request) override { bindings_.AddBinding(this, std::move(request)); } private: void LoadExtension(network::mojom::URLLoaderRequest loader, const network::ResourceRequest& request, network::mojom::URLLoaderClientPtr client, scoped_refptr extension, base::FilePath directory_path) { // Set up content security policy. std::string content_security_policy; bool send_cors_header = false; bool follow_symlinks_anywhere = false; if (extension) { GetSecurityPolicyForURL(request.url, extension.get(), is_web_view_request_, &content_security_policy, &send_cors_header, &follow_symlinks_anywhere); } 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. network::ResourceResponseHead head; const bool send_cors_headers = false; head.headers = BuildHttpHeaders(content_security_policy, send_cors_headers, base::Time()); std::string contents; GenerateBackgroundPageContents(extension.get(), &head.mime_type, &head.charset, &contents); uint32_t size = base::saturated_cast(contents.size()); mojo::DataPipe pipe(size); MojoResult result = pipe.producer_handle->WriteData( contents.data(), &size, MOJO_WRITE_DATA_FLAG_NONE); if (result != MOJO_RESULT_OK || size < contents.size()) { client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED)); return; } client->OnReceiveResponse(head); client->OnStartLoadingResponseBody(std::move(pipe.consumer_handle)); client->OnComplete(network::URLLoaderCompletionStatus(net::OK)); return; } // Component extension resources may be part of the embedder's resource // files, for example component_extension_resources.pak in Chrome. ComponentExtensionResourceInfo resource_info; const base::FilePath bundle_resource_path = ExtensionsBrowserClient::Get()->GetBundleResourcePath( request, directory_path, &resource_info); if (!bundle_resource_path.empty()) { ExtensionsBrowserClient::Get()->LoadResourceFromResourceBundle( request, std::move(loader), bundle_resource_path, resource_info, content_security_policy, std::move(client), send_cors_header); 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)) { 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 { 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::PostTaskWithTraitsAndReply( 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, content_security_policy, send_cors_header)); } static void OnFilePathAndLastModifiedTimeRead( const base::FilePath* read_file_path, const base::Time* last_modified_time, network::ResourceRequest request, network::mojom::URLLoaderRequest loader, network::mojom::URLLoaderClientPtr client, scoped_refptr content_verifier, const extensions::ExtensionResource& resource, const std::string& content_security_policy, bool send_cors_header) { request.url = net::FilePathToFileURL(*read_file_path); base::PostTaskWithTraits( FROM_HERE, {content::BrowserThread::IO}, base::BindOnce( &StartVerifyJob, std::move(request), std::move(loader), std::move(client), std::move(content_verifier), resource, BuildHttpHeaders(content_security_policy, send_cors_header, *last_modified_time))); } static void StartVerifyJob( network::ResourceRequest request, network::mojom::URLLoaderRequest loader, network::mojom::URLLoaderClientPtr client, scoped_refptr content_verifier, const ExtensionResource& resource, scoped_refptr response_headers) { scoped_refptr verify_job; if (content_verifier) { verify_job = content_verifier->CreateJobFor(resource.extension_id(), resource.extension_root(), resource.relative_path()); if (verify_job) verify_job->Start(content_verifier.get()); } content::CreateFileURLLoader( request, std::move(loader), std::move(client), std::make_unique(std::move(verify_job)), /* allow_directory_listing */ false, std::move(response_headers)); } content::BrowserContext* browser_context_; bool is_web_view_request_; // 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_; mojo::BindingSet bindings_; DISALLOW_COPY_AND_ASSIGN(ExtensionURLLoaderFactory); }; } // namespace scoped_refptr BuildHttpHeaders( const std::string& content_security_policy, bool send_cors_header, const base::Time& last_modified_time) { 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 (send_cors_header) { raw_headers.append(1, '\0'); raw_headers.append("Access-Control-Allow-Origin: *"); } if (!last_modified_time.is_null()) { // 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); raw_headers.append(1, '\0'); raw_headers.append("ETag: \""); raw_headers.append(etag); raw_headers.append("\""); // Also force revalidation. raw_headers.append(1, '\0'); raw_headers.append("cache-control: no-cache"); } raw_headers.append(2, '\0'); return new net::HttpResponseHeaders(raw_headers); } std::unique_ptr CreateExtensionProtocolHandler(bool is_incognito, extensions::InfoMap* extension_info_map) { return std::make_unique(is_incognito, extension_info_map); } void SetExtensionProtocolTestHandler(ExtensionProtocolTestHandler* handler) { g_test_handler = handler; } std::unique_ptr CreateExtensionNavigationURLLoaderFactory( content::BrowserContext* browser_context, bool is_web_view_request) { return std::make_unique(browser_context, is_web_view_request); } std::unique_ptr CreateExtensionURLLoaderFactory(int render_process_id, int render_frame_id) { return std::make_unique(render_process_id, render_frame_id); } } // namespace extensions