blob: d6462a48acfd02dcd0631679e4c7b3eacc53b52a [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 <map>
#include "base/bind.h"
#include "base/debug/crash_logging.h"
#include "base/lazy_instance.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/post_task.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/render_frame_host_impl.h"
#include "content/browser/resource_context_impl.h"
#include "content/browser/storage_partition_impl.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_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/url_constants.h"
#include "mojo/public/cpp/bindings/binding_set.h"
#include "services/network/public/mojom/network_service.mojom.h"
#include "third_party/zlib/google/compression_utils.h"
#include "ui/base/template_expressions.h"
namespace content {
namespace {
class WebUIURLLoaderFactory;
base::LazyInstance<std::map<GlobalFrameRoutingId,
std::unique_ptr<WebUIURLLoaderFactory>>>::Leaky
g_web_ui_url_loader_factories = LAZY_INSTANCE_INITIALIZER;
void CallOnError(network::mojom::URLLoaderClientPtrInfo client_info,
int error_code) {
network::mojom::URLLoaderClientPtr client;
client.Bind(std::move(client_info));
network::URLLoaderCompletionStatus status;
status.error_code = error_code;
client->OnComplete(status);
}
void ReadData(scoped_refptr<network::ResourceResponse> headers,
const ui::TemplateReplacements* replacements,
bool gzipped,
scoped_refptr<URLDataSourceImpl> data_source,
network::mojom::URLLoaderClientPtrInfo client_info,
scoped_refptr<base::RefCountedMemory> bytes) {
if (!bytes) {
CallOnError(std::move(client_info), net::ERR_FAILED);
return;
}
network::mojom::URLLoaderClientPtr client;
client.Bind(std::move(client_info));
client->OnReceiveResponse(headers->head);
base::StringPiece input(reinterpret_cast<const char*>(bytes->front()),
bytes->size());
// Treats empty gzipped data as unzipped.
if (!bytes->size())
gzipped = false;
if (replacements) {
std::string temp_string;
// We won't know the the final output size ahead of time, so we have to
// use an intermediate string.
base::StringPiece source;
std::string temp_str;
if (gzipped) {
temp_str.resize(compression::GetUncompressedSize(input));
source.set(temp_str.c_str(), temp_str.size());
CHECK(compression::GzipUncompress(input, source));
gzipped = false;
} else {
source = input;
}
temp_str = ui::ReplaceTemplateExpressions(source, *replacements);
bytes = base::RefCountedString::TakeString(&temp_str);
input.set(reinterpret_cast<const char*>(bytes->front()), bytes->size());
}
uint32_t output_size =
gzipped ? compression::GetUncompressedSize(input) : bytes->size();
mojo::DataPipe data_pipe(output_size);
void* buffer = nullptr;
uint32_t num_bytes = output_size;
MojoResult result = data_pipe.producer_handle->BeginWriteData(
&buffer, &num_bytes, MOJO_WRITE_DATA_FLAG_NONE);
CHECK_EQ(result, MOJO_RESULT_OK);
CHECK_GE(num_bytes, output_size);
if (gzipped) {
base::StringPiece output(static_cast<char*>(buffer), output_size);
CHECK(compression::GzipUncompress(input, output));
} else {
memcpy(buffer, bytes->front(), output_size);
}
result = data_pipe.producer_handle->EndWriteData(output_size);
CHECK_EQ(result, MOJO_RESULT_OK);
client->OnStartLoadingResponseBody(std::move(data_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(scoped_refptr<network::ResourceResponse> headers,
const ui::TemplateReplacements* replacements,
bool gzipped,
scoped_refptr<URLDataSourceImpl> source,
network::mojom::URLLoaderClientPtrInfo client_info,
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::CreateSequencedTaskRunnerWithTraits(
{base::TaskPriority::USER_BLOCKING, base::MayBlock(),
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})
->PostTask(FROM_HERE,
base::BindOnce(ReadData, headers, replacements, gzipped,
source, std::move(client_info), bytes));
}
void StartURLLoader(const network::ResourceRequest& request,
int frame_tree_node_id,
network::mojom::URLLoaderClientPtrInfo client_info,
ResourceContext* resource_context) {
// NOTE: this duplicates code in URLDataManagerBackend::StartRequest.
if (!URLDataManagerBackend::CheckURLIsValid(request.url)) {
CallOnError(std::move(client_info), net::ERR_INVALID_URL);
return;
}
URLDataSourceImpl* source =
GetURLDataManagerForResourceContext(resource_context)
->GetDataSourceFromURL(request.url);
if (!source) {
CallOnError(std::move(client_info), net::ERR_INVALID_URL);
return;
}
if (!source->source()->ShouldServiceRequest(request.url, resource_context,
-1)) {
CallOnError(std::move(client_info), net::ERR_INVALID_URL);
return;
}
std::string path;
URLDataManagerBackend::URLToRequestPath(request.url, &path);
std::string origin_header;
request.headers.GetHeader(net::HttpRequestHeaders::kOrigin, &origin_header);
scoped_refptr<net::HttpResponseHeaders> headers =
URLDataManagerBackend::GetHeaders(source, path, origin_header);
scoped_refptr<network::ResourceResponse> resource_response(
new network::ResourceResponse);
resource_response->head.headers = headers;
resource_response->head.mime_type = source->source()->GetMimeType(path);
// TODO: fill all the time related field i.e. request_time response_time
// request_start response_start
ResourceRequestInfo::WebContentsGetter wc_getter =
base::Bind(WebContents::FromFrameTreeNodeId, frame_tree_node_id);
bool gzipped = source->source()->IsGzipped(path);
const ui::TemplateReplacements* replacements = nullptr;
if (source->source()->GetMimeType(path) == "text/html")
replacements = source->GetReplacements();
// To keep the same behavior as the old WebUI code, we call the source to get
// the value for |gzipped| and |replacements| on the IO thread. Since
// |replacements| is owned by |source| keep a reference to it in the callback.
auto data_available_callback =
base::Bind(DataAvailable, resource_response, replacements, gzipped,
base::RetainedRef(source), base::Passed(&client_info));
// TODO(jam): once we only have this code path for WebUI, and not the
// URLLRequestJob one, then we should switch data sources to run on the UI
// thread by default.
scoped_refptr<base::SingleThreadTaskRunner> target_runner =
source->source()->TaskRunnerForRequestPath(path);
if (!target_runner) {
source->source()->StartDataRequest(path, std::move(wc_getter),
std::move(data_available_callback));
return;
}
// The DataSource wants StartDataRequest to be called on a specific
// thread, usually the UI thread, for this path.
target_runner->PostTask(
FROM_HERE,
base::BindOnce(&URLDataSource::StartDataRequest,
base::Unretained(source->source()), path,
std::move(wc_getter), std::move(data_available_callback)));
}
class WebUIURLLoaderFactory : public network::mojom::URLLoaderFactory,
public WebContentsObserver {
public:
// |allowed_hosts| is an optional set of allowed host names. If empty then
// all hosts are allowed.
WebUIURLLoaderFactory(RenderFrameHost* rfh,
const std::string& scheme,
base::flat_set<std::string> allowed_hosts)
: WebContentsObserver(WebContents::FromRenderFrameHost(rfh)),
render_frame_host_(rfh),
scheme_(scheme),
allowed_hosts_(std::move(allowed_hosts)) {
DCHECK(render_frame_host_);
}
~WebUIURLLoaderFactory() override {}
network::mojom::URLLoaderFactoryPtr CreateBinding() {
network::mojom::URLLoaderFactoryPtr factory;
loader_factory_bindings_.AddBinding(this, mojo::MakeRequest(&factory));
return factory;
}
// network::mojom::URLLoaderFactory implementation:
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(BrowserThread::UI);
if (request.url.scheme() != scheme_) {
DVLOG(1) << "Bad scheme: " << request.url.scheme();
ReceivedBadMessage(render_frame_host_->GetProcess(),
bad_message::WEBUI_BAD_SCHEME_ACCESS);
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() << '"';
ReceivedBadMessage(render_frame_host_->GetProcess(),
bad_message::WEBUI_BAD_HOST_ACCESS);
client->OnComplete(network::URLLoaderCompletionStatus(net::ERR_FAILED));
return;
}
if (request.url.host_piece() == kChromeUIBlobInternalsHost) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(&StartBlobInternalsURLLoader, request,
client.PassInterface(),
base::Unretained(ChromeBlobStorageContext::GetFor(
GetStoragePartition()->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.
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(
&StartURLLoader, request, render_frame_host_->GetFrameTreeNodeId(),
client.PassInterface(),
GetStoragePartition()->browser_context()->GetResourceContext()));
}
void Clone(network::mojom::URLLoaderFactoryRequest request) override {
loader_factory_bindings_.AddBinding(this, std::move(request));
}
// WebContentsObserver implementation:
void RenderFrameDeleted(RenderFrameHost* render_frame_host) override {
if (render_frame_host != render_frame_host_)
return;
g_web_ui_url_loader_factories.Get().erase(
GlobalFrameRoutingId(render_frame_host_->GetRoutingID(),
render_frame_host_->GetProcess()->GetID()));
}
const std::string& scheme() const { return scheme_; }
private:
StoragePartitionImpl* GetStoragePartition() {
return static_cast<StoragePartitionImpl*>(
render_frame_host_->GetProcess()->GetStoragePartition());
}
RenderFrameHost* render_frame_host_;
std::string scheme_;
const base::flat_set<std::string> allowed_hosts_; // if empty all allowed.
mojo::BindingSet<network::mojom::URLLoaderFactory> loader_factory_bindings_;
DISALLOW_COPY_AND_ASSIGN(WebUIURLLoaderFactory);
};
} // namespace
std::unique_ptr<network::mojom::URLLoaderFactory> CreateWebUIURLLoader(
RenderFrameHost* render_frame_host,
const std::string& scheme,
base::flat_set<std::string> allowed_hosts) {
return std::make_unique<WebUIURLLoaderFactory>(render_frame_host, scheme,
std::move(allowed_hosts));
}
network::mojom::URLLoaderFactoryPtr CreateWebUIURLLoaderBinding(
RenderFrameHost* render_frame_host,
const std::string& scheme) {
GlobalFrameRoutingId routing_id(render_frame_host->GetRoutingID(),
render_frame_host->GetProcess()->GetID());
if (g_web_ui_url_loader_factories.Get().find(routing_id) ==
g_web_ui_url_loader_factories.Get().end() ||
g_web_ui_url_loader_factories.Get()[routing_id]->scheme() != scheme) {
g_web_ui_url_loader_factories.Get()[routing_id] =
std::make_unique<WebUIURLLoaderFactory>(render_frame_host, scheme,
base::flat_set<std::string>());
}
return g_web_ui_url_loader_factories.Get()[routing_id]->CreateBinding();
}
} // namespace content