WebSocket.cpp revision cad810f21b803229eb11403f9209855525a25d57
1/*
2 * Copyright (C) 2009 Google Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32
33#if ENABLE(WEB_SOCKETS)
34
35#include "WebSocket.h"
36
37#include "DOMWindow.h"
38#include "Event.h"
39#include "EventException.h"
40#include "EventListener.h"
41#include "EventNames.h"
42#include "Logging.h"
43#include "MessageEvent.h"
44#include "ScriptExecutionContext.h"
45#include "ThreadableWebSocketChannel.h"
46#include "WebSocketChannel.h"
47#include <wtf/StdLibExtras.h>
48#include <wtf/text/CString.h>
49#include <wtf/text/StringBuilder.h>
50#include <wtf/text/StringConcatenate.h>
51
52namespace WebCore {
53
54static bool isValidProtocolString(const String& protocol)
55{
56    if (protocol.isNull())
57        return true;
58    if (protocol.isEmpty())
59        return false;
60    const UChar* characters = protocol.characters();
61    for (size_t i = 0; i < protocol.length(); i++) {
62        if (characters[i] < 0x20 || characters[i] > 0x7E)
63            return false;
64    }
65    return true;
66}
67
68static String encodeProtocolString(const String& protocol)
69{
70    StringBuilder builder;
71    for (size_t i = 0; i < protocol.length(); i++) {
72        if (protocol[i] < 0x20 || protocol[i] > 0x7E)
73            builder.append(String::format("\\u%04X", protocol[i]));
74        else if (protocol[i] == 0x5c)
75            builder.append("\\\\");
76        else
77            builder.append(protocol[i]);
78    }
79    return builder.toString();
80}
81
82static bool webSocketsAvailable = false;
83
84void WebSocket::setIsAvailable(bool available)
85{
86    webSocketsAvailable = available;
87}
88
89bool WebSocket::isAvailable()
90{
91    return webSocketsAvailable;
92}
93
94WebSocket::WebSocket(ScriptExecutionContext* context)
95    : ActiveDOMObject(context, this)
96    , m_state(CONNECTING)
97    , m_bufferedAmountAfterClose(0)
98{
99}
100
101WebSocket::~WebSocket()
102{
103    if (m_channel)
104        m_channel->disconnect();
105}
106
107void WebSocket::connect(const KURL& url, ExceptionCode& ec)
108{
109    connect(url, String(), ec);
110}
111
112void WebSocket::connect(const KURL& url, const String& protocol, ExceptionCode& ec)
113{
114    LOG(Network, "WebSocket %p connect to %s protocol=%s", this, url.string().utf8().data(), protocol.utf8().data());
115    m_url = url;
116    m_protocol = protocol;
117
118    if (!m_url.isValid()) {
119        scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Invalid url for WebSocket " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString());
120        m_state = CLOSED;
121        ec = SYNTAX_ERR;
122        return;
123    }
124
125    if (!m_url.protocolIs("ws") && !m_url.protocolIs("wss")) {
126        scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Wrong url scheme for WebSocket " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString());
127        m_state = CLOSED;
128        ec = SYNTAX_ERR;
129        return;
130    }
131    if (m_url.hasFragmentIdentifier()) {
132        scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "URL has fragment component " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString());
133        m_state = CLOSED;
134        ec = SYNTAX_ERR;
135        return;
136    }
137    if (!isValidProtocolString(m_protocol)) {
138        scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Wrong protocol for WebSocket '" + encodeProtocolString(m_protocol) + "'", 0, scriptExecutionContext()->securityOrigin()->toString());
139        m_state = CLOSED;
140        ec = SYNTAX_ERR;
141        return;
142    }
143    if (!portAllowed(url)) {
144        scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, makeString("WebSocket port ", String::number(url.port()), " blocked"), 0, scriptExecutionContext()->securityOrigin()->toString());
145        m_state = CLOSED;
146        ec = SECURITY_ERR;
147        return;
148    }
149
150    m_channel = ThreadableWebSocketChannel::create(scriptExecutionContext(), this, m_url, m_protocol);
151    m_channel->connect();
152    ActiveDOMObject::setPendingActivity(this);
153}
154
155bool WebSocket::send(const String& message, ExceptionCode& ec)
156{
157    LOG(Network, "WebSocket %p send %s", this, message.utf8().data());
158    if (m_state == CONNECTING) {
159        ec = INVALID_STATE_ERR;
160        return false;
161    }
162    // No exception is raised if the connection was once established but has subsequently been closed.
163    if (m_state == CLOSED) {
164        m_bufferedAmountAfterClose += message.utf8().length() + 2; // 2 for frameing
165        return false;
166    }
167    // FIXME: check message is valid utf8.
168    ASSERT(m_channel);
169    return m_channel->send(message);
170}
171
172void WebSocket::close()
173{
174    LOG(Network, "WebSocket %p close", this);
175    if (m_state == CLOSED)
176        return;
177    m_state = CLOSED;
178    m_bufferedAmountAfterClose = m_channel->bufferedAmount();
179    // didClose notification may be already queued, which we will inadvertently process while waiting for bufferedAmount() to return.
180    // In this case m_channel will be set to null during didClose() call, thus we need to test validness of m_channel here.
181    if (m_channel)
182        m_channel->close();
183}
184
185const KURL& WebSocket::url() const
186{
187    return m_url;
188}
189
190WebSocket::State WebSocket::readyState() const
191{
192    return m_state;
193}
194
195unsigned long WebSocket::bufferedAmount() const
196{
197    if (m_state == OPEN)
198        return m_channel->bufferedAmount();
199    return m_bufferedAmountAfterClose;
200}
201
202ScriptExecutionContext* WebSocket::scriptExecutionContext() const
203{
204    return ActiveDOMObject::scriptExecutionContext();
205}
206
207void WebSocket::contextDestroyed()
208{
209    LOG(Network, "WebSocket %p scriptExecutionContext destroyed", this);
210    ASSERT(!m_channel);
211    ASSERT(m_state == CLOSED);
212    ActiveDOMObject::contextDestroyed();
213}
214
215bool WebSocket::canSuspend() const
216{
217    return !m_channel;
218}
219
220void WebSocket::suspend(ReasonForSuspension)
221{
222    if (m_channel)
223        m_channel->suspend();
224}
225
226void WebSocket::resume()
227{
228    if (m_channel)
229        m_channel->resume();
230}
231
232void WebSocket::stop()
233{
234    bool pending = hasPendingActivity();
235    if (m_channel)
236        m_channel->disconnect();
237    m_channel = 0;
238    m_state = CLOSED;
239    ActiveDOMObject::stop();
240    if (pending)
241        ActiveDOMObject::unsetPendingActivity(this);
242}
243
244void WebSocket::didConnect()
245{
246    LOG(Network, "WebSocket %p didConnect", this);
247    if (m_state != CONNECTING) {
248        didClose(0);
249        return;
250    }
251    ASSERT(scriptExecutionContext());
252    m_state = OPEN;
253    dispatchEvent(Event::create(eventNames().openEvent, false, false));
254}
255
256void WebSocket::didReceiveMessage(const String& msg)
257{
258    LOG(Network, "WebSocket %p didReceiveMessage %s", this, msg.utf8().data());
259    if (m_state != OPEN)
260        return;
261    ASSERT(scriptExecutionContext());
262    RefPtr<MessageEvent> evt = MessageEvent::create();
263    evt->initMessageEvent(eventNames().messageEvent, false, false, SerializedScriptValue::create(msg), "", "", 0, 0);
264    dispatchEvent(evt);
265}
266
267void WebSocket::didReceiveMessageError()
268{
269    LOG(Network, "WebSocket %p didReceiveErrorMessage", this);
270    if (m_state != OPEN)
271        return;
272    ASSERT(scriptExecutionContext());
273    dispatchEvent(Event::create(eventNames().errorEvent, false, false));
274}
275
276void WebSocket::didClose(unsigned long unhandledBufferedAmount)
277{
278    LOG(Network, "WebSocket %p didClose", this);
279    if (!m_channel)
280        return;
281    m_state = CLOSED;
282    m_bufferedAmountAfterClose += unhandledBufferedAmount;
283    ASSERT(scriptExecutionContext());
284    dispatchEvent(Event::create(eventNames().closeEvent, false, false));
285    m_channel = 0;
286    if (hasPendingActivity())
287        ActiveDOMObject::unsetPendingActivity(this);
288}
289
290EventTargetData* WebSocket::eventTargetData()
291{
292    return &m_eventTargetData;
293}
294
295EventTargetData* WebSocket::ensureEventTargetData()
296{
297    return &m_eventTargetData;
298}
299
300}  // namespace WebCore
301
302#endif
303