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 "CString.h" 38 #include "DOMWindow.h" 39 #include "Event.h" 40 #include "EventException.h" 41 #include "EventListener.h" 42 #include "EventNames.h" 43 #include "Logging.h" 44 #include "MessageEvent.h" 45 #include "ScriptExecutionContext.h" 46 #include "StringBuilder.h" 47 #include "ThreadableWebSocketChannel.h" 48 #include "WebSocketChannel.h" 49 #include <wtf/StdLibExtras.h> 50 51 namespace WebCore { 52 53 static bool isValidProtocolString(const String& protocol) 54 { 55 if (protocol.isNull()) 56 return true; 57 if (protocol.isEmpty()) 58 return false; 59 const UChar* characters = protocol.characters(); 60 for (size_t i = 0; i < protocol.length(); i++) { 61 if (characters[i] < 0x20 || characters[i] > 0x7E) 62 return false; 63 } 64 return true; 65 } 66 67 static String encodeProtocolString(const String& protocol) 68 { 69 StringBuilder builder; 70 for (size_t i = 0; i < protocol.length(); i++) { 71 if (protocol[i] < 0x20 || protocol[i] > 0x7E) 72 builder.append(String::format("\\u%04X", protocol[i])); 73 else if (protocol[i] == 0x5c) 74 builder.append("\\\\"); 75 else 76 builder.append(protocol[i]); 77 } 78 return builder.toString(); 79 } 80 81 #if USE(V8) 82 83 static bool webSocketsAvailable = false; 84 85 void WebSocket::setIsAvailable(bool available) 86 { 87 webSocketsAvailable = available; 88 } 89 90 bool WebSocket::isAvailable() 91 { 92 return webSocketsAvailable; 93 } 94 95 #endif 96 97 WebSocket::WebSocket(ScriptExecutionContext* context) 98 : ActiveDOMObject(context, this) 99 , m_state(CONNECTING) 100 , m_bufferedAmountAfterClose(0) 101 { 102 } 103 104 WebSocket::~WebSocket() 105 { 106 if (m_channel) 107 m_channel->disconnect(); 108 } 109 110 void WebSocket::connect(const KURL& url, ExceptionCode& ec) 111 { 112 connect(url, String(), ec); 113 } 114 115 void WebSocket::connect(const KURL& url, const String& protocol, ExceptionCode& ec) 116 { 117 LOG(Network, "WebSocket %p connect to %s protocol=%s", this, url.string().utf8().data(), protocol.utf8().data()); 118 m_url = url; 119 m_protocol = protocol; 120 121 if (!m_url.isValid()) { 122 scriptExecutionContext()->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Invalid url for WebSocket " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString()); 123 m_state = CLOSED; 124 ec = SYNTAX_ERR; 125 return; 126 } 127 128 if (!m_url.protocolIs("ws") && !m_url.protocolIs("wss")) { 129 scriptExecutionContext()->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Wrong url scheme for WebSocket " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString()); 130 m_state = CLOSED; 131 ec = SYNTAX_ERR; 132 return; 133 } 134 if (m_url.hasFragmentIdentifier()) { 135 scriptExecutionContext()->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "URL has fragment component " + url.string(), 0, scriptExecutionContext()->securityOrigin()->toString()); 136 m_state = CLOSED; 137 ec = SYNTAX_ERR; 138 return; 139 } 140 if (!isValidProtocolString(m_protocol)) { 141 scriptExecutionContext()->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, "Wrong protocol for WebSocket '" + encodeProtocolString(m_protocol) + "'", 0, scriptExecutionContext()->securityOrigin()->toString()); 142 m_state = CLOSED; 143 ec = SYNTAX_ERR; 144 return; 145 } 146 if (!portAllowed(url)) { 147 scriptExecutionContext()->addMessage(ConsoleDestination, JSMessageSource, LogMessageType, ErrorMessageLevel, String::format("WebSocket port %d blocked", url.port()), 0, scriptExecutionContext()->securityOrigin()->toString()); 148 m_state = CLOSED; 149 ec = SECURITY_ERR; 150 return; 151 } 152 153 m_channel = ThreadableWebSocketChannel::create(scriptExecutionContext(), this, m_url, m_protocol); 154 m_channel->connect(); 155 ActiveDOMObject::setPendingActivity(this); 156 } 157 158 bool WebSocket::send(const String& message, ExceptionCode& ec) 159 { 160 LOG(Network, "WebSocket %p send %s", this, message.utf8().data()); 161 if (m_state == CONNECTING) { 162 ec = INVALID_STATE_ERR; 163 return false; 164 } 165 // No exception is raised if the connection was once established but has subsequently been closed. 166 if (m_state == CLOSED) { 167 m_bufferedAmountAfterClose += message.utf8().length() + 2; // 2 for framing 168 return false; 169 } 170 // FIXME: check message is valid utf8. 171 ASSERT(m_channel); 172 return m_channel->send(message); 173 } 174 175 void WebSocket::close() 176 { 177 LOG(Network, "WebSocket %p close", this); 178 if (m_state == CLOSED) 179 return; 180 m_state = CLOSED; 181 m_bufferedAmountAfterClose = m_channel->bufferedAmount(); 182 m_channel->close(); 183 } 184 185 const KURL& WebSocket::url() const 186 { 187 return m_url; 188 } 189 190 WebSocket::State WebSocket::readyState() const 191 { 192 return m_state; 193 } 194 195 unsigned long WebSocket::bufferedAmount() const 196 { 197 if (m_state == OPEN) 198 return m_channel->bufferedAmount(); 199 return m_bufferedAmountAfterClose; 200 } 201 202 ScriptExecutionContext* WebSocket::scriptExecutionContext() const 203 { 204 return ActiveDOMObject::scriptExecutionContext(); 205 } 206 207 void 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 215 void WebSocket::stop() 216 { 217 bool pending = hasPendingActivity(); 218 if (m_channel) 219 m_channel->disconnect(); 220 m_channel = 0; 221 m_state = CLOSED; 222 ActiveDOMObject::stop(); 223 if (pending) 224 ActiveDOMObject::unsetPendingActivity(this); 225 } 226 227 void WebSocket::didConnect() 228 { 229 LOG(Network, "WebSocket %p didConnect", this); 230 if (m_state != CONNECTING || !scriptExecutionContext()) { 231 didClose(0); 232 return; 233 } 234 m_state = OPEN; 235 dispatchEvent(Event::create(eventNames().openEvent, false, false)); 236 } 237 238 void WebSocket::didReceiveMessage(const String& msg) 239 { 240 LOG(Network, "WebSocket %p didReceiveMessage %s", this, msg.utf8().data()); 241 if (m_state != OPEN || !scriptExecutionContext()) 242 return; 243 RefPtr<MessageEvent> evt = MessageEvent::create(); 244 evt->initMessageEvent(eventNames().messageEvent, false, false, SerializedScriptValue::create(msg), "", "", 0, 0); 245 dispatchEvent(evt); 246 } 247 248 void WebSocket::didClose(unsigned long unhandledBufferedAmount) 249 { 250 LOG(Network, "WebSocket %p didClose", this); 251 m_state = CLOSED; 252 m_bufferedAmountAfterClose += unhandledBufferedAmount; 253 dispatchEvent(Event::create(eventNames().closeEvent, false, false)); 254 m_channel = 0; 255 if (hasPendingActivity()) 256 ActiveDOMObject::unsetPendingActivity(this); 257 } 258 259 EventTargetData* WebSocket::eventTargetData() 260 { 261 return &m_eventTargetData; 262 } 263 264 EventTargetData* WebSocket::ensureEventTargetData() 265 { 266 return &m_eventTargetData; 267 } 268 269 } // namespace WebCore 270 271 #endif 272