| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import <string> |
| |
| #import "base/functional/bind.h" |
| #import "base/strings/escape.h" |
| #import "base/strings/string_util.h" |
| #import "base/strings/stringprintf.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/test/ios/wait_util.h" |
| #import "base/test/metrics/histogram_tester.h" |
| #import "components/omnibox/common/omnibox_features.h" |
| #import "components/security_interstitials/core/https_only_mode_metrics.h" |
| #import "components/security_interstitials/core/omnibox_https_upgrade_metrics.h" |
| #import "ios/chrome/browser/https_upgrades/https_upgrade_app_interface.h" |
| #import "ios/chrome/browser/https_upgrades/https_upgrade_test_helper.h" |
| #import "ios/chrome/browser/metrics/metrics_app_interface.h" |
| #import "ios/chrome/browser/shared/model/prefs/pref_names.h" |
| #import "ios/chrome/test/earl_grey/chrome_earl_grey.h" |
| #import "ios/chrome/test/earl_grey/chrome_earl_grey_app_interface.h" |
| #import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h" |
| #import "ios/chrome/test/earl_grey/chrome_matchers.h" |
| #import "ios/chrome/test/earl_grey/chrome_test_case.h" |
| #import "ios/chrome/test/earl_grey/web_http_server_chrome_test_case.h" |
| #import "ios/components/security_interstitials/https_only_mode/feature.h" |
| #import "ios/testing/earl_grey/earl_grey_test.h" |
| #import "ios/testing/embedded_test_server_handlers.h" |
| #import "ios/web/common/features.h" |
| #import "ios/web/public/test/element_selector.h" |
| #import "net/test/embedded_test_server/default_handlers.h" |
| #import "net/test/embedded_test_server/http_request.h" |
| #import "net/test/embedded_test_server/http_response.h" |
| #import "net/test/embedded_test_server/request_handler_util.h" |
| #import "ui/base/l10n/l10n_util.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| using base::test::ios::kWaitForPageLoadTimeout; |
| using base::test::ios::WaitUntilConditionOrTimeout; |
| |
| namespace { |
| |
| const char kInterstitialText[] = |
| "You are seeing this warning because this site does not support HTTPS"; |
| |
| enum class TestType { |
| kHttpsOnlyMode, |
| kHttpsUpgrades, |
| }; |
| |
| } // namespace |
| |
| // Tests for HTTPS-Only Mode. |
| // TODO(crbug.com/1338585): Remove the "ZZZ" when the bug is fixed. |
| @interface ZZZ_HttpsOnlyModeTestCase : HttpsUpgradeTestCaseBase { |
| } |
| @end |
| |
| @implementation ZZZ_HttpsOnlyModeTestCase |
| |
| - (AppLaunchConfiguration)appConfigurationForTestCase { |
| AppLaunchConfiguration config; |
| config.relaunch_policy = NoForceRelaunchAndResetState; |
| config.features_enabled.push_back( |
| security_interstitials::features::kHttpsOnlyMode); |
| |
| config.features_disabled.push_back( |
| security_interstitials::features::kHttpsUpgrades); |
| // Disable omnibox navigation upgrades. |
| // typed_navigation_upgrade_tab_helper_egtest.mm already has a |
| // test case with both features enabled. |
| // (test_TypeHTTPWithGoodHTTPS_HTTPSOnlyModeEnabled_ShouldUpgrade) |
| config.features_disabled.push_back(omnibox::kDefaultTypedNavigationsToHttps); |
| return config; |
| } |
| |
| - (void)setUp { |
| [super setUp]; |
| [ChromeEarlGrey clearBrowsingHistory]; |
| [HttpsUpgradeAppInterface clearAllowlist]; |
| |
| if ([self testType] == TestType::kHttpsOnlyMode) { |
| [ChromeEarlGrey setBoolValue:YES forUserPref:prefs::kHttpsOnlyModeEnabled]; |
| } |
| } |
| |
| - (void)tearDown { |
| [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kHttpsOnlyModeEnabled]; |
| [HttpsUpgradeAppInterface clearAllowlist]; |
| |
| [super tearDown]; |
| } |
| |
| // Returns true if the HTTPS-Only Mode interstitial is enabled. |
| - (bool)isInterstitialEnabled { |
| return [self testType] == TestType::kHttpsOnlyMode && |
| [ChromeEarlGrey userBooleanPref:prefs::kHttpsOnlyModeEnabled]; |
| } |
| |
| - (TestType)testType { |
| return TestType::kHttpsOnlyMode; |
| } |
| |
| // Asserts that the navigation wasn't upgraded. |
| - (void)assertNoUpgrade { |
| GREYAssertNil([MetricsAppInterface |
| expectTotalCount:0 |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Shouldn't record event histogram"); |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| GREYAssert(![HttpsUpgradeAppInterface isOmniboxUpgradeTimerRunning], |
| @"Omnibox upgrade timer is unexpectedly running"); |
| |
| // Omnibox HTTPS Upgrades shouldn't handle this navigation. |
| GREYAssertNil( |
| [MetricsAppInterface |
| expectTotalCount:0 |
| forHistogram:@(security_interstitials::omnibox_https_upgrades:: |
| kEventHistogram)], |
| @"Omnibox HTTPS Upgrades unexpectedly recorded a histogram event"); |
| } |
| |
| // Asserts that the metrics are properly recorded for a successful upgrade. |
| - (void)assertSuccessfulUpgrade { |
| GREYAssertNil([MetricsAppInterface |
| expectTotalCount:2 |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record event histogram"); |
| |
| GREYAssertNil([MetricsAppInterface |
| expectCount:1 |
| forBucket:static_cast<int>( |
| security_interstitials::https_only_mode:: |
| Event::kUpgradeAttempted) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record upgrade attempt"); |
| GREYAssertNil([MetricsAppInterface |
| expectCount:1 |
| forBucket:static_cast<int>( |
| security_interstitials::https_only_mode:: |
| Event::kUpgradeSucceeded) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record upgrade attempt"); |
| |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| GREYAssert(![HttpsUpgradeAppInterface isOmniboxUpgradeTimerRunning], |
| @"Omnibox upgrade timer is unexpectedly running"); |
| |
| // Omnibox HTTPS Upgrades shouldn't handle this navigation. |
| GREYAssertNil( |
| [MetricsAppInterface |
| expectTotalCount:0 |
| forHistogram:@(security_interstitials::omnibox_https_upgrades:: |
| kEventHistogram)], |
| @"Omnibox HTTPS Upgrades unexpectedly recorded a histogram event"); |
| } |
| |
| // Asserts that the metrics are properly recorded for a failed upgrade. |
| // repeatCount is the expected number of times the upgrade failed. |
| - (void)assertFailedUpgrade:(int)repeatCount { |
| GREYAssertNil([MetricsAppInterface |
| expectTotalCount:(repeatCount * 2) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record event histogram"); |
| |
| GREYAssertNil([MetricsAppInterface |
| expectCount:repeatCount |
| forBucket:static_cast<int>( |
| security_interstitials::https_only_mode:: |
| Event::kUpgradeAttempted) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record upgrade attempt"); |
| GREYAssertNil([MetricsAppInterface |
| expectCount:repeatCount |
| forBucket:static_cast<int>( |
| security_interstitials::https_only_mode:: |
| Event::kUpgradeFailed) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record fail event"); |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| GREYAssert(![HttpsUpgradeAppInterface isOmniboxUpgradeTimerRunning], |
| @"Omnibox upgrade timer is unexpectedly running"); |
| |
| // Omnibox HTTPS Upgrades shouldn't handle this navigation. |
| GREYAssertNil( |
| [MetricsAppInterface |
| expectTotalCount:0 |
| forHistogram:@(security_interstitials::omnibox_https_upgrades:: |
| kEventHistogram)], |
| @"Omnibox HTTPS Upgrades unexpectedly recorded a histogram event"); |
| } |
| |
| // Asserts that the metrics are properly recorded for a timed-out upgrade. |
| // repeatCount is the expected number of times the upgrade failed. |
| - (void)assertTimedOutUpgrade:(int)repeatCount { |
| GREYAssertNil([MetricsAppInterface |
| expectTotalCount:(repeatCount * 2) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Incorrect number of records in event histogram"); |
| |
| GREYAssertNil([MetricsAppInterface |
| expectCount:repeatCount |
| forBucket:static_cast<int>( |
| security_interstitials::https_only_mode:: |
| Event::kUpgradeAttempted) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record upgrade attempt"); |
| GREYAssertNil([MetricsAppInterface |
| expectCount:repeatCount |
| forBucket:static_cast<int>( |
| security_interstitials::https_only_mode:: |
| Event::kUpgradeTimedOut) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record fail event"); |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| GREYAssert(![HttpsUpgradeAppInterface isOmniboxUpgradeTimerRunning], |
| @"Omnibox upgrade timer is unexpectedly running"); |
| |
| // Omnibox HTTPS Upgrades shouldn't handle this navigation. |
| GREYAssertNil( |
| [MetricsAppInterface |
| expectTotalCount:0 |
| forHistogram:@(security_interstitials::omnibox_https_upgrades:: |
| kEventHistogram)], |
| @"Omnibox HTTPS Upgrades unexpectedly recorded a histogram event"); |
| } |
| |
| #pragma mark - Tests |
| |
| // Disable HTTPS-Only Mode and navigate to an HTTP URL directly. |
| // - If HTTPS Upgrades is disabled, this should load the HTTP URL even though |
| // the upgraded HTTPS version serves good SSL. |
| // - Otherwise, it should load the HTTPS URL. |
| - (void)test_HttpsOnlyModeDisabled { |
| [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kHttpsOnlyModeEnabled]; |
| |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.goodHTTPSServer->port() |
| useFakeHTTPS:true]; |
| |
| [ChromeEarlGrey loadURL:GURL("chrome://version")]; |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| |
| if ([self testType] == TestType::kHttpsUpgrades) { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTPS_RESPONSE"]; |
| [self assertSuccessfulUpgrade]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| [self assertNoUpgrade]; |
| } |
| } |
| |
| // Tests that navigations to localhost URLs aren't upgraded. |
| - (void)test_Localhost_ShouldNotUpgrade { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.goodHTTPSServer->port() |
| useFakeHTTPS:true]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| GURL::Replacements replacements; |
| replacements.SetHostStr("localhost"); |
| GURL localhostURL = testURL.ReplaceComponents(replacements); |
| |
| [ChromeEarlGrey loadURL:localhostURL]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| [self assertNoUpgrade]; |
| } |
| |
| // Navigate to an HTTPS URL directly. The navigation shouldn't be upgraded. |
| - (void)test_GoodHTTPS_ShouldNotUpgrade { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.goodHTTPSServer->port() |
| useFakeHTTPS:true]; |
| |
| GURL testURL = self.goodHTTPSServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTPS_RESPONSE"]; |
| [self assertNoUpgrade]; |
| } |
| |
| // Navigate to an HTTP URL directly. The upgraded HTTPS version serves good SSL. |
| // This should end up loading the HTTPS version of the URL. |
| - (void)test_HTTPWithGoodHTTPS_ShouldUpgrade { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.goodHTTPSServer->port() |
| useFakeHTTPS:true]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTPS_RESPONSE"]; |
| [self assertSuccessfulUpgrade]; |
| } |
| |
| // Navigate to an HTTP URL by clicking a link. This should end up loading the |
| // HTTPS version of the URL. |
| - (void)test_HTTPWithGoodHTTPS_LinkClick_ShouldUpgrade { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.goodHTTPSServer->port() |
| useFakeHTTPS:true]; |
| int HTTPPort = self.testServer->port(); |
| |
| GURL testURL(base::StringPrintf( |
| "data:text/html," |
| "<a href='http://127.0.0.1:%d/good-https' id='link'>Link</a><br>READY", |
| HTTPPort)); |
| [ChromeEarlGrey loadURL:testURL]; |
| [ChromeEarlGrey waitForWebStateContainingText:"READY"]; |
| |
| // Click on the http link. Should load the https URL. |
| [ChromeEarlGrey tapWebStateElementWithID:@"link"]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTPS_RESPONSE"]; |
| [self assertSuccessfulUpgrade]; |
| } |
| |
| // Navigate to an HTTP URL directly. The upgraded HTTPS version serves good SSL |
| // which redirects to the original HTTP URL. This should show the interstitial. |
| - (void)test_HTTPSRedirectsToHTTP_ShouldFallback { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.goodHTTPSServer->port() |
| useFakeHTTPS:true]; |
| |
| [ChromeEarlGrey loadURL:GURL("chrome://version")]; |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| |
| GURL targetURL = self.testServer->GetURL("/"); |
| GURL upgradedURL = |
| self.goodHTTPSServer->GetURL("/?redirect=" + targetURL.spec()); |
| const std::string port_str = base::NumberToString(self.testServer->port()); |
| GURL::Replacements replacements; |
| replacements.SetPortStr(port_str); |
| GURL testURL = upgradedURL.ReplaceComponents(replacements); |
| |
| [ChromeEarlGrey loadURL:testURL]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertFailedUpgrade:1]; |
| |
| // Click through the interstitial. This should load the HTTP page. |
| [ChromeEarlGrey tapWebStateElementWithID:@"proceed-button"]; |
| } |
| |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| |
| // Going back should go to chrome://version. |
| [ChromeEarlGrey goBack]; |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| [self assertFailedUpgrade:1]; |
| } |
| |
| // Tests that prerendered navigations that should be upgraded are cancelled. |
| // This test is adapted from testTapPrerenderSuggestions() in |
| // prerender_egtest.mm. |
| // TODO(crbug.com/1315304): Reenable. |
| - (void)DISABLED_test_BadHTTPS_ShouldCancelPrerender { |
| // TODO(crbug.com/793306): Re-enable the test on iPad once the alternate |
| // letters problem is fixed. |
| if ([ChromeEarlGrey isIPadIdiom]) { |
| EARL_GREY_TEST_DISABLED( |
| @"Disabled for iPad due to alternate letters educational screen."); |
| } |
| |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| |
| [ChromeEarlGrey clearBrowsingHistory]; |
| |
| // Type the full URL. This will show an interstitial. This adds the URL to |
| // history. |
| GURL testURL = self.testServer->GetURL("/"); |
| NSString* pageString = base::SysUTF8ToNSString(testURL.GetContent()); |
| [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()] |
| performAction:grey_tap()]; |
| [ChromeEarlGrey |
| waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()]; |
| [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()] |
| performAction:grey_typeText([pageString stringByAppendingString:@"\n"])]; |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertFailedUpgrade:1]; |
| GREYAssertEqual(2, _HTTPResponseCounter, |
| @"The server should have responded twice"); |
| |
| // Click through the interstitial. |
| [ChromeEarlGrey tapWebStateElementWithID:@"proceed-button"]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| GREYAssertEqual(3, _HTTPResponseCounter, |
| @"The server should have responded three times"); |
| |
| // Close all tabs and reopen. This clears the allowlist because it's currently |
| // per-tab. |
| [[self class] closeAllTabs]; |
| [ChromeEarlGrey openNewTab]; |
| |
| // Type the begining of the address to have the autocomplete suggestion. |
| [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()] |
| performAction:grey_tap()]; |
| [ChromeEarlGrey |
| waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()]; |
| // Type a single character. This causes two prerender attempts. |
| [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()] |
| performAction:grey_typeText([pageString substringToIndex:1])]; |
| |
| // Wait until prerender request reaches the server. |
| bool prerendered = WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| return self->_HTTPResponseCounter > 3; |
| }); |
| GREYAssertTrue(prerendered, @"Prerender did not happen"); |
| |
| // Check the histograms. All prerender attempts must be cancelled. Relying on |
| // the histogram here isn't great, but there doesn't seem to be a good |
| // way of testing that prerenders have been cancelled. |
| GREYAssertNil( |
| [MetricsAppInterface expectCount:0 |
| forBucket:/*PRERENDER_FINAL_STATUS_USED=*/0 |
| forHistogram:@"Prerender.FinalStatus"], |
| @"Prerender was used"); |
| // TODO(crbug.com/1302509): Check that the CANCEL bucket has non-zero |
| // elements. Not currently supported by MetricsAppInterface. |
| } |
| |
| // Regression test for crbug.com/1379261. Checks that cancelling a prerendered |
| // navigation doesn't cause a crash. Steps are: |
| // 1. Disable HTTPS-Only Mode and visit an http:// URL. This puts the |
| // URL in browser history. |
| // 2. Close tabs and reopen. Enable HTTPS-Only Mode. |
| // 3. Type the first letter of the http:// URL in step 1. This will prerender |
| // the http URL. |
| // 4. Check that the prerender was cancelled properly. |
| // TODO(crbug.com/1315304): Reenable. |
| - (void)DISABLED_test_Prerender_CancelShouldNotCrash { |
| // TODO(crbug.com/793306): Re-enable the test on iPad once the alternate |
| // letters problem is fixed. |
| if ([ChromeEarlGrey isIPadIdiom]) { |
| EARL_GREY_TEST_DISABLED( |
| @"Disabled for iPad due to alternate letters educational screen."); |
| } |
| |
| // Step 1: Disable HTTPS-Only Mode and visit an http:// URL. This puts the |
| // URL in browser history. |
| [ChromeEarlGrey setBoolValue:NO forUserPref:prefs::kHttpsOnlyModeEnabled]; |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| [ChromeEarlGrey clearBrowsingHistory]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| NSString* testURLString = base::SysUTF8ToNSString(testURL.GetContent()); |
| [ChromeEarlGrey loadURL:testURL]; |
| GREYAssertEqual(1, _HTTPResponseCounter, |
| @"The server should have responded once"); |
| [ChromeEarlGrey goBack]; |
| |
| // Step 2: Close tabs and reopen. Enable HTTPS-Only Mode. |
| [[self class] closeAllTabs]; |
| [ChromeEarlGrey openNewTab]; |
| [ChromeEarlGrey setBoolValue:YES forUserPref:prefs::kHttpsOnlyModeEnabled]; |
| |
| // Step 3: Type the first letter of the http:// URL in step 1. This will |
| // prerender the http URL. |
| [[EarlGrey selectElementWithMatcher:chrome_test_util::FakeOmnibox()] |
| performAction:grey_tap()]; |
| [ChromeEarlGrey |
| waitForSufficientlyVisibleElementWithMatcher:chrome_test_util::Omnibox()]; |
| [[EarlGrey selectElementWithMatcher:chrome_test_util::Omnibox()] |
| performAction:grey_typeText([testURLString substringToIndex:1])]; |
| |
| bool prerendered = WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| // The first response was for the http:// URL. The remaining responses are |
| // prerendered. When the whole test suite runs, we may get more than one |
| // prerendered navigation here. |
| return self->_HTTPResponseCounter > 1; |
| }); |
| GREYAssertTrue(prerendered, @"Prerender did not happen"); |
| |
| // Step 4: Check that the prerender was cancelled properly. |
| // Check the histograms. All prerender attempts must be cancelled. Relying on |
| // the histogram here isn't great, but there doesn't seem to be a good |
| // way of testing that prerenders have been cancelled. |
| GREYAssertNil( |
| [MetricsAppInterface expectCount:0 |
| forBucket:/*PRERENDER_FINAL_STATUS_USED=*/0 |
| forHistogram:@"Prerender.FinalStatus"], |
| @"Prerender was used"); |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| GREYAssert(![HttpsUpgradeAppInterface isOmniboxUpgradeTimerRunning], |
| @"Omnibox upgrade timer is unexpectedly running"); |
| |
| // Check that the HTTPS-Only Mode tab helper recorded the prerender |
| // cancellation. |
| // First server response loaded normally, the rest should be prerenders. |
| int prerenderCount = self->_HTTPResponseCounter - 1; |
| GREYAssertNil([MetricsAppInterface |
| expectTotalCount:prerenderCount |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record event histogram"); |
| GREYAssertNil([MetricsAppInterface |
| expectCount:prerenderCount |
| forBucket:static_cast<int>( |
| security_interstitials::https_only_mode:: |
| Event::kPrerenderCancelled) |
| forHistogram:@(security_interstitials::https_only_mode:: |
| kEventHistogram)], |
| @"Failed to record prerender cancellation"); |
| } |
| |
| // Navigate to an HTTP URL and allowlist the URL. Then clear browsing data. |
| // This should clear the HTTP allowlist. |
| - (void)test_RemoveBrowsingData_ShouldClearAllowlist { |
| if (![self isInterstitialEnabled]) { |
| // Only relevant for HTTPS-Only Mode. |
| // TODO(crbug.com/1449050): Enable for HTTPS-Upgrades when it implements |
| // allowlisting. |
| return; |
| } |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertFailedUpgrade:1]; |
| |
| // Click through the interstitial. This should load the HTTP page. Histogram |
| // numbers shouldn't change. |
| [ChromeEarlGrey tapWebStateElementWithID:@"proceed-button"]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| [self assertFailedUpgrade:1]; |
| |
| // Reload. Since the URL is now allowlisted, this should immediately load |
| // HTTP without trying to upgrade. Histogram numbers shouldn't change. |
| [ChromeEarlGrey reload]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| [self assertFailedUpgrade:1]; |
| |
| // Clear the allowlist by clearing the browsing data. This clears the history |
| // programmatically, so it won't automatically reload the tabs. |
| [ChromeEarlGrey clearBrowsingHistory]; |
| |
| // Reloading the should show the interstitial again. |
| [ChromeEarlGrey reload]; |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertFailedUpgrade:2]; |
| |
| // Reload once more. |
| [ChromeEarlGrey reload]; |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertFailedUpgrade:3]; |
| } |
| |
| // Click on the "Learn more" link in the interstitial. This should open a |
| // new tab. |
| - (void)test_ClickLearnMore_ShouldOpenNewTab { |
| if (![self isInterstitialEnabled]) { |
| // Only relevant for HTTPS-Only mode tests. |
| return; |
| } |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertFailedUpgrade:1]; |
| |
| // Check tab count prior to tapping the link. |
| NSUInteger oldRegularTabCount = [ChromeEarlGreyAppInterface mainTabCount]; |
| NSUInteger oldIncognitoTabCount = |
| [ChromeEarlGreyAppInterface incognitoTabCount]; |
| |
| [ChromeEarlGrey tapWebStateElementWithID:@"learn-more-link"]; |
| |
| // A new tab should open after tapping the link. |
| [ChromeEarlGrey waitForMainTabCount:oldRegularTabCount + 1]; |
| [ChromeEarlGrey waitForIncognitoTabCount:oldIncognitoTabCount]; |
| } |
| |
| // Navigate to an HTTP URL directly. The upgraded HTTPS version serves bad SSL. |
| // The upgrade will fail and the HTTPS-Only mode interstitial will be shown. |
| // Reloading the page should show the interstitial again. |
| - (void)test_BadHTTPS_ReloadInterstitial { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| } |
| [self assertFailedUpgrade:1]; |
| |
| [ChromeEarlGrey reload]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| } |
| [self assertFailedUpgrade:2]; |
| } |
| |
| // Navigate to an HTTP URL directly. The upgraded HTTPS version serves slow SSL. |
| // The upgrade will fail and the HTTPS-Only mode interstitial will be shown. |
| // Reloading the page should show the interstitial again. |
| - (void)test_SlowHTTPS_ReloadInterstitial { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.slowServer->port() |
| useFakeHTTPS:true]; |
| // Set the fallback delay to zero. This will immediately stop the HTTPS |
| // upgrade attempt. |
| [HttpsUpgradeAppInterface setFallbackDelayForTesting:0]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| } |
| [self assertTimedOutUpgrade:1]; |
| |
| [ChromeEarlGrey reload]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| } |
| [self assertTimedOutUpgrade:2]; |
| } |
| |
| // Navigate to an HTTP URL directly. The upgraded HTTPS version serves bad SSL. |
| // The upgrade will fail and the HTTPS-Only mode interstitial will be shown. |
| // Click through the interstitial, then reload the page. The HTTP page should |
| // be shown. |
| - (void)test_BadHTTPS_ProceedInterstitial_Allowlisted { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| // Click through the interstitial. This should load the HTTP page. |
| [ChromeEarlGrey tapWebStateElementWithID:@"proceed-button"]; |
| } |
| [self assertFailedUpgrade:1]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| |
| // Reload. Since the URL is now allowlisted, this should immediately load |
| // HTTP without trying to upgrade. |
| [ChromeEarlGrey reload]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| |
| if ([self isInterstitialEnabled]) { |
| // If HTTPS-Only mode is enabled, clicking through the interstitial will |
| // allowlist the site so no new histogram entry will be recorded. |
| // Failed upgrades record two entries and this number shouldn't change. |
| GREYAssertNil([MetricsAppInterface |
| expectTotalCount:2 |
| forHistogram:@(security_interstitials:: |
| https_only_mode::kEventHistogram)], |
| @"Unexpected histogram event recorded."); |
| } else { |
| // HTTPS-Upgrades will attempt to upgrade and fail for the second time. |
| [self assertFailedUpgrade:2]; |
| } |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| |
| // Open a new tab and go to the same URL. Should load the page without an |
| // interstitial. |
| [ChromeEarlGrey openNewTab]; |
| [ChromeEarlGrey loadURL:testURL]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| if ([self isInterstitialEnabled]) { |
| [self assertFailedUpgrade:1]; |
| } else { |
| // HTTPS-Upgrades will attempt to upgrade and fail for the third time. |
| [self assertFailedUpgrade:3]; |
| } |
| |
| // Open an incognito tab and try there. Should show the interstitial as |
| // allowlist decisions don't carry over to incognito. |
| [ChromeEarlGrey openNewIncognitoTab]; |
| // Set the testing information for the incognito tab. |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| [ChromeEarlGrey loadURL:testURL]; |
| |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| // Click through the interstitial. This should load the HTTP page. |
| [ChromeEarlGrey tapWebStateElementWithID:@"proceed-button"]; |
| [self assertFailedUpgrade:2]; |
| } else { |
| // HTTPS-Upgrades will attempt to upgrade and fail for the fourth time. |
| [self assertFailedUpgrade:4]; |
| } |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| |
| [ChromeEarlGreyUI reload]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| if ([self isInterstitialEnabled]) { |
| // If HTTPS-Only mode is enabled, this should immediately load HTTP without |
| // trying to upgrade because it was allowlisted. |
| [self assertFailedUpgrade:2]; |
| } else { |
| // HTTPS-Upgrades will attempt to upgrade and fail for the fifth time. |
| [self assertFailedUpgrade:5]; |
| } |
| } |
| |
| // Same as testUpgrade_BadHTTPS_ProceedInterstitial_Allowlisted but uses |
| // a slow HTTPS response instead: |
| // Navigate to an HTTP URL directly. The upgraded HTTPS version serves a slow |
| // loading SSL page. The upgrade will be cancelled and the HTTPS-Only mode |
| // interstitial will be shown. Click through the interstitial, then reload the |
| // page. The HTTP page should be shown. |
| - (void)test_SlowHTTPS_ProceedInterstitial_Allowlisted { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.slowServer->port() |
| useFakeHTTPS:true]; |
| // Set the fallback delay to zero. This will immediately stop the HTTPS |
| // upgrade attempt. |
| [HttpsUpgradeAppInterface setFallbackDelayForTesting:0]; |
| |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| // Click through the interstitial. This should load the HTTP page. |
| [ChromeEarlGrey tapWebStateElementWithID:@"proceed-button"]; |
| } |
| [self assertTimedOutUpgrade:1]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| |
| // Reload. |
| [ChromeEarlGrey reload]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| if ([self isInterstitialEnabled]) { |
| // If HTTPS-Only mode is enabled, clicking through the interstitial will |
| // allowlist the site so no new histogram entry will be recorded. |
| // Failed upgrades record two entries and this number shouldn't change. |
| GREYAssertNil([MetricsAppInterface |
| expectTotalCount:2 |
| forHistogram:@(security_interstitials:: |
| https_only_mode::kEventHistogram)], |
| @"Unexpected histogram event recorded."); |
| } else { |
| // HTTPS-Upgrades will attempt to upgrade and fail for the second time. |
| [self assertTimedOutUpgrade:2]; |
| } |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| } |
| |
| // Navigate to an HTTP URL directly. The upgraded HTTPS version serves bad SSL. |
| // The upgrade will fail and the HTTPS-Only mode interstitial will be shown. |
| // Tap Go back on the interstitial. |
| - (void)test_BadHTTPS_GoBack { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| |
| [ChromeEarlGrey loadURL:GURL("chrome://version")]; |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| |
| // Load a site with a bad HTTPS upgrade. This shows an interstitial. |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| } |
| [self assertFailedUpgrade:1]; |
| |
| if ([self isInterstitialEnabled]) { |
| // Tap "Go back" on the interstitial. This should go back to |
| // chrome://version. |
| [ChromeEarlGrey tapWebStateElementWithID:@"primary-button"]; |
| } else { |
| [ChromeEarlGrey goBack]; |
| } |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| |
| // Go forward. Should hit the interstitial again. |
| [ChromeEarlGrey goForward]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertFailedUpgrade:2]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| // TODO(crbug.com/1449050): This should equal to 2 instead. |
| [self assertFailedUpgrade:1]; |
| } |
| } |
| |
| // Same as testUpgrade_BadHTTPS_GoBack but uses a slow HTTPS response instead: |
| // Navigate to an HTTP URL directly. The upgraded HTTPS version serves a slow |
| // loading HTTPS page. The upgrade will be cancelled and the HTTPS-Only mode |
| // interstitial will be shown. Tap Go back on the interstitial. |
| - (void)test_SlowHTTPS_GoBack { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.slowServer->port() |
| useFakeHTTPS:true]; |
| // Set the fallback delay to zero. This will immediately stop the HTTPS |
| // upgrade attempt. |
| [HttpsUpgradeAppInterface setFallbackDelayForTesting:0]; |
| |
| [ChromeEarlGrey loadURL:GURL("chrome://version")]; |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| |
| // Load a site with a slow HTTPS upgrade. This shows an interstitial. |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| } |
| [self assertTimedOutUpgrade:1]; |
| |
| if ([self isInterstitialEnabled]) { |
| // Tap "Go back" on the interstitial. This should go back to |
| // chrome://version. |
| [ChromeEarlGrey tapWebStateElementWithID:@"primary-button"]; |
| } else { |
| [ChromeEarlGrey goBack]; |
| } |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| |
| // Go forward. Should hit the interstitial again. |
| [ChromeEarlGrey goForward]; |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertTimedOutUpgrade:2]; |
| } else { |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| // TODO(crbug.com/1449050): This should equal to 2 instead. |
| [self assertTimedOutUpgrade:1]; |
| } |
| } |
| |
| // Navigate to an HTTP URL and click through the interstitial. Then, |
| // navigate to a new page and go back. This should load the HTTP URL |
| // without showing the interstitial again. |
| - (void)test_BadHTTPS_GoBackToAllowlistedSite { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.badHTTPSServer->port() |
| useFakeHTTPS:false]; |
| |
| [ChromeEarlGrey loadURL:GURL("about:blank")]; |
| |
| // Load a site with a bad HTTPS upgrade. This shows an interstitial. |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertFailedUpgrade:1]; |
| // Click through the interstitial. This should load the HTTP page. |
| [ChromeEarlGrey tapWebStateElementWithID:@"proceed-button"]; |
| } |
| |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| |
| // Go to a new page. |
| [ChromeEarlGrey loadURL:GURL("chrome://version")]; |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| |
| // Then go back to the HTTP URL. Since we previously clicked through its |
| // interstitial, this should immediately load the HTTP response. |
| [ChromeEarlGrey goBack]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| // Histogram numbers shouldn't change. |
| [self assertFailedUpgrade:1]; |
| } |
| |
| // Same as testUpgrade_BadHTTPS_GoBackToAllowlistedSite but uses a slow |
| // HTTPS response instead: |
| // Navigate to an HTTP URL with a slow HTTPS upgrade, click through the |
| // interstitial. Then, navigate to a new page and go back. This should load the |
| // HTTP URL without showing the interstitial again. |
| - (void)test_SlowHTTPS_GoBackToAllowlistedSite { |
| [HttpsUpgradeAppInterface setHTTPSPortForTesting:self.slowServer->port() |
| useFakeHTTPS:true]; |
| // Set the fallback delay to zero. This will immediately stop the HTTPS |
| // upgrade attempt. |
| [HttpsUpgradeAppInterface setFallbackDelayForTesting:0]; |
| |
| [ChromeEarlGrey loadURL:GURL("about:blank")]; |
| |
| // Load a site with a bad HTTPS upgrade. This shows an interstitial. |
| GURL testURL = self.testServer->GetURL("/"); |
| [ChromeEarlGrey loadURL:testURL]; |
| |
| if ([self isInterstitialEnabled]) { |
| [ChromeEarlGrey waitForWebStateContainingText:kInterstitialText]; |
| [self assertTimedOutUpgrade:1]; |
| // Click through the interstitial. This should load the HTTP page. |
| [ChromeEarlGrey tapWebStateElementWithID:@"proceed-button"]; |
| } |
| |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| GREYAssert(![HttpsUpgradeAppInterface isHttpsOnlyModeTimerRunning], |
| @"Timer is still running"); |
| |
| // Go to a new page. |
| [ChromeEarlGrey loadURL:GURL("chrome://version")]; |
| [ChromeEarlGrey waitForWebStateContainingText:"Revision"]; |
| |
| // Then go back to the HTTP URL. Since we previously clicked through its |
| // interstitial, this should immediately load the HTTP response. |
| [ChromeEarlGrey goBack]; |
| [ChromeEarlGrey waitForWebStateContainingText:"HTTP_RESPONSE"]; |
| // Histogram numbers shouldn't change. |
| [self assertTimedOutUpgrade:1]; |
| } |
| |
| @end |
| |
| // Tests for HTTPS-Upgrades feature. |
| // TODO(crbug.com/1338585): Remove the "ZZZ" when the bug is fixed. |
| @interface ZZZ_HttpsUpgradesTestCase : ZZZ_HttpsOnlyModeTestCase |
| @end |
| |
| @implementation ZZZ_HttpsUpgradesTestCase |
| |
| - (AppLaunchConfiguration)appConfigurationForTestCase { |
| AppLaunchConfiguration config = [super appConfigurationForTestCase]; |
| config.features_disabled = {omnibox::kDefaultTypedNavigationsToHttps}; |
| |
| config.features_enabled.push_back( |
| security_interstitials::features::kHttpsUpgrades); |
| return config; |
| } |
| |
| // This is currently needed to prevent this test case from being ignored. |
| - (void)testEmpty { |
| } |
| |
| - (TestType)testType { |
| return TestType::kHttpsUpgrades; |
| } |
| |
| @end |