| // Copyright 2021 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/ssl/https_only_mode_navigation_throttle.h" |
| |
| #include "base/feature_list.h" |
| #include "base/task/post_task.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/ssl/https_only_mode_tab_helper.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/security_interstitials/content/security_interstitial_tab_helper.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents.h" |
| |
| namespace { |
| |
| base::TimeDelta g_fallback_delay = base::TimeDelta::FromSeconds(3); |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<HttpsOnlyModeNavigationThrottle> |
| HttpsOnlyModeNavigationThrottle::MaybeCreateThrottleFor( |
| content::NavigationHandle* handle, |
| std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory, |
| PrefService* prefs) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // HTTPS-Only Mode is only relevant for primary main-frame HTTP(S) |
| // navigations. |
| if (!handle->GetURL().SchemeIsHTTPOrHTTPS() || |
| !handle->IsInPrimaryMainFrame() || handle->IsSameDocument()) { |
| return nullptr; |
| } |
| |
| if (!base::FeatureList::IsEnabled(features::kHttpsOnlyMode) || !prefs || |
| !prefs->GetBoolean(prefs::kHttpsOnlyModeEnabled)) { |
| return nullptr; |
| } |
| |
| return std::make_unique<HttpsOnlyModeNavigationThrottle>( |
| handle, std::move(blocking_page_factory)); |
| } |
| |
| HttpsOnlyModeNavigationThrottle::HttpsOnlyModeNavigationThrottle( |
| content::NavigationHandle* handle, |
| std::unique_ptr<SecurityBlockingPageFactory> blocking_page_factory) |
| : content::NavigationThrottle(handle), |
| blocking_page_factory_(std::move(blocking_page_factory)) {} |
| |
| HttpsOnlyModeNavigationThrottle::~HttpsOnlyModeNavigationThrottle() = default; |
| |
| content::NavigationThrottle::ThrottleCheckResult |
| HttpsOnlyModeNavigationThrottle::WillStartRequest() { |
| // If the navigation was upgraded by the Interceptor, start the timeout timer. |
| // Which navigations to upgrade is determined by the Interceptor not the |
| // Throttle. |
| auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents( |
| navigation_handle()->GetWebContents()); |
| if (tab_helper->is_navigation_upgraded()) { |
| timer_.Start(FROM_HERE, g_fallback_delay, this, |
| &HttpsOnlyModeNavigationThrottle::OnHttpsLoadTimeout); |
| } |
| |
| return content::NavigationThrottle::PROCEED; |
| } |
| |
| // Called if there is a non-OK net::Error in the completion status. |
| content::NavigationThrottle::ThrottleCheckResult |
| HttpsOnlyModeNavigationThrottle::WillFailRequest() { |
| // Cancel the request, stop the timer, and trigger the HTTPS-Only Mode |
| // interstitial in case of SSL errors or other net errors. |
| timer_.Stop(); |
| |
| auto* 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) && |
| handle->GetNetErrorCode() == net::OK) { |
| // Don't fallback. |
| return content::NavigationThrottle::PROCEED; |
| } |
| |
| // Only show the interstitial if the Interceptor attempted to upgrade the |
| // navigation. |
| auto* contents = handle->GetWebContents(); |
| auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents(contents); |
| if (tab_helper->is_navigation_upgraded()) { |
| std::unique_ptr<security_interstitials::HttpsOnlyModeBlockingPage> |
| blocking_page = blocking_page_factory_->CreateHttpsOnlyModeBlockingPage( |
| contents, handle->GetURL()); |
| std::string interstitial_html = blocking_page->GetHTMLContents(); |
| security_interstitials::SecurityInterstitialTabHelper:: |
| AssociateBlockingPage(handle->GetWebContents(), |
| handle->GetNavigationId(), |
| std::move(blocking_page)); |
| return content::NavigationThrottle::ThrottleCheckResult( |
| content::NavigationThrottle::CANCEL, net::ERR_BLOCKED_BY_CLIENT, |
| interstitial_html); |
| } |
| |
| return content::NavigationThrottle::PROCEED; |
| } |
| |
| content::NavigationThrottle::ThrottleCheckResult |
| HttpsOnlyModeNavigationThrottle::WillRedirectRequest() { |
| // HTTPS->HTTP downgrades may result in net::ERR_TOO_MANY_REDIRECTS, but these |
| // redirect loops should hit the cache and not cost too much. If they go too |
| // long, the fallback timer will kick in. ERR_TOO_MANY_REDIRECTS should result |
| // in the request failing and triggering fallback. |
| // |
| // Alternately, the Interceptor could log URLs seen and bail if it encounters |
| // a redirect loop, but it is simpler to rely on existing handling unless |
| // the optimization is needed. |
| |
| // If the timer is not yet started and this is now an upgraded navigation, |
| // then start the timer here. This can happen if the initial request is to |
| // HTTPS but then redirects to HTTP. |
| auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents( |
| navigation_handle()->GetWebContents()); |
| if (tab_helper->is_navigation_upgraded() && !timer_.IsRunning()) { |
| timer_.Start(FROM_HERE, g_fallback_delay, this, |
| &HttpsOnlyModeNavigationThrottle::OnHttpsLoadTimeout); |
| } |
| |
| return content::NavigationThrottle::PROCEED; |
| } |
| |
| content::NavigationThrottle::ThrottleCheckResult |
| HttpsOnlyModeNavigationThrottle::WillProcessResponse() { |
| // The HTTPS load succeeded. Stop the timer. |
| timer_.Stop(); |
| |
| // Clear the status for this navigation as it will successfully commit. |
| auto* tab_helper = HttpsOnlyModeTabHelper::FromWebContents( |
| navigation_handle()->GetWebContents()); |
| tab_helper->set_is_navigation_upgraded(false); |
| |
| return content::NavigationThrottle::PROCEED; |
| } |
| |
| const char* HttpsOnlyModeNavigationThrottle::GetNameForLogging() { |
| return "HttpsOnlyModeNavigationThrottle"; |
| } |
| |
| // static |
| void HttpsOnlyModeNavigationThrottle::set_timeout_for_testing( |
| int timeout_in_seconds) { |
| g_fallback_delay = base::TimeDelta::FromSeconds(timeout_in_seconds); |
| } |
| |
| void HttpsOnlyModeNavigationThrottle::OnHttpsLoadTimeout() { |
| // TODO(crbug.com/1226232): Trigger WillFailResponse. |
| } |