#include "content/browser/renderer_host/mixed_content_navigation_throttle.h"
#include <vector>
#include "base/containers/contains.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "content/browser/preloading/prerender/prerender_final_status.h"
#include "content/browser/preloading/prerender/prerender_metrics.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_delegate.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "net/base/url_util.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/common/security_context/insecure_request_policy.h"
#include "third_party/blink/public/common/web_preferences/web_preferences.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
#include "third_party/blink/public/mojom/frame/frame.mojom.h"
#include "third_party/blink/public/mojom/loader/mixed_content.mojom.h"
#include "third_party/blink/public/mojom/security_context/insecure_request_policy.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_constants.h"
#include "url/url_util.h"
namespace content {
namespace {
bool IsSecureScheme(const std::string& scheme) {
return base::Contains(url::GetSecureSchemes(), scheme);
}
bool ShouldTreatURLSchemeAsCorsEnabled(const GURL& url) {
return base::Contains(url::GetCorsEnabledSchemes(), url.scheme());
}
bool IsUrlPotentiallySecure(const GURL& url) {
return network::IsUrlPotentiallyTrustworthy(url);
}
bool DoesOriginSchemeRestrictMixedContent(const url::Origin& origin) {
return origin.GetTupleOrPrecursorTupleIfOpaque().scheme() ==
url::kHttpsScheme;
}
void UpdateRendererOnMixedContentFound(NavigationRequest* navigation_request,
const GURL& mixed_content_url,
bool was_allowed,
bool for_redirect) {
DCHECK(!navigation_request->IsInOutermostMainFrame());
RenderFrameHostImpl* rfh =
navigation_request->frame_tree_node()->current_frame_host();
DCHECK(!navigation_request->GetRedirectChain().empty());
GURL url_before_redirects = navigation_request->GetRedirectChain()[0];
rfh->GetAssociatedLocalFrame()->MixedContentFound(
mixed_content_url, navigation_request->GetURL(),
navigation_request->request_context_type(), was_allowed,
url_before_redirects, for_redirect,
navigation_request->common_params().source_location.Clone());
}
}
std::unique_ptr<NavigationThrottle>
MixedContentNavigationThrottle::CreateThrottleForNavigation(
NavigationHandle* navigation_handle) {
return std::make_unique<MixedContentNavigationThrottle>(navigation_handle);
}
MixedContentNavigationThrottle::MixedContentNavigationThrottle(
NavigationHandle* navigation_handle)
: NavigationThrottle(navigation_handle) {}
MixedContentNavigationThrottle::~MixedContentNavigationThrottle() {}
NavigationThrottle::ThrottleCheckResult
MixedContentNavigationThrottle::WillStartRequest() {
bool should_block = ShouldBlockNavigation(false);
return should_block ? CANCEL : PROCEED;
}
NavigationThrottle::ThrottleCheckResult
MixedContentNavigationThrottle::WillRedirectRequest() {
bool should_block = ShouldBlockNavigation(true);
if (!should_block) {
MaybeHandleCertificateError();
return PROCEED;
}
return CANCEL;
}
NavigationThrottle::ThrottleCheckResult
MixedContentNavigationThrottle::WillProcessResponse() {
MaybeHandleCertificateError();
return PROCEED;
}
const char* MixedContentNavigationThrottle::GetNameForLogging() {
return "MixedContentNavigationThrottle";
}
bool MixedContentNavigationThrottle::ShouldBlockNavigation(bool for_redirect) {
NavigationRequest* request = NavigationRequest::From(navigation_handle());
FrameTreeNode* node = request->frame_tree_node();
RenderFrameHostImpl* mixed_content_frame =
InWhichFrameIsContentMixed(node, request->GetURL());
if (!mixed_content_frame) {
MaybeSendBlinkFeatureUsageReport();
return false;
}
ReportBasicMixedContentFeatures(request->request_context_type(),
request->mixed_content_context_type());
bool block_all_mixed_content =
((mixed_content_frame->frame_tree_node()
->current_replication_state()
.insecure_request_policy &
blink::mojom::InsecureRequestPolicy::kBlockAllMixedContent) !=
blink::mojom::InsecureRequestPolicy::kLeaveInsecureRequestsAlone) ||
node->IsInFencedFrameTree();
const auto& prefs = mixed_content_frame->GetOrCreateWebPreferences();
bool strict_mode =
prefs.strict_mixed_content_checking || block_all_mixed_content;
blink::mojom::MixedContentContextType mixed_context_type =
request->mixed_content_context_type();
if (!ShouldTreatURLSchemeAsCorsEnabled(request->GetURL())) {
UMA_HISTOGRAM_BOOLEAN("SSL.NonWebbyMixedContentLoaded", true);
return false;
}
if (mixed_content_frame->CancelPrerendering(
PrerenderCancellationReason(PrerenderFinalStatus::kMixedContent))) {
return true;
}
bool allowed = false;
RenderFrameHostDelegate* frame_host_delegate =
node->current_frame_host()->delegate();
switch (mixed_context_type) {
case blink::mojom::MixedContentContextType::kOptionallyBlockable:
allowed = !strict_mode;
if (allowed) {
frame_host_delegate->PassiveInsecureContentFound(request->GetURL());
node->frame_tree().controller().ssl_manager()->DidDisplayMixedContent();
}
break;
case blink::mojom::MixedContentContextType::kBlockable: {
bool should_ask_delegate =
!strict_mode && (!prefs.strictly_block_blockable_mixed_content ||
prefs.allow_running_insecure_content);
allowed =
should_ask_delegate &&
frame_host_delegate->ShouldAllowRunningInsecureContent(
prefs.allow_running_insecure_content,
mixed_content_frame->GetLastCommittedOrigin(), request->GetURL());
if (allowed) {
const GURL& origin_url =
mixed_content_frame->GetLastCommittedOrigin().GetURL();
mixed_content_frame->OnDidRunInsecureContent(origin_url,
request->GetURL());
mixed_content_features_.insert(
blink::mojom::WebFeature::kMixedContentBlockableAllowed);
}
break;
}
case blink::mojom::MixedContentContextType::kShouldBeBlockable:
allowed = !strict_mode;
if (allowed)
node->frame_tree().controller().ssl_manager()->DidDisplayMixedContent();
break;
case blink::mojom::MixedContentContextType::kNotMixedContent:
NOTREACHED();
break;
};
UpdateRendererOnMixedContentFound(request,
mixed_content_frame->GetLastCommittedURL(),
allowed, for_redirect);
MaybeSendBlinkFeatureUsageReport();
return !allowed;
}
RenderFrameHostImpl* MixedContentNavigationThrottle::InWhichFrameIsContentMixed(
FrameTreeNode* node,
const GURL& url) {
if (!node->GetParentOrOuterDocument())
return nullptr;
RenderFrameHostImpl* mixed_content_frame = nullptr;
RenderFrameHostImpl* parent = node->GetParentOrOuterDocument();
RenderFrameHostImpl* root = parent->GetOutermostMainFrame();
if (!IsUrlPotentiallySecure(url)) {
if (DoesOriginSchemeRestrictMixedContent(root->GetLastCommittedOrigin()))
mixed_content_frame = root;
else if (DoesOriginSchemeRestrictMixedContent(
parent->GetLastCommittedOrigin())) {
mixed_content_frame = parent;
}
}
if (mixed_content_frame) {
if (mixed_content_frame->GetLastCommittedOrigin().scheme() !=
url::kHttpsScheme) {
mixed_content_features_.insert(
blink::mojom::WebFeature::
kMixedContentInNonHTTPSFrameThatRestrictsMixedContent);
}
} else if (!network::IsUrlPotentiallyTrustworthy(url) &&
(IsSecureScheme(root->GetLastCommittedOrigin().scheme()) ||
IsSecureScheme(parent->GetLastCommittedOrigin().scheme()))) {
mixed_content_features_.insert(
blink::mojom::WebFeature::
kMixedContentInSecureFrameThatDoesNotRestrictMixedContent);
}
return mixed_content_frame;
}
void MixedContentNavigationThrottle::MaybeSendBlinkFeatureUsageReport() {
if (!mixed_content_features_.empty()) {
NavigationRequest* request = NavigationRequest::From(navigation_handle());
RenderFrameHostImpl* rfh = request->frame_tree_node()->current_frame_host();
rfh->GetAssociatedLocalFrame()->ReportBlinkFeatureUsage(
std::vector<blink::mojom::WebFeature>(mixed_content_features_.begin(),
mixed_content_features_.end()));
mixed_content_features_.clear();
}
}
void MixedContentNavigationThrottle::ReportBasicMixedContentFeatures(
blink::mojom::RequestContextType request_context_type,
blink::mojom::MixedContentContextType mixed_content_context_type) {
mixed_content_features_.insert(
blink::mojom::WebFeature::kMixedContentPresent);
if (mixed_content_context_type ==
blink::mojom::MixedContentContextType::kBlockable) {
mixed_content_features_.insert(
blink::mojom::WebFeature::kMixedContentBlockable);
return;
}
blink::mojom::WebFeature feature;
switch (request_context_type) {
case blink::mojom::RequestContextType::INTERNAL:
feature = blink::mojom::WebFeature::kMixedContentInternal;
break;
case blink::mojom::RequestContextType::PREFETCH:
feature = blink::mojom::WebFeature::kMixedContentPrefetch;
break;
case blink::mojom::RequestContextType::AUDIO:
case blink::mojom::RequestContextType::DOWNLOAD:
case blink::mojom::RequestContextType::FAVICON:
case blink::mojom::RequestContextType::IMAGE:
case blink::mojom::RequestContextType::PLUGIN:
case blink::mojom::RequestContextType::VIDEO:
default:
NOTREACHED() << "RequestContextType has value " << request_context_type
<< " and has MixedContentContextType of "
<< mixed_content_context_type;
return;
}
mixed_content_features_.insert(feature);
}
void MixedContentNavigationThrottle::MaybeHandleCertificateError() {
if (navigation_handle()->IsInOutermostMainFrame()) {
return;
}
if (!navigation_handle()->GetSSLInfo()) {
return;
}
if (!net::IsCertStatusError(navigation_handle()->GetSSLInfo()->cert_status)) {
return;
}
NavigationRequest* request = NavigationRequest::From(navigation_handle());
RenderFrameHostImpl* rfh = request->frame_tree_node()->current_frame_host();
rfh->OnDidRunContentWithCertificateErrors();
}
bool MixedContentNavigationThrottle::IsMixedContentForTesting(
const GURL& origin_url,
const GURL& url) {
const url::Origin origin = url::Origin::Create(origin_url);
return !IsUrlPotentiallySecure(url) &&
DoesOriginSchemeRestrictMixedContent(origin);
}
}