blob: 5c026490a4e93ce568d6803643018a6519802199 [file] [log] [blame]
// 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_);
}