// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/ui/page_info/page_info_site_security_mediator.h"
#import "base/strings/sys_string_conversions.h"
#import "base/strings/utf_string_conversions.h"
#import "components/security_state/core/security_state.h"
#import "components/ssl_errors/error_info.h"
#import "components/strings/grit/components_branded_strings.h"
#import "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/reading_list/model/offline_page_tab_helper.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/ui/page_info/features.h"
#import "ios/chrome/browser/ui/page_info/page_info_constants.h"
#import "ios/chrome/browser/ui/page_info/page_info_site_security_description.h"
#import "ios/chrome/common/ui/colors/semantic_color_names.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/components/webui/web_ui_url_constants.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/security/ssl_status.h"
#import "ios/web/public/web_state.h"
#import "ui/base/l10n/l10n_util.h"
#import "url/gurl.h"
namespace {
// Build the certificate details based on the `SSLStatus` and the `URL`.
NSString* BuildCertificateDetailString(web::SSLStatus& SSLStatus,
const GURL& URL) {
NSMutableString* certificateDetails = [NSMutableString
stringWithString:l10n_util::GetNSString(
IDS_PAGE_INFO_SECURITY_TAB_INSECURE_IDENTITY)];
NSString* bullet = @"\n • ";
std::vector<ssl_errors::ErrorInfo> errors;
ssl_errors::ErrorInfo::GetErrorsForCertStatus(
SSLStatus.certificate, SSLStatus.cert_status, URL, &errors);
for (size_t i = 0; i < errors.size(); ++i) {
[certificateDetails appendString:bullet];
[certificateDetails
appendString:base::SysUTF16ToNSString(errors[i].short_description())];
}
return certificateDetails;
}
// Returns a messages, based on the `messagesComponents`, joined by a spacing.
NSString* BuildMessage(NSArray<NSString*>* messageComponents) {
DCHECK(messageComponents.count > 0);
NSMutableString* message =
[NSMutableString stringWithString:messageComponents[0]];
for (NSUInteger index = 1; index < messageComponents.count; index++) {
NSString* component = messageComponents[index];
if (component.length == 0)
continue;
[message appendString:@"\n\n"];
[message appendString:component];
}
return message;
}
} // namespace
@implementation PageInfoSiteSecurityMediator
+ (PageInfoSiteSecurityDescription*)configurationForWebState:
(web::WebState*)webState {
web::NavigationItem* navItem =
webState->GetNavigationManager()->GetVisibleItem();
const GURL& URL = navItem->GetURL();
web::SSLStatus& status = navItem->GetSSL();
bool offlinePage =
OfflinePageTabHelper::FromWebState(webState)->presenting_offline_page();
PageInfoSiteSecurityDescription* dataHolder =
[[PageInfoSiteSecurityDescription alloc] init];
if (offlinePage) {
dataHolder.siteURL =
l10n_util::GetNSString(IDS_IOS_PAGE_INFO_OFFLINE_PAGE_LABEL);
dataHolder.message = l10n_util::GetNSString(IDS_IOS_PAGE_INFO_OFFLINE_PAGE);
dataHolder.isEmpty = YES;
return dataHolder;
}
if (URL.SchemeIs(kChromeUIScheme)) {
dataHolder.siteURL =
l10n_util::GetNSString(IDS_IOS_PAGE_INFO_CHROME_PAGE_LABEL);
dataHolder.message = l10n_util::GetNSString(IDS_PAGE_INFO_INTERNAL_PAGE);
dataHolder.isEmpty = YES;
return dataHolder;
}
// At this point, this is a web page.
dataHolder.siteURL = base::SysUTF8ToNSString(URL.host());
dataHolder.isEmpty = NO;
dataHolder.status =
l10n_util::GetNSString(IDS_IOS_PAGE_INFO_SECURITY_STATUS_NOT_SECURE);
dataHolder.securityStatus = l10n_util::GetNSString(
IDS_IOS_PAGE_INFO_SECURITY_CONNECTION_STATUS_NOT_SECURE);
dataHolder.secure = NO;
dataHolder.isPageLoading = webState->IsLoading();
// Summary and details.
if (!status.certificate) {
// Not HTTPS. This maps to the WARNING security level. Show the red
// triangle icon in page info based on the same logic used to determine
// the iconography in the omnibox.
dataHolder.iconImage = DefaultSymbolTemplateWithPointSize(
kWarningSymbol, kPageInfoSymbolPointSize);
dataHolder.iconBackgroundColor = [UIColor colorNamed:kRed500Color];
if (IsRevampPageInfoIosEnabled()) {
dataHolder.message =
l10n_util::GetNSString(IDS_PAGE_INFO_NOT_SECURE_DETAILS);
} else {
dataHolder.message =
[NSString stringWithFormat:@"%@ BEGIN_LINK %@ END_LINK",
l10n_util::GetNSString(
IDS_PAGE_INFO_NOT_SECURE_DETAILS),
l10n_util::GetNSString(IDS_LEARN_MORE)];
}
return dataHolder;
}
// It is possible to have `SECURITY_STYLE_AUTHENTICATION_BROKEN` and non-error
// `cert_status` for WKWebView because `security_style` and `cert_status`
// are
// calculated using different API, which may lead to different cert
// verification results.
if (net::IsCertStatusError(status.cert_status) ||
status.security_style == web::SECURITY_STYLE_AUTHENTICATION_BROKEN) {
// HTTPS with major errors
dataHolder.iconImage = DefaultSymbolTemplateWithPointSize(
kWarningSymbol, kPageInfoSymbolPointSize);
dataHolder.iconBackgroundColor = [UIColor colorNamed:kRed500Color];
NSString* certificateDetails = BuildCertificateDetailString(status, URL);
if (IsRevampPageInfoIosEnabled()) {
dataHolder.message = BuildMessage(@[
l10n_util::GetNSString(IDS_PAGE_INFO_NOT_SECURE_DETAILS),
certificateDetails
]);
} else {
dataHolder.message = BuildMessage(@[
[NSString stringWithFormat:@"%@ BEGIN_LINK %@ END_LINK",
l10n_util::GetNSString(
IDS_PAGE_INFO_NOT_SECURE_DETAILS),
l10n_util::GetNSString(IDS_LEARN_MORE)],
certificateDetails
]);
}
return dataHolder;
}
// The remaining states are valid HTTPS, or HTTPS with minor errors.
std::u16string issuerName(
base::UTF8ToUTF16(status.certificate->issuer().GetDisplayName()));
// Have certificateDetails be an empty string to help building the message.
NSString* certificateDetails = @"";
if (!issuerName.empty()) {
// Show the issuer name if it's available.
// TODO(crbug.com/41183995): Implement a certificate viewer instead.
certificateDetails = l10n_util::GetNSStringF(
IDS_IOS_PAGE_INFO_SECURITY_TAB_SECURE_IDENTITY, issuerName);
}
if (status.content_status == web::SSLStatus::DISPLAYED_INSECURE_CONTENT) {
// HTTPS with mixed content. This maps to the WARNING security level in M80,
// so assume the WARNING state when determining whether to swap the icon for
// a red triangle. This will result in an inconsistency between the omnibox
// and page info if the mixed content WARNING feature is disabled.
dataHolder.iconImage = DefaultSymbolTemplateWithPointSize(
kWarningSymbol, kPageInfoSymbolPointSize);
dataHolder.iconBackgroundColor = [UIColor colorNamed:kRed500Color];
if (IsRevampPageInfoIosEnabled()) {
dataHolder.message = BuildMessage(@[
l10n_util::GetNSString(IDS_PAGE_INFO_MIXED_CONTENT_DETAILS),
certificateDetails
]);
} else {
dataHolder.message = BuildMessage(@[
[NSString stringWithFormat:@"%@ BEGIN_LINK %@ END_LINK",
l10n_util::GetNSString(
IDS_PAGE_INFO_MIXED_CONTENT_DETAILS),
l10n_util::GetNSString(IDS_LEARN_MORE)],
certificateDetails
]);
}
return dataHolder;
}
// Valid HTTPS
dataHolder.status =
l10n_util::GetNSString(IDS_IOS_PAGE_INFO_SECURITY_STATUS_SECURE);
dataHolder.securityStatus = l10n_util::GetNSString(
IDS_IOS_PAGE_INFO_SECURITY_CONNECTION_STATUS_SECURE);
dataHolder.secure = YES;
dataHolder.iconImage = IsRevampPageInfoIosEnabled()
? DefaultSymbolTemplateWithPointSize(
kSecureSymbol, kPageInfoSymbolPointSize)
: nil;
dataHolder.iconBackgroundColor = [UIColor colorNamed:kGreen500Color];
if (IsRevampPageInfoIosEnabled()) {
dataHolder.message = BuildMessage(@[
l10n_util::GetNSString(IDS_PAGE_INFO_SECURE_DETAILS), certificateDetails
]);
} else {
dataHolder.message = BuildMessage(@[
[NSString
stringWithFormat:@"%@ BEGIN_LINK %@ END_LINK",
l10n_util::GetNSString(IDS_PAGE_INFO_SECURE_DETAILS),
l10n_util::GetNSString(IDS_LEARN_MORE)],
certificateDetails
]);
}
DCHECK(!(status.cert_status & net::CERT_STATUS_IS_EV))
<< "Extended Validation should be disabled";
return dataHolder;
}
@end