diff options
author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/websockets | |
parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
download | external_webkit-cad810f21b803229eb11403f9209855525a25d57.zip external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2 |
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/websockets')
17 files changed, 3047 insertions, 0 deletions
diff --git a/Source/WebCore/websockets/ThreadableWebSocketChannel.cpp b/Source/WebCore/websockets/ThreadableWebSocketChannel.cpp new file mode 100644 index 0000000..28b9263 --- /dev/null +++ b/Source/WebCore/websockets/ThreadableWebSocketChannel.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#include "config.h" + +#if ENABLE(WEB_SOCKETS) + +#include "ThreadableWebSocketChannel.h" + +#include "PlatformString.h" +#include "ScriptExecutionContext.h" +#include "ThreadableWebSocketChannelClientWrapper.h" +#include "WebSocketChannel.h" +#include "WebSocketChannelClient.h" +#include "WorkerContext.h" +#include "WorkerRunLoop.h" +#include "WorkerThread.h" +#include "WorkerThreadableWebSocketChannel.h" + +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +static const char webSocketChannelMode[] = "webSocketChannelMode"; + +PassRefPtr<ThreadableWebSocketChannel> ThreadableWebSocketChannel::create(ScriptExecutionContext* context, WebSocketChannelClient* client, const KURL& url, const String& protocol) +{ + ASSERT(context); + ASSERT(client); + +#if ENABLE(WORKERS) + if (context->isWorkerContext()) { + WorkerContext* workerContext = static_cast<WorkerContext*>(context); + WorkerRunLoop& runLoop = workerContext->thread()->runLoop(); + String mode = webSocketChannelMode; + mode.append(String::number(runLoop.createUniqueId())); + return WorkerThreadableWebSocketChannel::create(workerContext, client, mode, url, protocol); + } +#endif // ENABLE(WORKERS) + + ASSERT(context->isDocument()); + return WebSocketChannel::create(context, client, url, protocol); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) diff --git a/Source/WebCore/websockets/ThreadableWebSocketChannel.h b/Source/WebCore/websockets/ThreadableWebSocketChannel.h new file mode 100644 index 0000000..05b3767 --- /dev/null +++ b/Source/WebCore/websockets/ThreadableWebSocketChannel.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef ThreadableWebSocketChannel_h +#define ThreadableWebSocketChannel_h + +#if ENABLE(WEB_SOCKETS) + +#include <wtf/Forward.h> +#include <wtf/Noncopyable.h> +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +class KURL; +class ScriptExecutionContext; +class WebSocketChannelClient; + +class ThreadableWebSocketChannel : public Noncopyable { +public: + static PassRefPtr<ThreadableWebSocketChannel> create(ScriptExecutionContext*, WebSocketChannelClient*, const KURL&, const String& protocol); + + virtual void connect() = 0; + virtual bool send(const String& message) = 0; + virtual unsigned long bufferedAmount() const = 0; + virtual void close() = 0; + virtual void disconnect() = 0; // Will suppress didClose(). + + virtual void suspend() = 0; + virtual void resume() = 0; + + void ref() { refThreadableWebSocketChannel(); } + void deref() { derefThreadableWebSocketChannel(); } + +protected: + virtual ~ThreadableWebSocketChannel() { } + virtual void refThreadableWebSocketChannel() = 0; + virtual void derefThreadableWebSocketChannel() = 0; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // ThreadableWebSocketChannel_h diff --git a/Source/WebCore/websockets/ThreadableWebSocketChannelClientWrapper.h b/Source/WebCore/websockets/ThreadableWebSocketChannelClientWrapper.h new file mode 100644 index 0000000..4099615 --- /dev/null +++ b/Source/WebCore/websockets/ThreadableWebSocketChannelClientWrapper.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef ThreadableWebSocketChannelClientWrapper_h +#define ThreadableWebSocketChannelClientWrapper_h + +#if ENABLE(WEB_SOCKETS) + +#include "PlatformString.h" +#include "WebSocketChannelClient.h" +#include <wtf/Forward.h> +#include <wtf/PassRefPtr.h> +#include <wtf/Threading.h> +#include <wtf/Vector.h> + +namespace WebCore { + +class ThreadableWebSocketChannelClientWrapper : public ThreadSafeShared<ThreadableWebSocketChannelClientWrapper> { +public: + static PassRefPtr<ThreadableWebSocketChannelClientWrapper> create(WebSocketChannelClient* client) + { + return adoptRef(new ThreadableWebSocketChannelClientWrapper(client)); + } + + void clearSyncMethodDone() + { + m_syncMethodDone = false; + } + void setSyncMethodDone() + { + m_syncMethodDone = true; + } + + bool syncMethodDone() const + { + return m_syncMethodDone; + } + + bool sent() const + { + return m_sent; + } + void setSent(bool sent) + { + m_sent = sent; + m_syncMethodDone = true; + } + + unsigned long bufferedAmount() const + { + return m_bufferedAmount; + } + void setBufferedAmount(unsigned long bufferedAmount) + { + m_bufferedAmount = bufferedAmount; + m_syncMethodDone = true; + } + + void clearClient() + { + m_client = 0; + } + + void didConnect() + { + m_pendingConnected = true; + if (!m_suspended) + processPendingEvents(); + } + + void didReceiveMessage(const String& msg) + { + m_pendingMessages.append(msg); + if (!m_suspended) + processPendingEvents(); + } + + void didClose(unsigned long unhandledBufferedAmount) + { + m_pendingClosed = true; + m_bufferedAmount = unhandledBufferedAmount; + if (!m_suspended) + processPendingEvents(); + } + + void suspend() + { + m_suspended = true; + } + + void resume() + { + m_suspended = false; + processPendingEvents(); + } + +protected: + ThreadableWebSocketChannelClientWrapper(WebSocketChannelClient* client) + : m_client(client) + , m_syncMethodDone(false) + , m_sent(false) + , m_bufferedAmount(0) + , m_suspended(false) + , m_pendingConnected(false) + , m_pendingClosed(false) + { + } + + void processPendingEvents() + { + ASSERT(!m_suspended); + if (m_pendingConnected) { + m_pendingConnected = false; + if (m_client) + m_client->didConnect(); + } + + Vector<String> messages; + messages.swap(m_pendingMessages); + for (Vector<String>::const_iterator iter = messages.begin(); iter != messages.end(); ++iter) { + if (m_client) + m_client->didReceiveMessage(*iter); + } + + if (m_pendingClosed) { + m_pendingClosed = false; + if (m_client) + m_client->didClose(m_bufferedAmount); + } + } + + WebSocketChannelClient* m_client; + bool m_syncMethodDone; + bool m_sent; + unsigned long m_bufferedAmount; + bool m_suspended; + bool m_pendingConnected; + Vector<String> m_pendingMessages; + bool m_pendingClosed; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // ThreadableWebSocketChannelClientWrapper_h diff --git a/Source/WebCore/websockets/WebSocket.cpp b/Source/WebCore/websockets/WebSocket.cpp new file mode 100644 index 0000000..358a742 --- /dev/null +++ b/Source/WebCore/websockets/WebSocket.cpp @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#include "config.h" + +#if ENABLE(WEB_SOCKETS) + +#include "WebSocket.h" + +#include "DOMWindow.h" +#include "Event.h" +#include "EventException.h" +#include "EventListener.h" +#include "EventNames.h" +#include "Logging.h" +#include "MessageEvent.h" +#include "ScriptExecutionContext.h" +#include "ThreadableWebSocketChannel.h" +#include "WebSocketChannel.h" +#include <wtf/StdLibExtras.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> +#include <wtf/text/StringConcatenate.h> + +namespace WebCore { + +static bool isValidProtocolString(const String& protocol) +{ + if (protocol.isNull()) + return true; + if (protocol.isEmpty()) + return false; + const UChar* characters = protocol.characters(); + for (size_t i = 0; i < protocol.length(); i++) { + if (characters[i] < 0x20 || characters[i] > 0x7E) + return false; + } + return true; +} + +static String encodeProtocolString(const String& protocol) +{ + StringBuilder builder; + for (size_t i = 0; i < protocol.length(); i++) { + if (protocol[i] < 0x20 || protocol[i] > 0x7E) + builder.append(String::format("\\u%04X", protocol[i])); + else if (protocol[i] == 0x5c) + builder.append("\\\\"); + else + builder.append(protocol[i]); + } + return builder.toString(); +} + +static bool webSocketsAvailable = false; + +void WebSocket::setIsAvailable(bool available) +{ + webSocketsAvailable = available; +} + +bool WebSocket::isAvailable() +{ + return webSocketsAvailable; +} + +WebSocket::WebSocket(ScriptExecutionContext* context) + : ActiveDOMObject(context, this) + , m_state(CONNECTING) + , m_bufferedAmountAfterClose(0) +{ +} + +WebSocket::~WebSocket() +{ + if (m_channel) + m_channel->disconnect(); +} + +void WebSocket::connect(const KURL& url, ExceptionCode& ec) +{ + connect(url, String(), ec); +} + +void WebSocket::connect(const KURL& url, const String& protocol, ExceptionCode& ec) +{ + LOG(Network, "WebSocket %p connect to %s protocol=%s", this, url.string().utf8().data(), protocol.utf8().data()); + m_url = url; + m_protocol = protocol; + + if (!m_url.isValid()) { + scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Invalid url for WebSocket " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString()); + m_state = CLOSED; + ec = SYNTAX_ERR; + return; + } + + if (!m_url.protocolIs("ws") && !m_url.protocolIs("wss")) { + scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Wrong url scheme for WebSocket " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString()); + m_state = CLOSED; + ec = SYNTAX_ERR; + return; + } + if (m_url.hasFragmentIdentifier()) { + scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "URL has fragment component " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString()); + m_state = CLOSED; + ec = SYNTAX_ERR; + return; + } + if (!isValidProtocolString(m_protocol)) { + scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Wrong protocol for WebSocket '" + encodeProtocolString(m_protocol) + "'", 0, scriptExecutionContext()->securityOrigin()->toString()); + m_state = CLOSED; + ec = SYNTAX_ERR; + return; + } + if (!portAllowed(url)) { + scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, makeString("WebSocket port ", String::number(url.port()), " blocked"), 0, scriptExecutionContext()->securityOrigin()->toString()); + m_state = CLOSED; + ec = SECURITY_ERR; + return; + } + + m_channel = ThreadableWebSocketChannel::create(scriptExecutionContext(), this, m_url, m_protocol); + m_channel->connect(); + ActiveDOMObject::setPendingActivity(this); +} + +bool WebSocket::send(const String& message, ExceptionCode& ec) +{ + LOG(Network, "WebSocket %p send %s", this, message.utf8().data()); + if (m_state == CONNECTING) { + ec = INVALID_STATE_ERR; + return false; + } + // No exception is raised if the connection was once established but has subsequently been closed. + if (m_state == CLOSED) { + m_bufferedAmountAfterClose += message.utf8().length() + 2; // 2 for frameing + return false; + } + // FIXME: check message is valid utf8. + ASSERT(m_channel); + return m_channel->send(message); +} + +void WebSocket::close() +{ + LOG(Network, "WebSocket %p close", this); + if (m_state == CLOSED) + return; + m_state = CLOSED; + m_bufferedAmountAfterClose = m_channel->bufferedAmount(); + // didClose notification may be already queued, which we will inadvertently process while waiting for bufferedAmount() to return. + // In this case m_channel will be set to null during didClose() call, thus we need to test validness of m_channel here. + if (m_channel) + m_channel->close(); +} + +const KURL& WebSocket::url() const +{ + return m_url; +} + +WebSocket::State WebSocket::readyState() const +{ + return m_state; +} + +unsigned long WebSocket::bufferedAmount() const +{ + if (m_state == OPEN) + return m_channel->bufferedAmount(); + return m_bufferedAmountAfterClose; +} + +ScriptExecutionContext* WebSocket::scriptExecutionContext() const +{ + return ActiveDOMObject::scriptExecutionContext(); +} + +void WebSocket::contextDestroyed() +{ + LOG(Network, "WebSocket %p scriptExecutionContext destroyed", this); + ASSERT(!m_channel); + ASSERT(m_state == CLOSED); + ActiveDOMObject::contextDestroyed(); +} + +bool WebSocket::canSuspend() const +{ + return !m_channel; +} + +void WebSocket::suspend(ReasonForSuspension) +{ + if (m_channel) + m_channel->suspend(); +} + +void WebSocket::resume() +{ + if (m_channel) + m_channel->resume(); +} + +void WebSocket::stop() +{ + bool pending = hasPendingActivity(); + if (m_channel) + m_channel->disconnect(); + m_channel = 0; + m_state = CLOSED; + ActiveDOMObject::stop(); + if (pending) + ActiveDOMObject::unsetPendingActivity(this); +} + +void WebSocket::didConnect() +{ + LOG(Network, "WebSocket %p didConnect", this); + if (m_state != CONNECTING) { + didClose(0); + return; + } + ASSERT(scriptExecutionContext()); + m_state = OPEN; + dispatchEvent(Event::create(eventNames().openEvent, false, false)); +} + +void WebSocket::didReceiveMessage(const String& msg) +{ + LOG(Network, "WebSocket %p didReceiveMessage %s", this, msg.utf8().data()); + if (m_state != OPEN) + return; + ASSERT(scriptExecutionContext()); + RefPtr<MessageEvent> evt = MessageEvent::create(); + evt->initMessageEvent(eventNames().messageEvent, false, false, SerializedScriptValue::create(msg), "", "", 0, 0); + dispatchEvent(evt); +} + +void WebSocket::didReceiveMessageError() +{ + LOG(Network, "WebSocket %p didReceiveErrorMessage", this); + if (m_state != OPEN) + return; + ASSERT(scriptExecutionContext()); + dispatchEvent(Event::create(eventNames().errorEvent, false, false)); +} + +void WebSocket::didClose(unsigned long unhandledBufferedAmount) +{ + LOG(Network, "WebSocket %p didClose", this); + if (!m_channel) + return; + m_state = CLOSED; + m_bufferedAmountAfterClose += unhandledBufferedAmount; + ASSERT(scriptExecutionContext()); + dispatchEvent(Event::create(eventNames().closeEvent, false, false)); + m_channel = 0; + if (hasPendingActivity()) + ActiveDOMObject::unsetPendingActivity(this); +} + +EventTargetData* WebSocket::eventTargetData() +{ + return &m_eventTargetData; +} + +EventTargetData* WebSocket::ensureEventTargetData() +{ + return &m_eventTargetData; +} + +} // namespace WebCore + +#endif diff --git a/Source/WebCore/websockets/WebSocket.h b/Source/WebCore/websockets/WebSocket.h new file mode 100644 index 0000000..7f63cbf --- /dev/null +++ b/Source/WebCore/websockets/WebSocket.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef WebSocket_h +#define WebSocket_h + +#if ENABLE(WEB_SOCKETS) + +#include "ActiveDOMObject.h" +#include "EventListener.h" +#include "EventNames.h" +#include "EventTarget.h" +#include "KURL.h" +#include "WebSocketChannelClient.h" +#include <wtf/Forward.h> +#include <wtf/OwnPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/text/AtomicStringHash.h> + +namespace WebCore { + + class ThreadableWebSocketChannel; + + class WebSocket : public RefCounted<WebSocket>, public EventTarget, public ActiveDOMObject, public WebSocketChannelClient { + public: + static void setIsAvailable(bool); + static bool isAvailable(); + static PassRefPtr<WebSocket> create(ScriptExecutionContext* context) { return adoptRef(new WebSocket(context)); } + virtual ~WebSocket(); + + enum State { + CONNECTING = 0, + OPEN = 1, + CLOSED = 2 + }; + + void connect(const KURL&, ExceptionCode&); + void connect(const KURL&, const String& protocol, ExceptionCode&); + + bool send(const String& message, ExceptionCode&); + + void close(); + + const KURL& url() const; + State readyState() const; + unsigned long bufferedAmount() const; + + DEFINE_ATTRIBUTE_EVENT_LISTENER(open); + DEFINE_ATTRIBUTE_EVENT_LISTENER(message); + DEFINE_ATTRIBUTE_EVENT_LISTENER(error); + DEFINE_ATTRIBUTE_EVENT_LISTENER(close); + + // EventTarget + virtual WebSocket* toWebSocket() { return this; } + + virtual ScriptExecutionContext* scriptExecutionContext() const; + virtual void contextDestroyed(); + virtual bool canSuspend() const; + virtual void suspend(ReasonForSuspension); + virtual void resume(); + virtual void stop(); + + using RefCounted<WebSocket>::ref; + using RefCounted<WebSocket>::deref; + + // WebSocketChannelClient + virtual void didConnect(); + virtual void didReceiveMessage(const String& message); + virtual void didReceiveMessageError(); + virtual void didClose(unsigned long unhandledBufferedAmount); + + private: + WebSocket(ScriptExecutionContext*); + + virtual void refEventTarget() { ref(); } + virtual void derefEventTarget() { deref(); } + virtual EventTargetData* eventTargetData(); + virtual EventTargetData* ensureEventTargetData(); + + RefPtr<ThreadableWebSocketChannel> m_channel; + + State m_state; + KURL m_url; + String m_protocol; + EventTargetData m_eventTargetData; + unsigned long m_bufferedAmountAfterClose; + }; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocket_h diff --git a/Source/WebCore/websockets/WebSocket.idl b/Source/WebCore/websockets/WebSocket.idl new file mode 100644 index 0000000..6850d36 --- /dev/null +++ b/Source/WebCore/websockets/WebSocket.idl @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2009 Google Inc. All rights reserved. + * Copyright (C) 2010 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +module websockets { + + interface [ + Conditional=WEB_SOCKETS, + CanBeConstructed, + CustomConstructFunction, + ConstructorParameters=1, + V8CustomConstructor, + EventTarget, + NoStaticTables + ] WebSocket { + readonly attribute DOMString URL; + + // ready state + const unsigned short CONNECTING = 0; + const unsigned short OPEN = 1; + const unsigned short CLOSED = 2; + readonly attribute unsigned short readyState; + + readonly attribute unsigned long bufferedAmount; + + // networking + attribute EventListener onopen; + attribute EventListener onmessage; + attribute EventListener onerror; + attribute EventListener onclose; + + [RequiresAllArguments] boolean send(in DOMString data) raises(DOMException); + void close(); + + // EventTarget interface + void addEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + void removeEventListener(in DOMString type, + in EventListener listener, + in boolean useCapture); + boolean dispatchEvent(in Event evt) + raises(EventException); + }; +} diff --git a/Source/WebCore/websockets/WebSocketChannel.cpp b/Source/WebCore/websockets/WebSocketChannel.cpp new file mode 100644 index 0000000..09fcd6b --- /dev/null +++ b/Source/WebCore/websockets/WebSocketChannel.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#include "config.h" + +#if ENABLE(WEB_SOCKETS) + +#include "WebSocketChannel.h" + +#include "CookieJar.h" +#include "Document.h" +#include "InspectorInstrumentation.h" +#include "Logging.h" +#include "Page.h" +#include "PlatformString.h" +#include "ProgressTracker.h" +#include "ScriptExecutionContext.h" +#include "SocketStreamError.h" +#include "SocketStreamHandle.h" +#include "WebSocketChannelClient.h" +#include "WebSocketHandshake.h" + +#include <wtf/text/CString.h> +#include <wtf/text/StringConcatenate.h> +#include <wtf/text/StringHash.h> +#include <wtf/Deque.h> +#include <wtf/FastMalloc.h> +#include <wtf/HashMap.h> + +namespace WebCore { + +WebSocketChannel::WebSocketChannel(ScriptExecutionContext* context, WebSocketChannelClient* client, const KURL& url, const String& protocol) + : m_context(context) + , m_client(client) + , m_handshake(url, protocol, context) + , m_buffer(0) + , m_bufferSize(0) + , m_resumeTimer(this, &WebSocketChannel::resumeTimerFired) + , m_suspended(false) + , m_closed(false) + , m_shouldDiscardReceivedData(false) + , m_unhandledBufferedAmount(0) + , m_identifier(0) +{ + if (m_context->isDocument()) + if (Page* page = static_cast<Document*>(m_context)->page()) + m_identifier = page->progress()->createUniqueIdentifier(); + + if (m_identifier) + InspectorInstrumentation::didCreateWebSocket(m_context, m_identifier, url, m_context->url()); +} + +WebSocketChannel::~WebSocketChannel() +{ + fastFree(m_buffer); +} + +void WebSocketChannel::connect() +{ + LOG(Network, "WebSocketChannel %p connect", this); + ASSERT(!m_handle); + ASSERT(!m_suspended); + m_handshake.reset(); + ref(); + m_handle = SocketStreamHandle::create(m_handshake.url(), this); +} + +bool WebSocketChannel::send(const String& msg) +{ + LOG(Network, "WebSocketChannel %p send %s", this, msg.utf8().data()); + ASSERT(m_handle); + ASSERT(!m_suspended); + Vector<char> buf; + buf.append('\0'); // frame type + CString utf8 = msg.utf8(); + buf.append(utf8.data(), utf8.length()); + buf.append('\xff'); // frame end + return m_handle->send(buf.data(), buf.size()); +} + +unsigned long WebSocketChannel::bufferedAmount() const +{ + LOG(Network, "WebSocketChannel %p bufferedAmount", this); + ASSERT(m_handle); + ASSERT(!m_suspended); + return m_handle->bufferedAmount(); +} + +void WebSocketChannel::close() +{ + LOG(Network, "WebSocketChannel %p close", this); + ASSERT(!m_suspended); + if (m_handle) + m_handle->close(); // will call didClose() +} + +void WebSocketChannel::disconnect() +{ + LOG(Network, "WebSocketChannel %p disconnect", this); + if (m_identifier && m_context) + InspectorInstrumentation::didCloseWebSocket(m_context, m_identifier); + m_handshake.clearScriptExecutionContext(); + m_client = 0; + m_context = 0; + if (m_handle) + m_handle->close(); +} + +void WebSocketChannel::suspend() +{ + m_suspended = true; +} + +void WebSocketChannel::resume() +{ + m_suspended = false; + if ((m_buffer || m_closed) && m_client && !m_resumeTimer.isActive()) + m_resumeTimer.startOneShot(0); +} + +void WebSocketChannel::didOpen(SocketStreamHandle* handle) +{ + LOG(Network, "WebSocketChannel %p didOpen", this); + ASSERT(handle == m_handle); + if (!m_context) + return; + if (m_identifier) + InspectorInstrumentation::willSendWebSocketHandshakeRequest(m_context, m_identifier, m_handshake.clientHandshakeRequest()); + CString handshakeMessage = m_handshake.clientHandshakeMessage(); + if (!handle->send(handshakeMessage.data(), handshakeMessage.length())) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error sending handshake message.", 0, m_handshake.clientOrigin()); + handle->close(); + } +} + +void WebSocketChannel::didClose(SocketStreamHandle* handle) +{ + LOG(Network, "WebSocketChannel %p didClose", this); + if (m_identifier && m_context) + InspectorInstrumentation::didCloseWebSocket(m_context, m_identifier); + ASSERT_UNUSED(handle, handle == m_handle || !m_handle); + m_closed = true; + if (m_handle) { + m_unhandledBufferedAmount = m_handle->bufferedAmount(); + if (m_suspended) + return; + WebSocketChannelClient* client = m_client; + m_client = 0; + m_context = 0; + m_handle = 0; + if (client) + client->didClose(m_unhandledBufferedAmount); + } + deref(); +} + +void WebSocketChannel::didReceiveData(SocketStreamHandle* handle, const char* data, int len) +{ + LOG(Network, "WebSocketChannel %p didReceiveData %d", this, len); + RefPtr<WebSocketChannel> protect(this); // The client can close the channel, potentially removing the last reference. + ASSERT(handle == m_handle); + if (!m_context) { + return; + } + if (!m_client) { + m_shouldDiscardReceivedData = true; + handle->close(); + return; + } + if (m_shouldDiscardReceivedData) + return; + if (!appendToBuffer(data, len)) { + m_shouldDiscardReceivedData = true; + handle->close(); + return; + } + while (!m_suspended && m_client && m_buffer) + if (!processBuffer()) + break; +} + +void WebSocketChannel::didFail(SocketStreamHandle* handle, const SocketStreamError& error) +{ + LOG(Network, "WebSocketChannel %p didFail", this); + ASSERT(handle == m_handle || !m_handle); + if (m_context) { + String message; + if (error.isNull()) + message = "WebSocket network error"; + else if (error.localizedDescription().isNull()) + message = makeString("WebSocket network error: error code ", String::number(error.errorCode())); + else + message = makeString("WebSocket network error: ", error.localizedDescription()); + m_context->addMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, 0, error.failingURL()); + } + m_shouldDiscardReceivedData = true; + handle->close(); +} + +void WebSocketChannel::didReceiveAuthenticationChallenge(SocketStreamHandle*, const AuthenticationChallenge&) +{ +} + +void WebSocketChannel::didCancelAuthenticationChallenge(SocketStreamHandle*, const AuthenticationChallenge&) +{ +} + +bool WebSocketChannel::appendToBuffer(const char* data, size_t len) +{ + size_t newBufferSize = m_bufferSize + len; + if (newBufferSize < m_bufferSize) { + LOG(Network, "WebSocket buffer overflow (%lu+%lu)", static_cast<unsigned long>(m_bufferSize), static_cast<unsigned long>(len)); + return false; + } + char* newBuffer = 0; + if (tryFastMalloc(newBufferSize).getValue(newBuffer)) { + if (m_buffer) + memcpy(newBuffer, m_buffer, m_bufferSize); + memcpy(newBuffer + m_bufferSize, data, len); + fastFree(m_buffer); + m_buffer = newBuffer; + m_bufferSize = newBufferSize; + return true; + } + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, makeString("WebSocket frame (at ", String::number(static_cast<unsigned long>(newBufferSize)), " bytes) is too long."), 0, m_handshake.clientOrigin()); + return false; +} + +void WebSocketChannel::skipBuffer(size_t len) +{ + ASSERT(len <= m_bufferSize); + m_bufferSize -= len; + if (!m_bufferSize) { + fastFree(m_buffer); + m_buffer = 0; + return; + } + memmove(m_buffer, m_buffer + len, m_bufferSize); +} + +bool WebSocketChannel::processBuffer() +{ + ASSERT(!m_suspended); + ASSERT(m_client); + ASSERT(m_buffer); + if (m_shouldDiscardReceivedData) + return false; + + if (m_handshake.mode() == WebSocketHandshake::Incomplete) { + int headerLength = m_handshake.readServerHandshake(m_buffer, m_bufferSize); + if (headerLength <= 0) + return false; + if (m_handshake.mode() == WebSocketHandshake::Connected) { + if (m_identifier) + InspectorInstrumentation::didReceiveWebSocketHandshakeResponse(m_context, m_identifier, m_handshake.serverHandshakeResponse()); + if (!m_handshake.serverSetCookie().isEmpty()) { + if (m_context->isDocument()) { + Document* document = static_cast<Document*>(m_context); + if (cookiesEnabled(document)) { + ExceptionCode ec; // Exception (for sandboxed documents) ignored. + document->setCookie(m_handshake.serverSetCookie(), ec); + } + } + } + // FIXME: handle set-cookie2. + LOG(Network, "WebSocketChannel %p connected", this); + skipBuffer(headerLength); + m_client->didConnect(); + LOG(Network, "remaining in read buf %lu", static_cast<unsigned long>(m_bufferSize)); + return m_buffer; + } + LOG(Network, "WebSocketChannel %p connection failed", this); + skipBuffer(headerLength); + m_shouldDiscardReceivedData = true; + if (!m_closed) + m_handle->close(); + return false; + } + if (m_handshake.mode() != WebSocketHandshake::Connected) + return false; + + const char* nextFrame = m_buffer; + const char* p = m_buffer; + const char* end = p + m_bufferSize; + + unsigned char frameByte = static_cast<unsigned char>(*p++); + if ((frameByte & 0x80) == 0x80) { + size_t length = 0; + bool errorFrame = false; + while (p < end) { + if (length > std::numeric_limits<size_t>::max() / 128) { + LOG(Network, "frame length overflow %lu", static_cast<unsigned long>(length)); + errorFrame = true; + break; + } + size_t newLength = length * 128; + unsigned char msgByte = static_cast<unsigned char>(*p); + unsigned int lengthMsgByte = msgByte & 0x7f; + if (newLength > std::numeric_limits<size_t>::max() - lengthMsgByte) { + LOG(Network, "frame length overflow %lu+%u", static_cast<unsigned long>(newLength), lengthMsgByte); + errorFrame = true; + break; + } + newLength += lengthMsgByte; + if (newLength < length) { // sanity check + LOG(Network, "frame length integer wrap %lu->%lu", static_cast<unsigned long>(length), static_cast<unsigned long>(newLength)); + errorFrame = true; + break; + } + length = newLength; + ++p; + if (!(msgByte & 0x80)) + break; + } + if (p + length < p) { + LOG(Network, "frame buffer pointer wrap %p+%lu->%p", p, static_cast<unsigned long>(length), p + length); + errorFrame = true; + } + if (errorFrame) { + skipBuffer(m_bufferSize); // Save memory. + m_shouldDiscardReceivedData = true; + m_client->didReceiveMessageError(); + if (!m_client) + return false; + if (!m_closed) + m_handle->close(); + return false; + } + ASSERT(p + length >= p); + if (p + length < end) { + p += length; + nextFrame = p; + ASSERT(nextFrame > m_buffer); + skipBuffer(nextFrame - m_buffer); + m_client->didReceiveMessageError(); + return m_buffer; + } + return false; + } + + const char* msgStart = p; + while (p < end && *p != '\xff') + ++p; + if (p < end && *p == '\xff') { + int msgLength = p - msgStart; + ++p; + nextFrame = p; + if (frameByte == 0x00) { + String msg = String::fromUTF8(msgStart, msgLength); + skipBuffer(nextFrame - m_buffer); + m_client->didReceiveMessage(msg); + } else { + skipBuffer(nextFrame - m_buffer); + m_client->didReceiveMessageError(); + } + return m_buffer; + } + return false; +} + +void WebSocketChannel::resumeTimerFired(Timer<WebSocketChannel>* timer) +{ + ASSERT_UNUSED(timer, timer == &m_resumeTimer); + + RefPtr<WebSocketChannel> protect(this); // The client can close the channel, potentially removing the last reference. + while (!m_suspended && m_client && m_buffer) + if (!processBuffer()) + break; + if (!m_suspended && m_client && m_closed && m_handle) + didClose(m_handle.get()); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) diff --git a/Source/WebCore/websockets/WebSocketChannel.h b/Source/WebCore/websockets/WebSocketChannel.h new file mode 100644 index 0000000..9c52377 --- /dev/null +++ b/Source/WebCore/websockets/WebSocketChannel.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef WebSocketChannel_h +#define WebSocketChannel_h + +#if ENABLE(WEB_SOCKETS) + +#include "SocketStreamHandleClient.h" +#include "ThreadableWebSocketChannel.h" +#include "Timer.h" +#include "WebSocketHandshake.h" +#include <wtf/Forward.h> +#include <wtf/RefCounted.h> +#include <wtf/Vector.h> + +namespace WebCore { + + class ScriptExecutionContext; + class SocketStreamHandle; + class SocketStreamError; + class WebSocketChannelClient; + + class WebSocketChannel : public RefCounted<WebSocketChannel>, public SocketStreamHandleClient, public ThreadableWebSocketChannel { + public: + static PassRefPtr<WebSocketChannel> create(ScriptExecutionContext* context, WebSocketChannelClient* client, const KURL& url, const String& protocol) { return adoptRef(new WebSocketChannel(context, client, url, protocol)); } + virtual ~WebSocketChannel(); + + virtual void connect(); + virtual bool send(const String& message); + virtual unsigned long bufferedAmount() const; + virtual void close(); + virtual void disconnect(); // Will suppress didClose(). + + virtual void suspend(); + virtual void resume(); + + virtual void didOpen(SocketStreamHandle*); + virtual void didClose(SocketStreamHandle*); + virtual void didReceiveData(SocketStreamHandle*, const char*, int); + virtual void didFail(SocketStreamHandle*, const SocketStreamError&); + virtual void didReceiveAuthenticationChallenge(SocketStreamHandle*, const AuthenticationChallenge&); + virtual void didCancelAuthenticationChallenge(SocketStreamHandle*, const AuthenticationChallenge&); + + using RefCounted<WebSocketChannel>::ref; + using RefCounted<WebSocketChannel>::deref; + + protected: + virtual void refThreadableWebSocketChannel() { ref(); } + virtual void derefThreadableWebSocketChannel() { deref(); } + + private: + WebSocketChannel(ScriptExecutionContext*, WebSocketChannelClient*, const KURL&, const String& protocol); + + bool appendToBuffer(const char* data, size_t len); + void skipBuffer(size_t len); + bool processBuffer(); + void resumeTimerFired(Timer<WebSocketChannel>* timer); + + ScriptExecutionContext* m_context; + WebSocketChannelClient* m_client; + WebSocketHandshake m_handshake; + RefPtr<SocketStreamHandle> m_handle; + char* m_buffer; + size_t m_bufferSize; + + Timer<WebSocketChannel> m_resumeTimer; + bool m_suspended; + bool m_closed; + bool m_shouldDiscardReceivedData; + unsigned long m_unhandledBufferedAmount; + + unsigned long m_identifier; // m_identifier == 0 means that we could not obtain a valid identifier. + }; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocketChannel_h diff --git a/Source/WebCore/websockets/WebSocketChannelClient.h b/Source/WebCore/websockets/WebSocketChannelClient.h new file mode 100644 index 0000000..1551073 --- /dev/null +++ b/Source/WebCore/websockets/WebSocketChannelClient.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef WebSocketChannelClient_h +#define WebSocketChannelClient_h + +#if ENABLE(WEB_SOCKETS) + +namespace WebCore { + + class WebSocketChannelClient { + public: + virtual ~WebSocketChannelClient() { } + virtual void didConnect() { } + virtual void didReceiveMessage(const String&) { } + virtual void didReceiveMessageError() { } + virtual void didClose(unsigned long /* unhandledBufferedAmount */) { } + + protected: + WebSocketChannelClient() { } + }; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocketChannelClient_h diff --git a/Source/WebCore/websockets/WebSocketHandshake.cpp b/Source/WebCore/websockets/WebSocketHandshake.cpp new file mode 100644 index 0000000..5049098 --- /dev/null +++ b/Source/WebCore/websockets/WebSocketHandshake.cpp @@ -0,0 +1,598 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#include "config.h" + +#if ENABLE(WEB_SOCKETS) + +#include "WebSocketHandshake.h" + +#include "CharacterNames.h" +#include "Cookie.h" +#include "CookieJar.h" +#include "Document.h" +#include "HTTPHeaderMap.h" +#include "KURL.h" +#include "Logging.h" +#include "ScriptExecutionContext.h" +#include "SecurityOrigin.h" + +#include <wtf/MD5.h> +#include <wtf/RandomNumber.h> +#include <wtf/StdLibExtras.h> +#include <wtf/StringExtras.h> +#include <wtf/Vector.h> +#include <wtf/text/AtomicString.h> +#include <wtf/text/CString.h> +#include <wtf/text/StringBuilder.h> +#include <wtf/text/StringConcatenate.h> + +namespace WebCore { + +static const char randomCharacterInSecWebSocketKey[] = "!\"#$%&'()*+,-./:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"; + +static String resourceName(const KURL& url) +{ + String name = url.path(); + if (name.isEmpty()) + name = "/"; + if (!url.query().isNull()) + name += "?" + url.query(); + ASSERT(!name.isEmpty()); + ASSERT(!name.contains(' ')); + return name; +} + +static String hostName(const KURL& url, bool secure) +{ + ASSERT(url.protocolIs("wss") == secure); + StringBuilder builder; + builder.append(url.host().lower()); + if (url.port() && ((!secure && url.port() != 80) || (secure && url.port() != 443))) { + builder.append(':'); + builder.append(String::number(url.port())); + } + return builder.toString(); +} + +static const size_t maxConsoleMessageSize = 128; +static String trimConsoleMessage(const char* p, size_t len) +{ + String s = String(p, std::min<size_t>(len, maxConsoleMessageSize)); + if (len > maxConsoleMessageSize) + s.append(horizontalEllipsis); + return s; +} + +static void generateSecWebSocketKey(uint32_t& number, String& key) +{ + uint32_t space = static_cast<uint32_t>(randomNumber() * 12) + 1; + uint32_t max = 4294967295U / space; + number = static_cast<uint32_t>(randomNumber() * max); + uint32_t product = number * space; + + String s = String::number(product); + int n = static_cast<int>(randomNumber() * 12) + 1; + DEFINE_STATIC_LOCAL(String, randomChars, (randomCharacterInSecWebSocketKey)); + for (int i = 0; i < n; i++) { + int pos = static_cast<int>(randomNumber() * (s.length() + 1)); + int chpos = static_cast<int>(randomNumber() * randomChars.length()); + s.insert(randomChars.substring(chpos, 1), pos); + } + DEFINE_STATIC_LOCAL(String, spaceChar, (" ")); + for (uint32_t i = 0; i < space; i++) { + int pos = static_cast<int>(randomNumber() * (s.length() - 1)) + 1; + s.insert(spaceChar, pos); + } + ASSERT(s[0] != ' '); + ASSERT(s[s.length() - 1] != ' '); + key = s; +} + +static void generateKey3(unsigned char key3[8]) +{ + for (int i = 0; i < 8; i++) + key3[i] = randomNumber() * 256; +} + +static void setChallengeNumber(unsigned char* buf, uint32_t number) +{ + unsigned char* p = buf + 3; + for (int i = 0; i < 4; i++) { + *p = number & 0xFF; + --p; + number >>= 8; + } +} + +static void generateExpectedChallengeResponse(uint32_t number1, uint32_t number2, unsigned char key3[8], unsigned char expectedChallenge[16]) +{ + unsigned char challenge[16]; + setChallengeNumber(&challenge[0], number1); + setChallengeNumber(&challenge[4], number2); + memcpy(&challenge[8], key3, 8); + MD5 md5; + md5.addBytes(challenge, sizeof(challenge)); + Vector<uint8_t, 16> digest; + md5.checksum(digest); + memcpy(expectedChallenge, digest.data(), 16); +} + +WebSocketHandshake::WebSocketHandshake(const KURL& url, const String& protocol, ScriptExecutionContext* context) + : m_url(url) + , m_clientProtocol(protocol) + , m_secure(m_url.protocolIs("wss")) + , m_context(context) + , m_mode(Incomplete) +{ + uint32_t number1; + uint32_t number2; + generateSecWebSocketKey(number1, m_secWebSocketKey1); + generateSecWebSocketKey(number2, m_secWebSocketKey2); + generateKey3(m_key3); + generateExpectedChallengeResponse(number1, number2, m_key3, m_expectedChallengeResponse); +} + +WebSocketHandshake::~WebSocketHandshake() +{ +} + +const KURL& WebSocketHandshake::url() const +{ + return m_url; +} + +void WebSocketHandshake::setURL(const KURL& url) +{ + m_url = url.copy(); +} + +const String WebSocketHandshake::host() const +{ + return m_url.host().lower(); +} + +const String& WebSocketHandshake::clientProtocol() const +{ + return m_clientProtocol; +} + +void WebSocketHandshake::setClientProtocol(const String& protocol) +{ + m_clientProtocol = protocol; +} + +bool WebSocketHandshake::secure() const +{ + return m_secure; +} + +String WebSocketHandshake::clientOrigin() const +{ + return m_context->securityOrigin()->toString(); +} + +String WebSocketHandshake::clientLocation() const +{ + StringBuilder builder; + builder.append(m_secure ? "wss" : "ws"); + builder.append("://"); + builder.append(hostName(m_url, m_secure)); + builder.append(resourceName(m_url)); + return builder.toString(); +} + +CString WebSocketHandshake::clientHandshakeMessage() const +{ + // Keep the following consistent with clientHandshakeRequest(). + StringBuilder builder; + + builder.append("GET "); + builder.append(resourceName(m_url)); + builder.append(" HTTP/1.1\r\n"); + + Vector<String> fields; + fields.append("Upgrade: WebSocket"); + fields.append("Connection: Upgrade"); + fields.append("Host: " + hostName(m_url, m_secure)); + fields.append("Origin: " + clientOrigin()); + if (!m_clientProtocol.isEmpty()) + fields.append("Sec-WebSocket-Protocol: " + m_clientProtocol); + + KURL url = httpURLForAuthenticationAndCookies(); + if (m_context->isDocument()) { + Document* document = static_cast<Document*>(m_context); + String cookie = cookieRequestHeaderFieldValue(document, url); + if (!cookie.isEmpty()) + fields.append("Cookie: " + cookie); + // Set "Cookie2: <cookie>" if cookies 2 exists for url? + } + + fields.append("Sec-WebSocket-Key1: " + m_secWebSocketKey1); + fields.append("Sec-WebSocket-Key2: " + m_secWebSocketKey2); + + // Fields in the handshake are sent by the client in a random order; the + // order is not meaningful. Thus, it's ok to send the order we constructed + // the fields. + + for (size_t i = 0; i < fields.size(); i++) { + builder.append(fields[i]); + builder.append("\r\n"); + } + + builder.append("\r\n"); + + CString handshakeHeader = builder.toString().utf8(); + char* characterBuffer = 0; + CString msg = CString::newUninitialized(handshakeHeader.length() + sizeof(m_key3), characterBuffer); + memcpy(characterBuffer, handshakeHeader.data(), handshakeHeader.length()); + memcpy(characterBuffer + handshakeHeader.length(), m_key3, sizeof(m_key3)); + return msg; +} + +WebSocketHandshakeRequest WebSocketHandshake::clientHandshakeRequest() const +{ + // Keep the following consistent with clientHandshakeMessage(). + // FIXME: do we need to store m_secWebSocketKey1, m_secWebSocketKey2 and + // m_key3 in WebSocketHandshakeRequest? + WebSocketHandshakeRequest request("GET", m_url); + request.addHeaderField("Upgrade", "WebSocket"); + request.addHeaderField("Connection", "Upgrade"); + request.addHeaderField("Host", hostName(m_url, m_secure)); + request.addHeaderField("Origin", clientOrigin()); + if (!m_clientProtocol.isEmpty()) + request.addHeaderField("Sec-WebSocket-Protocol:", m_clientProtocol); + + KURL url = httpURLForAuthenticationAndCookies(); + if (m_context->isDocument()) { + Document* document = static_cast<Document*>(m_context); + String cookie = cookieRequestHeaderFieldValue(document, url); + if (!cookie.isEmpty()) + request.addHeaderField("Cookie", cookie); + // Set "Cookie2: <cookie>" if cookies 2 exists for url? + } + + request.addHeaderField("Sec-WebSocket-Key1", m_secWebSocketKey1); + request.addHeaderField("Sec-WebSocket-Key2", m_secWebSocketKey2); + request.setKey3(m_key3); + + return request; +} + +void WebSocketHandshake::reset() +{ + m_mode = Incomplete; + + m_wsOrigin = String(); + m_wsLocation = String(); + m_wsProtocol = String(); + m_setCookie = String(); + m_setCookie2 = String(); +} + +void WebSocketHandshake::clearScriptExecutionContext() +{ + m_context = 0; +} + +int WebSocketHandshake::readServerHandshake(const char* header, size_t len) +{ + m_mode = Incomplete; + int statusCode; + String statusText; + int lineLength = readStatusLine(header, len, statusCode, statusText); + if (lineLength == -1) + return -1; + if (statusCode == -1) { + m_mode = Failed; + return len; + } + LOG(Network, "response code: %d", statusCode); + m_response.setStatusCode(statusCode); + m_response.setStatusText(statusText); + if (statusCode != 101) { + m_mode = Failed; + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, makeString("Unexpected response code: ", String::number(statusCode)), 0, clientOrigin()); + return len; + } + m_mode = Normal; + if (!strnstr(header, "\r\n\r\n", len)) { + // Just hasn't been received fully yet. + m_mode = Incomplete; + return -1; + } + const char* p = readHTTPHeaders(header + lineLength, header + len); + if (!p) { + LOG(Network, "readHTTPHeaders failed"); + m_mode = Failed; + return len; + } + processHeaders(); + if (!checkResponseHeaders()) { + LOG(Network, "header process failed"); + m_mode = Failed; + return p - header; + } + if (len < static_cast<size_t>(p - header + sizeof(m_expectedChallengeResponse))) { + // Just hasn't been received /expected/ yet. + m_mode = Incomplete; + return -1; + } + m_response.setChallengeResponse(static_cast<const unsigned char*>(static_cast<const void*>(p))); + if (memcmp(p, m_expectedChallengeResponse, sizeof(m_expectedChallengeResponse))) { + m_mode = Failed; + return (p - header) + sizeof(m_expectedChallengeResponse); + } + m_mode = Connected; + return (p - header) + sizeof(m_expectedChallengeResponse); +} + +WebSocketHandshake::Mode WebSocketHandshake::mode() const +{ + return m_mode; +} + +const String& WebSocketHandshake::serverWebSocketOrigin() const +{ + return m_wsOrigin; +} + +void WebSocketHandshake::setServerWebSocketOrigin(const String& webSocketOrigin) +{ + m_wsOrigin = webSocketOrigin; +} + +const String& WebSocketHandshake::serverWebSocketLocation() const +{ + return m_wsLocation; +} + +void WebSocketHandshake::setServerWebSocketLocation(const String& webSocketLocation) +{ + m_wsLocation = webSocketLocation; +} + +const String& WebSocketHandshake::serverWebSocketProtocol() const +{ + return m_wsProtocol; +} + +void WebSocketHandshake::setServerWebSocketProtocol(const String& webSocketProtocol) +{ + m_wsProtocol = webSocketProtocol; +} + +const String& WebSocketHandshake::serverSetCookie() const +{ + return m_setCookie; +} + +void WebSocketHandshake::setServerSetCookie(const String& setCookie) +{ + m_setCookie = setCookie; +} + +const String& WebSocketHandshake::serverSetCookie2() const +{ + return m_setCookie2; +} + +void WebSocketHandshake::setServerSetCookie2(const String& setCookie2) +{ + m_setCookie2 = setCookie2; +} + +const WebSocketHandshakeResponse& WebSocketHandshake::serverHandshakeResponse() const +{ + return m_response; +} + +KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const +{ + KURL url = m_url.copy(); + bool couldSetProtocol = url.setProtocol(m_secure ? "https" : "http"); + ASSERT_UNUSED(couldSetProtocol, couldSetProtocol); + return url; +} + +// Returns the header length (including "\r\n"), or -1 if we have not received enough data yet. +// If the line is malformed or the status code is not a 3-digit number, +// statusCode and statusText will be set to -1 and a null string, respectively. +int WebSocketHandshake::readStatusLine(const char* header, size_t headerLength, int& statusCode, String& statusText) +{ + statusCode = -1; + statusText = String(); + + const char* space1 = 0; + const char* space2 = 0; + const char* p; + size_t consumedLength; + + for (p = header, consumedLength = 0; consumedLength < headerLength; p++, consumedLength++) { + if (*p == ' ') { + if (!space1) + space1 = p; + else if (!space2) + space2 = p; + } else if (*p == '\n') + break; + } + if (consumedLength == headerLength) + return -1; // We have not received '\n' yet. + + const char* end = p + 1; + if (end - header > INT_MAX) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Status line is too long: " + trimConsoleMessage(header, maxConsoleMessageSize + 1), 0, clientOrigin()); + return INT_MAX; + } + int lineLength = end - header; + + if (!space1 || !space2) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "No response code found: " + trimConsoleMessage(header, lineLength - 1), 0, clientOrigin()); + return lineLength; + } + + // The line must end with "\r\n". + if (*(end - 2) != '\r') { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Status line does not end with CRLF", 0, clientOrigin()); + return lineLength; + } + + String statusCodeString(space1 + 1, space2 - space1 - 1); + if (statusCodeString.length() != 3) // Status code must consist of three digits. + return lineLength; + for (int i = 0; i < 3; ++i) + if (statusCodeString[i] < '0' || statusCodeString[i] > '9') { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Invalid status code: " + statusCodeString, 0, clientOrigin()); + return lineLength; + } + + bool ok = false; + statusCode = statusCodeString.toInt(&ok); + ASSERT(ok); + + statusText = String(space2 + 1, end - space2 - 3); // Exclude "\r\n". + return lineLength; +} + +const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end) +{ + m_response.clearHeaderFields(); + + Vector<char> name; + Vector<char> value; + for (const char* p = start; p < end; p++) { + name.clear(); + value.clear(); + + for (; p < end; p++) { + switch (*p) { + case '\r': + if (name.isEmpty()) { + if (p + 1 < end && *(p + 1) == '\n') + return p + 2; + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "CR doesn't follow LF at " + trimConsoleMessage(p, end - p), 0, clientOrigin()); + return 0; + } + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected CR in name at " + trimConsoleMessage(name.data(), name.size()), 0, clientOrigin()); + return 0; + case '\n': + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected LF in name at " + trimConsoleMessage(name.data(), name.size()), 0, clientOrigin()); + return 0; + case ':': + break; + default: + name.append(*p); + continue; + } + if (*p == ':') { + ++p; + break; + } + } + + for (; p < end && *p == 0x20; p++) { } + + for (; p < end; p++) { + switch (*p) { + case '\r': + break; + case '\n': + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Unexpected LF in value at " + trimConsoleMessage(value.data(), value.size()), 0, clientOrigin()); + return 0; + default: + value.append(*p); + } + if (*p == '\r') { + ++p; + break; + } + } + if (p >= end || *p != '\n') { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "CR doesn't follow LF after value at " + trimConsoleMessage(p, end - p), 0, clientOrigin()); + return 0; + } + AtomicString nameStr(String::fromUTF8(name.data(), name.size())); + String valueStr = String::fromUTF8(value.data(), value.size()); + if (nameStr.isNull()) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "invalid UTF-8 sequence in header name", 0, clientOrigin()); + return 0; + } + if (valueStr.isNull()) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "invalid UTF-8 sequence in header value", 0, clientOrigin()); + return 0; + } + LOG(Network, "name=%s value=%s", nameStr.string().utf8().data(), valueStr.utf8().data()); + m_response.addHeaderField(nameStr, valueStr); + } + ASSERT_NOT_REACHED(); + return 0; +} + +void WebSocketHandshake::processHeaders() +{ + ASSERT(m_mode == Normal); + const HTTPHeaderMap& headers = m_response.headerFields(); + m_wsOrigin = headers.get("sec-websocket-origin"); + m_wsLocation = headers.get("sec-websocket-location"); + m_wsProtocol = headers.get("sec-websocket-protocol"); + m_setCookie = headers.get("set-cookie"); + m_setCookie2 = headers.get("set-cookie2"); +} + +bool WebSocketHandshake::checkResponseHeaders() +{ + if (m_wsOrigin.isNull()) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'sec-websocket-origin' header is missing", 0, clientOrigin()); + return false; + } + if (m_wsLocation.isNull()) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: 'sec-websocket-location' header is missing", 0, clientOrigin()); + return false; + } + + if (clientOrigin() != m_wsOrigin) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: origin mismatch: " + clientOrigin() + " != " + m_wsOrigin, 0, clientOrigin()); + return false; + } + if (clientLocation() != m_wsLocation) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: location mismatch: " + clientLocation() + " != " + m_wsLocation, 0, clientOrigin()); + return false; + } + if (!m_clientProtocol.isEmpty() && m_clientProtocol != m_wsProtocol) { + m_context->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Error during WebSocket handshake: protocol mismatch: " + m_clientProtocol + " != " + m_wsProtocol, 0, clientOrigin()); + return false; + } + return true; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) diff --git a/Source/WebCore/websockets/WebSocketHandshake.h b/Source/WebCore/websockets/WebSocketHandshake.h new file mode 100644 index 0000000..a5b4260 --- /dev/null +++ b/Source/WebCore/websockets/WebSocketHandshake.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2009 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef WebSocketHandshake_h +#define WebSocketHandshake_h + +#if ENABLE(WEB_SOCKETS) + +#include "KURL.h" +#include "PlatformString.h" +#include "WebSocketHandshakeRequest.h" +#include "WebSocketHandshakeResponse.h" +#include <wtf/Noncopyable.h> + +namespace WebCore { + + class ScriptExecutionContext; + + class WebSocketHandshake : public Noncopyable { + public: + enum Mode { + Incomplete, Normal, Failed, Connected + }; + WebSocketHandshake(const KURL&, const String& protocol, ScriptExecutionContext*); + ~WebSocketHandshake(); + + const KURL& url() const; + void setURL(const KURL&); + const String host() const; + + const String& clientProtocol() const; + void setClientProtocol(const String& protocol); + + bool secure() const; + + String clientOrigin() const; + String clientLocation() const; + + CString clientHandshakeMessage() const; + WebSocketHandshakeRequest clientHandshakeRequest() const; + + void reset(); + void clearScriptExecutionContext(); + + int readServerHandshake(const char* header, size_t len); + Mode mode() const; + + const String& serverWebSocketOrigin() const; + void setServerWebSocketOrigin(const String& webSocketOrigin); + + const String& serverWebSocketLocation() const; + void setServerWebSocketLocation(const String& webSocketLocation); + + const String& serverWebSocketProtocol() const; + void setServerWebSocketProtocol(const String& webSocketProtocol); + + const String& serverSetCookie() const; + void setServerSetCookie(const String& setCookie); + const String& serverSetCookie2() const; + void setServerSetCookie2(const String& setCookie2); + + const WebSocketHandshakeResponse& serverHandshakeResponse() const; + + private: + KURL httpURLForAuthenticationAndCookies() const; + + int readStatusLine(const char* header, size_t headerLength, int& statusCode, String& statusText); + + // Reads all headers except for the two predefined ones. + const char* readHTTPHeaders(const char* start, const char* end); + void processHeaders(); + bool checkResponseHeaders(); + + KURL m_url; + String m_clientProtocol; + bool m_secure; + ScriptExecutionContext* m_context; + + Mode m_mode; + + String m_wsOrigin; + String m_wsLocation; + String m_wsProtocol; + String m_setCookie; + String m_setCookie2; + + String m_secWebSocketKey1; + String m_secWebSocketKey2; + unsigned char m_key3[8]; + unsigned char m_expectedChallengeResponse[16]; + + WebSocketHandshakeResponse m_response; + }; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocketHandshake_h diff --git a/Source/WebCore/websockets/WebSocketHandshakeRequest.cpp b/Source/WebCore/websockets/WebSocketHandshakeRequest.cpp new file mode 100644 index 0000000..1132a44 --- /dev/null +++ b/Source/WebCore/websockets/WebSocketHandshakeRequest.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#include "config.h" + +#if ENABLE(WEB_SOCKETS) + +#include "WebSocketHandshakeRequest.h" + +#include <cstring> + +using namespace std; + +namespace WebCore { + +WebSocketHandshakeRequest::Key3::Key3() +{ + memset(value, 0, sizeof(value)); +} + +void WebSocketHandshakeRequest::Key3::set(const unsigned char key3[8]) +{ + memcpy(value, key3, sizeof(value)); +} + +WebSocketHandshakeRequest::WebSocketHandshakeRequest(const String& requestMethod, const KURL& url) + : m_url(url) + , m_requestMethod(requestMethod) +{ +} + +WebSocketHandshakeRequest::~WebSocketHandshakeRequest() +{ +} + +String WebSocketHandshakeRequest::requestMethod() const +{ + return m_requestMethod; +} + +KURL WebSocketHandshakeRequest::url() const +{ + return m_url; +} + +void WebSocketHandshakeRequest::addHeaderField(const char* name, const String& value) +{ + m_headerFields.add(name, value); +} + +const HTTPHeaderMap& WebSocketHandshakeRequest::headerFields() const +{ + return m_headerFields; +} + +WebSocketHandshakeRequest::Key3 WebSocketHandshakeRequest::key3() const +{ + return m_key3; +} + +void WebSocketHandshakeRequest::setKey3(const unsigned char key3[8]) +{ + m_key3.set(key3); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) diff --git a/Source/WebCore/websockets/WebSocketHandshakeRequest.h b/Source/WebCore/websockets/WebSocketHandshakeRequest.h new file mode 100644 index 0000000..792f67e --- /dev/null +++ b/Source/WebCore/websockets/WebSocketHandshakeRequest.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef WebSocketHandshakeRequest_h +#define WebSocketHandshakeRequest_h + +#if ENABLE(WEB_SOCKETS) + +#include "HTTPHeaderMap.h" +#include "KURL.h" +#include "PlatformString.h" + +namespace WebCore { + +class WebSocketHandshakeRequest { +public: + WebSocketHandshakeRequest(const String& requestMethod, const KURL&); + ~WebSocketHandshakeRequest(); + + String requestMethod() const; + KURL url() const; + + const HTTPHeaderMap& headerFields() const; + void addHeaderField(const char* name, const String& value); + + struct Key3 { + unsigned char value[8]; + + Key3(); + void set(const unsigned char key3[8]); + }; + Key3 key3() const; + void setKey3(const unsigned char key3[8]); + +private: + KURL m_url; + String m_requestMethod; + HTTPHeaderMap m_headerFields; + Key3 m_key3; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocketHandshakeRequest_h diff --git a/Source/WebCore/websockets/WebSocketHandshakeResponse.cpp b/Source/WebCore/websockets/WebSocketHandshakeResponse.cpp new file mode 100644 index 0000000..2e0dad7 --- /dev/null +++ b/Source/WebCore/websockets/WebSocketHandshakeResponse.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#include "config.h" + +#if ENABLE(WEB_SOCKETS) + +#include "WebSocketHandshakeResponse.h" + +#include <wtf/Assertions.h> +#include <wtf/text/AtomicString.h> + +using namespace std; + +namespace WebCore { + +WebSocketHandshakeResponse::ChallengeResponse::ChallengeResponse() +{ + memset(value, 0, sizeof(value)); +} + +void WebSocketHandshakeResponse::ChallengeResponse::set(const unsigned char challengeResponse[16]) +{ + memcpy(value, challengeResponse, sizeof(value)); +} + +WebSocketHandshakeResponse::WebSocketHandshakeResponse() +{ +} + +WebSocketHandshakeResponse::~WebSocketHandshakeResponse() +{ +} + +int WebSocketHandshakeResponse::statusCode() const +{ + return m_statusCode; +} + +void WebSocketHandshakeResponse::setStatusCode(int statusCode) +{ + ASSERT(statusCode >= 100 && statusCode < 600); + m_statusCode = statusCode; +} + +const String& WebSocketHandshakeResponse::statusText() const +{ + return m_statusText; +} + +void WebSocketHandshakeResponse::setStatusText(const String& statusText) +{ + m_statusText = statusText; +} + +const HTTPHeaderMap& WebSocketHandshakeResponse::headerFields() const +{ + return m_headerFields; +} + +void WebSocketHandshakeResponse::addHeaderField(const AtomicString& name, const String& value) +{ + m_headerFields.add(name, value); +} + +void WebSocketHandshakeResponse::clearHeaderFields() +{ + m_headerFields.clear(); +} + +const WebSocketHandshakeResponse::ChallengeResponse& WebSocketHandshakeResponse::challengeResponse() const +{ + return m_challengeResponse; +} + +void WebSocketHandshakeResponse::setChallengeResponse(const unsigned char challengeResponse[16]) +{ + m_challengeResponse.set(challengeResponse); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) diff --git a/Source/WebCore/websockets/WebSocketHandshakeResponse.h b/Source/WebCore/websockets/WebSocketHandshakeResponse.h new file mode 100644 index 0000000..fe435ee --- /dev/null +++ b/Source/WebCore/websockets/WebSocketHandshakeResponse.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef WebSocketHandshakeResponse_h +#define WebSocketHandshakeResponse_h + +#if ENABLE(WEB_SOCKETS) + +#include "HTTPHeaderMap.h" +#include "PlatformString.h" +#include <wtf/Forward.h> + +namespace WebCore { + +class WebSocketHandshakeResponse { +public: + WebSocketHandshakeResponse(); + ~WebSocketHandshakeResponse(); + + int statusCode() const; + void setStatusCode(int statusCode); + const String& statusText() const; + void setStatusText(const String& statusText); + const HTTPHeaderMap& headerFields() const; + void addHeaderField(const AtomicString& name, const String& value); + void clearHeaderFields(); + + struct ChallengeResponse { + unsigned char value[16]; + + ChallengeResponse(); + void set(const unsigned char challengeResponse[16]); + }; + const ChallengeResponse& challengeResponse() const; + void setChallengeResponse(const unsigned char challengeResponse[16]); + +private: + int m_statusCode; + String m_statusText; + HTTPHeaderMap m_headerFields; + ChallengeResponse m_challengeResponse; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocketHandshakeResponse_h diff --git a/Source/WebCore/websockets/WorkerThreadableWebSocketChannel.cpp b/Source/WebCore/websockets/WorkerThreadableWebSocketChannel.cpp new file mode 100644 index 0000000..fd82591 --- /dev/null +++ b/Source/WebCore/websockets/WorkerThreadableWebSocketChannel.cpp @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2009, 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#include "config.h" + +#if ENABLE(WEB_SOCKETS) && ENABLE(WORKERS) + +#include "WorkerThreadableWebSocketChannel.h" + +#include "CrossThreadTask.h" +#include "PlatformString.h" +#include "ScriptExecutionContext.h" +#include "ThreadableWebSocketChannelClientWrapper.h" +#include "WebSocketChannel.h" +#include "WebSocketChannelClient.h" +#include "WorkerContext.h" +#include "WorkerLoaderProxy.h" +#include "WorkerRunLoop.h" +#include "WorkerThread.h" + +#include <wtf/PassRefPtr.h> + +namespace WebCore { + +WorkerThreadableWebSocketChannel::WorkerThreadableWebSocketChannel(WorkerContext* context, WebSocketChannelClient* client, const String& taskMode, const KURL& url, const String& protocol) + : m_workerContext(context) + , m_workerClientWrapper(ThreadableWebSocketChannelClientWrapper::create(client)) + , m_bridge(Bridge::create(m_workerClientWrapper, m_workerContext, taskMode, url, protocol)) +{ +} + +WorkerThreadableWebSocketChannel::~WorkerThreadableWebSocketChannel() +{ + if (m_bridge) + m_bridge->disconnect(); +} + +void WorkerThreadableWebSocketChannel::connect() +{ + if (m_bridge) + m_bridge->connect(); +} + +bool WorkerThreadableWebSocketChannel::send(const String& message) +{ + if (!m_bridge) + return false; + return m_bridge->send(message); +} + +unsigned long WorkerThreadableWebSocketChannel::bufferedAmount() const +{ + if (!m_bridge) + return 0; + return m_bridge->bufferedAmount(); +} + +void WorkerThreadableWebSocketChannel::close() +{ + if (m_bridge) + m_bridge->close(); +} + +void WorkerThreadableWebSocketChannel::disconnect() +{ + m_bridge->disconnect(); + m_bridge.clear(); +} + +void WorkerThreadableWebSocketChannel::suspend() +{ + m_workerClientWrapper->suspend(); + if (m_bridge) + m_bridge->suspend(); +} + +void WorkerThreadableWebSocketChannel::resume() +{ + m_workerClientWrapper->resume(); + if (m_bridge) + m_bridge->resume(); +} + +WorkerThreadableWebSocketChannel::Peer::Peer(RefPtr<ThreadableWebSocketChannelClientWrapper> clientWrapper, WorkerLoaderProxy& loaderProxy, ScriptExecutionContext* context, const String& taskMode, const KURL& url, const String& protocol) + : m_workerClientWrapper(clientWrapper) + , m_loaderProxy(loaderProxy) + , m_mainWebSocketChannel(WebSocketChannel::create(context, this, url, protocol)) + , m_taskMode(taskMode) +{ + ASSERT(isMainThread()); +} + +WorkerThreadableWebSocketChannel::Peer::~Peer() +{ + ASSERT(isMainThread()); + if (m_mainWebSocketChannel) + m_mainWebSocketChannel->disconnect(); +} + +void WorkerThreadableWebSocketChannel::Peer::connect() +{ + ASSERT(isMainThread()); + if (!m_mainWebSocketChannel) + return; + m_mainWebSocketChannel->connect(); +} + +static void workerContextDidSend(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, bool sent) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->setSent(sent); +} + +void WorkerThreadableWebSocketChannel::Peer::send(const String& message) +{ + ASSERT(isMainThread()); + if (!m_mainWebSocketChannel || !m_workerClientWrapper) + return; + bool sent = m_mainWebSocketChannel->send(message); + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidSend, m_workerClientWrapper, sent), m_taskMode); +} + +static void workerContextDidGetBufferedAmount(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, unsigned long bufferedAmount) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->setBufferedAmount(bufferedAmount); +} + +void WorkerThreadableWebSocketChannel::Peer::bufferedAmount() +{ + ASSERT(isMainThread()); + if (!m_mainWebSocketChannel || !m_workerClientWrapper) + return; + unsigned long bufferedAmount = m_mainWebSocketChannel->bufferedAmount(); + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidGetBufferedAmount, m_workerClientWrapper, bufferedAmount), m_taskMode); +} + +void WorkerThreadableWebSocketChannel::Peer::close() +{ + ASSERT(isMainThread()); + if (!m_mainWebSocketChannel) + return; + m_mainWebSocketChannel->close(); + m_mainWebSocketChannel = 0; +} + +void WorkerThreadableWebSocketChannel::Peer::disconnect() +{ + ASSERT(isMainThread()); + if (!m_mainWebSocketChannel) + return; + m_mainWebSocketChannel->disconnect(); + m_mainWebSocketChannel = 0; +} + +void WorkerThreadableWebSocketChannel::Peer::suspend() +{ + ASSERT(isMainThread()); + if (!m_mainWebSocketChannel) + return; + m_mainWebSocketChannel->suspend(); +} + +void WorkerThreadableWebSocketChannel::Peer::resume() +{ + ASSERT(isMainThread()); + if (!m_mainWebSocketChannel) + return; + m_mainWebSocketChannel->resume(); +} + +static void workerContextDidConnect(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->didConnect(); +} + +void WorkerThreadableWebSocketChannel::Peer::didConnect() +{ + ASSERT(isMainThread()); + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidConnect, m_workerClientWrapper), m_taskMode); +} + +static void workerContextDidReceiveMessage(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, const String& message) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->didReceiveMessage(message); +} + +void WorkerThreadableWebSocketChannel::Peer::didReceiveMessage(const String& message) +{ + ASSERT(isMainThread()); + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidReceiveMessage, m_workerClientWrapper, message), m_taskMode); +} + +static void workerContextDidClose(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, unsigned long unhandledBufferedAmount) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + workerClientWrapper->didClose(unhandledBufferedAmount); +} + +void WorkerThreadableWebSocketChannel::Peer::didClose(unsigned long unhandledBufferedAmount) +{ + ASSERT(isMainThread()); + m_mainWebSocketChannel = 0; + m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidClose, m_workerClientWrapper, unhandledBufferedAmount), m_taskMode); +} + +void WorkerThreadableWebSocketChannel::Bridge::setWebSocketChannel(ScriptExecutionContext* context, Bridge* thisPtr, Peer* peer, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper) +{ + ASSERT_UNUSED(context, context->isWorkerContext()); + thisPtr->m_peer = peer; + workerClientWrapper->setSyncMethodDone(); +} + +void WorkerThreadableWebSocketChannel::Bridge::mainThreadCreateWebSocketChannel(ScriptExecutionContext* context, Bridge* thisPtr, RefPtr<ThreadableWebSocketChannelClientWrapper> clientWrapper, const String& taskMode, const KURL& url, const String& protocol) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + + Peer* peer = Peer::create(clientWrapper, thisPtr->m_loaderProxy, context, taskMode, url, protocol); + thisPtr->m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&Bridge::setWebSocketChannel, thisPtr, peer, clientWrapper), taskMode); +} + +WorkerThreadableWebSocketChannel::Bridge::Bridge(PassRefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, PassRefPtr<WorkerContext> workerContext, const String& taskMode, const KURL& url, const String& protocol) + : m_workerClientWrapper(workerClientWrapper) + , m_workerContext(workerContext) + , m_loaderProxy(m_workerContext->thread()->workerLoaderProxy()) + , m_taskMode(taskMode) + , m_peer(0) +{ + ASSERT(m_workerClientWrapper.get()); + setMethodNotCompleted(); + m_loaderProxy.postTaskToLoader(createCallbackTask(&Bridge::mainThreadCreateWebSocketChannel, this, m_workerClientWrapper, m_taskMode, url, protocol)); + waitForMethodCompletion(); + ASSERT(m_peer); +} + +WorkerThreadableWebSocketChannel::Bridge::~Bridge() +{ + disconnect(); +} + +void WorkerThreadableWebSocketChannel::mainThreadConnect(ScriptExecutionContext* context, Peer* peer) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + ASSERT(peer); + + peer->connect(); +} + +void WorkerThreadableWebSocketChannel::Bridge::connect() +{ + ASSERT(m_workerClientWrapper); + ASSERT(m_peer); + m_loaderProxy.postTaskToLoader(createCallbackTask(&WorkerThreadableWebSocketChannel::mainThreadConnect, m_peer)); +} + +void WorkerThreadableWebSocketChannel::mainThreadSend(ScriptExecutionContext* context, Peer* peer, const String& message) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + ASSERT(peer); + + peer->send(message); +} + +bool WorkerThreadableWebSocketChannel::Bridge::send(const String& message) +{ + if (!m_workerClientWrapper) + return false; + ASSERT(m_peer); + setMethodNotCompleted(); + m_loaderProxy.postTaskToLoader(createCallbackTask(&WorkerThreadableWebSocketChannel::mainThreadSend, m_peer, message)); + RefPtr<Bridge> protect(this); + waitForMethodCompletion(); + ThreadableWebSocketChannelClientWrapper* clientWrapper = m_workerClientWrapper.get(); + return clientWrapper && clientWrapper->sent(); +} + +void WorkerThreadableWebSocketChannel::mainThreadBufferedAmount(ScriptExecutionContext* context, Peer* peer) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + ASSERT(peer); + + peer->bufferedAmount(); +} + +unsigned long WorkerThreadableWebSocketChannel::Bridge::bufferedAmount() +{ + if (!m_workerClientWrapper) + return 0; + ASSERT(m_peer); + setMethodNotCompleted(); + m_loaderProxy.postTaskToLoader(createCallbackTask(&WorkerThreadableWebSocketChannel::mainThreadBufferedAmount, m_peer)); + RefPtr<Bridge> protect(this); + waitForMethodCompletion(); + ThreadableWebSocketChannelClientWrapper* clientWrapper = m_workerClientWrapper.get(); + if (clientWrapper) + return clientWrapper->bufferedAmount(); + return 0; +} + +void WorkerThreadableWebSocketChannel::mainThreadClose(ScriptExecutionContext* context, Peer* peer) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + ASSERT(peer); + + peer->close(); +} + +void WorkerThreadableWebSocketChannel::Bridge::close() +{ + ASSERT(m_peer); + m_loaderProxy.postTaskToLoader(createCallbackTask(&WorkerThreadableWebSocketChannel::mainThreadClose, m_peer)); +} + +void WorkerThreadableWebSocketChannel::mainThreadDestroy(ScriptExecutionContext* context, Peer* peer) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + ASSERT(peer); + + delete peer; +} + +void WorkerThreadableWebSocketChannel::Bridge::disconnect() +{ + clearClientWrapper(); + if (m_peer) { + Peer* peer = m_peer; + m_peer = 0; + m_loaderProxy.postTaskToLoader(createCallbackTask(&mainThreadDestroy, peer)); + } + m_workerContext = 0; +} + +void WorkerThreadableWebSocketChannel::mainThreadSuspend(ScriptExecutionContext* context, Peer* peer) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + ASSERT(peer); + + peer->suspend(); +} + +void WorkerThreadableWebSocketChannel::Bridge::suspend() +{ + ASSERT(m_peer); + m_loaderProxy.postTaskToLoader(createCallbackTask(&WorkerThreadableWebSocketChannel::mainThreadSuspend, m_peer)); +} + +void WorkerThreadableWebSocketChannel::mainThreadResume(ScriptExecutionContext* context, Peer* peer) +{ + ASSERT(isMainThread()); + ASSERT_UNUSED(context, context->isDocument()); + ASSERT(peer); + + peer->resume(); +} + +void WorkerThreadableWebSocketChannel::Bridge::resume() +{ + ASSERT(m_peer); + m_loaderProxy.postTaskToLoader(createCallbackTask(&WorkerThreadableWebSocketChannel::mainThreadResume, m_peer)); +} + +void WorkerThreadableWebSocketChannel::Bridge::clearClientWrapper() +{ + m_workerClientWrapper->clearClient(); +} + +void WorkerThreadableWebSocketChannel::Bridge::setMethodNotCompleted() +{ + ASSERT(m_workerClientWrapper); + m_workerClientWrapper->clearSyncMethodDone(); +} + +// Caller of this function should hold a reference to the bridge, because this function may call WebSocket::didClose() in the end, +// which causes the bridge to get disconnected from the WebSocket and deleted if there is no other reference. +void WorkerThreadableWebSocketChannel::Bridge::waitForMethodCompletion() +{ + if (!m_workerContext) + return; + WorkerRunLoop& runLoop = m_workerContext->thread()->runLoop(); + MessageQueueWaitResult result = MessageQueueMessageReceived; + ThreadableWebSocketChannelClientWrapper* clientWrapper = m_workerClientWrapper.get(); + while (m_workerContext && clientWrapper && !clientWrapper->syncMethodDone() && result != MessageQueueTerminated) { + result = runLoop.runInMode(m_workerContext.get(), m_taskMode); // May cause this bridge to get disconnected, which makes m_workerContext become null. + clientWrapper = m_workerClientWrapper.get(); + } +} + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) diff --git a/Source/WebCore/websockets/WorkerThreadableWebSocketChannel.h b/Source/WebCore/websockets/WorkerThreadableWebSocketChannel.h new file mode 100644 index 0000000..366e4fa --- /dev/null +++ b/Source/WebCore/websockets/WorkerThreadableWebSocketChannel.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2009, 2010 Google 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: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * 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. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT + * OWNER OR 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. + */ + +#ifndef WorkerThreadableWebSocketChannel_h +#define WorkerThreadableWebSocketChannel_h + +#if ENABLE(WEB_SOCKETS) && ENABLE(WORKERS) + +#include "PlatformString.h" +#include "ThreadableWebSocketChannel.h" +#include "WebSocketChannelClient.h" + +#include <wtf/PassRefPtr.h> +#include <wtf/RefCounted.h> +#include <wtf/RefPtr.h> +#include <wtf/Threading.h> + +namespace WebCore { + +class KURL; +class ScriptExecutionContext; +class ThreadableWebSocketChannelClientWrapper; +class WorkerContext; +class WorkerLoaderProxy; +class WorkerRunLoop; + +class WorkerThreadableWebSocketChannel : public RefCounted<WorkerThreadableWebSocketChannel>, public ThreadableWebSocketChannel { +public: + static PassRefPtr<ThreadableWebSocketChannel> create(WorkerContext* workerContext, WebSocketChannelClient* client, const String& taskMode, const KURL& url, const String& protocol) + { + return adoptRef(new WorkerThreadableWebSocketChannel(workerContext, client, taskMode, url, protocol)); + } + virtual ~WorkerThreadableWebSocketChannel(); + + virtual void connect(); + virtual bool send(const String& message); + virtual unsigned long bufferedAmount() const; + virtual void close(); + virtual void disconnect(); // Will suppress didClose(). + virtual void suspend(); + virtual void resume(); + + using RefCounted<WorkerThreadableWebSocketChannel>::ref; + using RefCounted<WorkerThreadableWebSocketChannel>::deref; + +protected: + virtual void refThreadableWebSocketChannel() { ref(); } + virtual void derefThreadableWebSocketChannel() { deref(); } + +private: + // Generated by the bridge. The Peer and its bridge should have identical + // lifetimes. + class Peer : public WebSocketChannelClient, public Noncopyable { + public: + static Peer* create(RefPtr<ThreadableWebSocketChannelClientWrapper> clientWrapper, WorkerLoaderProxy& loaderProxy, ScriptExecutionContext* context, const String& taskMode, const KURL& url, const String& protocol) + { + return new Peer(clientWrapper, loaderProxy, context, taskMode, url, protocol); + } + ~Peer(); + + void connect(); + void send(const String& message); + void bufferedAmount(); + void close(); + void disconnect(); + void suspend(); + void resume(); + + virtual void didConnect(); + virtual void didReceiveMessage(const String& message); + virtual void didClose(unsigned long unhandledBufferedAmount); + + private: + Peer(RefPtr<ThreadableWebSocketChannelClientWrapper>, WorkerLoaderProxy&, ScriptExecutionContext*, const String& taskMode, const KURL&, const String& protocol); + + RefPtr<ThreadableWebSocketChannelClientWrapper> m_workerClientWrapper; + WorkerLoaderProxy& m_loaderProxy; + RefPtr<ThreadableWebSocketChannel> m_mainWebSocketChannel; + String m_taskMode; + }; + + // Bridge for Peer. Running on the worker thread. + class Bridge : public RefCounted<Bridge> { + public: + static PassRefPtr<Bridge> create(PassRefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, PassRefPtr<WorkerContext> workerContext, const String& taskMode, const KURL& url, const String& protocol) + { + return adoptRef(new Bridge(workerClientWrapper, workerContext, taskMode, url, protocol)); + } + ~Bridge(); + void connect(); + bool send(const String& message); + unsigned long bufferedAmount(); + void close(); + void disconnect(); + void suspend(); + void resume(); + + using RefCounted<Bridge>::ref; + using RefCounted<Bridge>::deref; + + private: + Bridge(PassRefPtr<ThreadableWebSocketChannelClientWrapper>, PassRefPtr<WorkerContext>, const String& taskMode, const KURL&, const String& protocol); + + static void setWebSocketChannel(ScriptExecutionContext*, Bridge* thisPtr, Peer*, RefPtr<ThreadableWebSocketChannelClientWrapper>); + + // Executed on the main thread to create a Peer for this bridge. + static void mainThreadCreateWebSocketChannel(ScriptExecutionContext*, Bridge* thisPtr, RefPtr<ThreadableWebSocketChannelClientWrapper>, const String& taskMode, const KURL&, const String& protocol); + + // Executed on the worker context's thread. + void clearClientWrapper(); + + void setMethodNotCompleted(); + void waitForMethodCompletion(); + + RefPtr<ThreadableWebSocketChannelClientWrapper> m_workerClientWrapper; + RefPtr<WorkerContext> m_workerContext; + WorkerLoaderProxy& m_loaderProxy; + String m_taskMode; + Peer* m_peer; + }; + + WorkerThreadableWebSocketChannel(WorkerContext*, WebSocketChannelClient*, const String& taskMode, const KURL&, const String& protocol); + + static void mainThreadConnect(ScriptExecutionContext*, Peer*); + static void mainThreadSend(ScriptExecutionContext*, Peer*, const String& message); + static void mainThreadBufferedAmount(ScriptExecutionContext*, Peer*); + static void mainThreadClose(ScriptExecutionContext*, Peer*); + static void mainThreadDestroy(ScriptExecutionContext*, Peer*); + static void mainThreadSuspend(ScriptExecutionContext*, Peer*); + static void mainThreadResume(ScriptExecutionContext*, Peer*); + + RefPtr<WorkerContext> m_workerContext; + RefPtr<ThreadableWebSocketChannelClientWrapper> m_workerClientWrapper; + RefPtr<Bridge> m_bridge; +}; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WorkerThreadableWebSocketChannel_h |