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