| // Copyright 2014 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 "components/security_interstitials/content/captive_portal_blocking_page.h" |
| |
| #include <utility> |
| |
| #include "base/i18n/rtl.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "components/captive_portal/captive_portal_detector.h" |
| #include "components/captive_portal/captive_portal_metrics.h" |
| #include "components/safe_browsing/common/safe_browsing_prefs.h" |
| #include "components/security_interstitials/content/cert_report_helper.h" |
| #include "components/security_interstitials/content/security_interstitial_controller_client.h" |
| #include "components/security_interstitials/content/ssl_cert_reporter.h" |
| #include "components/security_interstitials/core/controller_client.h" |
| #include "components/security_interstitials/core/metrics_helper.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "components/url_formatter/url_formatter.h" |
| #include "components/wifi/wifi_service.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/ssl_status.h" |
| #include "content/public/browser/web_contents.h" |
| #include "net/base/network_change_notifier.h" |
| #include "net/base/network_interfaces.h" |
| #include "net/ssl/ssl_info.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if defined(OS_ANDROID) |
| #include "net/android/network_library.h" |
| #endif |
| |
| // static |
| const void* const CaptivePortalBlockingPage::kTypeForTesting = |
| &CaptivePortalBlockingPage::kTypeForTesting; |
| |
| CaptivePortalBlockingPage::CaptivePortalBlockingPage( |
| content::WebContents* web_contents, |
| const GURL& request_url, |
| const GURL& login_url, |
| std::unique_ptr<SSLCertReporter> ssl_cert_reporter, |
| const net::SSLInfo& ssl_info, |
| std::unique_ptr< |
| security_interstitials::SecurityInterstitialControllerClient> |
| controller_client, |
| const OpenLoginCallback& open_login_callback) |
| : SSLBlockingPageBase(web_contents, |
| CertificateErrorReport::INTERSTITIAL_CAPTIVE_PORTAL, |
| ssl_info, |
| request_url, |
| std::move(ssl_cert_reporter), |
| false /* overridable */, |
| base::Time::Now(), |
| std::move(controller_client)), |
| open_login_callback_(open_login_callback), |
| login_url_(login_url), |
| ssl_info_(ssl_info) { |
| captive_portal::CaptivePortalMetrics::LogCaptivePortalBlockingPageEvent( |
| captive_portal::CaptivePortalMetrics::SHOW_ALL); |
| } |
| |
| CaptivePortalBlockingPage::~CaptivePortalBlockingPage() = default; |
| |
| const void* CaptivePortalBlockingPage::GetTypeForTesting() { |
| return CaptivePortalBlockingPage::kTypeForTesting; |
| } |
| |
| void CaptivePortalBlockingPage::OverrideWifiInfoForTesting( |
| bool is_wifi_connection, |
| const std::string& wifi_ssid) { |
| is_wifi_info_overridden_for_testing_ = true; |
| is_wifi_connection_for_testing_ = is_wifi_connection; |
| wifi_ssid_for_testing_ = wifi_ssid; |
| } |
| |
| bool CaptivePortalBlockingPage::IsWifiConnection() const { |
| if (is_wifi_info_overridden_for_testing_) |
| return is_wifi_connection_for_testing_; |
| |
| // |net::NetworkChangeNotifier::GetConnectionType| isn't accurate on Linux |
| // and Windows. See https://crbug.com/160537 for details. |
| // TODO(meacer): Add heuristics to get a more accurate connection type on |
| // these platforms. |
| return net::NetworkChangeNotifier::GetConnectionType() == |
| net::NetworkChangeNotifier::CONNECTION_WIFI; |
| } |
| |
| std::string CaptivePortalBlockingPage::GetWiFiSSID() const { |
| if (is_wifi_info_overridden_for_testing_) |
| return wifi_ssid_for_testing_; |
| |
| // On Windows and Mac, |WiFiService| provides an easy to use API to get the |
| // currently associated WiFi access point. |WiFiService| isn't available on |
| // Linux so |net::GetWifiSSID| is used instead. |
| std::string ssid; |
| #if defined(OS_WIN) || defined(OS_MACOSX) |
| std::unique_ptr<wifi::WiFiService> wifi_service(wifi::WiFiService::Create()); |
| wifi_service->Initialize(nullptr); |
| std::string error; |
| wifi_service->GetConnectedNetworkSSID(&ssid, &error); |
| if (!error.empty()) |
| return std::string(); |
| #elif defined(OS_LINUX) |
| ssid = net::GetWifiSSID(); |
| #elif defined(OS_ANDROID) |
| ssid = net::android::GetWifiSSID(); |
| #endif |
| // TODO(meacer): Handle non UTF8 SSIDs. |
| if (!base::IsStringUTF8(ssid)) |
| return std::string(); |
| return ssid; |
| } |
| |
| bool CaptivePortalBlockingPage::ShouldCreateNewNavigation() const { |
| // Captive portal interstitials always create new navigation entries, as |
| // opposed to SafeBrowsing subresource interstitials which just block access |
| // to the current page and don't create a new entry. |
| return true; |
| } |
| |
| void CaptivePortalBlockingPage::PopulateInterstitialStrings( |
| base::DictionaryValue* load_time_data) { |
| load_time_data->SetString("iconClass", "icon-offline"); |
| load_time_data->SetString("type", "CAPTIVE_PORTAL"); |
| load_time_data->SetBoolean("overridable", false); |
| load_time_data->SetBoolean("hide_primary_button", false); |
| |
| // |IsWifiConnection| isn't accurate on some platforms, so always try to get |
| // the Wi-Fi SSID even if |IsWifiConnection| is false. |
| std::string wifi_ssid = GetWiFiSSID(); |
| bool is_wifi = !wifi_ssid.empty() || IsWifiConnection(); |
| |
| load_time_data->SetString( |
| "primaryButtonText", |
| l10n_util::GetStringUTF16(IDS_CAPTIVE_PORTAL_BUTTON_OPEN_LOGIN_PAGE)); |
| |
| base::string16 tab_title = |
| l10n_util::GetStringUTF16(is_wifi ? IDS_CAPTIVE_PORTAL_HEADING_WIFI |
| : IDS_CAPTIVE_PORTAL_HEADING_WIRED); |
| load_time_data->SetString("tabTitle", tab_title); |
| load_time_data->SetString("heading", tab_title); |
| |
| base::string16 paragraph; |
| if (login_url_.is_empty() || |
| login_url_.spec() == captive_portal::CaptivePortalDetector::kDefaultURL) { |
| // Don't show the login url when it's empty or is the portal detection URL. |
| // login_url_ can be empty when: |
| // - The captive portal intercepted requests without HTTP redirects, in |
| // which case the login url would be the same as the captive portal |
| // detection url. |
| // - The captive portal was detected via Captive portal certificate list. |
| // - The captive portal was reported by the OS. |
| if (wifi_ssid.empty()) { |
| paragraph = l10n_util::GetStringUTF16( |
| is_wifi ? IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIFI |
| : IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIRED); |
| } else { |
| paragraph = l10n_util::GetStringFUTF16( |
| IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_NO_LOGIN_URL_WIFI_SSID, |
| net::EscapeForHTML(base::UTF8ToUTF16(wifi_ssid))); |
| } |
| } else { |
| // Portal redirection was done with HTTP redirects, so show the login URL. |
| // If |languages| is empty, punycode in |login_host| will always be decoded. |
| base::string16 login_host = url_formatter::IDNToUnicode(login_url_.host()); |
| if (base::i18n::IsRTL()) |
| base::i18n::WrapStringWithLTRFormatting(&login_host); |
| |
| if (wifi_ssid.empty()) { |
| paragraph = l10n_util::GetStringFUTF16( |
| is_wifi ? IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIFI |
| : IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIRED, |
| login_host); |
| } else { |
| paragraph = l10n_util::GetStringFUTF16( |
| IDS_CAPTIVE_PORTAL_PRIMARY_PARAGRAPH_WIFI_SSID, |
| net::EscapeForHTML(base::UTF8ToUTF16(wifi_ssid)), login_host); |
| } |
| } |
| load_time_data->SetString("primaryParagraph", paragraph); |
| // Explicitly specify other expected fields to empty. |
| load_time_data->SetString("openDetails", ""); |
| load_time_data->SetString("closeDetails", ""); |
| load_time_data->SetString("explanationParagraph", ""); |
| load_time_data->SetString("finalParagraph", ""); |
| load_time_data->SetString("recurrentErrorParagraph", ""); |
| load_time_data->SetBoolean("show_recurrent_error_paragraph", false); |
| |
| if (cert_report_helper()) |
| cert_report_helper()->PopulateExtendedReportingOption(load_time_data); |
| else |
| load_time_data->SetBoolean(security_interstitials::kDisplayCheckBox, false); |
| } |
| |
| void CaptivePortalBlockingPage::CommandReceived(const std::string& command) { |
| if (command == "\"pageLoadComplete\"") { |
| // content::WaitForRenderFrameReady sends this message when the page |
| // load completes. Ignore it. |
| return; |
| } |
| int command_num = 0; |
| bool command_is_num = base::StringToInt(command, &command_num); |
| DCHECK(command_is_num) << command; |
| security_interstitials::SecurityInterstitialCommand cmd = |
| static_cast<security_interstitials::SecurityInterstitialCommand>( |
| command_num); |
| cert_report_helper()->HandleReportingCommands(cmd, |
| controller()->GetPrefService()); |
| switch (cmd) { |
| case security_interstitials::CMD_OPEN_LOGIN: |
| captive_portal::CaptivePortalMetrics::LogCaptivePortalBlockingPageEvent( |
| captive_portal::CaptivePortalMetrics::OPEN_LOGIN_PAGE); |
| open_login_callback_.Run(web_contents()); |
| break; |
| case security_interstitials::CMD_OPEN_REPORTING_PRIVACY: |
| controller()->OpenExtendedReportingPrivacyPolicy(true); |
| break; |
| case security_interstitials::CMD_OPEN_WHITEPAPER: |
| controller()->OpenExtendedReportingWhitepaper(true); |
| break; |
| case security_interstitials::CMD_ERROR: |
| case security_interstitials::CMD_TEXT_FOUND: |
| case security_interstitials::CMD_TEXT_NOT_FOUND: |
| // Commands are for testing. |
| break; |
| default: |
| NOTREACHED() << "Command " << cmd |
| << " isn't handled by the captive portal interstitial."; |
| } |
| } |
| |
| void CaptivePortalBlockingPage::OverrideEntry(content::NavigationEntry* entry) { |
| entry->GetSSL() = content::SSLStatus(ssl_info_); |
| } |