/* * Copyright (C) 2018 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "HTTPServer.h" #import "PlatformUtilities.h" #import "Test.h" #import "TestWKWebView.h" #import "Utilities.h" #import #import #import #import #import #import #import #import #import #import #import TEST(NetworkProcess, Entitlements) { auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:adoptNS([[WKWebViewConfiguration alloc] init]).get()]); [webView synchronouslyLoadTestPageNamed:@"simple"]; WKWebsiteDataStore *store = [webView configuration].websiteDataStore; bool hasEntitlement = [store _networkProcessHasEntitlementForTesting:@"com.apple.rootless.storage.WebKitNetworkingSandbox"]; #if PLATFORM(MAC) && USE(APPLE_INTERNAL_SDK) EXPECT_TRUE(hasEntitlement); #else EXPECT_FALSE(hasEntitlement); #endif EXPECT_FALSE([store _networkProcessHasEntitlementForTesting:@"test failure case"]); } TEST(WebKit, HTTPReferer) { auto checkReferer = [] (NSURL *baseURL, const char* expectedReferer) { using namespace TestWebKitAPI; bool done = false; HTTPServer server([&] (Connection connection) { connection.receiveHTTPRequest([connection, expectedReferer, &done] (Vector&& request) { if (expectedReferer) { auto expectedHeaderField = makeString("Referer: ", expectedReferer, "\r\n"); EXPECT_TRUE(strnstr(request.data(), expectedHeaderField.utf8().data(), request.size())); } else EXPECT_FALSE(strnstr(request.data(), "Referer:", request.size())); done = true; }); }); auto webView = adoptNS([WKWebView new]); [webView loadHTMLString:[NSString stringWithFormat:@"
", server.port()] baseURL:baseURL]; Util::run(&done); }; Vector a5k(5000, 'a'); Vector a3k(3000, 'a'); NSString *longPath = [NSString stringWithFormat:@"http://webkit.org/%s?asdf", a5k.data()]; NSString *shorterPath = [NSString stringWithFormat:@"http://webkit.org/%s?asdf", a3k.data()]; NSString *longHost = [NSString stringWithFormat:@"http://webkit.org%s/path", a5k.data()]; NSString *shorterHost = [NSString stringWithFormat:@"http://webkit.org%s/path", a3k.data()]; checkReferer([NSURL URLWithString:longPath], "http://webkit.org/"); checkReferer([NSURL URLWithString:shorterPath], shorterPath.UTF8String); checkReferer([NSURL URLWithString:longHost], nullptr); checkReferer([NSURL URLWithString:shorterHost], shorterHost.UTF8String); } TEST(NetworkProcess, LaunchOnlyWhenNecessary) { RetainPtr websiteDataStore; @autoreleasepool { auto webView = adoptNS([WKWebView new]); websiteDataStore = [webView configuration].websiteDataStore; [websiteDataStore _setResourceLoadStatisticsEnabled:YES]; [[webView configuration].processPool _registerURLSchemeAsSecure:@"test"]; [[webView configuration].processPool _registerURLSchemeAsBypassingContentSecurityPolicy:@"test"]; } TestWebKitAPI::Util::spinRunLoop(10); EXPECT_FALSE([websiteDataStore _networkProcessExists]); } TEST(NetworkProcess, CrashWhenNotAssociatedWithDataStore) { pid_t networkProcessPID = 0; @autoreleasepool { auto viewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]); auto dataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]); auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]); [viewConfiguration setWebsiteDataStore:dataStore.get()]; auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:viewConfiguration.get()]); [webView loadHTMLString:@"foo" baseURL:[NSURL URLWithString:@"about:blank"]]; while (![dataStore _networkProcessIdentifier]) TestWebKitAPI::Util::spinRunLoop(10); networkProcessPID = [dataStore _networkProcessIdentifier]; EXPECT_TRUE(!!networkProcessPID); } TestWebKitAPI::Util::spinRunLoop(10); // Kill the network process once it is no longer associated with any WebsiteDataStore. kill(networkProcessPID, 9); TestWebKitAPI::Util::spinRunLoop(10); auto viewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]); auto dataStoreConfiguration = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]); auto dataStore = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration:dataStoreConfiguration.get()]); [viewConfiguration setWebsiteDataStore:dataStore.get()]; auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:viewConfiguration.get()]); [webView synchronouslyLoadTestPageNamed:@"simple"]; EXPECT_NE(networkProcessPID, [webView configuration].websiteDataStore._networkProcessIdentifier); } TEST(NetworkProcess, TerminateWhenUnused) { RetainPtr retainedPool; @autoreleasepool { auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]); configuration.get().websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore]; retainedPool = configuration.get().processPool; auto webView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 0, 0) configuration:configuration.get()]); [webView synchronouslyLoadTestPageNamed:@"simple"]; EXPECT_TRUE([WKWebsiteDataStore _defaultNetworkProcessExists]); } while ([WKWebsiteDataStore _defaultNetworkProcessExists]) TestWebKitAPI::Util::spinRunLoop(); retainedPool = nil; @autoreleasepool { auto webView = adoptNS([WKWebView new]); [webView synchronouslyLoadTestPageNamed:@"simple"]; EXPECT_TRUE([WKWebsiteDataStore _defaultNetworkProcessExists]); } while ([WKWebsiteDataStore _defaultNetworkProcessExists]) TestWebKitAPI::Util::spinRunLoop(); } TEST(NetworkProcess, DoNotLaunchOnDataStoreDestruction) { auto storeConfiguration1 = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]); auto websiteDataStore1 = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration: storeConfiguration1.get()]); EXPECT_FALSE([WKWebsiteDataStore _defaultNetworkProcessExists]); @autoreleasepool { auto storeConfiguration2 = adoptNS([[_WKWebsiteDataStoreConfiguration alloc] initNonPersistentConfiguration]); auto websiteDataStore2 = adoptNS([[WKWebsiteDataStore alloc] _initWithConfiguration: storeConfiguration2.get()]); } TestWebKitAPI::Util::spinRunLoop(10); EXPECT_FALSE([WKWebsiteDataStore _defaultNetworkProcessExists]); } TEST(NetworkProcess, CORSPreflightCachePartitioned) { using namespace TestWebKitAPI; size_t preflightRequestsReceived { 0 }; HTTPServer server([&] (Connection connection) { connection.receiveHTTPRequest([&, connection] (Vector&& request) { const char* expectedRequestBegin = "OPTIONS / HTTP/1.1\r\n"; EXPECT_TRUE(!memcmp(request.data(), expectedRequestBegin, strlen(expectedRequestBegin))); EXPECT_TRUE(strnstr(request.data(), "Origin: http://example.com\r\n", request.size())); EXPECT_TRUE(strnstr(request.data(), "Access-Control-Request-Method: DELETE\r\n", request.size())); constexpr auto response = "HTTP/1.1 204 No Content\r\n" "Access-Control-Allow-Origin: http://example.com\r\n" "Access-Control-Allow-Methods: OPTIONS, GET, POST, DELETE\r\n" "Cache-Control: max-age=604800\r\n\r\n"_s; connection.send(response, [&, connection] { connection.receiveHTTPRequest([&, connection] (Vector&& request) { const char* expectedRequestBegin = "DELETE / HTTP/1.1\r\n"; EXPECT_TRUE(!memcmp(request.data(), expectedRequestBegin, strlen(expectedRequestBegin))); EXPECT_TRUE(strnstr(request.data(), "Origin: http://example.com\r\n", request.size())); constexpr auto response = "HTTP/1.1 200 OK\r\n" "Content-Length: 2\r\n\r\n" "hi"_s; connection.send(response, [&, connection] { preflightRequestsReceived++; }); }); }); }); }); NSString *html = [NSString stringWithFormat:@"", server.port()]; NSURL *baseURL = [NSURL URLWithString:@"http://example.com/"]; auto firstWebView = adoptNS([WKWebView new]); [firstWebView loadHTMLString:html baseURL:baseURL]; while (preflightRequestsReceived != 1) TestWebKitAPI::Util::spinRunLoop(); auto configuration = adoptNS([WKWebViewConfiguration new]); configuration.get().websiteDataStore = [WKWebsiteDataStore nonPersistentDataStore]; auto secondWebView = adoptNS([[WKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]); [secondWebView loadHTMLString:html baseURL:baseURL]; while (preflightRequestsReceived != 2) TestWebKitAPI::Util::spinRunLoop(); } static Vector> receivedMessagesVector; static bool receivedMessage = false; @interface BroadcastChannelMessageHandler : NSObject @end @implementation BroadcastChannelMessageHandler - (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message { receivedMessagesVector.append(message); receivedMessage = true; } @end static void waitUntilNetworkProcessIsResponsive(WKWebView *webView1, WKWebView *webView2) { // We've just terminated and relaunched the network process. However, there is no easy well to tell if the webviews' // WebProcesses have been notified of the network process crash or not (we only know that the UIProcess was). Because // we don't want the test to go on with the WebProcesses using stale NetworkProcessConnections, we use the following // trick to wait until both WebProcesses are able to communicate with the new NetworkProcess: // The first WebProcess tries setting a cookie until the second Webview is able to see it. auto expectedCookieString = makeString("TEST=", createVersion4UUIDString()); auto setTestCookieString = makeString("setInterval(() => { document.cookie='", expectedCookieString, "'; }, 100);"); [webView1 evaluateJavaScript:(NSString *)setTestCookieString completionHandler: [&] (id result, NSError *error) { EXPECT_TRUE(!error); }]; bool canSecondWebViewSeeNewCookie = false; do { TestWebKitAPI::Util::spinRunLoop(10); String cookieString = (NSString *)[webView2 objectByEvaluatingJavaScript:@"document.cookie"]; canSecondWebViewSeeNewCookie = cookieString.contains(expectedCookieString); } while (!canSecondWebViewSeeNewCookie); } TEST(NetworkProcess, BroadcastChannelCrashRecovery) { auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]); auto messageHandler = adoptNS([[BroadcastChannelMessageHandler alloc] init]); [[webViewConfiguration userContentController] addScriptMessageHandler:messageHandler.get() name:@"test"]; auto webView1 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]); auto webView2 = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:webViewConfiguration.get()]); receivedMessage = false; receivedMessagesVector.clear(); NSString *html = [NSString stringWithFormat:@""]; NSURL *baseURL = [NSURL URLWithString:@"http://example.com/"]; [webView1 synchronouslyLoadHTMLString:html baseURL:baseURL]; [webView2 synchronouslyLoadHTMLString:html baseURL:baseURL]; auto webPID1 = [webView1 _webProcessIdentifier]; auto webPID2 = [webView2 _webProcessIdentifier]; EXPECT_NE(webPID1, 0); EXPECT_NE(webPID2, 0); EXPECT_NE(webPID1, webPID2); auto networkPID = [[WKWebsiteDataStore defaultDataStore] _networkProcessIdentifier]; EXPECT_NE(networkPID, 0); EXPECT_FALSE(receivedMessage); EXPECT_TRUE(receivedMessagesVector.isEmpty()); // Test that initial communication from webView1 to webView2 works. receivedMessage = false; receivedMessagesVector.clear(); bool finishedRunningScript = false; [webView1 evaluateJavaScript:@"bc.postMessage('foo')" completionHandler: [&] (id result, NSError *error) { EXPECT_TRUE(!error); finishedRunningScript = true; }]; TestWebKitAPI::Util::run(&finishedRunningScript); TestWebKitAPI::Util::run(&receivedMessage); TestWebKitAPI::Util::spinRunLoop(10); EXPECT_EQ(receivedMessagesVector.size(), 1U); EXPECT_EQ([receivedMessagesVector[0] webView], webView2); EXPECT_WK_STREQ([receivedMessagesVector[0] body], @"foo"); // Test that initial communication from webView2 to webView1 works. receivedMessage = false; receivedMessagesVector.clear(); finishedRunningScript = false; [webView2 evaluateJavaScript:@"bc.postMessage('bar')" completionHandler: [&] (id result, NSError *error) { EXPECT_TRUE(!error); finishedRunningScript = true; }]; TestWebKitAPI::Util::run(&finishedRunningScript); TestWebKitAPI::Util::run(&receivedMessage); TestWebKitAPI::Util::spinRunLoop(10); EXPECT_EQ(receivedMessagesVector.size(), 1U); EXPECT_EQ([receivedMessagesVector[0] webView], webView1); EXPECT_WK_STREQ([receivedMessagesVector[0] body], @"bar"); // Kill the network process. kill(networkPID, 9); while ([[WKWebsiteDataStore defaultDataStore] _networkProcessIdentifier] == networkPID) TestWebKitAPI::Util::spinRunLoop(10); waitUntilNetworkProcessIsResponsive(webView1.get(), webView2.get()); // Test that initial communication from webView1 to webView2 works. receivedMessage = false; receivedMessagesVector.clear(); finishedRunningScript = false; [webView1 evaluateJavaScript:@"bc.postMessage('foo2')" completionHandler: [&] (id result, NSError *error) { EXPECT_TRUE(!error); finishedRunningScript = true; }]; TestWebKitAPI::Util::run(&finishedRunningScript); TestWebKitAPI::Util::run(&receivedMessage); TestWebKitAPI::Util::spinRunLoop(10); EXPECT_EQ(receivedMessagesVector.size(), 1U); EXPECT_EQ([receivedMessagesVector[0] webView], webView2); EXPECT_WK_STREQ([receivedMessagesVector[0] body], @"foo2"); // Test that initial communication from webView2 to webView1 works. receivedMessage = false; receivedMessagesVector.clear(); finishedRunningScript = false; [webView2 evaluateJavaScript:@"bc.postMessage('bar2')" completionHandler: [&] (id result, NSError *error) { EXPECT_TRUE(!error); finishedRunningScript = true; }]; TestWebKitAPI::Util::run(&finishedRunningScript); TestWebKitAPI::Util::run(&receivedMessage); TestWebKitAPI::Util::spinRunLoop(10); EXPECT_EQ(receivedMessagesVector.size(), 1U); EXPECT_EQ([receivedMessagesVector[0] webView], webView1); EXPECT_WK_STREQ([receivedMessagesVector[0] body], @"bar2"); auto networkPID2 = [[WKWebsiteDataStore defaultDataStore] _networkProcessIdentifier]; EXPECT_NE(networkPID2, 0); EXPECT_NE(networkPID, networkPID2); EXPECT_EQ(webPID1, [webView1 _webProcessIdentifier]); EXPECT_EQ(webPID2, [webView2 _webProcessIdentifier]); } #if HAVE(NSURLSESSION_TASK_DELEGATE) @interface TestDataTaskDelegate : NSObject<_WKDataTaskDelegate> @property (nonatomic, copy) void(^didReceiveAuthenticationChallenge)(_WKDataTask *, NSURLAuthenticationChallenge *, void(^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)); @property (nonatomic, copy) void(^willPerformHTTPRedirection)(_WKDataTask *, NSHTTPURLResponse *, NSURLRequest *, void(^)(_WKDataTaskRedirectPolicy)); @property (nonatomic, copy) void (^didReceiveResponse)(_WKDataTask *, NSURLResponse *, void (^)(_WKDataTaskResponsePolicy)); @property (nonatomic, copy) void (^didReceiveData)(_WKDataTask *, NSData *); @property (nonatomic, copy) void (^didCompleteWithError)(_WKDataTask *, NSError *); @end @implementation TestDataTaskDelegate - (void)dataTask:(_WKDataTask *)dataTask didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler { if (_didReceiveAuthenticationChallenge) _didReceiveAuthenticationChallenge(dataTask, challenge, completionHandler); else completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil); } - (void)dataTask:(_WKDataTask *)dataTask willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request decisionHandler:(void (^)(_WKDataTaskRedirectPolicy))decisionHandler { if (_willPerformHTTPRedirection) _willPerformHTTPRedirection(dataTask, response, request, decisionHandler); else decisionHandler(_WKDataTaskRedirectPolicyAllow); } - (void)dataTask:(_WKDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response decisionHandler:(void (^)(_WKDataTaskResponsePolicy))decisionHandler { if (_didReceiveResponse) _didReceiveResponse(dataTask, response, decisionHandler); else decisionHandler(_WKDataTaskResponsePolicyAllow); } - (void)dataTask:(_WKDataTask *)dataTask didReceiveData:(NSData *)data { if (_didReceiveData) _didReceiveData(dataTask, data); } - (void)dataTask:(_WKDataTask *)dataTask didCompleteWithError:(NSError *)error { if (_didCompleteWithError) _didCompleteWithError(dataTask, error); } @end TEST(_WKDataTask, Basic) { using namespace TestWebKitAPI; constexpr auto html = ""_s; constexpr auto secondResponse = "second response"_s; Vector secondRequest; auto server = HTTPServer(HTTPServer::UseCoroutines::Yes, [&](Connection connection) -> Task { while (1) { auto request = co_await connection.awaitableReceiveHTTPRequest(); auto path = HTTPServer::parsePath(request); if (path == "/initial_request"_s) { co_await connection.awaitableSend(HTTPResponse(html).serialize()); continue; } if (path == "/second_request"_s) { secondRequest = WTFMove(request); co_await connection.awaitableSend(HTTPResponse(secondResponse).serialize()); continue; } EXPECT_FALSE(true); } }); auto webView = adoptNS([TestWKWebView new]); [webView synchronouslyLoadRequest:server.request("/initial_request"_s)]; __block bool done = false; RetainPtr postRequest = adoptNS([server.request("/second_request"_s) mutableCopy]); [postRequest setMainDocumentURL:postRequest.get().URL]; [postRequest setHTTPMethod:@"POST"]; auto requestBody = "request body"; [postRequest setHTTPBody:[NSData dataWithBytes:requestBody length:strlen(requestBody)]]; [webView _dataTaskWithRequest:postRequest.get() completionHandler:^(_WKDataTask *task) { auto delegate = adoptNS([TestDataTaskDelegate new]); task.delegate = delegate.get(); __block bool receivedResponse = false; delegate.get().didReceiveResponse = ^(_WKDataTask *, NSURLResponse *response, void (^decisionHandler)(_WKDataTaskResponsePolicy)) { EXPECT_WK_STREQ(response.URL.absoluteString, postRequest.get().URL.absoluteString); receivedResponse = true; decisionHandler(_WKDataTaskResponsePolicyAllow); }; __block bool receivedData = false; delegate.get().didReceiveData = ^(_WKDataTask *, NSData *data) { EXPECT_TRUE(receivedResponse); EXPECT_EQ(data.length, strlen(secondResponse)); EXPECT_TRUE(!memcmp(data.bytes, secondResponse, data.length)); receivedData = true; }; delegate.get().didCompleteWithError = ^(_WKDataTask *, NSError *error) { EXPECT_TRUE(receivedData); EXPECT_NULL(error); done = true; }; }]; Util::run(&done); EXPECT_TRUE(strnstr(secondRequest.data(), "Cookie: testkey=value\r\n", secondRequest.size())); EXPECT_WK_STREQ(HTTPServer::parseBody(secondRequest), requestBody); done = false; __block RetainPtr<_WKDataTask> retainedTask; [webView _dataTaskWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"blob:blank"]] completionHandler:^(_WKDataTask *task) { retainedTask = task; auto delegate = adoptNS([TestDataTaskDelegate new]); task.delegate = delegate.get(); __block bool receivedResponse = false; delegate.get().didReceiveResponse = ^(_WKDataTask *, NSURLResponse *response, void (^decisionHandler)(_WKDataTaskResponsePolicy)) { receivedResponse = true; }; __block bool receivedData = false; delegate.get().didReceiveData = ^(_WKDataTask *, NSData *data) { receivedData = true; }; delegate.get().didCompleteWithError = ^(_WKDataTask *task, NSError *error) { EXPECT_FALSE(receivedResponse); EXPECT_FALSE(receivedData); EXPECT_WK_STREQ(error.domain, NSURLErrorDomain); EXPECT_EQ(error.code, NSURLErrorUnsupportedURL); EXPECT_NOT_NULL(task.delegate); done = true; }; }]; Util::run(&done); EXPECT_NOT_NULL(retainedTask.get()); EXPECT_NULL(retainedTask.get().delegate); } TEST(_WKDataTask, Challenge) { using namespace TestWebKitAPI; HTTPServer server(HTTPServer::respondWithChallengeThenOK, HTTPServer::Protocol::Https); auto webView = adoptNS([TestWKWebView new]); __block bool done = false; [webView _dataTaskWithRequest:server.request() completionHandler:^(_WKDataTask *task) { auto delegate = adoptNS([TestDataTaskDelegate new]); task.delegate = delegate.get(); __block bool receivedServerTrustChallenge = false; __block bool receivedBasicAuthChallenge = false; delegate.get().didReceiveAuthenticationChallenge = ^(_WKDataTask *, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { EXPECT_FALSE(receivedBasicAuthChallenge); EXPECT_FALSE(receivedServerTrustChallenge); receivedServerTrustChallenge = true; completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]); return; } EXPECT_WK_STREQ(challenge.protectionSpace.authenticationMethod, NSURLAuthenticationMethodHTTPBasic); EXPECT_FALSE(receivedBasicAuthChallenge); EXPECT_TRUE(receivedServerTrustChallenge); receivedBasicAuthChallenge = true; completionHandler(NSURLSessionAuthChallengeUseCredential, nil); }; __block bool receivedData = false; delegate.get().didReceiveData = ^(_WKDataTask *, NSData *data) { const char* expectedResponse = ""; EXPECT_EQ(data.length, strlen(expectedResponse)); EXPECT_TRUE(!memcmp(data.bytes, expectedResponse, data.length)); receivedData = true; }; delegate.get().didCompleteWithError = ^(_WKDataTask *, NSError *error) { EXPECT_TRUE(receivedData); EXPECT_TRUE(receivedBasicAuthChallenge); EXPECT_TRUE(receivedServerTrustChallenge); EXPECT_NULL(error); done = true; }; }]; Util::run(&done); } void sendLoop(TestWebKitAPI::Connection connection, bool& sentWithError) { Vector bytes(1000, 0); connection.sendAndReportError(WTFMove(bytes), [&, connection] (bool sawError) { if (sawError) sentWithError = true; else sendLoop(connection, sentWithError); }); } TEST(_WKDataTask, Cancel) { using namespace TestWebKitAPI; bool sentWithError { false }; HTTPServer server([&] (Connection connection) { connection.receiveHTTPRequest([&, connection] (Vector&&) { constexpr auto header = "HTTP/1.1 200 OK\r\n\r\n"_s; connection.send(header, [&, connection] { sendLoop(connection, sentWithError); }); }); }); auto webView = adoptNS([WKWebView new]); [webView _dataTaskWithRequest:server.request() completionHandler:^(_WKDataTask *task) { auto delegate = adoptNS([TestDataTaskDelegate new]); task.delegate = delegate.get(); delegate.get().didReceiveResponse = ^(_WKDataTask *task, NSURLResponse *response, void (^decisionHandler)(_WKDataTaskResponsePolicy)) { decisionHandler(_WKDataTaskResponsePolicyAllow); dispatch_async(dispatch_get_main_queue(), ^{ EXPECT_NOT_NULL(task.delegate); [task cancel]; EXPECT_NULL(task.delegate); }); }; }]; Util::run(&sentWithError); __block bool completed { false }; [webView _dataTaskWithRequest:server.request() completionHandler:^(_WKDataTask *task) { auto delegate = adoptNS([TestDataTaskDelegate new]); task.delegate = delegate.get(); delegate.get().didReceiveResponse = ^(_WKDataTask *task, NSURLResponse *response, void (^decisionHandler)(_WKDataTaskResponsePolicy)) { decisionHandler(_WKDataTaskResponsePolicyCancel); }; delegate.get().didCompleteWithError = ^(_WKDataTask *, NSError *error) { EXPECT_WK_STREQ(error.domain, NSURLErrorDomain); EXPECT_EQ(error.code, NSURLErrorCancelled); completed = true; }; }]; Util::run(&completed); } TEST(_WKDataTask, Redirect) { using namespace TestWebKitAPI; HTTPServer server { { { "/"_s, { 301, { { "Location"_s, "/redirectTarget"_s }, { "Custom-Name"_s, "Custom-Value"_s } } } }, { "/redirectTarget"_s, { "hi"_s } }, } }; auto webView = adoptNS([WKWebView new]); RetainPtr serverRequest = server.request(); __block bool receivedData { false }; [webView _dataTaskWithRequest:serverRequest.get() completionHandler:^(_WKDataTask *task) { auto delegate = adoptNS([TestDataTaskDelegate new]); task.delegate = delegate.get(); delegate.get().willPerformHTTPRedirection = ^(_WKDataTask *task, NSHTTPURLResponse *response, NSURLRequest *request, void(^decisionHandler)(_WKDataTaskRedirectPolicy)) { EXPECT_WK_STREQ(serverRequest.get().URL.absoluteString, response.URL.absoluteString); EXPECT_WK_STREQ([serverRequest.get().URL.absoluteString stringByAppendingString:@"redirectTarget"], request.URL.absoluteString); EXPECT_WK_STREQ([response valueForHTTPHeaderField:@"Custom-Name"], "Custom-Value"); decisionHandler(_WKDataTaskRedirectPolicyAllow); }; delegate.get().didReceiveData = ^(_WKDataTask *, NSData *data) { EXPECT_EQ(data.length, strlen("hi")); EXPECT_TRUE(!memcmp(data.bytes, "hi", data.length)); receivedData = true; }; }]; Util::run(&receivedData); __block bool completed { false }; __block bool receivedResponse { false }; [webView _dataTaskWithRequest:serverRequest.get() completionHandler:^(_WKDataTask *task) { auto delegate = adoptNS([TestDataTaskDelegate new]); task.delegate = delegate.get(); delegate.get().willPerformHTTPRedirection = ^(_WKDataTask *task, NSHTTPURLResponse *response, NSURLRequest *request, void(^decisionHandler)(_WKDataTaskRedirectPolicy)) { decisionHandler(_WKDataTaskRedirectPolicyCancel); }; delegate.get().didReceiveResponse = ^(_WKDataTask *task, NSURLResponse *response, void (^decisionHandler)(_WKDataTaskResponsePolicy)) { EXPECT_WK_STREQ(response.URL.absoluteString, serverRequest.get().URL.absoluteString); EXPECT_EQ(((NSHTTPURLResponse *)response).statusCode, 301); receivedResponse = true; decisionHandler(_WKDataTaskResponsePolicyAllow); }; delegate.get().didCompleteWithError = ^(_WKDataTask *, NSError *error) { EXPECT_TRUE(receivedResponse); EXPECT_NULL(error); completed = true; }; }]; Util::run(&completed); } TEST(_WKDataTask, Crash) { using namespace TestWebKitAPI; HTTPServer server(HTTPServer::respondWithOK); auto webView = adoptNS([WKWebView new]); __block bool done = false; [webView _dataTaskWithRequest:server.request() completionHandler:^(_WKDataTask *task) { auto delegate = adoptNS([TestDataTaskDelegate new]); task.delegate = delegate.get(); delegate.get().didReceiveResponse = ^(_WKDataTask *task, NSURLResponse *response, void (^decisionHandler)(_WKDataTaskResponsePolicy)) { kill(webView.get().configuration.websiteDataStore._networkProcessIdentifier, SIGKILL); decisionHandler(_WKDataTaskResponsePolicyAllow); }; delegate.get().didCompleteWithError = ^(_WKDataTask *, NSError *error) { EXPECT_WK_STREQ(error.domain, WebKitErrorDomain); EXPECT_EQ(error.code, 300); done = true; }; }]; Util::run(&done); } #endif // HAVE(NSURLSESSION_TASK_DELEGATE)