diff --git a/ios/web/BUILD.gn b/ios/web/BUILD.gn index b432f3b057a0e..91e448d66fee0 100644 --- a/ios/web/BUILD.gn +++ b/ios/web/BUILD.gn @@ -491,6 +491,7 @@ source_set("ios_web_webui_unittests") { "//components/url_formatter", "//ios/net", "//ios/testing:ocmock_support", + "//ios/web/js_messaging", "//ios/web/public/test", "//ios/web/public/test:test_fixture", "//ios/web/public/test/fakes", diff --git a/ios/web/webui/crw_web_ui_scheme_handler.mm b/ios/web/webui/crw_web_ui_scheme_handler.mm index b9efad1e1e5b2..fda51182aa461 100644 --- a/ios/web/webui/crw_web_ui_scheme_handler.mm +++ b/ios/web/webui/crw_web_ui_scheme_handler.mm @@ -9,6 +9,8 @@ #import "base/files/file_path.h" #import "base/strings/sys_string_conversions.h" +#import "ios/web/js_messaging/web_view_web_state_map.h" +#import "ios/web/public/web_state.h" #import "ios/web/webui/url_fetcher_block_adapter.h" #import "ios/web/webui/web_ui_constants.h" #import "ios/web/webui/web_ui_ios_controller_factory_registry.h" @@ -64,10 +66,20 @@ NSInteger GetErrorCodeForUrl(const GURL& URL) { // The "Access-Control-Allow-Origin" header is required below to allow // requests from any WebUI page to load chrome://resources URLs. However, // requests between different WebUI pages are blocked directly instead. - if (!webView.URL || - (!URL.DomainIs(web::kWebUIResourcesHost) && - url::SchemeHostPort(URL) != - url::SchemeHostPort(net::GURLWithNSURL(webView.URL)))) { + // Subresource requests are gated on the WebState's last committed URL + // because both `webView.URL` and `backForwardList.currentItem.URL` may + // already point at the destination of an in-progress navigation. The main + // document request itself is loaded before commit and is identified by + // matching `webView.URL`. + GURL webViewURL = net::GURLWithNSURL(webView.URL); + web::WebState* webState = web::GetWebStateForWebView(webView); + GURL committedURL = webState ? webState->GetLastCommittedURL() : GURL(); + BOOL isMainDocumentRequest = URL.EqualsIgnoringRef(webViewURL); + BOOL isSharedResourceRequest = URL.DomainIs(web::kWebUIResourcesHost) && + GetErrorCodeForUrl(committedURL) == 0; + if (!webState || !webViewURL.is_valid() || + (!isMainDocumentRequest && !isSharedResourceRequest && + url::SchemeHostPort(URL) != url::SchemeHostPort(committedURL))) { NSError* error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorNoPermissionsToReadFile diff --git a/ios/web/webui/crw_web_ui_scheme_handler_unittest.mm b/ios/web/webui/crw_web_ui_scheme_handler_unittest.mm index ad39a59975df8..38c60a5a16b50 100644 --- a/ios/web/webui/crw_web_ui_scheme_handler_unittest.mm +++ b/ios/web/webui/crw_web_ui_scheme_handler_unittest.mm @@ -7,6 +7,8 @@ #import "base/run_loop.h" #import "base/strings/sys_string_conversions.h" #import "base/test/ios/wait_util.h" +#import "ios/web/js_messaging/web_view_web_state_map.h" +#import "ios/web/public/test/fakes/fake_web_state.h" #import "ios/web/public/test/web_test.h" #import "ios/web/public/webui/web_ui_ios_controller.h" #import "ios/web/public/webui/web_ui_ios_controller_factory.h" @@ -118,6 +120,17 @@ class CRWWebUISchemeManagerTest : public WebTest { return [NSURL URLWithString:url]; } + // Returns a mock WKWebView whose `URL` property returns `web_view_url` and + // which is associated with a FakeWebState whose last committed URL is + // `committed_url`. + id CreateMockWebView(NSURL* web_view_url, const GURL& committed_url) { + id web_view = OCMClassMock([WKWebView class]); + OCMStub([web_view URL]).andReturn(web_view_url); + fake_web_state_.SetCurrentURL(committed_url); + SetAssociatedWebViewForWebState(web_view, &fake_web_state_); + return web_view; + } + scoped_refptr GetSharedURLLoaderFactory() { return test_shared_loader_factory_; } @@ -134,19 +147,19 @@ class CRWWebUISchemeManagerTest : public WebTest { network::TestURLLoaderFactory test_url_loader_factory_; scoped_refptr test_shared_loader_factory_; FakeWebUIIOSControllerFactory factory_; + FakeWebState fake_web_state_; }; // Tests that calling start on the scheme handler returns some data when the URL // is a WebUI URL. TEST_F(CRWWebUISchemeManagerTest, StartTaskWithCorrectURL) { CRWWebUISchemeHandler* scheme_handler = CreateSchemeHandler(); - id web_view = OCMClassMock([WKWebView class]); + id web_view = + CreateMockWebView(GetWebUIURL(), net::GURLWithNSURL(GetWebUIURL())); FakeSchemeTask* url_scheme_task = [[FakeSchemeTask alloc] init]; NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:GetWebUIURL()]; request.mainDocumentURL = GetWebUIURL(); - OCMStub([web_view URL]).andReturn(request.mainDocumentURL); - url_scheme_task.request = request; [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; @@ -208,16 +221,74 @@ TEST_F(CRWWebUISchemeManagerTest, StartTaskWithWrongMainDocumentURL) { EXPECT_TRUE(url_scheme_task.receivedError); } +// Tests that the main document request is allowed while a WebUI navigation is +// in progress and the previously committed page is not a WebUI page. +TEST_F(CRWWebUISchemeManagerTest, MainDocumentAllowedDuringProvisionalLoad) { + CRWWebUISchemeHandler* scheme_handler = CreateSchemeHandler(); + NSURL* main_url = [NSURL URLWithString:@"chrome://clown/"]; + id web_view = CreateMockWebView(main_url, GURL("https://example.com/")); + FakeSchemeTask* url_scheme_task = [[FakeSchemeTask alloc] init]; + NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:main_url]; + request.mainDocumentURL = main_url; + url_scheme_task.request = request; + + [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; + + RespondWithData(net::GURLWithNSURL(request.URL), "{}"); + EXPECT_TRUE(url_scheme_task.receivedData); + EXPECT_FALSE(url_scheme_task.receivedError); +} + +// Tests that subresource requests for the in-progress navigation host are +// blocked while the previously committed page is not a WebUI page. +TEST_F(CRWWebUISchemeManagerTest, SubresourceBlockedDuringProvisionalLoad) { + CRWWebUISchemeHandler* scheme_handler = CreateSchemeHandler(); + NSURL* main_url = [NSURL URLWithString:@"chrome://clown/"]; + id web_view = CreateMockWebView(main_url, GURL("https://example.com/")); + FakeSchemeTask* url_scheme_task = [[FakeSchemeTask alloc] init]; + NSMutableURLRequest* request = [NSMutableURLRequest + requestWithURL:[NSURL URLWithString:@"chrome://clown/data.js"]]; + request.mainDocumentURL = main_url; + url_scheme_task.request = request; + + [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; + + RespondWithData(net::GURLWithNSURL(request.URL), "{}"); + EXPECT_FALSE(url_scheme_task.receivedData); + EXPECT_TRUE(url_scheme_task.receivedError); + EXPECT_EQ(NSURLErrorNoPermissionsToReadFile, url_scheme_task.error.code); +} + +// Tests that shared resource requests are blocked while the previously +// committed page is not a WebUI page. +TEST_F(CRWWebUISchemeManagerTest, SharedResourceBlockedDuringProvisionalLoad) { + CRWWebUISchemeHandler* scheme_handler = CreateSchemeHandler(); + NSURL* main_url = [NSURL URLWithString:@"chrome://clown/"]; + id web_view = CreateMockWebView(main_url, GURL("https://example.com/")); + FakeSchemeTask* url_scheme_task = [[FakeSchemeTask alloc] init]; + NSMutableURLRequest* request = [NSMutableURLRequest + requestWithURL:[NSURL URLWithString:@"chrome://resources/shared.js"]]; + request.mainDocumentURL = main_url; + url_scheme_task.request = request; + + [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; + + RespondWithData(net::GURLWithNSURL(request.URL), "{}"); + EXPECT_FALSE(url_scheme_task.receivedData); + EXPECT_TRUE(url_scheme_task.receivedError); + EXPECT_EQ(NSURLErrorNoPermissionsToReadFile, url_scheme_task.error.code); +} + // Tests that calling stop right after start prevent the handler from returning // data. TEST_F(CRWWebUISchemeManagerTest, StopTask) { CRWWebUISchemeHandler* scheme_handler = CreateSchemeHandler(); - id web_view = OCMClassMock([WKWebView class]); + id web_view = + CreateMockWebView(GetWebUIURL(), net::GURLWithNSURL(GetWebUIURL())); FakeSchemeTask* url_scheme_task = [[FakeSchemeTask alloc] init]; NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:GetWebUIURL()]; request.mainDocumentURL = GetWebUIURL(); - OCMStub([web_view URL]).andReturn(request.mainDocumentURL); url_scheme_task.request = request; [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; @@ -231,14 +302,14 @@ TEST_F(CRWWebUISchemeManagerTest, StopTask) { // Tests that proper mime-type is returned for a given chrome:// request. TEST_F(CRWWebUISchemeManagerTest, CheckMimetypeOfChromeScheme) { CRWWebUISchemeHandler* scheme_handler = CreateSchemeHandler(); - id web_view = OCMClassMock([WKWebView class]); + NSURL* main_url = [NSURL URLWithString:@"chrome://clown/"]; + id web_view = CreateMockWebView(main_url, net::GURLWithNSURL(main_url)); FakeSchemeTask* url_scheme_task = [[FakeSchemeTask alloc] init]; // Check javascript NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome://clown/res/clown.js"]]; - request.mainDocumentURL = [NSURL URLWithString:@"chrome://clown/"]; - OCMStub([web_view URL]).andReturn(request.mainDocumentURL); + request.mainDocumentURL = main_url; url_scheme_task.request = request; [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; RespondWithData(net::GURLWithNSURL(request.URL), "{}"); @@ -250,8 +321,7 @@ TEST_F(CRWWebUISchemeManagerTest, CheckMimetypeOfChromeScheme) { // Check css. request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome://clown/res/clown.css"]]; - request.mainDocumentURL = [NSURL URLWithString:@"chrome://clown/"]; - OCMStub([web_view URL]).andReturn(request.mainDocumentURL); + request.mainDocumentURL = main_url; url_scheme_task.request = request; [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; RespondWithData(net::GURLWithNSURL(request.URL), "{}"); @@ -263,8 +333,7 @@ TEST_F(CRWWebUISchemeManagerTest, CheckMimetypeOfChromeScheme) { // Check svg. request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome://clown/res/clown.svg"]]; - request.mainDocumentURL = [NSURL URLWithString:@"chrome://clown/"]; - OCMStub([web_view URL]).andReturn(request.mainDocumentURL); + request.mainDocumentURL = main_url; url_scheme_task.request = request; [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; RespondWithData(net::GURLWithNSURL(request.URL), "{}"); @@ -277,8 +346,7 @@ TEST_F(CRWWebUISchemeManagerTest, CheckMimetypeOfChromeScheme) { request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"chrome://clown/res/clown.anything"]]; - request.mainDocumentURL = [NSURL URLWithString:@"chrome://clown/"]; - OCMStub([web_view URL]).andReturn(request.mainDocumentURL); + request.mainDocumentURL = main_url; url_scheme_task.request = request; [scheme_handler webView:web_view startURLSchemeTask:url_scheme_task]; RespondWithData(net::GURLWithNSURL(request.URL), "{}");