diff options
Diffstat (limited to 'WebCore/websockets')
| -rw-r--r-- | WebCore/websockets/WebSocket.cpp | 115 | ||||
| -rw-r--r-- | WebCore/websockets/WebSocket.h | 117 | ||||
| -rw-r--r-- | WebCore/websockets/WebSocket.idl | 6 | ||||
| -rw-r--r-- | WebCore/websockets/WebSocketChannel.cpp | 246 | ||||
| -rw-r--r-- | WebCore/websockets/WebSocketChannel.h | 86 | ||||
| -rw-r--r-- | WebCore/websockets/WebSocketChannelClient.h | 53 | ||||
| -rw-r--r-- | WebCore/websockets/WebSocketHandshake.cpp | 462 | ||||
| -rw-r--r-- | WebCore/websockets/WebSocketHandshake.h | 113 |
8 files changed, 1099 insertions, 99 deletions
diff --git a/WebCore/websockets/WebSocket.cpp b/WebCore/websockets/WebSocket.cpp index 999c1ff..16211a7 100644 --- a/WebCore/websockets/WebSocket.cpp +++ b/WebCore/websockets/WebSocket.cpp @@ -34,13 +34,58 @@ #include "WebSocket.h" +#include "CString.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 "SecurityOrigin.h" +#include "WebSocketChannel.h" #include <wtf/StdLibExtras.h> namespace WebCore { +class ProcessWebSocketEventTask : public ScriptExecutionContext::Task { +public: + typedef void (WebSocket::*Method)(Event*); + static PassRefPtr<ProcessWebSocketEventTask> create(PassRefPtr<WebSocket> webSocket, PassRefPtr<Event> event) + { + return adoptRef(new ProcessWebSocketEventTask(webSocket, event)); + } + virtual void performTask(ScriptExecutionContext*) + { + ExceptionCode ec = 0; + m_webSocket->dispatchEvent(m_event.get(), ec); + ASSERT(!ec); + } + + private: + ProcessWebSocketEventTask(PassRefPtr<WebSocket> webSocket, PassRefPtr<Event> event) + : m_webSocket(webSocket) + , m_event(event) { } + + RefPtr<WebSocket> m_webSocket; + RefPtr<Event> m_event; +}; + +static bool isValidProtocolString(const WebCore::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] < 0x21 || characters[i] > 0x7E) + return false; + } + return true; +} + WebSocket::WebSocket(ScriptExecutionContext* context) : ActiveDOMObject(context, this) , m_state(CONNECTING) @@ -59,39 +104,49 @@ void WebSocket::connect(const KURL& url, ExceptionCode& 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.protocolIs("ws") && !m_url.protocolIs("wss")) { + LOG_ERROR("Error: wrong url for WebSocket %s", url.string().utf8().data()); m_state = CLOSED; ec = SYNTAX_ERR; return; } - if (!m_protocol.isNull() && m_protocol.isEmpty()) { + if (!isValidProtocolString(m_protocol)) { + LOG_ERROR("Error: wrong protocol for WebSocket %s", m_protocol.utf8().data()); m_state = CLOSED; ec = SYNTAX_ERR; return; } - // FIXME: Check protocol is valid form. - // FIXME: Connect WebSocketChannel. + // FIXME: if m_url.port() is blocking port, raise SECURITY_ERR. + + m_channel = WebSocketChannel::create(scriptExecutionContext(), this, m_url, m_protocol); + m_channel->connect(); } -bool WebSocket::send(const String&, ExceptionCode& ec) +bool WebSocket::send(const String& message, ExceptionCode& ec) { - if (m_state != OPEN) { + LOG(Network, "WebSocket %p send %s", this, message.utf8().data()); + if (m_state == CONNECTING) { ec = INVALID_STATE_ERR; return false; } - // FIXME: send message on WebSocketChannel. - return false; + // No exception is raised if the connection was once established but has subsequently been closed. + if (m_state == CLOSED) + return false; + // FIXME: check message is valid utf8. + return m_channel->send(message); } void WebSocket::close() { + LOG(Network, "WebSocket %p close", this); if (m_state == CLOSED) return; m_state = CLOSED; - // FIXME: close WebSocketChannel. + m_channel->close(); } const KURL& WebSocket::url() const @@ -106,7 +161,8 @@ WebSocket::State WebSocket::readyState() const unsigned long WebSocket::bufferedAmount() const { - // FIXME: ask platform code to get buffered amount to be sent. + if (m_state == OPEN) + return m_channel->bufferedAmount(); return 0; } @@ -115,58 +171,43 @@ ScriptExecutionContext* WebSocket::scriptExecutionContext() const return ActiveDOMObject::scriptExecutionContext(); } -void WebSocket::addEventListener(const AtomicString&, PassRefPtr<EventListener>, bool) -{ - // FIXME: implement this. -} - -void WebSocket::removeEventListener(const AtomicString&, EventListener*, bool) -{ - // FIXME: implement this. -} - -bool WebSocket::dispatchEvent(PassRefPtr<Event>, ExceptionCode&) -{ - // FIXME: implement this. - return false; -} - void WebSocket::didConnect() { + LOG(Network, "WebSocket %p didConnect", this); if (m_state != CONNECTING) { didClose(); return; } m_state = OPEN; - dispatchOpenEvent(); + scriptExecutionContext()->postTask(ProcessWebSocketEventTask::create(this, 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; - dispatchMessageEvent(msg); + RefPtr<MessageEvent> evt = MessageEvent::create(); + // FIXME: origin, lastEventId, source, messagePort. + evt->initMessageEvent(eventNames().messageEvent, false, false, SerializedScriptValue::create(msg), "", "", 0, 0); + scriptExecutionContext()->postTask(ProcessWebSocketEventTask::create(this, evt)); } void WebSocket::didClose() { + LOG(Network, "WebSocket %p didClose", this); m_state = CLOSED; - dispatchCloseEvent(); -} - -void WebSocket::dispatchOpenEvent() -{ - // FIXME: implement this. + scriptExecutionContext()->postTask(ProcessWebSocketEventTask::create(this, Event::create(eventNames().closeEvent, false, false))); } -void WebSocket::dispatchMessageEvent(const String&) +EventTargetData* WebSocket::eventTargetData() { - // FIXME: implement this. + return &m_eventTargetData; } -void WebSocket::dispatchCloseEvent() +EventTargetData* WebSocket::ensureEventTargetData() { - // FIXME: implement this. + return &m_eventTargetData; } } // namespace WebCore diff --git a/WebCore/websockets/WebSocket.h b/WebCore/websockets/WebSocket.h index 485e2cc..c5b7ee7 100644 --- a/WebCore/websockets/WebSocket.h +++ b/WebCore/websockets/WebSocket.h @@ -31,93 +31,92 @@ #ifndef WebSocket_h #define WebSocket_h +#if ENABLE(WEB_SOCKETS) + #include "ActiveDOMObject.h" #include "AtomicStringHash.h" #include "EventListener.h" +#include "EventNames.h" #include "EventTarget.h" #include "KURL.h" +#include "WebSocketChannelClient.h" #include <wtf/OwnPtr.h> #include <wtf/RefCounted.h> namespace WebCore { -class String; - -class WebSocket : public RefCounted<WebSocket>, public EventTarget, public ActiveDOMObject { -public: - static PassRefPtr<WebSocket> create(ScriptExecutionContext* context) { return adoptRef(new WebSocket(context)); } - virtual ~WebSocket(); - - enum State { - CONNECTING = 0, - OPEN = 1, - CLOSED = 2 - }; + class String; + class WebSocketChannel; - void connect(const KURL& url, ExceptionCode&); - void connect(const KURL& url, const String& protocol, ExceptionCode&); + class WebSocket : public RefCounted<WebSocket>, public EventTarget, public ActiveDOMObject, public WebSocketChannelClient { + public: + static PassRefPtr<WebSocket> create(ScriptExecutionContext* context) { return adoptRef(new WebSocket(context)); } + virtual ~WebSocket(); - bool send(const String& message, ExceptionCode&); + enum State { + CONNECTING = 0, + OPEN = 1, + CLOSED = 2 + }; - void close(); + void connect(const KURL&, ExceptionCode&); + void connect(const KURL&, const String& protocol, ExceptionCode&); - const KURL& url() const; - State readyState() const; - unsigned long bufferedAmount() const; + bool send(const String& message, ExceptionCode&); - void setOnopen(PassRefPtr<EventListener> eventListener) { m_onopen = eventListener; } - EventListener* onopen() const { return m_onopen.get(); } - void setOnmessage(PassRefPtr<EventListener> eventListener) { m_onmessage = eventListener; } - EventListener* onmessage() const { return m_onmessage.get(); } - void setOnclose(PassRefPtr<EventListener> eventListener) { m_onclose = eventListener; } - EventListener* onclose() const { return m_onclose.get(); } + void close(); - // EventTarget - virtual WebSocket* toWebSocket() { return this; } + const KURL& url() const; + State readyState() const; + unsigned long bufferedAmount() const; - virtual ScriptExecutionContext* scriptExecutionContext() const; + DEFINE_ATTRIBUTE_EVENT_LISTENER(open); + DEFINE_ATTRIBUTE_EVENT_LISTENER(message); + DEFINE_ATTRIBUTE_EVENT_LISTENER(close); - virtual void addEventListener(const AtomicString& eventType, PassRefPtr<EventListener>, bool useCapture); - virtual void removeEventListener(const AtomicString& eventType, EventListener*, bool useCapture); - virtual bool dispatchEvent(PassRefPtr<Event>, ExceptionCode&); + // EventTarget + virtual WebSocket* toWebSocket() { return this; } - // ActiveDOMObject - // virtual bool hasPendingActivity() const; - // virtual void contextDestroyed(); - // virtual bool canSuspend() const; - // virtual void suspend(); - // virtual void resume(); - // virtual void stop(); + virtual ScriptExecutionContext* scriptExecutionContext() const; - using RefCounted<WebSocket>::ref; - using RefCounted<WebSocket>::deref; + // ActiveDOMObject + // virtual bool hasPendingActivity() const; + // virtual void contextDestroyed(); + // virtual bool canSuspend() const; + // virtual void suspend(); + // virtual void resume(); + // virtual void stop(); -private: - WebSocket(ScriptExecutionContext*); + using RefCounted<WebSocket>::ref; + using RefCounted<WebSocket>::deref; - virtual void refEventTarget() { ref(); } - virtual void derefEventTarget() { deref(); } + // WebSocketChannelClient + virtual void didConnect(); + virtual void didReceiveMessage(const String& msg); + virtual void didClose(); - // WebSocketChannelClient - void didConnect(); - void didReceiveMessage(const String& msg); - void didClose(); + private: + WebSocket(ScriptExecutionContext*); - void dispatchOpenEvent(); - void dispatchMessageEvent(const String& msg); - void dispatchCloseEvent(); + virtual void refEventTarget() { ref(); } + virtual void derefEventTarget() { deref(); } + virtual EventTargetData* eventTargetData(); + virtual EventTargetData* ensureEventTargetData(); - // FIXME: add WebSocketChannel. + void dispatchOpenEvent(Event*); + void dispatchMessageEvent(Event*); + void dispatchCloseEvent(Event*); - RefPtr<EventListener> m_onopen; - RefPtr<EventListener> m_onmessage; - RefPtr<EventListener> m_onclose; + RefPtr<WebSocketChannel> m_channel; - State m_state; - KURL m_url; - String m_protocol; -}; + State m_state; + KURL m_url; + String m_protocol; + EventTargetData m_eventTargetData; + }; } // namespace WebCore +#endif // ENABLE(WEB_SOCKETS) + #endif // WebSocket_h diff --git a/WebCore/websockets/WebSocket.idl b/WebCore/websockets/WebSocket.idl index cdb916f..04606fe 100644 --- a/WebCore/websockets/WebSocket.idl +++ b/WebCore/websockets/WebSocket.idl @@ -31,9 +31,9 @@ module websockets { interface [ - CustomMarkFunction, - NoStaticTables, - Conditional=WEB_SOCKETS + Conditional=WEB_SOCKETS, + EventTarget, + NoStaticTables ] WebSocket { readonly attribute DOMString URL; diff --git a/WebCore/websockets/WebSocketChannel.cpp b/WebCore/websockets/WebSocketChannel.cpp new file mode 100644 index 0000000..145cd34 --- /dev/null +++ b/WebCore/websockets/WebSocketChannel.cpp @@ -0,0 +1,246 @@ +/* + * 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 "CString.h" +#include "CookieJar.h" +#include "Document.h" +#include "Logging.h" +#include "PlatformString.h" +#include "ScriptExecutionContext.h" +#include "SocketStreamError.h" +#include "SocketStreamHandle.h" +#include "StringHash.h" +#include "WebSocketChannelClient.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_unhandledBufferSize(0) +{ +} + +WebSocketChannel::~WebSocketChannel() +{ + fastFree(m_buffer); +} + +void WebSocketChannel::connect() +{ + LOG(Network, "WebSocketChannel %p connect", this); + ASSERT(!m_handle.get()); + m_handshake.reset(); + m_handle = SocketStreamHandle::create(m_handshake.url(), this); +} + +bool WebSocketChannel::send(const String& msg) +{ + LOG(Network, "WebSocketChannel %p send %s", this, msg.utf8().data()); + Vector<char> buf; + buf.append('\0'); // frame type + buf.append(msg.utf8().data(), msg.utf8().length()); + buf.append('\xff'); // frame end + if (!m_handle.get()) { + m_unhandledBufferSize += buf.size(); + return false; + } + return m_handle->send(buf.data(), buf.size()); +} + +unsigned long WebSocketChannel::bufferedAmount() const +{ + LOG(Network, "WebSocketChannel %p bufferedAmount", this); + if (!m_handle.get()) + return m_unhandledBufferSize; + return m_handle->bufferedAmount(); +} + +void WebSocketChannel::close() +{ + LOG(Network, "WebSocketChannel %p close", this); + if (m_handle.get()) + m_handle->close(); // will call didClose() +} + +void WebSocketChannel::willOpenStream(SocketStreamHandle*, const KURL&) +{ +} + +void WebSocketChannel::willSendData(SocketStreamHandle*, const char*, int) +{ +} + +void WebSocketChannel::didOpen(SocketStreamHandle* handle) +{ + LOG(Network, "WebSocketChannel %p didOpen", this); + ASSERT(handle == m_handle.get()); + const CString& handshakeMessage = m_handshake.clientHandshakeMessage(); + if (!handle->send(handshakeMessage.data(), handshakeMessage.length())) { + LOG(Network, "Error in sending handshake message."); + handle->close(); + } +} + +void WebSocketChannel::didClose(SocketStreamHandle* handle) +{ + LOG(Network, "WebSocketChannel %p didClose", this); + ASSERT(handle == m_handle.get() || !m_handle.get()); + if (!m_handle.get()) + return; + m_unhandledBufferSize = handle->bufferedAmount(); + WebSocketChannelClient* client = m_client; + m_client = 0; + m_handle = 0; + client->didClose(); +} + +void WebSocketChannel::didReceiveData(SocketStreamHandle* handle, const char* data, int len) +{ + LOG(Network, "WebSocketChannel %p didReceiveData %d", this, len); + ASSERT(handle == m_handle.get()); + if (!appendToBuffer(data, len)) { + handle->close(); + return; + } + if (m_handshake.mode() != WebSocketHandshake::Connected) { + int headerLength = m_handshake.readServerHandshake(m_buffer, m_bufferSize); + if (headerLength <= 0) + return; + switch (m_handshake.mode()) { + case WebSocketHandshake::Connected: + if (!m_handshake.serverSetCookie().isEmpty()) { + if (m_context->isDocument()) { + Document* document = static_cast<Document*>(m_context); + if (cookiesEnabled(document)) + document->setCookie(m_handshake.serverSetCookie()); + } + } + // FIXME: handle set-cookie2. + LOG(Network, "WebSocketChannel %p connected", this); + m_client->didConnect(); + break; + default: + LOG(Network, "WebSocketChannel %p connection failed", this); + handle->close(); + return; + } + skipBuffer(headerLength); + if (!m_buffer) + return; + LOG(Network, "remaining in read buf %ul", m_bufferSize); + } + + const char* nextFrame = m_buffer; + const char* p = m_buffer; + const char* end = p + m_bufferSize; + while (p < end) { + unsigned char frameByte = static_cast<unsigned char>(*p++); + if ((frameByte & 0x80) == 0x80) { + int length = 0; + while (p < end && (*p & 0x80) == 0x80) { + if (length > std::numeric_limits<int>::max() / 128) { + LOG(Network, "frame length overflow %d", length); + handle->close(); + return; + } + length = length * 128 + *p & 0x7f; + ++p; + } + if (p + length < end) { + p += length; + nextFrame = p; + } + } else { + const char* msgStart = p; + while (p < end && *p != '\xff') + ++p; + if (p < end && *p == '\xff') { + if (frameByte == 0x00) + m_client->didReceiveMessage(String::fromUTF8(msgStart, p - msgStart)); + ++p; + nextFrame = p; + } + } + } + skipBuffer(nextFrame - m_buffer); +} + +void WebSocketChannel::didFail(SocketStreamHandle* handle, const SocketStreamError&) +{ + LOG(Network, "WebSocketChannel %p didFail", this); + ASSERT(handle == m_handle.get() || !m_handle.get()); + handle->close(); +} + +bool WebSocketChannel::appendToBuffer(const char* data, int len) +{ + char* newBuffer = 0; + if (tryFastMalloc(m_bufferSize + len).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 += len; + return true; + } + LOG(Network, "Too long WebSocket frame %d", m_bufferSize + len); + return false; +} + +void WebSocketChannel::skipBuffer(int 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); +} + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) diff --git a/WebCore/websockets/WebSocketChannel.h b/WebCore/websockets/WebSocketChannel.h new file mode 100644 index 0000000..75f41f6 --- /dev/null +++ b/WebCore/websockets/WebSocketChannel.h @@ -0,0 +1,86 @@ +/* + * 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 "WebSocketHandshake.h" +#include <wtf/RefCounted.h> + +namespace WebCore { + + class ScriptExecutionContext; + class String; + class SocketStreamHandle; + class SocketStreamError; + class WebSocketChannelClient; + + class WebSocketChannel : public RefCounted<WebSocketChannel>, public SocketStreamHandleClient { + public: + static PassRefPtr<WebSocketChannel> create(ScriptExecutionContext* context, WebSocketChannelClient* client, const KURL& url, const String& protocol) { return new WebSocketChannel(context, client, url, protocol); } + virtual ~WebSocketChannel(); + + virtual void connect(); + + virtual bool send(const String& msg); + virtual unsigned long bufferedAmount() const; + + virtual void close(); + + virtual void willOpenStream(SocketStreamHandle*, const KURL&); + virtual void willSendData(SocketStreamHandle*, const char*, int); + virtual void didOpen(SocketStreamHandle*); + virtual void didClose(SocketStreamHandle*); + virtual void didReceiveData(SocketStreamHandle*, const char*, int); + virtual void didFail(SocketStreamHandle*, const SocketStreamError&); + + private: + WebSocketChannel(ScriptExecutionContext*, WebSocketChannelClient*, const KURL&, const String&); + + bool appendToBuffer(const char* data, int len); + void skipBuffer(int len); + + ScriptExecutionContext* m_context; + WebSocketChannelClient* m_client; + WebSocketHandshake m_handshake; + RefPtr<SocketStreamHandle> m_handle; + char* m_buffer; + int m_bufferSize; + unsigned long m_unhandledBufferSize; + }; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocketChannel_h diff --git a/WebCore/websockets/WebSocketChannelClient.h b/WebCore/websockets/WebSocketChannelClient.h new file mode 100644 index 0000000..463cada --- /dev/null +++ b/WebCore/websockets/WebSocketChannelClient.h @@ -0,0 +1,53 @@ +/* + * 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 didClose() { } + + protected: + WebSocketChannelClient() { } + }; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocketChannelClient_h diff --git a/WebCore/websockets/WebSocketHandshake.cpp b/WebCore/websockets/WebSocketHandshake.cpp new file mode 100644 index 0000000..691fa1c --- /dev/null +++ b/WebCore/websockets/WebSocketHandshake.cpp @@ -0,0 +1,462 @@ +/* + * 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 "AtomicString.h" +#include "CString.h" +#include "CookieJar.h" +#include "Document.h" +#include "HTTPHeaderMap.h" +#include "KURL.h" +#include "Logging.h" +#include "ScriptExecutionContext.h" +#include "SecurityOrigin.h" +#include "StringBuilder.h" +#include <wtf/StringExtras.h> +#include <wtf/Vector.h> + +namespace WebCore { + +const char webSocketServerHandshakeHeader[] = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"; +const char webSocketUpgradeHeader[] = "Upgrade: WebSocket\r\n"; +const char webSocketConnectionHeader[] = "Connection: Upgrade\r\n"; + +static String extractResponseCode(const char* header, int len) +{ + const char* space1 = 0; + const char* space2 = 0; + const char* p; + for (p = header; p - header < len; p++) { + if (*p == ' ') { + if (!space1) + space1 = p; + else if (!space2) + space2 = p; + } else if (*p == '\n') + break; + } + if (p - header == len) + return String(); + if (!space1 || !space2) + return ""; + return String(space1 + 1, space2 - space1 - 1); +} + +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) +{ +} + +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; +} + +void WebSocketHandshake::setSecure(bool secure) +{ + m_secure = 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(m_url.host().lower()); + if (m_url.port()) { + if ((!m_secure && m_url.port() != 80) || (m_secure && m_url.port() != 443)) { + builder.append(":"); + builder.append(String::number(m_url.port())); + } + } + builder.append(m_url.path()); + return builder.toString(); +} + +CString WebSocketHandshake::clientHandshakeMessage() const +{ + StringBuilder builder; + + builder.append("GET "); + builder.append(m_url.path()); + if (!m_url.query().isEmpty()) { + builder.append("?"); + builder.append(m_url.query()); + } + builder.append(" HTTP/1.1\r\n"); + builder.append("Upgrade: WebSocket\r\n"); + builder.append("Connection: Upgrade\r\n"); + builder.append("Host: "); + builder.append(m_url.host().lower()); + if (m_url.port()) { + if ((!m_secure && m_url.port() != 80) || (m_secure && m_url.port() != 443)) { + builder.append(":"); + builder.append(String::number(m_url.port())); + } + } + builder.append("\r\n"); + builder.append("Origin: "); + builder.append(clientOrigin()); + builder.append("\r\n"); + if (!m_clientProtocol.isEmpty()) { + builder.append("WebSocket-Protocol: "); + builder.append(m_clientProtocol); + builder.append("\r\n"); + } + KURL url = httpURLForAuthenticationAndCookies(); + // FIXME: set authentication information or cookies for url. + // Set "Authorization: <credentials>" if authentication information exists for url. + if (m_context->isDocument()) { + Document* document = static_cast<Document*>(m_context); + String cookie = cookies(document, url); + if (!cookie.isEmpty()) { + builder.append("Cookie: "); + builder.append(cookie); + builder.append("\r\n"); + } + // Set "Cookie2: <cookie>" if cookies 2 exists for url? + } + builder.append("\r\n"); + return builder.toString().utf8(); +} + +void WebSocketHandshake::reset() +{ + m_mode = Incomplete; + + m_wsOrigin = String(); + m_wsLocation = String(); + m_wsProtocol = String(); + m_setCookie = String(); + m_setCookie2 = String(); +} + +int WebSocketHandshake::readServerHandshake(const char* header, size_t len) +{ + m_mode = Incomplete; + if (len < sizeof(webSocketServerHandshakeHeader) - 1) { + // Just hasn't been received fully yet. + return -1; + } + if (!memcmp(header, webSocketServerHandshakeHeader, sizeof(webSocketServerHandshakeHeader) - 1)) + m_mode = Normal; + else { + const String& code = extractResponseCode(header, len); + if (code.isNull()) { + LOG(Network, "short server handshake: %s", header); + return -1; + } + if (code.isEmpty()) { + LOG(Network, "no response code found: %s", header); + return len; + } + LOG(Network, "response code: %s", code.utf8().data()); + if (code == "401") { + LOG(Network, "Authentication required"); + return len; + } else { + LOG(Network, "Mismatch server handshake: %s", header); + return len; + } + } + const char* p = header + sizeof(webSocketServerHandshakeHeader) - 1; + const char* end = header + len + 1; + + if (m_mode == Normal) { + size_t headerSize = end - p; + if (headerSize < sizeof(webSocketUpgradeHeader) - 1) + return 0; + if (memcmp(p, webSocketUpgradeHeader, sizeof(webSocketUpgradeHeader) - 1)) { + LOG(Network, "Bad upgrade header: %s", p); + return p - header + sizeof(webSocketUpgradeHeader) - 1; + } + p += sizeof(webSocketUpgradeHeader) - 1; + + headerSize = end - p; + if (headerSize < sizeof(webSocketConnectionHeader) - 1) + return -1; + if (memcmp(p, webSocketConnectionHeader, sizeof(webSocketConnectionHeader) - 1)) { + LOG(Network, "Bad connection header: %s", p); + return p - header + sizeof(webSocketConnectionHeader) - 1; + } + p += sizeof(webSocketConnectionHeader) - 1; + } + + if (!strnstr(p, "\r\n\r\n", end - p)) { + // Just hasn't been received fully yet. + return -1; + } + HTTPHeaderMap headers; + p = readHTTPHeaders(p, end, &headers); + if (!p) { + LOG(Network, "readHTTPHeaders failed"); + m_mode = Failed; + return len; + } + if (!processHeaders(headers)) { + LOG(Network, "header process failed"); + m_mode = Failed; + return p - header; + } + switch (m_mode) { + case Normal: + checkResponseHeaders(); + break; + default: + m_mode = Failed; + break; + } + return p - header; +} + +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; +} + +KURL WebSocketHandshake::httpURLForAuthenticationAndCookies() const +{ + KURL url = m_url.copy(); + url.setProtocol(m_secure ? "https" : "http"); + return url; +} + +const char* WebSocketHandshake::readHTTPHeaders(const char* start, const char* end, HTTPHeaderMap* headers) +{ + 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; + LOG(Network, "CR doesn't follow LF p=%p end=%p", p, end); + return 0; + } + LOG(Network, "Unexpected CR in name"); + return 0; + case '\n': + LOG(Network, "Unexpected LF in name"); + return 0; + case ':': + break; + default: + if (*p >= 0x41 && *p <= 0x5a) + name.append(*p + 0x20); + else + 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': + LOG(Network, "Unexpected LF in value"); + return 0; + default: + value.append(*p); + } + if (*p == '\r') { + ++p; + break; + } + } + if (p >= end || *p != '\n') { + LOG(Network, "CR doesn't follow LF after value p=%p end=%p", p, end); + return 0; + } + AtomicString nameStr(String::fromUTF8(name.data(), name.size())); + String valueStr = String::fromUTF8(value.data(), value.size()); + LOG(Network, "name=%s value=%s", nameStr.string().utf8().data(), valueStr.utf8().data()); + headers->add(nameStr, valueStr); + } + ASSERT_NOT_REACHED(); + return 0; +} + +bool WebSocketHandshake::processHeaders(const HTTPHeaderMap& headers) +{ + for (HTTPHeaderMap::const_iterator it = headers.begin(); it != headers.end(); ++it) { + switch (m_mode) { + case Normal: + if (it->first == "websocket-origin") + m_wsOrigin = it->second; + else if (it->first == "websocket-location") + m_wsLocation = it->second; + else if (it->first == "websocket-protocol") + m_wsProtocol = it->second; + else if (it->first == "set-cookie") + m_setCookie = it->second; + else if (it->first == "set-cookie2") + m_setCookie2 = it->second; + continue; + case Incomplete: + case Failed: + case Connected: + ASSERT_NOT_REACHED(); + } + ASSERT_NOT_REACHED(); + } + return true; +} + +void WebSocketHandshake::checkResponseHeaders() +{ + ASSERT(m_mode == Normal); + m_mode = Failed; + if (m_wsOrigin.isNull() || m_wsLocation.isNull()) + return; + + if (clientOrigin() != m_wsOrigin) { + LOG(Network, "Mismatch origin: %s != %s", clientOrigin().utf8().data(), m_wsOrigin.utf8().data()); + return; + } + if (clientLocation() != m_wsLocation) { + LOG(Network, "Mismatch location: %s != %s", clientLocation().utf8().data(), m_wsLocation.utf8().data()); + return; + } + if (!m_clientProtocol.isEmpty() && m_clientProtocol != m_wsProtocol) { + LOG(Network, "Mismatch protocol: %s != %s", m_clientProtocol.utf8().data(), m_wsProtocol.utf8().data()); + return; + } + m_mode = Connected; + return; +} + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) diff --git a/WebCore/websockets/WebSocketHandshake.h b/WebCore/websockets/WebSocketHandshake.h new file mode 100644 index 0000000..d5dbe68 --- /dev/null +++ b/WebCore/websockets/WebSocketHandshake.h @@ -0,0 +1,113 @@ +/* + * 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 <wtf/Noncopyable.h> + +namespace WebCore { + + class ScriptExecutionContext; + class HTTPHeaderMap; + + 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; + void setSecure(bool secure); + + String clientOrigin() const; + String clientLocation() const; + + CString clientHandshakeMessage() const; + + void reset(); + + 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); + + private: + KURL httpURLForAuthenticationAndCookies() const; + + // Reads all headers except for the two predefined ones. + const char* readHTTPHeaders(const char* start, const char* end, HTTPHeaderMap* headers); + bool processHeaders(const HTTPHeaderMap& headers); + void 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; + }; + +} // namespace WebCore + +#endif // ENABLE(WEB_SOCKETS) + +#endif // WebSocketHandshake_h |
