/* * Copyright (C) 2021 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 "TestUIDelegate.h" #import #import #import #import namespace TestWebKitAPI { TEST(WebSocket, LongMessageNoDeflate) { // https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 constexpr size_t headerSizeWithLargePayloadLength = 10; constexpr size_t maskingKeySize = 4; constexpr uint8_t payloadLengthIndicatingLargeExtendedPayloadLength = 127; constexpr uint8_t fin = 0x80; constexpr uint8_t textFrame = 0x1; constexpr uint64_t twoMegabytes = 1024 * 1024 * 2; constexpr size_t expectedReceiveSize = twoMegabytes + headerSizeWithLargePayloadLength + maskingKeySize; HTTPServer server([=](Connection connection) { connection.webSocketHandshake([=] { connection.receiveBytes([=] (Vector&& received) { constexpr size_t headerSize = headerSizeWithLargePayloadLength + maskingKeySize; EXPECT_EQ(expectedReceiveSize, received.size()); std::array expectedHeader { 0x81, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, }; for (size_t i = 0 ; i < headerSizeWithLargePayloadLength; i++) EXPECT_EQ(received[i], expectedHeader[i]); std::array mask { received[10], received[11], received[12], received[13] }; for (size_t i = headerSize; i < expectedReceiveSize; i++) EXPECT_EQ(received[i] ^ mask[(i - headerSize) % maskingKeySize], 'x'); Vector bytesToSend; bytesToSend.reserveInitialCapacity(twoMegabytes + headerSizeWithLargePayloadLength); bytesToSend.uncheckedAppend(fin | textFrame); bytesToSend.uncheckedAppend(payloadLengthIndicatingLargeExtendedPayloadLength); for (size_t i = 0; i < 8; i++) bytesToSend.uncheckedAppend((twoMegabytes >> (8 * (7 - i))) & 0xFF); for (size_t i = 0; i < twoMegabytes; i++) bytesToSend.uncheckedAppend('x'); connection.send(WTFMove(bytesToSend)); }, expectedReceiveSize); }); }); NSString *html = [NSString stringWithFormat:@"" "", server.port()]; auto webView = adoptNS([WKWebView new]); [webView loadHTMLString:html baseURL:nil]; EXPECT_WK_STREQ([webView _test_waitForAlert], "PASS"); } TEST(WebSocket, PageWithAttributedBundleIdentifierDestroyed) { HTTPServer server([](Connection connection) { connection.webSocketHandshake(); }); NSString *html = [NSString stringWithFormat:@"" "", server.port()]; pid_t originalNetworkProcessPID { 0 }; auto configuration = adoptNS([WKWebViewConfiguration new]); __block size_t webSocketsConnected { 0 }; auto delegate = adoptNS([TestUIDelegate new]); delegate.get().runJavaScriptAlertPanelWithMessage = ^(WKWebView *, NSString *message, WKFrameInfo *, void (^completionHandler)(void)) { EXPECT_WK_STREQ(message, "opened successfully"); webSocketsConnected++; completionHandler(); }; constexpr size_t viewCount = 20; @autoreleasepool { std::array, viewCount> views; for (size_t i = 0; i < viewCount; i++) { configuration.get()._attributedBundleIdentifier = [NSString stringWithFormat:@"test.bundle.identifier.%zu", i]; views[i] = adoptNS([[WKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100) configuration:configuration.get()]); views[i].get().UIDelegate = delegate.get(); [views[i] loadHTMLString:html baseURL:nil]; } while (webSocketsConnected < viewCount) Util::spinRunLoop(); originalNetworkProcessPID = configuration.get().websiteDataStore._networkProcessIdentifier; } Util::spinRunLoop(viewCount * 3); EXPECT_EQ(originalNetworkProcessPID, configuration.get().websiteDataStore._networkProcessIdentifier); } #if HAVE(NSURLSESSION_WEBSOCKET) TEST(WebSocket, CloseCode) { bool receivedWebSocketClose { false }; Vector closeData; HTTPServer webSocketServer([&](Connection connection) { connection.webSocketHandshake([&, connection] { connection.receiveBytes([&, connection](Vector&& v) { // https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 constexpr uint8_t connectionCloseOpcode { 0x08 }; constexpr uint8_t opcodeMask { 0x08 }; constexpr uint8_t maskMask { 0x80 }; constexpr uint8_t payloadLengthMask { 0x7F }; constexpr uint8_t headerSize { 6 }; ASSERT_GT(v.size(), 1u); EXPECT_EQ(v[0] & opcodeMask, connectionCloseOpcode); EXPECT_TRUE(v[1] & maskMask); size_t length = v[1] & payloadLengthMask; ASSERT_EQ(length, v.size() - headerSize); std::array mask { v[2], v[3], v[4], v[5] }; for (size_t i = headerSize; i < v.size(); i++) closeData.append(v[i] ^ mask[(i - headerSize) % 4]); receivedWebSocketClose = true; connection.receiveBytes([](Vector&& v) { EXPECT_EQ(v.size(), 0u); }); }); }); }); auto htmlWithOnOpen = [&] (const char* onopen) { return [NSString stringWithFormat:@"" "", webSocketServer.port(), onopen]; }; HTTPServer httpServer({ { "/navigateAway"_s, { htmlWithOnOpen("window.location = '/navigationTarget'") }}, { "/navigationTarget"_s, { "hi"_s } }, { "/closeCustomCode"_s, { htmlWithOnOpen("ws.close(3000)") } }, { "/closeNoArguments"_s, { htmlWithOnOpen("ws.close()") } }, { "/closeBothParameters"_s, { htmlWithOnOpen("ws.close(3001, 'custom reason')") } }, }); auto appendString = [] (Vector& vector, const char* string) { auto length = strlen(string); for (size_t i = 0; i < length; i++) vector.append(string[i]); }; auto webView = adoptNS([WKWebView new]); [webView loadRequest:httpServer.request("/navigateAway"_s)]; Util::run(&receivedWebSocketClose); Vector expected { 0x3, 0xe9 }; // NSURLSessionWebSocketCloseCodeGoingAway appendString(expected, "WebSocket is closed due to suspension."); EXPECT_EQ(closeData, expected); receivedWebSocketClose = false; closeData = { }; expected = { 0xb, 0xb8 }; // 3000 [webView loadRequest:httpServer.request("/closeCustomCode"_s)]; Util::run(&receivedWebSocketClose); EXPECT_EQ(closeData, expected); receivedWebSocketClose = false; closeData = { }; expected = { }; [webView loadRequest:httpServer.request("/closeNoArguments"_s)]; Util::run(&receivedWebSocketClose); EXPECT_EQ(closeData, expected); receivedWebSocketClose = false; closeData = { }; expected = { 0xb, 0xb9 }; // 3001 appendString(expected, "custom reason"); [webView loadRequest:httpServer.request("/closeBothParameters"_s)]; Util::run(&receivedWebSocketClose); EXPECT_EQ(closeData, expected); } #endif // HAVE(NSURLSESSION_WEBSOCKET) TEST(WebSocket, BlockedWithSubresources) { HTTPServer server([](Connection connection) { connection.webSocketHandshake(); }); NSString *html = [NSString stringWithFormat:@"" "", server.port()]; { auto configuration = adoptNS([WKWebViewConfiguration new]); configuration.get()._loadsSubresources = NO; auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]); [webView loadHTMLString:html baseURL:nil]; EXPECT_WK_STREQ([webView _test_waitForAlert], "FAIL - error undefined"); } auto webView = adoptNS([WKWebView new]); [webView loadHTMLString:html baseURL:nil]; EXPECT_WK_STREQ([webView _test_waitForAlert], "opened successfully"); } }