1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
// 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::NavigationThrottleRegistry& registry,
std::unique_ptr<PdfStreamDelegate> stream_delegate)
: content::NavigationThrottle(registry),
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.
// The main frame may contain the PDF extension for non-PdfOopif cases; it
// should never be navigated away from the extension.
if (navigation_handle()->IsInMainFrame()) {
return stream_delegate_->ShouldAllowPdfExtensionFrameNavigation(
navigation_handle())
? PROCEED
: BLOCK_REQUEST;
}
// 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.
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
|