blob: df71296fc2c51f5ba7fdad3bdf535dad1bf88356 [file] [log] [blame]
// 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 <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#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/non_network_url_loader_factory_base.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/media_router_extension_access_logger.h"
#include "extensions/browser/process_map.h"
#include "extensions/browser/process_map_factory.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/identifiability_metrics.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/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/url_loader_completion_status.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 "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.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<network::mojom::URLLoaderClient> real_client)
: url_(url),
ukm_source_id_(ukm_source_id),
real_client_(std::move(real_client)) {}
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<network::mojom::URLLoaderClient> real_client_;
};
mojo::PendingRemote<network::mojom::URLLoaderClient> WrapWithMetricsIfNeeded(
const GURL& url,
ukm::SourceIdObj ukm_source_id,
mojo::PendingRemote<network::mojom::URLLoaderClient> in_client) {
if (ukm_source_id == ukm::kInvalidSourceIdObj)
return in_client;
mojo::PendingRemote<network::mojom::URLLoaderClient> proxy_client_remote;
auto proxy_client = std::make_unique<ResultRecordingClient>(
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 = "<!DOCTYPE html>\n<body>\n";
for (const auto& script : BackgroundInfo::GetBackgroundScripts(extension)) {
*data += "<script src=\"";
*data += script;
*data += "\"></script>\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,
blink::mojom::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 == blink::mojom::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(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 during update check when
// ServiceWorkerImportedScriptUpdateCheck is enabled.
if (child_id == content::ChildProcessHost::kInvalidUniqueID &&
(blink::IsResourceTypeFrame(resource_type) ||
(base::FeatureList::IsEnabled(blink::features::kPlzDedicatedWorker) &&
resource_type == blink::mojom::ResourceType::kWorker) ||
resource_type == blink::mojom::ResourceType::kSharedWorker ||
resource_type == blink::mojom::ResourceType::kScript ||
resource_type == blink::mojom::ResourceType::kServiceWorker)) {
return true;
}
// Allow the extension module embedder to grant permission for loads.
if (ExtensionsBrowserClient::Get()->AllowCrossRendererResourceLoad(
request, 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;
}
void GetSecurityPolicyForURL(const network::ResourceRequest& request,
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 = request.url.path();
// Use default CSP for <webview>.
if (!is_web_view_request) {
*content_security_policy =
extensions::CSPInfo::GetResourceContentSecurityPolicy(extension,
resource_path);
}
if (extensions::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;
}
class FileLoaderObserver : public content::FileURLLoaderObserver {
public:
explicit FileLoaderObserver(scoped_refptr<ContentVerifyJob> 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.
const bool is_seek_contiguous = result == bytes_read_;
if (result > 0 && verify_job_.get() && !is_seek_contiguous)
verify_job_ = nullptr;
}
void OnRead(base::span<char> 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<const char*>(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<base::ElapsedTimer> request_timer_;
scoped_refptr<ContentVerifyJob> verify_job_;
// To synchronize access to all members.
base::Lock lock_;
DISALLOW_COPY_AND_ASSIGN(FileLoaderObserver);
};
class ExtensionURLLoaderFactory
: public content::NonNetworkURLLoaderFactoryBase {
public:
static mojo::PendingRemote<network::mojom::URLLoaderFactory> Create(
content::BrowserContext* browser_context,
ukm::SourceIdObj ukm_source_id,
bool is_web_view_request,
int render_process_id) {
DCHECK(browser_context);
mojo::PendingRemote<network::mojom::URLLoaderFactory> 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 NonNetworkURLLoaderFactoryBase::OnDisconnect method.
ExtensionURLLoaderFactory(
content::BrowserContext* browser_context,
ukm::SourceIdObj ukm_source_id,
bool is_web_view_request,
int render_process_id,
mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver)
: content::NonNetworkURLLoaderFactoryBase(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<network::mojom::URLLoader> loader,
int32_t routing_id,
int32_t request_id,
uint32_t options,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation)
override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (browser_context_->ShutdownStarted()) {
mojo::Remote<network::mojom::URLLoaderClient>(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<const Extension> 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,
static_cast<blink::mojom::ResourceType>(request.resource_type),
static_cast<ui::PageTransition>(request.transition_type),
render_process_id_, browser_context_->IsOffTheRecord(),
extension.get(), incognito_enabled, enabled_extensions,
*process_map)) {
mojo::Remote<network::mojom::URLLoaderClient>(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<network::mojom::URLLoaderClient>(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<network::mojom::URLLoader> loader,
const network::ResourceRequest& request,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
scoped_refptr<const Extension> 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, extension.get(), is_web_view_request_,
&content_security_policy, &send_cors_header,
&follow_symlinks_anywhere);
}
// If the extension is the Media Router Component Extension used to support
// Casting scenarios, log metrics needed to track migration away from this
// extension.
// TODO(crbug.com/1097594): Remove this metric logging once migration away
// from the Media Router Component Extension completes.
const MediaRouterExtensionAccessLogger* media_router_access_logger =
ExtensionsBrowserClient::Get()->GetMediaRouterAccessLogger();
if (media_router_access_logger && request.request_initiator.has_value() &&
(extension.get()->id() == extension_misc::kCastExtensionIdRelease ||
extension.get()->id() == extension_misc::kCastExtensionIdDev)) {
media_router_access_logger->LogMediaRouterComponentExtensionUse(
request.request_initiator.value(), browser_context_);
}
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();
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<uint32_t>(contents.size());
mojo::DataPipe pipe(size);
MojoResult result = pipe.producer_handle->WriteData(
contents.data(), &size, MOJO_WRITE_DATA_FLAG_NONE);
mojo::Remote<network::mojom::URLLoaderClient> client_remote(
std::move(client));
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(pipe.consumer_handle));
client_remote->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.
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,
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)) {
mojo::Remote<network::mojom::URLLoaderClient>(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<network::mojom::URLLoaderClient>(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<ContentVerifier> 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,
content_security_policy, send_cors_header));
}
static void OnFilePathAndLastModifiedTimeRead(
const base::FilePath* read_file_path,
const base::Time* last_modified_time,
network::ResourceRequest request,
mojo::PendingReceiver<network::mojom::URLLoader> loader,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
scoped_refptr<ContentVerifier> content_verifier,
const extensions::ExtensionResource& resource,
const std::string& content_security_policy,
bool send_cors_header) {
request.url = net::FilePathToFileURL(*read_file_path);
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
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,
mojo::PendingReceiver<network::mojom::URLLoader> loader,
mojo::PendingRemote<network::mojom::URLLoaderClient> client,
scoped_refptr<ContentVerifier> content_verifier,
const ExtensionResource& resource,
scoped_refptr<net::HttpResponseHeaders> response_headers) {
scoped_refptr<ContentVerifyJob> 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<FileLoaderObserver>(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<BrowserContextShutdownNotifierFactory>
s_factory;
return s_factory.get();
}
// No copying.
BrowserContextShutdownNotifierFactory(
const BrowserContextShutdownNotifierFactory&) = delete;
BrowserContextShutdownNotifierFactory& operator=(
const BrowserContextShutdownNotifierFactory&) = delete;
private:
friend class base::NoDestructor<BrowserContextShutdownNotifierFactory>;
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<extensions::InfoMap> extension_info_map_;
base::CallbackListSubscription browser_context_shutdown_subscription_;
DISALLOW_COPY_AND_ASSIGN(ExtensionURLLoaderFactory);
};
} // namespace
scoped_refptr<net::HttpResponseHeaders> 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: *");
raw_headers.append(1, '\0');
raw_headers.append("Cross-Origin-Resource-Policy: cross-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);
}
void SetExtensionProtocolTestHandler(ExtensionProtocolTestHandler* handler) {
g_test_handler = handler;
}
mojo::PendingRemote<network::mojom::URLLoaderFactory>
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<network::mojom::URLLoaderFactory>
CreateExtensionWorkerMainResourceURLLoaderFactory(
content::BrowserContext* browser_context) {
return ExtensionURLLoaderFactory::Create(
browser_context, ukm::kInvalidSourceIdObj,
/*is_web_view_request=*/false,
content::ChildProcessHost::kInvalidUniqueID);
}
mojo::PendingRemote<network::mojom::URLLoaderFactory>
CreateExtensionServiceWorkerScriptURLLoaderFactory(
content::BrowserContext* browser_context) {
return ExtensionURLLoaderFactory::Create(
browser_context, ukm::kInvalidSourceIdObj,
/*is_web_view_request=*/false,
content::ChildProcessHost::kInvalidUniqueID);
}
mojo::PendingRemote<network::mojom::URLLoaderFactory>
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