1 // Copyright 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/ui/toolbar/toolbar_model_impl.h" 6 7 #include "base/command_line.h" 8 #include "base/metrics/field_trial.h" 9 #include "base/prefs/pref_service.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "base/time/time.h" 12 #include "chrome/browser/autocomplete/autocomplete_classifier.h" 13 #include "chrome/browser/autocomplete/autocomplete_classifier_factory.h" 14 #include "chrome/browser/autocomplete/chrome_autocomplete_scheme_classifier.h" 15 #include "chrome/browser/profiles/profile.h" 16 #include "chrome/browser/search/search.h" 17 #include "chrome/browser/ssl/ssl_error_info.h" 18 #include "chrome/browser/ui/toolbar/toolbar_model_delegate.h" 19 #include "chrome/common/chrome_constants.h" 20 #include "chrome/common/chrome_switches.h" 21 #include "chrome/common/pref_names.h" 22 #include "chrome/common/url_constants.h" 23 #include "chrome/grit/generated_resources.h" 24 #include "components/google/core/browser/google_util.h" 25 #include "components/omnibox/autocomplete_input.h" 26 #include "components/omnibox/autocomplete_match.h" 27 #include "content/public/browser/cert_store.h" 28 #include "content/public/browser/navigation_controller.h" 29 #include "content/public/browser/navigation_entry.h" 30 #include "content/public/browser/web_contents.h" 31 #include "content/public/browser/web_ui.h" 32 #include "content/public/common/content_constants.h" 33 #include "content/public/common/ssl_status.h" 34 #include "grit/components_scaled_resources.h" 35 #include "grit/theme_resources.h" 36 #include "net/base/net_util.h" 37 #include "net/cert/cert_status_flags.h" 38 #include "net/cert/x509_certificate.h" 39 #include "net/ssl/ssl_connection_status_flags.h" 40 #include "ui/base/l10n/l10n_util.h" 41 42 #if defined(OS_CHROMEOS) 43 #include "chrome/browser/chromeos/policy/policy_cert_service.h" 44 #include "chrome/browser/chromeos/policy/policy_cert_service_factory.h" 45 #endif 46 47 using content::NavigationController; 48 using content::NavigationEntry; 49 using content::SSLStatus; 50 using content::WebContents; 51 52 namespace { 53 54 // Converts a SHA-1 field trial group into the appropriate SecurityLevel. 55 bool GetSecurityLevelForFieldTrialGroup(const std::string& group, 56 ToolbarModel::SecurityLevel* level) { 57 if (group == "Error") 58 *level = ToolbarModel::SECURITY_ERROR; 59 else if (group == "Warning") 60 *level = ToolbarModel::SECURITY_WARNING; 61 else if (group == "HTTP") 62 *level = ToolbarModel::NONE; 63 else 64 return false; 65 return true; 66 } 67 68 } // namespace 69 70 ToolbarModelImpl::ToolbarModelImpl(ToolbarModelDelegate* delegate) 71 : delegate_(delegate) { 72 } 73 74 ToolbarModelImpl::~ToolbarModelImpl() { 75 } 76 77 // static 78 ToolbarModel::SecurityLevel ToolbarModelImpl::GetSecurityLevelForWebContents( 79 content::WebContents* web_contents) { 80 if (!web_contents) 81 return NONE; 82 83 NavigationEntry* entry = web_contents->GetController().GetVisibleEntry(); 84 if (!entry) 85 return NONE; 86 87 const SSLStatus& ssl = entry->GetSSL(); 88 switch (ssl.security_style) { 89 case content::SECURITY_STYLE_UNKNOWN: 90 case content::SECURITY_STYLE_UNAUTHENTICATED: 91 return NONE; 92 93 case content::SECURITY_STYLE_AUTHENTICATION_BROKEN: 94 return SECURITY_ERROR; 95 96 case content::SECURITY_STYLE_AUTHENTICATED: { 97 #if defined(OS_CHROMEOS) 98 policy::PolicyCertService* service = 99 policy::PolicyCertServiceFactory::GetForProfile( 100 Profile::FromBrowserContext(web_contents->GetBrowserContext())); 101 if (service && service->UsedPolicyCertificates()) 102 return SECURITY_POLICY_WARNING; 103 #endif 104 if (!!(ssl.content_status & SSLStatus::DISPLAYED_INSECURE_CONTENT)) 105 return SECURITY_WARNING; 106 scoped_refptr<net::X509Certificate> cert; 107 if (content::CertStore::GetInstance()->RetrieveCert(ssl.cert_id, &cert) && 108 (ssl.cert_status & net::CERT_STATUS_SHA1_SIGNATURE_PRESENT)) { 109 // The internal representation of the dates for UI treatment of SHA-1. 110 // See http://crbug.com/401365 for details 111 static const int64_t kJanuary2017 = INT64_C(13127702400000000); 112 static const int64_t kJune2016 = INT64_C(13109213000000000); 113 static const int64_t kJanuary2016 = INT64_C(13096080000000000); 114 115 ToolbarModel::SecurityLevel security_level = NONE; 116 // Gated behind a field trial, so that it is possible to adjust the 117 // UI treatment (to be more or less severe, as necessary) over the 118 // course of multiple releases. 119 // See http://crbug.com/401365 for the timeline, with the end state 120 // being that > kJanuary2017 = Error, and > kJanuary2016 = 121 // Warning, and kJune2016 disappearing entirely. 122 if (cert->valid_expiry() >= 123 base::Time::FromInternalValue(kJanuary2017) && 124 GetSecurityLevelForFieldTrialGroup( 125 base::FieldTrialList::FindFullName("SHA1ToolbarUIJanuary2017"), 126 &security_level)) { 127 return security_level; 128 } 129 if (cert->valid_expiry() >= base::Time::FromInternalValue(kJune2016) && 130 GetSecurityLevelForFieldTrialGroup( 131 base::FieldTrialList::FindFullName("SHA1ToolbarUIJune2016"), 132 &security_level)) { 133 return security_level; 134 } 135 if (cert->valid_expiry() >= 136 base::Time::FromInternalValue(kJanuary2016) && 137 GetSecurityLevelForFieldTrialGroup( 138 base::FieldTrialList::FindFullName("SHA1ToolbarUIJanuary2016"), 139 &security_level)) { 140 return security_level; 141 } 142 } 143 if (net::IsCertStatusError(ssl.cert_status)) { 144 DCHECK(net::IsCertStatusMinorError(ssl.cert_status)); 145 return SECURITY_WARNING; 146 } 147 if (net::SSLConnectionStatusToVersion(ssl.connection_status) == 148 net::SSL_CONNECTION_VERSION_SSL3) { 149 // SSLv3 will be removed in the future. 150 return SECURITY_WARNING; 151 } 152 if ((ssl.cert_status & net::CERT_STATUS_IS_EV) && cert.get()) 153 return EV_SECURE; 154 return SECURE; 155 } 156 default: 157 NOTREACHED(); 158 return NONE; 159 } 160 } 161 162 // ToolbarModelImpl Implementation. 163 base::string16 ToolbarModelImpl::GetText() const { 164 base::string16 search_terms(GetSearchTerms(false)); 165 if (!search_terms.empty()) 166 return search_terms; 167 168 if (WouldOmitURLDueToOriginChip()) 169 return base::string16(); 170 171 return GetFormattedURL(NULL); 172 } 173 174 base::string16 ToolbarModelImpl::GetFormattedURL(size_t* prefix_end) const { 175 std::string languages; // Empty if we don't have a |navigation_controller|. 176 Profile* profile = GetProfile(); 177 if (profile) 178 languages = profile->GetPrefs()->GetString(prefs::kAcceptLanguages); 179 180 GURL url(GetURL()); 181 if (url.spec().length() > content::kMaxURLDisplayChars) 182 url = url.IsStandard() ? url.GetOrigin() : GURL(url.scheme() + ":"); 183 // Note that we can't unescape spaces here, because if the user copies this 184 // and pastes it into another program, that program may think the URL ends at 185 // the space. 186 return AutocompleteInput::FormattedStringWithEquivalentMeaning( 187 url, net::FormatUrl(url, languages, net::kFormatUrlOmitAll, 188 net::UnescapeRule::NORMAL, NULL, prefix_end, NULL), 189 ChromeAutocompleteSchemeClassifier(profile)); 190 } 191 192 base::string16 ToolbarModelImpl::GetCorpusNameForMobile() const { 193 if (!WouldPerformSearchTermReplacement(false)) 194 return base::string16(); 195 GURL url(GetURL()); 196 // If there is a query in the url fragment look for the corpus name there, 197 // otherwise look for the corpus name in the query parameters. 198 const std::string& query_str(google_util::HasGoogleSearchQueryParam( 199 url.ref()) ? url.ref() : url.query()); 200 url::Component query(0, query_str.length()), key, value; 201 const char kChipKey[] = "sboxchip"; 202 while (url::ExtractQueryKeyValue(query_str.c_str(), &query, &key, &value)) { 203 if (key.is_nonempty() && query_str.substr(key.begin, key.len) == kChipKey) { 204 return net::UnescapeAndDecodeUTF8URLComponent( 205 query_str.substr(value.begin, value.len), 206 net::UnescapeRule::NORMAL); 207 } 208 } 209 return base::string16(); 210 } 211 212 GURL ToolbarModelImpl::GetURL() const { 213 const NavigationController* navigation_controller = GetNavigationController(); 214 if (navigation_controller) { 215 const NavigationEntry* entry = navigation_controller->GetVisibleEntry(); 216 if (entry) 217 return ShouldDisplayURL() ? entry->GetVirtualURL() : GURL(); 218 } 219 220 return GURL(url::kAboutBlankURL); 221 } 222 223 bool ToolbarModelImpl::WouldPerformSearchTermReplacement( 224 bool ignore_editing) const { 225 return !GetSearchTerms(ignore_editing).empty(); 226 } 227 228 ToolbarModel::SecurityLevel ToolbarModelImpl::GetSecurityLevel( 229 bool ignore_editing) const { 230 // When editing, assume no security style. 231 return (input_in_progress() && !ignore_editing) ? 232 NONE : GetSecurityLevelForWebContents(delegate_->GetActiveWebContents()); 233 } 234 235 int ToolbarModelImpl::GetIcon() const { 236 if (WouldPerformSearchTermReplacement(false)) { 237 // The secured version of the search icon is necessary if neither the search 238 // button nor origin chip are present to indicate the security state. 239 return (chrome::GetDisplaySearchButtonConditions() == 240 chrome::DISPLAY_SEARCH_BUTTON_NEVER) && 241 !chrome::ShouldDisplayOriginChip() ? 242 IDR_OMNIBOX_SEARCH_SECURED : IDR_OMNIBOX_SEARCH; 243 } 244 245 return GetIconForSecurityLevel(GetSecurityLevel(false)); 246 } 247 248 int ToolbarModelImpl::GetIconForSecurityLevel(SecurityLevel level) const { 249 static int icon_ids[NUM_SECURITY_LEVELS] = { 250 IDR_LOCATION_BAR_HTTP, 251 IDR_OMNIBOX_HTTPS_VALID, 252 IDR_OMNIBOX_HTTPS_VALID, 253 IDR_OMNIBOX_HTTPS_WARNING, 254 IDR_OMNIBOX_HTTPS_POLICY_WARNING, 255 IDR_OMNIBOX_HTTPS_INVALID, 256 }; 257 DCHECK(arraysize(icon_ids) == NUM_SECURITY_LEVELS); 258 return icon_ids[level]; 259 } 260 261 base::string16 ToolbarModelImpl::GetEVCertName() const { 262 if (GetSecurityLevel(false) != EV_SECURE) 263 return base::string16(); 264 265 // Note: Navigation controller and active entry are guaranteed non-NULL or 266 // the security level would be NONE. 267 scoped_refptr<net::X509Certificate> cert; 268 content::CertStore::GetInstance()->RetrieveCert( 269 GetNavigationController()->GetVisibleEntry()->GetSSL().cert_id, &cert); 270 271 // EV are required to have an organization name and country. 272 DCHECK(!cert->subject().organization_names.empty()); 273 DCHECK(!cert->subject().country_name.empty()); 274 return l10n_util::GetStringFUTF16( 275 IDS_SECURE_CONNECTION_EV, 276 base::UTF8ToUTF16(cert->subject().organization_names[0]), 277 base::UTF8ToUTF16(cert->subject().country_name)); 278 } 279 280 bool ToolbarModelImpl::ShouldDisplayURL() const { 281 // Note: The order here is important. 282 // - The WebUI test must come before the extension scheme test because there 283 // can be WebUIs that have extension schemes (e.g. the bookmark manager). In 284 // that case, we should prefer what the WebUI instance says. 285 // - The view-source test must come before the NTP test because of the case 286 // of view-source:chrome://newtab, which should display its URL despite what 287 // chrome://newtab says. 288 NavigationController* controller = GetNavigationController(); 289 NavigationEntry* entry = controller ? controller->GetVisibleEntry() : NULL; 290 if (entry) { 291 if (entry->IsViewSourceMode() || 292 entry->GetPageType() == content::PAGE_TYPE_INTERSTITIAL) { 293 return true; 294 } 295 296 GURL url = entry->GetURL(); 297 GURL virtual_url = entry->GetVirtualURL(); 298 if (url.SchemeIs(content::kChromeUIScheme) || 299 virtual_url.SchemeIs(content::kChromeUIScheme)) { 300 if (!url.SchemeIs(content::kChromeUIScheme)) 301 url = virtual_url; 302 return url.host() != chrome::kChromeUINewTabHost; 303 } 304 } 305 306 return !chrome::IsInstantNTP(delegate_->GetActiveWebContents()); 307 } 308 309 bool ToolbarModelImpl::WouldOmitURLDueToOriginChip() const { 310 const char kInterstitialShownKey[] = "interstitial_shown"; 311 312 // When users type URLs and hit enter, continue to show those URLs until 313 // the navigation commits or an interstitial is shown, because having the 314 // omnibox clear immediately feels like the input was ignored. 315 NavigationController* navigation_controller = GetNavigationController(); 316 if (navigation_controller) { 317 NavigationEntry* pending_entry = navigation_controller->GetPendingEntry(); 318 if (pending_entry) { 319 const NavigationEntry* visible_entry = 320 navigation_controller->GetVisibleEntry(); 321 base::string16 unused; 322 // Keep track that we've shown the origin chip on an interstitial so it 323 // can be shown even after the interstitial was dismissed, to avoid 324 // showing the chip, removing it and then showing it again. 325 if (visible_entry && 326 visible_entry->GetPageType() == content::PAGE_TYPE_INTERSTITIAL && 327 !pending_entry->GetExtraData(kInterstitialShownKey, &unused)) 328 pending_entry->SetExtraData(kInterstitialShownKey, base::string16()); 329 const ui::PageTransition transition_type = 330 pending_entry->GetTransitionType(); 331 if ((transition_type & ui::PAGE_TRANSITION_TYPED) != 0 && 332 !pending_entry->GetExtraData(kInterstitialShownKey, &unused)) 333 return false; 334 } 335 } 336 337 if (!delegate_->InTabbedBrowser() || !ShouldDisplayURL() || 338 !url_replacement_enabled()) 339 return false; 340 341 if (chrome::ShouldDisplayOriginChip()) 342 return true; 343 344 const chrome::OriginChipCondition chip_condition = 345 chrome::GetOriginChipCondition(); 346 return (chip_condition == chrome::ORIGIN_CHIP_ALWAYS) || 347 ((chip_condition == chrome::ORIGIN_CHIP_ON_SRP) && 348 WouldPerformSearchTermReplacement(false)); 349 } 350 351 NavigationController* ToolbarModelImpl::GetNavigationController() const { 352 // This |current_tab| can be NULL during the initialization of the 353 // toolbar during window creation (i.e. before any tabs have been added 354 // to the window). 355 WebContents* current_tab = delegate_->GetActiveWebContents(); 356 return current_tab ? ¤t_tab->GetController() : NULL; 357 } 358 359 Profile* ToolbarModelImpl::GetProfile() const { 360 NavigationController* navigation_controller = GetNavigationController(); 361 return navigation_controller ? 362 Profile::FromBrowserContext(navigation_controller->GetBrowserContext()) : 363 NULL; 364 } 365 366 base::string16 ToolbarModelImpl::GetSearchTerms(bool ignore_editing) const { 367 if (!url_replacement_enabled() || (input_in_progress() && !ignore_editing)) 368 return base::string16(); 369 370 const WebContents* web_contents = delegate_->GetActiveWebContents(); 371 base::string16 search_terms(chrome::GetSearchTerms(web_contents)); 372 if (search_terms.empty()) { 373 // We mainly do this to enforce the subsequent DCHECK. 374 return base::string16(); 375 } 376 377 // If the page is still loading and the security style is unknown, consider 378 // the page secure. Without this, after the user hit enter on some search 379 // terms, the omnibox would change to displaying the loading URL before 380 // changing back to the search terms once they could be extracted, thus 381 // causing annoying flicker. 382 DCHECK(web_contents); 383 const NavigationController& nav_controller = web_contents->GetController(); 384 const NavigationEntry* entry = nav_controller.GetVisibleEntry(); 385 if ((entry != nav_controller.GetLastCommittedEntry()) && 386 (entry->GetSSL().security_style == content::SECURITY_STYLE_UNKNOWN)) 387 return search_terms; 388 389 // If the URL is using a Google base URL specified via the command line, we 390 // bypass the security check below. 391 if (entry && 392 google_util::StartsWithCommandLineGoogleBaseURL(entry->GetVirtualURL())) 393 return search_terms; 394 395 // Otherwise, extract search terms for HTTPS pages that do not have a security 396 // error. 397 ToolbarModel::SecurityLevel security_level = GetSecurityLevel(ignore_editing); 398 return ((security_level == NONE) || (security_level == SECURITY_ERROR)) ? 399 base::string16() : search_terms; 400 } 401