blob: 30f2c1df1c9e584d7c3c85648a8f10ce51d5cca3 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/pdf/browser/pdf_navigation_throttle.h"
#include <memory>
#include <optional>
#include <utility>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/task/sequenced_task_runner.h"
#include "components/pdf/browser/pdf_stream_delegate.h"
#include "components/pdf/common/constants.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/browser/web_contents.h"
#include "net/http/http_response_headers.h"
#include "pdf/pdf_features.h"
#include "services/network/public/cpp/web_sandbox_flags.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
namespace pdf {
content::NavigationThrottle::ThrottleCheckResult
PdfNavigationThrottle::WillProcessResponse() {
// OOPIF PDF viewer only.
if (!chrome_pdf::features::IsOopifPdfEnabled()) {
return PROCEED;
}
const net::HttpResponseHeaders* response_headers =
navigation_handle()->GetResponseHeaders();
if (!response_headers) {
return PROCEED;
}
std::string mime_type;
response_headers->GetMimeType(&mime_type);
if (mime_type != kPDFMimeType) {
return PROCEED;
}
// Fenced frames should not be able to load PDFs.
bool is_sandboxed_pdf = (navigation_handle()->SandboxFlagsToCommit() &
network::mojom::WebSandboxFlags::kPlugins) !=
network::mojom::WebSandboxFlags::kNone;
if (!is_sandboxed_pdf) {
return PROCEED;
}
stream_delegate_->OnPdfEmbedderSandboxed(
navigation_handle()->GetFrameTreeNodeId());
return ThrottleCheckResult(CANCEL, net::ERR_BLOCKED_BY_CLIENT);
}
PdfNavigationThrottle::PdfNavigationThrottle(
content::NavigationHandle* navigation_handle,
std::unique_ptr<PdfStreamDelegate> stream_delegate)
: content::NavigationThrottle(navigation_handle),
stream_delegate_(std::move(stream_delegate)) {
DCHECK(stream_delegate_);
}
PdfNavigationThrottle::~PdfNavigationThrottle() = default;
const char* PdfNavigationThrottle::GetNameForLogging() {
return "PdfNavigationThrottle";
}
content::NavigationThrottle::ThrottleCheckResult
PdfNavigationThrottle::WillStartRequest() {
// Intercepts navigations to a PDF stream URL in a PDF content frame and
// re-navigates to the original PDF URL.
// Skip main frame navigations, as the main frame should never be navigating
// to the stream URL.
if (navigation_handle()->IsInMainFrame()) {
return PROCEED;
}
// Skip unless navigating to the stream URL.
const std::optional<GURL> original_url =
stream_delegate_->MapToOriginalUrl(*navigation_handle());
if (!original_url.has_value()) {
// Block any non-PDF navigations in internal PDF extension and content
// frames. Allow all other navigations to proceed.
return stream_delegate_->ShouldAllowPdfFrameNavigation(navigation_handle())
? PROCEED
: BLOCK_REQUEST;
}
// Uses the same pattern as `PDFIFrameNavigationThrottle` to redirect
// navigation to the original URL. We'll use this to navigate to the correct
// origin, while `PdfURLLoaderRequestInterceptor` will intercept the request
// and replace its content.
content::OpenURLParams params =
content::OpenURLParams::FromNavigationHandle(navigation_handle());
params.url = original_url.value();
params.transition = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
params.is_renderer_initiated = false;
params.is_pdf = true;
// The parent frame should always exist after main frame navigations are
// filtered out at the beginning of this method, and it has the expected
// embedder URL based on the checks in
// `PdfStreamDelegate::MapToOriginalUrl()`. For the PDF viewer, the parent
// frame is the PDF extension frame. For Print Preview, the parent frame is
// the embedder frame.
content::RenderFrameHost* embedder_frame =
navigation_handle()->GetParentFrame();
CHECK(embedder_frame);
// Reset the source SiteInstance. This is a workaround for a lifetime bug:
// leaving the source SiteInstance in OpenURLParams could inadvertently
// prolong the SiteInstance's lifetime beyond the lifetime of the
// BrowserContext it's associated with. The BrowserContext could get
// destroyed after the task below is scheduled but before it runs (see
// https://crbug.com/1382761), and even though the task checks if the frame is
// null to return early in that case, the task's OpenURLParams would only get
// destroyed and decrement the source SiteInstance's refcount at the time of
// that early return, which is already after the BrowserContext is destroyed.
// This can cause logic in the SiteInstance destructor to trip up if it tries
// to use the SiteInstance's BrowserContext.
//
// The source SiteInstance of this navigation should always be SiteInstance of
// the parent frame's, which is the embedder frame. Hence, if the navigation
// task does run and does not get canceled due to the embedder frame becoming
// null, we can restore the source SiteInstance at that point.
//
// TODO(crbug.com/40061670): This should be fixed in a more systematic way.
DCHECK_EQ(params.source_site_instance, embedder_frame->GetSiteInstance());
params.source_site_instance.reset();
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
[](content::GlobalRenderFrameHostId frame_id,
const content::OpenURLParams& params) {
auto* embedder_frame = content::RenderFrameHost::FromID(frame_id);
if (!embedder_frame) {
return;
}
// Restore the source SiteInstance that was cleared out of the
// original OpenURLParams.
content::OpenURLParams new_params = params;
new_params.source_site_instance = embedder_frame->GetSiteInstance();
// `MimeHandlerViewGuest` navigates its embedder for calls to
// `WebContents::OpenURL()`, so use `LoadURLWithParams()` directly
// instead.
content::WebContents::FromRenderFrameHost(embedder_frame)
->GetController()
.LoadURLWithParams(
content::NavigationController::LoadURLParams(new_params));
// Note that we don't need to register the stream's URL loader as a
// subresource, as `MimeHandlerViewGuest::ReadyToCommitNavigation()`
// will handle this as soon as we navigate to a
// non-`kPdfExtensionId` URL.
},
embedder_frame->GetGlobalId(), std::move(params)));
return CANCEL_AND_IGNORE;
}
} // namespace pdf