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 53namespace WebCore { 54 55static 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 69static 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 83static bool webSocketsAvailable = false; 84 85void WebSocket::setIsAvailable(bool available) 86{ 87 webSocketsAvailable = available; 88} 89 90bool WebSocket::isAvailable() 91{ 92 return webSocketsAvailable; 93} 94 95WebSocket::WebSocket(ScriptExecutionContext* context) 96 : ActiveDOMObject(context, this) 97 , m_state(CONNECTING) 98 , m_bufferedAmountAfterClose(0) 99{ 100} 101 102WebSocket::~WebSocket() 103{ 104 if (m_channel) 105 m_channel->disconnect(); 106} 107 108void WebSocket::connect(const KURL& url, ExceptionCode& ec) 109{ 110 connect(url, String(), ec); 111} 112 113void 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 156bool 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 173void 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 186const KURL& WebSocket::url() const 187{ 188 return m_url; 189} 190 191WebSocket::State WebSocket::readyState() const 192{ 193 return m_state; 194} 195 196unsigned long WebSocket::bufferedAmount() const 197{ 198 if (m_state == OPEN) 199 return m_channel->bufferedAmount(); 200 return m_bufferedAmountAfterClose; 201} 202 203ScriptExecutionContext* WebSocket::scriptExecutionContext() const 204{ 205 return ActiveDOMObject::scriptExecutionContext(); 206} 207 208void 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 216bool WebSocket::canSuspend() const 217{ 218 return !m_channel; 219} 220 221void WebSocket::suspend(ReasonForSuspension) 222{ 223 if (m_channel) 224 m_channel->suspend(); 225} 226 227void WebSocket::resume() 228{ 229 if (m_channel) 230 m_channel->resume(); 231} 232 233void 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 245void 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 257void 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 268void 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 277void 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 294EventTargetData* WebSocket::eventTargetData() 295{ 296 return &m_eventTargetData; 297} 298 299EventTargetData* WebSocket::ensureEventTargetData() 300{ 301 return &m_eventTargetData; 302} 303 304} // namespace WebCore 305 306#endif 307