blob: 59fd17e90a4f93ebc61e6ee58fffebb6667a84ef [file] [log] [blame]
// Copyright 2017 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/security_interstitials/content/ssl_error_navigation_throttle.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "build/buildflag.h"
#include "components/guest_view/buildflags/buildflags.h"
#include "components/security_interstitials/content/security_interstitial_page.h"
#include "components/security_interstitials/content/security_interstitial_tab_helper.h"
#include "content/public/browser/navigation_handle.h"
#include "net/cert/cert_status_flags.h"
#if BUILDFLAG(ENABLE_GUEST_VIEW)
#include "components/guest_view/browser/guest_view_base.h"
#endif
namespace {
// Returns true if `handle`'s navigation is happening in a WebContents
// that uses SSL interstitials. Returns false if a plain error page should be
// used instead.
bool WebContentsUsesInterstitials(content::NavigationHandle* handle) {
#if !BUILDFLAG(ENABLE_GUEST_VIEW)
// Guests are the only remaining use of inner WebContents, so without them
// `handle`'s WebContents is always the outermost one, and should use
// interstitials.
return true;
#else
guest_view::GuestViewBase* guest =
guest_view::GuestViewBase::FromNavigationHandle(handle);
if (!guest) {
// GuestViews are the only remaining inner WebContents, so show an
// interstitial if this isn't a guest.
return true;
}
// Some guest view types still show SSL interstitials.
return guest->RequiresSslInterstitials();
#endif
}
} // namespace
SSLErrorNavigationThrottle::SSLErrorNavigationThrottle(
content::NavigationThrottleRegistry& registry,
SSLErrorNavigationThrottle::HandleSSLErrorCallback
handle_ssl_error_callback,
IsInHostedAppCallback is_in_hosted_app_callback,
ShouldIgnoreInterstitialBecauseNavigationDefaultedToHttpsCallback
should_ignore_interstitial_because_navigation_defaulted_to_https_callback)
: content::NavigationThrottle(registry),
handle_ssl_error_callback_(std::move(handle_ssl_error_callback)),
is_in_hosted_app_callback_(std::move(is_in_hosted_app_callback)),
should_ignore_interstitial_because_navigation_defaulted_to_https_callback_(
std::move(
should_ignore_interstitial_because_navigation_defaulted_to_https_callback)) {
}
SSLErrorNavigationThrottle::~SSLErrorNavigationThrottle() = default;
content::NavigationThrottle::ThrottleCheckResult
SSLErrorNavigationThrottle::WillFailRequest() {
content::NavigationHandle* handle = navigation_handle();
// Check the network error code in case we are here due to a non-ssl related
// error. SSLInfo also needs to be checked to cover cases where an SSL error
// does not trigger an interstitial, such as chrome://network-errors.
if (!net::IsCertificateError(handle->GetNetErrorCode()) ||
!net::IsCertStatusError(
handle->GetSSLInfo().value_or(net::SSLInfo()).cert_status)) {
return content::NavigationThrottle::PROCEED;
}
// Do not set special error page HTML for non-primary pages (e.g. regular
// subframe, prerendering, fenced-frame). Those are handled as normal
// network errors. Some guest views are an exception if kGuestViewMPArch is
// enabled, as their main frame won't be a primary main frame.
if (!(handle->IsInPrimaryMainFrame() || handle->IsGuestViewMainFrame()) ||
!WebContentsUsesInterstitials(handle)) {
return content::NavigationThrottle::PROCEED;
}
// If the scheme of this navigation was upgraded to HTTPS (because the user
// didn't type a scheme), don't show an error.
// TypedNavigationUpgradeThrottle or HttpsOnlyModeNavigationThrottle will
// handle the error and fall back to HTTP as needed.
if (std::move(
should_ignore_interstitial_because_navigation_defaulted_to_https_callback_)
.Run(handle)) {
return content::NavigationThrottle::PROCEED;
}
const net::SSLInfo info = handle->GetSSLInfo().value_or(net::SSLInfo());
int cert_status = info.cert_status;
QueueShowInterstitial(std::move(handle_ssl_error_callback_),
handle->GetWebContents(), handle->GetNetErrorCode(),
cert_status, info, handle->GetURL());
return content::NavigationThrottle::ThrottleCheckResult(
content::NavigationThrottle::DEFER);
}
content::NavigationThrottle::ThrottleCheckResult
SSLErrorNavigationThrottle::WillProcessResponse() {
content::NavigationHandle* handle = navigation_handle();
// If there was no certificate error, SSLInfo will be empty.
const net::SSLInfo info = handle->GetSSLInfo().value_or(net::SSLInfo());
int cert_status = info.cert_status;
if (!net::IsCertStatusError(cert_status)) {
return content::NavigationThrottle::PROCEED;
}
// Do not set special error page HTML for non-primary pages (e.g. regular
// subframe, prerendering, fenced-frame). Those are handled as normal
// network errors.
if (!(handle->IsInPrimaryMainFrame() || handle->IsGuestViewMainFrame()) ||
!WebContentsUsesInterstitials(handle)) {
return content::NavigationThrottle::PROCEED;
}
// Hosted Apps should not be allowed to run if there is a problem with their
// certificate. So, when a user tries to open such an app, we show an
// interstitial, even if the user has previously clicked through one. Clicking
// through the interstitial will continue the navigation in a regular browser
// window.
if (std::move(is_in_hosted_app_callback_).Run(handle->GetWebContents())) {
QueueShowInterstitial(std::move(handle_ssl_error_callback_),
handle->GetWebContents(),
// The navigation handle's net error code will be
// net::OK, because the net stack has allowed the
// response to proceed. Synthesize a net error from
// the cert status instead.
net::MapCertStatusToNetError(cert_status),
cert_status, info, handle->GetURL());
return content::NavigationThrottle::ThrottleCheckResult(
content::NavigationThrottle::DEFER);
}
return content::NavigationThrottle::PROCEED;
}
const char* SSLErrorNavigationThrottle::GetNameForLogging() {
return "SSLErrorNavigationThrottle";
}
void SSLErrorNavigationThrottle::QueueShowInterstitial(
HandleSSLErrorCallback handle_ssl_error_callback,
content::WebContents* web_contents,
int net_error,
int cert_status,
const net::SSLInfo& ssl_info,
const GURL& request_url) {
// It is safe to call this without posting because SSLErrorHandler will always
// call ShowInterstitial asynchronously, giving the throttle time to defer the
// navigation.
std::move(handle_ssl_error_callback)
.Run(web_contents, net_error, ssl_info, request_url,
base::BindOnce(&SSLErrorNavigationThrottle::ShowInterstitial,
weak_ptr_factory_.GetWeakPtr(), net_error));
}
void SSLErrorNavigationThrottle::ShowInterstitial(
int net_error,
std::unique_ptr<security_interstitials::SecurityInterstitialPage>
blocking_page) {
// Get the error page content before giving up ownership of |blocking_page|.
std::string error_page_content = blocking_page->GetHTMLContents();
content::NavigationHandle* handle = navigation_handle();
// Do not display insterstitials for SSL errors from non-primary pages (e.g.
// prerendering, fenced-frame). For prerendering specifically, we
// should already have canceled the prerender from OnSSLCertificateError
// before the throttle runs.
DCHECK(handle->IsInPrimaryMainFrame() || handle->IsGuestViewMainFrame());
security_interstitials::SecurityInterstitialTabHelper::AssociateBlockingPage(
handle, std::move(blocking_page));
CancelDeferredNavigation(content::NavigationThrottle::ThrottleCheckResult(
content::NavigationThrottle::CANCEL, static_cast<net::Error>(net_error),
error_page_content));
}