/* * Copyright (C) 2016-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 "WebSocketTaskCocoa.h" #if HAVE(NSURLSESSION_WEBSOCKET) #import "NetworkSessionCocoa.h" #import "NetworkSocketChannel.h" #import #import #import #import #import #import namespace WebKit { using namespace WebCore; WebSocketTask::WebSocketTask(NetworkSocketChannel& channel, WebPageProxyIdentifier pageID, WeakPtr&& sessionSet, const WebCore::ResourceRequest& request, const WebCore::ClientOrigin& clientOrigin, RetainPtr&& task) : m_channel(channel) , m_task(WTFMove(task)) , m_pageID(pageID) , m_sessionSet(WTFMove(sessionSet)) , m_partition(request.cachePartition()) { // We use topOrigin in case of service worker websocket connections, for which pageID does not link to a real page. // In that case, let's only call the callback for same origin loads. if (clientOrigin.topOrigin == clientOrigin.clientOrigin) m_topOrigin = clientOrigin.topOrigin; readNextMessage(); m_channel.didSendHandshakeRequest(ResourceRequest { [m_task currentRequest] }); } WebSocketTask::~WebSocketTask() { } void WebSocketTask::readNextMessage() { [m_task receiveMessageWithCompletionHandler: makeBlockPtr([this, weakThis = WeakPtr { *this }](NSURLSessionWebSocketMessage* _Nullable message, NSError * _Nullable error) { if (!weakThis) return; if (error) { // If closeCode is not zero, we are closing the connection and didClose will be called for us. if ([m_task closeCode]) return; if (!m_receivedDidConnect) { ResourceResponse response { [m_task response] }; if (!response.isNull()) m_channel.didReceiveHandshakeResponse(WTFMove(response)); } m_channel.didReceiveMessageError([error localizedDescription]); didClose(WebCore::WebSocketChannel::CloseEventCodeAbnormalClosure, emptyString()); return; } if (message.type == NSURLSessionWebSocketMessageTypeString) m_channel.didReceiveText(message.string); else m_channel.didReceiveBinaryData(static_cast(message.data.bytes), message.data.length); readNextMessage(); }).get()]; } void WebSocketTask::cancel() { [m_task cancel]; } void WebSocketTask::resume() { [m_task resume]; } void WebSocketTask::didConnect(const String& protocol) { String extensionsValue; auto response = [m_task response]; if ([response isKindOfClass:[NSHTTPURLResponse class]]) extensionsValue = [(NSHTTPURLResponse *)response valueForHTTPHeaderField:@"Sec-WebSocket-Extensions"]; m_receivedDidConnect = true; m_channel.didConnect(protocol, extensionsValue); m_channel.didReceiveHandshakeResponse(ResourceResponse { [m_task response] }); } void WebSocketTask::didClose(unsigned short code, const String& reason) { if (m_receivedDidClose) return; m_receivedDidClose = true; m_channel.didClose(code, reason); } void WebSocketTask::sendString(const IPC::DataReference& utf8String, CompletionHandler&& callback) { auto text = adoptNS([[NSString alloc] initWithBytes:utf8String.data() length:utf8String.size() encoding:NSUTF8StringEncoding]); if (!text) { callback(); return; } auto message = adoptNS([[NSURLSessionWebSocketMessage alloc] initWithString:text.get()]); [m_task sendMessage:message.get() completionHandler:makeBlockPtr([callback = WTFMove(callback)](NSError * _Nullable) mutable { callback(); }).get()]; } void WebSocketTask::sendData(const IPC::DataReference& data, CompletionHandler&& callback) { auto nsData = adoptNS([[NSData alloc] initWithBytes:data.data() length:data.size()]); auto message = adoptNS([[NSURLSessionWebSocketMessage alloc] initWithData:nsData.get()]); [m_task sendMessage:message.get() completionHandler:makeBlockPtr([callback = WTFMove(callback)](NSError * _Nullable) mutable { callback(); }).get()]; } void WebSocketTask::close(int32_t code, const String& reason) { if (code == WebCore::WebSocketChannel::CloseEventCodeNotSpecified) code = NSURLSessionWebSocketCloseCodeInvalid; auto utf8 = reason.utf8(); auto nsData = adoptNS([[NSData alloc] initWithBytes:utf8.data() length:utf8.length()]); if ([m_task respondsToSelector:@selector(_sendCloseCode:reason:)]) { [m_task _sendCloseCode:(NSURLSessionWebSocketCloseCode)code reason:nsData.get()]; return; } [m_task cancelWithCloseCode:(NSURLSessionWebSocketCloseCode)code reason:nsData.get()]; } WebSocketTask::TaskIdentifier WebSocketTask::identifier() const { return [m_task taskIdentifier]; } NetworkSessionCocoa* WebSocketTask::networkSession() { return static_cast(m_channel.session()); } } #endif