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 "ScriptCallStack.h"
45 #include "ScriptExecutionContext.h"
46 #include "ThreadableWebSocketChannel.h"
47 #include "WebSocketChannel.h"
48 #include <wtf/StdLibExtras.h>
49 #include <wtf/text/CString.h>
50 #include <wtf/text/StringBuilder.h>
51 #include <wtf/text/StringConcatenate.h>
52
53 namespace WebCore {
54
isValidProtocolString(const String & protocol)55 static bool isValidProtocolString(const String& protocol)
56 {
57 if (protocol.isNull())
58 return true;
59 if (protocol.isEmpty())
60 return false;
61 const UChar* characters = protocol.characters();
62 for (size_t i = 0; i < protocol.length(); i++) {
63 if (characters[i] < 0x20 || characters[i] > 0x7E)
64 return false;
65 }
66 return true;
67 }
68
encodeProtocolString(const String & protocol)69 static String encodeProtocolString(const String& protocol)
70 {
71 StringBuilder builder;
72 for (size_t i = 0; i < protocol.length(); i++) {
73 if (protocol[i] < 0x20 || protocol[i] > 0x7E)
74 builder.append(String::format("\\u%04X", protocol[i]));
75 else if (protocol[i] == 0x5c)
76 builder.append("\\\\");
77 else
78 builder.append(protocol[i]);
79 }
80 return builder.toString();
81 }
82
83 static bool webSocketsAvailable = false;
84
setIsAvailable(bool available)85 void WebSocket::setIsAvailable(bool available)
86 {
87 webSocketsAvailable = available;
88 }
89
isAvailable()90 bool WebSocket::isAvailable()
91 {
92 return webSocketsAvailable;
93 }
94
WebSocket(ScriptExecutionContext * context)95 WebSocket::WebSocket(ScriptExecutionContext* context)
96 : ActiveDOMObject(context, this)
97 , m_state(CONNECTING)
98 , m_bufferedAmountAfterClose(0)
99 {
100 }
101
~WebSocket()102 WebSocket::~WebSocket()
103 {
104 if (m_channel)
105 m_channel->disconnect();
106 }
107
connect(const KURL & url,ExceptionCode & ec)108 void WebSocket::connect(const KURL& url, ExceptionCode& ec)
109 {
110 connect(url, String(), ec);
111 }
112
connect(const KURL & url,const String & protocol,ExceptionCode & ec)113 void WebSocket::connect(const KURL& url, const String& protocol, ExceptionCode& ec)
114 {
115 LOG(Network, "WebSocket %p connect to %s protocol=%s", this, url.string().utf8().data(), protocol.utf8().data());
116 m_url = url;
117 m_protocol = protocol;
118
119 if (!m_url.isValid()) {
120 scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Invalid url for WebSocket " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString(), 0);
121 m_state = CLOSED;
122 ec = SYNTAX_ERR;
123 return;
124 }
125
126 if (!m_url.protocolIs("ws") && !m_url.protocolIs("wss")) {
127 scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Wrong url scheme for WebSocket " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString(), 0);
128 m_state = CLOSED;
129 ec = SYNTAX_ERR;
130 return;
131 }
132 if (m_url.hasFragmentIdentifier()) {
133 scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "URL has fragment component " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString(), 0);
134 m_state = CLOSED;
135 ec = SYNTAX_ERR;
136 return;
137 }
138 if (!isValidProtocolString(m_protocol)) {
139 scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, "Wrong protocol for WebSocket '" + encodeProtocolString(m_protocol) + "'", 0, scriptExecutionContext()->securityOrigin()->toString(), 0);
140 m_state = CLOSED;
141 ec = SYNTAX_ERR;
142 return;
143 }
144 if (!portAllowed(url)) {
145 scriptExecutionContext()->addMessage(JSMessageSource, LogMessageType, ErrorMessageLevel, makeString("WebSocket port ", String::number(url.port()), " blocked"), 0, scriptExecutionContext()->securityOrigin()->toString(), 0);
146 m_state = CLOSED;
147 ec = SECURITY_ERR;
148 return;
149 }
150
151 m_channel = ThreadableWebSocketChannel::create(scriptExecutionContext(), this, m_url, m_protocol);
152 m_channel->connect();
153 ActiveDOMObject::setPendingActivity(this);
154 }
155
send(const String & message,ExceptionCode & ec)156 bool WebSocket::send(const String& message, ExceptionCode& ec)
157 {
158 LOG(Network, "WebSocket %p send %s", this, message.utf8().data());
159 if (m_state == CONNECTING) {
160 ec = INVALID_STATE_ERR;
161 return false;
162 }
163 // No exception is raised if the connection was once established but has subsequently been closed.
164 if (m_state == CLOSED) {
165 m_bufferedAmountAfterClose += message.utf8().length() + 2; // 2 for frameing
166 return false;
167 }
168 // FIXME: check message is valid utf8.
169 ASSERT(m_channel);
170 return m_channel->send(message);
171 }
172
close()173 void WebSocket::close()
174 {
175 LOG(Network, "WebSocket %p close", this);
176 if (m_state == CLOSED)
177 return;
178 m_state = CLOSED;
179 m_bufferedAmountAfterClose = m_channel->bufferedAmount();
180 // didClose notification may be already queued, which we will inadvertently process while waiting for bufferedAmount() to return.
181 // In this case m_channel will be set to null during didClose() call, thus we need to test validness of m_channel here.
182 if (m_channel)
183 m_channel->close();
184 }
185
url() const186 const KURL& WebSocket::url() const
187 {
188 return m_url;
189 }
190
readyState() const191 WebSocket::State WebSocket::readyState() const
192 {
193 return m_state;
194 }
195
bufferedAmount() const196 unsigned long WebSocket::bufferedAmount() const
197 {
198 if (m_state == OPEN)
199 return m_channel->bufferedAmount();
200 return m_bufferedAmountAfterClose;
201 }
202
scriptExecutionContext() const203 ScriptExecutionContext* WebSocket::scriptExecutionContext() const
204 {
205 return ActiveDOMObject::scriptExecutionContext();
206 }
207
contextDestroyed()208 void WebSocket::contextDestroyed()
209 {
210 LOG(Network, "WebSocket %p scriptExecutionContext destroyed", this);
211 ASSERT(!m_channel);
212 ASSERT(m_state == CLOSED);
213 ActiveDOMObject::contextDestroyed();
214 }
215
canSuspend() const216 bool WebSocket::canSuspend() const
217 {
218 return !m_channel;
219 }
220
suspend(ReasonForSuspension)221 void WebSocket::suspend(ReasonForSuspension)
222 {
223 if (m_channel)
224 m_channel->suspend();
225 }
226
resume()227 void WebSocket::resume()
228 {
229 if (m_channel)
230 m_channel->resume();
231 }
232
stop()233 void WebSocket::stop()
234 {
235 bool pending = hasPendingActivity();
236 if (m_channel)
237 m_channel->disconnect();
238 m_channel = 0;
239 m_state = CLOSED;
240 ActiveDOMObject::stop();
241 if (pending)
242 ActiveDOMObject::unsetPendingActivity(this);
243 }
244
didConnect()245 void WebSocket::didConnect()
246 {
247 LOG(Network, "WebSocket %p didConnect", this);
248 if (m_state != CONNECTING) {
249 didClose(0);
250 return;
251 }
252 ASSERT(scriptExecutionContext());
253 m_state = OPEN;
254 dispatchEvent(Event::create(eventNames().openEvent, false, false));
255 }
256
didReceiveMessage(const String & msg)257 void WebSocket::didReceiveMessage(const String& msg)
258 {
259 LOG(Network, "WebSocket %p didReceiveMessage %s", this, msg.utf8().data());
260 if (m_state != OPEN)
261 return;
262 ASSERT(scriptExecutionContext());
263 RefPtr<MessageEvent> evt = MessageEvent::create();
264 evt->initMessageEvent(eventNames().messageEvent, false, false, SerializedScriptValue::create(msg), "", "", 0, 0);
265 dispatchEvent(evt);
266 }
267
didReceiveMessageError()268 void WebSocket::didReceiveMessageError()
269 {
270 LOG(Network, "WebSocket %p didReceiveErrorMessage", this);
271 if (m_state != OPEN)
272 return;
273 ASSERT(scriptExecutionContext());
274 dispatchEvent(Event::create(eventNames().errorEvent, false, false));
275 }
276
didClose(unsigned long unhandledBufferedAmount)277 void WebSocket::didClose(unsigned long unhandledBufferedAmount)
278 {
279 LOG(Network, "WebSocket %p didClose", this);
280 if (!m_channel)
281 return;
282 m_state = CLOSED;
283 m_bufferedAmountAfterClose += unhandledBufferedAmount;
284 ASSERT(scriptExecutionContext());
285 dispatchEvent(Event::create(eventNames().closeEvent, false, false));
286 if (m_channel) {
287 m_channel->disconnect();
288 m_channel = 0;
289 }
290 if (hasPendingActivity())
291 ActiveDOMObject::unsetPendingActivity(this);
292 }
293
eventTargetData()294 EventTargetData* WebSocket::eventTargetData()
295 {
296 return &m_eventTargetData;
297 }
298
ensureEventTargetData()299 EventTargetData* WebSocket::ensureEventTargetData()
300 {
301 return &m_eventTargetData;
302 }
303
304 } // namespace WebCore
305
306 #endif
307