blob: ae5c53a945707ea269db0468f3e12a90d61cbafb [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/plugins/plugin_response_interceptor_url_loader_throttle.h"
#include <utility>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/guid.h"
#include "base/ignore_result.h"
#include "chrome/browser/extensions/api/streams_private/streams_private_api.h"
#include "chrome/browser/plugins/plugin_utils.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/download_utils.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_attach_helper.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_handlers/mime_types_handler.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "services/network/public/mojom/content_security_policy.mojom.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "third_party/blink/public/mojom/loader/transferrable_url_loader.mojom.h"
namespace {
void ClearAllButFrameAncestors(network::mojom::URLResponseHead* response_head) {
response_head->headers->RemoveHeader("Content-Security-Policy");
response_head->headers->RemoveHeader("Content-Security-Policy-Report-Only");
if (!response_head->parsed_headers)
return;
std::vector<network::mojom::ContentSecurityPolicyPtr>& csp =
response_head->parsed_headers->content_security_policy;
std::vector<network::mojom::ContentSecurityPolicyPtr> cleared;
for (auto& policy : csp) {
auto frame_ancestors = policy->directives.find(
network::mojom::CSPDirectiveName::FrameAncestors);
if (frame_ancestors == policy->directives.end())
continue;
auto cleared_policy = network::mojom::ContentSecurityPolicy::New();
cleared_policy->self_origin = std::move(policy->self_origin);
cleared_policy->header = std::move(policy->header);
cleared_policy->header->header_value = "";
cleared_policy
->directives[network::mojom::CSPDirectiveName::FrameAncestors] =
std::move(frame_ancestors->second);
auto raw_frame_ancestors = policy->raw_directives.find(
network::mojom::CSPDirectiveName::FrameAncestors);
if (raw_frame_ancestors == policy->raw_directives.end()) {
DCHECK(false);
} else {
cleared_policy->header->header_value =
"frame-ancestors " + raw_frame_ancestors->second;
response_head->headers->AddHeader(
cleared_policy->header->type ==
network::mojom::ContentSecurityPolicyType::kEnforce
? "Content-Security-Policy"
: "Content-Security-Policy-Report-Only",
cleared_policy->header->header_value);
cleared_policy
->raw_directives[network::mojom::CSPDirectiveName::FrameAncestors] =
std::move(raw_frame_ancestors->second);
}
cleared.push_back(std::move(cleared_policy));
}
csp.swap(cleared);
}
} // namespace
PluginResponseInterceptorURLLoaderThrottle::
PluginResponseInterceptorURLLoaderThrottle(
network::mojom::RequestDestination request_destination,
int frame_tree_node_id)
: request_destination_(request_destination),
frame_tree_node_id_(frame_tree_node_id) {}
PluginResponseInterceptorURLLoaderThrottle::
~PluginResponseInterceptorURLLoaderThrottle() = default;
void PluginResponseInterceptorURLLoaderThrottle::WillProcessResponse(
const GURL& response_url,
network::mojom::URLResponseHead* response_head,
bool* defer) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (content::download_utils::MustDownload(response_url,
response_head->headers.get(),
response_head->mime_type)) {
return;
}
content::WebContents* web_contents =
content::WebContents::FromFrameTreeNodeId(frame_tree_node_id_);
if (!web_contents)
return;
std::string extension_id = PluginUtils::GetExtensionIdForMimeType(
web_contents->GetBrowserContext(), response_head->mime_type);
if (extension_id.empty())
return;
// Chrome's PDF Extension does not work properly in the face of a restrictive
// Content-Security-Policy, and does not currently respect the policy anyway.
// Ignore CSP served on a PDF response. https://crbug.com/271452
if (extension_id == extension_misc::kPdfExtensionId &&
response_head->headers) {
// We still want to honor the frame-ancestors directive in the
// AncestorThrottle.
ClearAllButFrameAncestors(response_head);
}
MimeTypesHandler::ReportUsedHandler(extension_id);
std::string view_id = base::GenerateGUID();
// The string passed down to the original client with the response body.
std::string payload = view_id;
mojo::PendingRemote<network::mojom::URLLoader> dummy_new_loader;
ignore_result(dummy_new_loader.InitWithNewPipeAndPassReceiver());
mojo::Remote<network::mojom::URLLoaderClient> new_client;
mojo::PendingReceiver<network::mojom::URLLoaderClient> new_client_receiver =
new_client.BindNewPipeAndPassReceiver();
uint32_t data_pipe_size = 64U;
// Provide the MimeHandlerView code a chance to override the payload. This is
// the case where the resource is handled by frame-based MimeHandlerView.
*defer = extensions::MimeHandlerViewAttachHelper::
OverrideBodyForInterceptedResponse(
frame_tree_node_id_, response_url, response_head->mime_type, view_id,
&payload, &data_pipe_size,
base::BindOnce(
&PluginResponseInterceptorURLLoaderThrottle::ResumeLoad,
weak_factory_.GetWeakPtr()));
mojo::ScopedDataPipeProducerHandle producer_handle;
mojo::ScopedDataPipeConsumerHandle consumer_handle;
CHECK_EQ(
mojo::CreateDataPipe(data_pipe_size, producer_handle, consumer_handle),
MOJO_RESULT_OK);
uint32_t len = static_cast<uint32_t>(payload.size());
CHECK_EQ(MOJO_RESULT_OK,
producer_handle->WriteData(payload.c_str(), &len,
MOJO_WRITE_DATA_FLAG_ALL_OR_NONE));
new_client->OnStartLoadingResponseBody(std::move(consumer_handle));
network::URLLoaderCompletionStatus status(net::OK);
status.decoded_body_length = len;
new_client->OnComplete(status);
mojo::PendingRemote<network::mojom::URLLoader> original_loader;
mojo::PendingReceiver<network::mojom::URLLoaderClient> original_client;
delegate_->InterceptResponse(std::move(dummy_new_loader),
std::move(new_client_receiver), &original_loader,
&original_client);
// Make a deep copy of URLResponseHead before passing it cross-thread.
auto deep_copied_response = response_head->Clone();
if (response_head->headers) {
deep_copied_response->headers =
base::MakeRefCounted<net::HttpResponseHeaders>(
response_head->headers->raw_headers());
}
auto transferrable_loader = blink::mojom::TransferrableURLLoader::New();
transferrable_loader->url = GURL(
extensions::Extension::GetBaseURLFromExtensionId(extension_id).spec() +
base::GenerateGUID());
transferrable_loader->url_loader = std::move(original_loader);
transferrable_loader->url_loader_client = std::move(original_client);
transferrable_loader->head = std::move(deep_copied_response);
transferrable_loader->head->intercepted_by_plugin = true;
bool embedded =
request_destination_ != network::mojom::RequestDestination::kDocument;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&extensions::StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent,
extension_id, view_id, embedded, frame_tree_node_id_,
-1 /* render_process_id */, -1 /* render_frame_id */,
std::move(transferrable_loader), response_url));
}
void PluginResponseInterceptorURLLoaderThrottle::ResumeLoad() {
delegate_->Resume();
}