blob: 542bec76937d0c0ccc2f82ce4dadd657cc9be146 [file] [log] [blame]
// Copyright 2017 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 "content/public/browser/web_ui_url_loader_factory.h"
#include <utility>
#include "base/bind.h"
#include "base/debug/crash_logging.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_piece.h"
#include "base/task/thread_pool.h"
#include "content/browser/bad_message.h"
#include "content/browser/blob_storage/blob_internals_url_loader.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
#include "content/browser/loader/non_network_url_loader_factory_base.h"
#include "content/browser/webui/network_error_url_loader.h"
#include "content/browser/webui/url_data_manager_backend.h"
#include "content/browser/webui/url_data_source_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/url_constants.h"
#include "mojo/public/cpp/bindings/message.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 "services/network/public/cpp/parsed_headers.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "ui/base/template_expressions.h"
namespace content {
namespace {
class WebUIURLLoaderFactory;
void CallOnError(
mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote,
int error_code) {
mojo::Remote<network::mojom::URLLoaderClient> client(
std::move(client_remote));
network::URLLoaderCompletionStatus status;
status.error_code = error_code;
client->OnComplete(status);
}
void ReadData(
network::mojom::URLResponseHeadPtr headers,
const ui::TemplateReplacements* replacements,
bool replace_in_js,
scoped_refptr<URLDataSourceImpl> data_source,
mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote,
scoped_refptr<base::RefCountedMemory> bytes) {
if (!bytes) {
CallOnError(std::move(client_remote), net::ERR_FAILED);
return;
}
if (replacements) {
// We won't know the the final output size ahead of time, so we have to
// use an intermediate string.
base::StringPiece input(reinterpret_cast<const char*>(bytes->front()),
bytes->size());
std::string temp_str;
if (replace_in_js) {
CHECK(
ui::ReplaceTemplateExpressionsInJS(input, *replacements, &temp_str));
} else {
temp_str = ui::ReplaceTemplateExpressions(input, *replacements);
}
bytes = base::RefCountedString::TakeString(&temp_str);
}
uint32_t output_size = bytes->size();
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes = output_size;
mojo::ScopedDataPipeProducerHandle pipe_producer_handle;
mojo::ScopedDataPipeConsumerHandle pipe_consumer_handle;
MojoResult create_result = mojo::CreateDataPipe(
&options, &pipe_producer_handle, &pipe_consumer_handle);
CHECK_EQ(create_result, MOJO_RESULT_OK);
void* buffer = nullptr;
uint32_t num_bytes = output_size;
MojoResult result = pipe_producer_handle->BeginWriteData(
&buffer, &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
CHECK_EQ(result, MOJO_RESULT_OK);
CHECK_GE(num_bytes, output_size);
memcpy(buffer, bytes->front(), output_size);
result = pipe_producer_handle->EndWriteData(output_size);
CHECK_EQ(result, MOJO_RESULT_OK);
// For media content, |content_length| must be known upfront for data that is
// assumed to be fully buffered (as opposed to streamed from the network),
// otherwise the media player will get confused and refuse to play.
// Content delivered via chrome:// URLs is assumed fully buffered.
headers->content_length = output_size;
mojo::Remote<network::mojom::URLLoaderClient> client(
std::move(client_remote));
client->OnReceiveResponse(std::move(headers));
client->OnStartLoadingResponseBody(std::move(pipe_consumer_handle));
network::URLLoaderCompletionStatus status(net::OK);
status.encoded_data_length = output_size;
status.encoded_body_length = output_size;
status.decoded_body_length = output_size;
client->OnComplete(status);
}
void DataAvailable(
network::mojom::URLResponseHeadPtr headers,
const ui::TemplateReplacements* replacements,
bool replace_in_js,
scoped_refptr<URLDataSourceImpl> source,
mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote,
scoped_refptr<base::RefCountedMemory> bytes) {
// Since the bytes are from the memory mapped resource file, copying the
// data can lead to disk access. Needs to be posted to a SequencedTaskRunner
// as Mojo requires a SequencedTaskRunnerHandle in scope.
base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::USER_BLOCKING, base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})
->PostTask(FROM_HERE, base::BindOnce(ReadData, std::move(headers),
replacements, replace_in_js, source,
std::move(client_remote), bytes));
}
void StartURLLoader(
const network::ResourceRequest& request,
int frame_tree_node_id,
mojo::PendingRemote<network::mojom::URLLoaderClient> client_remote,
BrowserContext* browser_context) {
// NOTE: this duplicates code in URLDataManagerBackend::StartRequest.
if (!URLDataManagerBackend::CheckURLIsValid(request.url)) {
CallOnError(std::move(client_remote), net::ERR_INVALID_URL);
return;
}
URLDataSourceImpl* source =
URLDataManagerBackend::GetForBrowserContext(browser_context)
->GetDataSourceFromURL(request.url);
if (!source) {
CallOnError(std::move(client_remote), net::ERR_INVALID_URL);
return;
}
if (!source->source()->ShouldServiceRequest(request.url, browser_context,
-1)) {
CallOnError(std::move(client_remote), net::ERR_INVALID_URL);
return;
}
std::string path = URLDataSource::URLToRequestPath(request.url);
std::string origin_header;
request.headers.GetHeader(net::HttpRequestHeaders::kOrigin, &origin_header);
scoped_refptr<net::HttpResponseHeaders> headers =
URLDataManagerBackend::GetHeaders(source, path, origin_header);
auto resource_response = network::mojom::URLResponseHead::New();
resource_response->headers = headers;
// Headers from WebUI are trusted, so parsing can happen from a non-sandboxed
// process.
resource_response->parsed_headers =
network::PopulateParsedHeaders(resource_response->headers, request.url);
resource_response->mime_type = source->source()->GetMimeType(path);
// TODO: fill all the time related field i.e. request_time response_time
// request_start response_start
WebContents::Getter wc_getter =
base::BindRepeating(WebContents::FromFrameTreeNodeId, frame_tree_node_id);
bool replace_in_js =
source->source()->ShouldReplaceI18nInJS() &&
source->source()->GetMimeType(path) == "application/javascript";
const ui::TemplateReplacements* replacements = nullptr;
if (source->source()->GetMimeType(path) == "text/html" || replace_in_js)
replacements = source->source()->GetReplacements();
// To keep the same behavior as the old WebUI code, we call the source to get
// the value for |replacements| on the IO thread. Since |replacements| is
// owned by |source| keep a reference to it in the callback.
URLDataSource::GotDataCallback data_available_callback = base::BindOnce(
DataAvailable, std::move(resource_response), replacements, replace_in_js,
base::RetainedRef(source), std::move(client_remote));
source->source()->StartDataRequest(request.url, std::move(wc_getter),
std::move(data_available_callback));
}
class WebUIURLLoaderFactory : public NonNetworkURLLoaderFactoryBase {
public:
// Returns mojo::PendingRemote to a newly constructed WebUIURLLoaderFactory.
// 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).
//
// |allowed_hosts| is an optional set of allowed host names. If empty then
// all hosts are allowed.
static mojo::PendingRemote<network::mojom::URLLoaderFactory> Create(
FrameTreeNode* ftn,
const std::string& scheme,
base::flat_set<std::string> allowed_hosts) {
mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote;
// The WebUIURLLoaderFactory will delete itself when there are no more
// receivers - see the NonNetworkURLLoaderFactoryBase::OnDisconnect method.
new WebUIURLLoaderFactory(ftn, scheme, std::move(allowed_hosts),
pending_remote.InitWithNewPipeAndPassReceiver());
return pending_remote;
}
private:
~WebUIURLLoaderFactory() override = default;
// network::mojom::URLLoaderFactory implementation:
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(BrowserThread::UI);
auto* ftn = FrameTreeNode::GloballyFindByID(frame_tree_node_id_);
if (!ftn) {
CallOnError(std::move(client), net::ERR_FAILED);
return;
}
BrowserContext* browser_context =
ftn->current_frame_host()->GetBrowserContext();
if (request.url.scheme() != scheme_) {
DVLOG(1) << "Bad scheme: " << request.url.scheme();
mojo::ReportBadMessage("Incorrect scheme");
mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
return;
}
if (!allowed_hosts_.empty() &&
(!request.url.has_host() ||
allowed_hosts_.find(request.url.host()) == allowed_hosts_.end())) {
// Temporary reporting the bad WebUI host for for http://crbug.com/837328.
static auto* crash_key = base::debug::AllocateCrashKeyString(
"webui_url", base::debug::CrashKeySize::Size64);
base::debug::SetCrashKeyString(crash_key, request.url.spec());
DVLOG(1) << "Bad host: \"" << request.url.host() << '"';
mojo::ReportBadMessage("Incorrect host");
mojo::Remote<network::mojom::URLLoaderClient>(std::move(client))
->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
return;
}
if (request.url.host_piece() == kChromeUIBlobInternalsHost) {
GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&StartBlobInternalsURLLoader, request, std::move(client),
base::Unretained(
ChromeBlobStorageContext::GetFor(browser_context))));
return;
}
if (request.url.host_piece() == kChromeUINetworkErrorHost ||
request.url.host_piece() == kChromeUIDinoHost) {
StartNetworkErrorsURLLoader(request, std::move(client));
return;
}
// We pass the FrameTreeNode ID to get to the WebContents because requests
// from frames can happen while the RFH is changed for a cross-process
// navigation. The URLDataSources just need the WebContents; the specific
// frame doesn't matter.
StartURLLoader(request, frame_tree_node_id_, std::move(client),
browser_context);
}
const std::string& scheme() const { return scheme_; }
WebUIURLLoaderFactory(
FrameTreeNode* ftn,
const std::string& scheme,
base::flat_set<std::string> allowed_hosts,
mojo::PendingReceiver<network::mojom::URLLoaderFactory> factory_receiver)
: NonNetworkURLLoaderFactoryBase(std::move(factory_receiver)),
frame_tree_node_id_(ftn->frame_tree_node_id()),
scheme_(scheme),
allowed_hosts_(std::move(allowed_hosts)) {}
int const frame_tree_node_id_;
const std::string scheme_;
const base::flat_set<std::string> allowed_hosts_; // if empty all allowed.
DISALLOW_COPY_AND_ASSIGN(WebUIURLLoaderFactory);
};
} // namespace
mojo::PendingRemote<network::mojom::URLLoaderFactory>
CreateWebUIURLLoaderFactory(RenderFrameHost* render_frame_host,
const std::string& scheme,
base::flat_set<std::string> allowed_hosts) {
return WebUIURLLoaderFactory::Create(FrameTreeNode::From(render_frame_host),
scheme, std::move(allowed_hosts));
}
} // namespace content