| // 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 "base/scoped_observation.h" |
| #import "base/test/ios/wait_util.h" |
| #import "base/test/scoped_feature_list.h" |
| #import "ios/web/common/features.h" |
| #import "ios/web/navigation/crw_wk_navigation_handler.h" |
| #import "ios/web/public/navigation/https_upgrade_type.h" |
| #import "ios/web/public/navigation/navigation_context.h" |
| #import "ios/web/public/navigation/navigation_item.h" |
| #import "ios/web/public/test/fakes/fake_web_client.h" |
| #import "ios/web/public/test/navigation_test_util.h" |
| #import "ios/web/public/web_state_observer.h" |
| #import "ios/web/security/wk_web_view_security_util.h" |
| #import "ios/web/test/web_int_test.h" |
| #import "ios/web/util/error_translation_util.h" |
| #import "net/base/apple/url_conversions.h" |
| #import "net/test/embedded_test_server/default_handlers.h" |
| #import "net/test/embedded_test_server/embedded_test_server.h" |
| |
| using base::test::ios::kWaitForPageLoadTimeout; |
| using web::HttpsUpgradeType; |
| |
| namespace { |
| |
| // A WebStateObserver that observes that the navigation is finished and keeps |
| // track of the error type (SSL or net error). |
| class FailedWebStateObserver : public web::WebStateObserver { |
| public: |
| // Type of the error that caused the navigation to fail. |
| enum class ErrorType { |
| kNone, |
| // The navigation failed due to an SSL error such as an invalid certificate. |
| kSSLError, |
| // The navigation failed due to a net error such as an invalid hostname. |
| kNetError |
| }; |
| |
| FailedWebStateObserver() = default; |
| FailedWebStateObserver(const FailedWebStateObserver&) = delete; |
| FailedWebStateObserver& operator=(const FailedWebStateObserver&) = delete; |
| |
| void DidFinishNavigation( |
| web::WebState* web_state, |
| web::NavigationContext* navigation_context) override { |
| did_finish_ = true; |
| failed_https_upgrade_type_ = |
| navigation_context->GetFailedHttpsUpgradeType(); |
| |
| DCHECK_EQ(ErrorType::kNone, error_type_); |
| NSError* error = navigation_context->GetError(); |
| if (web::IsWKWebViewSSLCertError(error)) { |
| error_type_ = ErrorType::kSSLError; |
| } else { |
| int error_code = 0; |
| if (!web::GetNetErrorFromIOSErrorCode( |
| error.code, &error_code, |
| net::NSURLWithGURL(navigation_context->GetUrl()))) { |
| error_code = net::ERR_FAILED; |
| } |
| if (error_code != net::OK) { |
| error_type_ = ErrorType::kNetError; |
| } |
| } |
| } |
| |
| void WebStateDestroyed(web::WebState* web_state) override { NOTREACHED(); } |
| |
| bool did_finish() const { return did_finish_; } |
| web::HttpsUpgradeType failed_https_upgrade_type() const { |
| return failed_https_upgrade_type_; |
| } |
| ErrorType error_type() const { return error_type_; } |
| |
| private: |
| bool did_finish_ = false; |
| web::HttpsUpgradeType failed_https_upgrade_type_ = |
| web::HttpsUpgradeType::kNone; |
| ErrorType error_type_ = ErrorType::kNone; |
| }; |
| |
| } // namespace |
| |
| namespace web { |
| |
| class CRWKNavigationHandlerIntTest : public WebIntTest { |
| protected: |
| CRWKNavigationHandlerIntTest() |
| : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| net::test_server::RegisterDefaultHandlers(&server_); |
| net::test_server::RegisterDefaultHandlers(&https_server_); |
| } |
| |
| FakeWebClient* GetWebClient() override { |
| return static_cast<FakeWebClient*>(WebIntTest::GetWebClient()); |
| } |
| |
| // Tests the failed HTTPS upgradestatus of a navigation. Navigates to `url` |
| // using `https_upgrade_type` as the HTTPS upgrade type. Expects |
| // GetFailedHTTPSUpgradeType() to be equal to `https_upgrade_type`. |
| // Expects the navigation error to be of type `expected_error_type`. |
| void TestFailedHttpsUpgrade( |
| const GURL& url, |
| HttpsUpgradeType https_upgrade_type, |
| HttpsUpgradeType expected_failed_upgrade_type, |
| FailedWebStateObserver::ErrorType expected_error_type) { |
| web::NavigationManager::WebLoadParams params(url); |
| params.transition_type = ui::PAGE_TRANSITION_TYPED; |
| params.https_upgrade_type = https_upgrade_type; |
| |
| FailedWebStateObserver observer; |
| base::ScopedObservation<WebState, WebStateObserver> scoped_observer( |
| &observer); |
| scoped_observer.Observe(web_state()); |
| web_state()->GetNavigationManager()->LoadURLWithParams(params); |
| |
| // Need to use a pointer to `observer` as the block wants to capture it by |
| // value (even if marked with __block) which would not work. |
| FailedWebStateObserver* observer_ptr = &observer; |
| EXPECT_TRUE( |
| base::test::ios::WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{ |
| // Run the event loop, otherwise the HTTPS connection times out |
| // instead of failing with an SSL error. |
| base::RunLoop().RunUntilIdle(); |
| return observer_ptr->did_finish() && |
| (observer_ptr->failed_https_upgrade_type() == |
| expected_failed_upgrade_type); |
| })); |
| EXPECT_EQ(expected_error_type, observer.error_type()); |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| net::test_server::EmbeddedTestServer server_; |
| net::EmbeddedTestServer https_server_; |
| }; |
| |
| // Tests that reloading a page with a different default User Agent updates the |
| // item. |
| TEST_F(CRWKNavigationHandlerIntTest, ReloadWithDifferentUserAgent) { |
| FakeWebClient* web_client = GetWebClient(); |
| web_client->SetDefaultUserAgent(UserAgentType::MOBILE); |
| |
| ASSERT_TRUE(server_.Start()); |
| GURL url(server_.GetURL("/echo")); |
| ASSERT_TRUE(LoadUrl(url)); |
| |
| NavigationItem* item = web_state()->GetNavigationManager()->GetVisibleItem(); |
| EXPECT_EQ(UserAgentType::MOBILE, item->GetUserAgentType()); |
| |
| web_client->SetDefaultUserAgent(UserAgentType::DESKTOP); |
| |
| web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL, |
| /* check_for_repost = */ true); |
| |
| EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout( |
| base::test::ios::kWaitForPageLoadTimeout, ^{ |
| NavigationItem* item_after_reload = |
| web_state()->GetNavigationManager()->GetVisibleItem(); |
| return item_after_reload->GetUserAgentType() == UserAgentType::DESKTOP; |
| })); |
| } |
| |
| // Tests that reloading a failed page that should not have a User Agent doesn't |
| // trigger a DCHECK (preventing crbug.com/1360567). |
| TEST_F(CRWKNavigationHandlerIntTest, ReloadNONEUserAgentErrorPage) { |
| FakeWebClient* web_client = GetWebClient(); |
| web_client->SetDefaultUserAgent(UserAgentType::MOBILE); |
| |
| GURL url("testwebui://extensions"); |
| ASSERT_TRUE(LoadUrl(url)); |
| |
| NavigationItem* item = web_state()->GetNavigationManager()->GetVisibleItem(); |
| EXPECT_EQ(UserAgentType::NONE, item->GetUserAgentType()); |
| |
| web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL, |
| /* check_for_repost = */ true); |
| |
| // Make sure the load has time to start. |
| base::test::ios::SpinRunLoopWithMinDelay(base::Milliseconds(10)); |
| |
| EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout( |
| base::test::ios::kWaitForPageLoadTimeout, ^{ |
| return !web_state()->IsLoading(); |
| })); |
| } |
| |
| // Tests that an SSL or net error on a navigation that wasn't upgraded to HTTPS |
| // doesn't set the IsFailedHTTPSUpgrade() bit on the navigation context. |
| TEST_F(CRWKNavigationHandlerIntTest, FailedHTTPSUpgrade_NotUpgraded_SSLError) { |
| ASSERT_TRUE(https_server_.Start()); |
| GURL url(https_server_.GetURL("/")); |
| TestFailedHttpsUpgrade(url, HttpsUpgradeType::kNone, HttpsUpgradeType::kNone, |
| FailedWebStateObserver::ErrorType::kSSLError); |
| } |
| |
| // Tests that an SSL error on a navigation that was upgraded to HTTPS |
| // sets the IsFailedHTTPSUpgrade() bit on the navigation context. |
| TEST_F(CRWKNavigationHandlerIntTest, FailedHTTPSUpgrade_Upgraded_SSLError) { |
| ASSERT_TRUE(https_server_.Start()); |
| GURL url(https_server_.GetURL("/")); |
| TestFailedHttpsUpgrade(url, HttpsUpgradeType::kHttpsOnlyMode, |
| HttpsUpgradeType::kHttpsOnlyMode, |
| FailedWebStateObserver::ErrorType::kSSLError); |
| } |
| |
| // Tests that a net error on a navigation that wasn't upgraded to HTTPS |
| // doesn't set the IsFailedHTTPSUpgrade() bit on the navigation context. |
| // TODO(crbug.com/433316885): Re-enable this test. |
| TEST_F(CRWKNavigationHandlerIntTest, |
| DISABLED_FailedHTTPSUpgrade_NotUpgraded_NetError) { |
| GURL url("https://site.test"); |
| TestFailedHttpsUpgrade(url, HttpsUpgradeType::kNone, HttpsUpgradeType::kNone, |
| FailedWebStateObserver::ErrorType::kNetError); |
| } |
| |
| // Tests that a net error on a navigation that was upgraded to HTTPS |
| // sets the IsFailedHTTPSUpgrade() bit on the navigation context. |
| // TODO(crbug.com/433316885): Re-enable this test. |
| TEST_F(CRWKNavigationHandlerIntTest, |
| DISABLED_FailedHTTPSUpgrade_Upgraded_NetError) { |
| GURL url("https://site.test"); |
| TestFailedHttpsUpgrade(url, HttpsUpgradeType::kHttpsOnlyMode, |
| HttpsUpgradeType::kHttpsOnlyMode, |
| FailedWebStateObserver::ErrorType::kNetError); |
| } |
| |
| } // namespace web |