1/* 2 * Copyright (C) 2011 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 "Blob.h" 38#include "CloseEvent.h" 39#include "ContentSecurityPolicy.h" 40#include "DOMWindow.h" 41#include "Document.h" 42#include "Event.h" 43#include "EventException.h" 44#include "EventListener.h" 45#include "EventNames.h" 46#include "ExceptionCode.h" 47#include "Frame.h" 48#include "Logging.h" 49#include "MessageEvent.h" 50#include "ScriptController.h" 51#include "ScriptExecutionContext.h" 52#include "SecurityOrigin.h" 53#include "ThreadableWebSocketChannel.h" 54#include "WebSocketChannel.h" 55#include <inspector/ScriptCallStack.h> 56#include <runtime/ArrayBuffer.h> 57#include <runtime/ArrayBufferView.h> 58#include <wtf/HashSet.h> 59#include <wtf/PassOwnPtr.h> 60#include <wtf/StdLibExtras.h> 61#include <wtf/text/CString.h> 62#include <wtf/text/StringBuilder.h> 63#include <wtf/text/WTFString.h> 64 65namespace WebCore { 66 67const size_t maxReasonSizeInBytes = 123; 68 69static inline bool isValidProtocolCharacter(UChar character) 70{ 71 // Hybi-10 says "(Subprotocol string must consist of) characters in the range U+0021 to U+007E not including 72 // separator characters as defined in [RFC2616]." 73 const UChar minimumProtocolCharacter = '!'; // U+0021. 74 const UChar maximumProtocolCharacter = '~'; // U+007E. 75 return character >= minimumProtocolCharacter && character <= maximumProtocolCharacter 76 && character != '"' && character != '(' && character != ')' && character != ',' && character != '/' 77 && !(character >= ':' && character <= '@') // U+003A - U+0040 (':', ';', '<', '=', '>', '?', '@'). 78 && !(character >= '[' && character <= ']') // U+005B - U+005D ('[', '\\', ']'). 79 && character != '{' && character != '}'; 80} 81 82static bool isValidProtocolString(const String& protocol) 83{ 84 if (protocol.isEmpty()) 85 return false; 86 for (size_t i = 0; i < protocol.length(); ++i) { 87 if (!isValidProtocolCharacter(protocol[i])) 88 return false; 89 } 90 return true; 91} 92 93static String encodeProtocolString(const String& protocol) 94{ 95 StringBuilder builder; 96 for (size_t i = 0; i < protocol.length(); i++) { 97 if (protocol[i] < 0x20 || protocol[i] > 0x7E) 98 builder.append(String::format("\\u%04X", protocol[i])); 99 else if (protocol[i] == 0x5c) 100 builder.append("\\\\"); 101 else 102 builder.append(protocol[i]); 103 } 104 return builder.toString(); 105} 106 107static String joinStrings(const Vector<String>& strings, const char* separator) 108{ 109 StringBuilder builder; 110 for (size_t i = 0; i < strings.size(); ++i) { 111 if (i) 112 builder.append(separator); 113 builder.append(strings[i]); 114 } 115 return builder.toString(); 116} 117 118static unsigned long saturateAdd(unsigned long a, unsigned long b) 119{ 120 if (std::numeric_limits<unsigned long>::max() - a < b) 121 return std::numeric_limits<unsigned long>::max(); 122 return a + b; 123} 124 125static bool webSocketsAvailable = true; 126 127void WebSocket::setIsAvailable(bool available) 128{ 129 webSocketsAvailable = available; 130} 131 132bool WebSocket::isAvailable() 133{ 134 return webSocketsAvailable; 135} 136 137const char* WebSocket::subProtocolSeperator() 138{ 139 return ", "; 140} 141 142WebSocket::WebSocket(ScriptExecutionContext& context) 143 : ActiveDOMObject(&context) 144 , m_state(CONNECTING) 145 , m_bufferedAmount(0) 146 , m_bufferedAmountAfterClose(0) 147 , m_binaryType(BinaryTypeBlob) 148 , m_subprotocol("") 149 , m_extensions("") 150{ 151} 152 153WebSocket::~WebSocket() 154{ 155 if (m_channel) 156 m_channel->disconnect(); 157} 158 159PassRefPtr<WebSocket> WebSocket::create(ScriptExecutionContext& context) 160{ 161 RefPtr<WebSocket> webSocket(adoptRef(new WebSocket(context))); 162 webSocket->suspendIfNeeded(); 163 return webSocket.release(); 164} 165 166PassRefPtr<WebSocket> WebSocket::create(ScriptExecutionContext& context, const String& url, ExceptionCode& ec) 167{ 168 Vector<String> protocols; 169 return WebSocket::create(context, url, protocols, ec); 170} 171 172PassRefPtr<WebSocket> WebSocket::create(ScriptExecutionContext& context, const String& url, const Vector<String>& protocols, ExceptionCode& ec) 173{ 174 if (url.isNull()) { 175 ec = SYNTAX_ERR; 176 return 0; 177 } 178 179 RefPtr<WebSocket> webSocket(adoptRef(new WebSocket(context))); 180 webSocket->suspendIfNeeded(); 181 182 webSocket->connect(context.completeURL(url), protocols, ec); 183 if (ec) 184 return 0; 185 186 return webSocket.release(); 187} 188 189PassRefPtr<WebSocket> WebSocket::create(ScriptExecutionContext& context, const String& url, const String& protocol, ExceptionCode& ec) 190{ 191 Vector<String> protocols; 192 protocols.append(protocol); 193 return WebSocket::create(context, url, protocols, ec); 194} 195 196void WebSocket::connect(const String& url, ExceptionCode& ec) 197{ 198 Vector<String> protocols; 199 connect(url, protocols, ec); 200} 201 202void WebSocket::connect(const String& url, const String& protocol, ExceptionCode& ec) 203{ 204 Vector<String> protocols; 205 protocols.append(protocol); 206 connect(url, protocols, ec); 207} 208 209void WebSocket::connect(const String& url, const Vector<String>& protocols, ExceptionCode& ec) 210{ 211 LOG(Network, "WebSocket %p connect() url='%s'", this, url.utf8().data()); 212 m_url = URL(URL(), url); 213 214 if (!m_url.isValid()) { 215 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Invalid url for WebSocket " + m_url.stringCenterEllipsizedToLength()); 216 m_state = CLOSED; 217 ec = SYNTAX_ERR; 218 return; 219 } 220 221 if (!m_url.protocolIs("ws") && !m_url.protocolIs("wss")) { 222 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Wrong url scheme for WebSocket " + m_url.stringCenterEllipsizedToLength()); 223 m_state = CLOSED; 224 ec = SYNTAX_ERR; 225 return; 226 } 227 if (m_url.hasFragmentIdentifier()) { 228 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "URL has fragment component " + m_url.stringCenterEllipsizedToLength()); 229 m_state = CLOSED; 230 ec = SYNTAX_ERR; 231 return; 232 } 233 if (!portAllowed(m_url)) { 234 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "WebSocket port " + String::number(m_url.port()) + " blocked"); 235 m_state = CLOSED; 236 ec = SECURITY_ERR; 237 return; 238 } 239 240 // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved. 241 bool shouldBypassMainWorldContentSecurityPolicy = false; 242 if (scriptExecutionContext()->isDocument()) { 243 Document* document = toDocument(scriptExecutionContext()); 244 shouldBypassMainWorldContentSecurityPolicy = document->frame()->script().shouldBypassMainWorldContentSecurityPolicy(); 245 } 246 if (!shouldBypassMainWorldContentSecurityPolicy && !scriptExecutionContext()->contentSecurityPolicy()->allowConnectToSource(m_url)) { 247 m_state = CLOSED; 248 249 // FIXME: Should this be throwing an exception? 250 ec = SECURITY_ERR; 251 return; 252 } 253 254 m_channel = ThreadableWebSocketChannel::create(scriptExecutionContext(), this); 255 256 // FIXME: There is a disagreement about restriction of subprotocols between WebSocket API and hybi-10 protocol 257 // draft. The former simply says "only characters in the range U+0021 to U+007E are allowed," while the latter 258 // imposes a stricter rule: "the elements MUST be non-empty strings with characters as defined in [RFC2616], 259 // and MUST all be unique strings." 260 // 261 // Here, we throw SYNTAX_ERR if the given protocols do not meet the latter criteria. This behavior does not 262 // comply with WebSocket API specification, but it seems to be the only reasonable way to handle this conflict. 263 for (size_t i = 0; i < protocols.size(); ++i) { 264 if (!isValidProtocolString(protocols[i])) { 265 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Wrong protocol for WebSocket '" + encodeProtocolString(protocols[i]) + "'"); 266 m_state = CLOSED; 267 ec = SYNTAX_ERR; 268 return; 269 } 270 } 271 HashSet<String> visited; 272 for (size_t i = 0; i < protocols.size(); ++i) { 273 if (!visited.add(protocols[i]).isNewEntry) { 274 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "WebSocket protocols contain duplicates: '" + encodeProtocolString(protocols[i]) + "'"); 275 m_state = CLOSED; 276 ec = SYNTAX_ERR; 277 return; 278 } 279 } 280 281 String protocolString; 282 if (!protocols.isEmpty()) 283 protocolString = joinStrings(protocols, subProtocolSeperator()); 284 285 m_channel->connect(m_url, protocolString); 286 ActiveDOMObject::setPendingActivity(this); 287} 288 289void WebSocket::send(const String& message, ExceptionCode& ec) 290{ 291 LOG(Network, "WebSocket %p send() Sending String '%s'", this, message.utf8().data()); 292 if (m_state == CONNECTING) { 293 ec = INVALID_STATE_ERR; 294 return; 295 } 296 // No exception is raised if the connection was once established but has subsequently been closed. 297 if (m_state == CLOSING || m_state == CLOSED) { 298 size_t payloadSize = message.utf8().length(); 299 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); 300 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); 301 return; 302 } 303 ASSERT(m_channel); 304 ThreadableWebSocketChannel::SendResult result = m_channel->send(message); 305 if (result == ThreadableWebSocketChannel::InvalidMessage) { 306 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, ASCIILiteral("Websocket message contains invalid character(s).")); 307 ec = SYNTAX_ERR; 308 return; 309 } 310} 311 312void WebSocket::send(ArrayBuffer* binaryData, ExceptionCode& ec) 313{ 314 LOG(Network, "WebSocket %p send() Sending ArrayBuffer %p", this, binaryData); 315 ASSERT(binaryData); 316 if (m_state == CONNECTING) { 317 ec = INVALID_STATE_ERR; 318 return; 319 } 320 if (m_state == CLOSING || m_state == CLOSED) { 321 unsigned payloadSize = binaryData->byteLength(); 322 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); 323 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); 324 return; 325 } 326 ASSERT(m_channel); 327 m_channel->send(*binaryData, 0, binaryData->byteLength()); 328} 329 330void WebSocket::send(ArrayBufferView* arrayBufferView, ExceptionCode& ec) 331{ 332 LOG(Network, "WebSocket %p send() Sending ArrayBufferView %p", this, arrayBufferView); 333 ASSERT(arrayBufferView); 334 if (m_state == CONNECTING) { 335 ec = INVALID_STATE_ERR; 336 return; 337 } 338 if (m_state == CLOSING || m_state == CLOSED) { 339 unsigned payloadSize = arrayBufferView->byteLength(); 340 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); 341 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); 342 return; 343 } 344 ASSERT(m_channel); 345 RefPtr<ArrayBuffer> arrayBuffer(arrayBufferView->buffer()); 346 m_channel->send(*arrayBuffer, arrayBufferView->byteOffset(), arrayBufferView->byteLength()); 347} 348 349void WebSocket::send(Blob* binaryData, ExceptionCode& ec) 350{ 351 LOG(Network, "WebSocket %p send() Sending Blob '%s'", this, binaryData->url().stringCenterEllipsizedToLength().utf8().data()); 352 if (m_state == CONNECTING) { 353 ec = INVALID_STATE_ERR; 354 return; 355 } 356 if (m_state == CLOSING || m_state == CLOSED) { 357 unsigned long payloadSize = static_cast<unsigned long>(binaryData->size()); 358 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize); 359 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize)); 360 return; 361 } 362 ASSERT(m_channel); 363 m_channel->send(*binaryData); 364} 365 366void WebSocket::close(int code, const String& reason, ExceptionCode& ec) 367{ 368 if (code == WebSocketChannel::CloseEventCodeNotSpecified) 369 LOG(Network, "WebSocket %p close() without code and reason", this); 370 else { 371 LOG(Network, "WebSocket %p close() code=%d reason='%s'", this, code, reason.utf8().data()); 372 if (!(code == WebSocketChannel::CloseEventCodeNormalClosure || (WebSocketChannel::CloseEventCodeMinimumUserDefined <= code && code <= WebSocketChannel::CloseEventCodeMaximumUserDefined))) { 373 ec = INVALID_ACCESS_ERR; 374 return; 375 } 376 CString utf8 = reason.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD); 377 if (utf8.length() > maxReasonSizeInBytes) { 378 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, ASCIILiteral("WebSocket close message is too long.")); 379 ec = SYNTAX_ERR; 380 return; 381 } 382 } 383 384 if (m_state == CLOSING || m_state == CLOSED) 385 return; 386 if (m_state == CONNECTING) { 387 m_state = CLOSING; 388 m_channel->fail("WebSocket is closed before the connection is established."); 389 return; 390 } 391 m_state = CLOSING; 392 if (m_channel) 393 m_channel->close(code, reason); 394} 395 396const URL& WebSocket::url() const 397{ 398 return m_url; 399} 400 401WebSocket::State WebSocket::readyState() const 402{ 403 return m_state; 404} 405 406unsigned long WebSocket::bufferedAmount() const 407{ 408 return saturateAdd(m_bufferedAmount, m_bufferedAmountAfterClose); 409} 410 411String WebSocket::protocol() const 412{ 413 return m_subprotocol; 414} 415 416String WebSocket::extensions() const 417{ 418 return m_extensions; 419} 420 421String WebSocket::binaryType() const 422{ 423 switch (m_binaryType) { 424 case BinaryTypeBlob: 425 return "blob"; 426 case BinaryTypeArrayBuffer: 427 return "arraybuffer"; 428 } 429 ASSERT_NOT_REACHED(); 430 return String(); 431} 432 433void WebSocket::setBinaryType(const String& binaryType) 434{ 435 if (binaryType == "blob") { 436 m_binaryType = BinaryTypeBlob; 437 return; 438 } 439 if (binaryType == "arraybuffer") { 440 m_binaryType = BinaryTypeArrayBuffer; 441 return; 442 } 443 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "'" + binaryType + "' is not a valid value for binaryType; binaryType remains unchanged."); 444} 445 446EventTargetInterface WebSocket::eventTargetInterface() const 447{ 448 return WebSocketEventTargetInterfaceType; 449} 450 451ScriptExecutionContext* WebSocket::scriptExecutionContext() const 452{ 453 return ActiveDOMObject::scriptExecutionContext(); 454} 455 456void WebSocket::contextDestroyed() 457{ 458 LOG(Network, "WebSocket %p contextDestroyed()", this); 459 ASSERT(!m_channel); 460 ASSERT(m_state == CLOSED); 461 ActiveDOMObject::contextDestroyed(); 462} 463 464bool WebSocket::canSuspend() const 465{ 466 return !m_channel; 467} 468 469void WebSocket::suspend(ReasonForSuspension) 470{ 471 if (m_channel) 472 m_channel->suspend(); 473} 474 475void WebSocket::resume() 476{ 477 if (m_channel) 478 m_channel->resume(); 479} 480 481void WebSocket::stop() 482{ 483 bool pending = hasPendingActivity(); 484 if (m_channel) 485 m_channel->disconnect(); 486 m_channel = 0; 487 m_state = CLOSED; 488 ActiveDOMObject::stop(); 489 if (pending) 490 ActiveDOMObject::unsetPendingActivity(this); 491} 492 493void WebSocket::didConnect() 494{ 495 LOG(Network, "WebSocket %p didConnect()", this); 496 if (m_state != CONNECTING) { 497 didClose(0, ClosingHandshakeIncomplete, WebSocketChannel::CloseEventCodeAbnormalClosure, ""); 498 return; 499 } 500 ASSERT(scriptExecutionContext()); 501 m_state = OPEN; 502 m_subprotocol = m_channel->subprotocol(); 503 m_extensions = m_channel->extensions(); 504 dispatchEvent(Event::create(eventNames().openEvent, false, false)); 505} 506 507void WebSocket::didReceiveMessage(const String& msg) 508{ 509 LOG(Network, "WebSocket %p didReceiveMessage() Text message '%s'", this, msg.utf8().data()); 510 if (m_state != OPEN) 511 return; 512 ASSERT(scriptExecutionContext()); 513 dispatchEvent(MessageEvent::create(msg, SecurityOrigin::create(m_url)->toString())); 514} 515 516void WebSocket::didReceiveBinaryData(PassOwnPtr<Vector<char>> binaryData) 517{ 518 LOG(Network, "WebSocket %p didReceiveBinaryData() %lu byte binary message", this, static_cast<unsigned long>(binaryData->size())); 519 switch (m_binaryType) { 520 case BinaryTypeBlob: { 521 // FIXME: We just received the data from NetworkProcess, and are sending it back. This is inefficient. 522 RefPtr<Blob> blob = Blob::create(WTF::move(*binaryData), emptyString()); 523 dispatchEvent(MessageEvent::create(blob.release(), SecurityOrigin::create(m_url)->toString())); 524 break; 525 } 526 527 case BinaryTypeArrayBuffer: 528 dispatchEvent(MessageEvent::create(ArrayBuffer::create(binaryData->data(), binaryData->size()), SecurityOrigin::create(m_url)->toString())); 529 break; 530 } 531} 532 533void WebSocket::didReceiveMessageError() 534{ 535 LOG(Network, "WebSocket %p didReceiveErrorMessage()", this); 536 ASSERT(scriptExecutionContext()); 537 dispatchEvent(Event::create(eventNames().errorEvent, false, false)); 538} 539 540void WebSocket::didUpdateBufferedAmount(unsigned long bufferedAmount) 541{ 542 LOG(Network, "WebSocket %p didUpdateBufferedAmount() New bufferedAmount is %lu", this, bufferedAmount); 543 if (m_state == CLOSED) 544 return; 545 m_bufferedAmount = bufferedAmount; 546} 547 548void WebSocket::didStartClosingHandshake() 549{ 550 LOG(Network, "WebSocket %p didStartClosingHandshake()", this); 551 m_state = CLOSING; 552} 553 554void WebSocket::didClose(unsigned long unhandledBufferedAmount, ClosingHandshakeCompletionStatus closingHandshakeCompletion, unsigned short code, const String& reason) 555{ 556 LOG(Network, "WebSocket %p didClose()", this); 557 if (!m_channel) 558 return; 559 bool wasClean = m_state == CLOSING && !unhandledBufferedAmount && closingHandshakeCompletion == ClosingHandshakeComplete && code != WebSocketChannel::CloseEventCodeAbnormalClosure; 560 m_state = CLOSED; 561 m_bufferedAmount = unhandledBufferedAmount; 562 ASSERT(scriptExecutionContext()); 563 RefPtr<CloseEvent> event = CloseEvent::create(wasClean, code, reason); 564 dispatchEvent(event); 565 if (m_channel) { 566 m_channel->disconnect(); 567 m_channel = 0; 568 } 569 if (hasPendingActivity()) 570 ActiveDOMObject::unsetPendingActivity(this); 571} 572 573size_t WebSocket::getFramingOverhead(size_t payloadSize) 574{ 575 static const size_t hybiBaseFramingOverhead = 2; // Every frame has at least two-byte header. 576 static const size_t hybiMaskingKeyLength = 4; // Every frame from client must have masking key. 577 static const size_t minimumPayloadSizeWithTwoByteExtendedPayloadLength = 126; 578 static const size_t minimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000; 579 size_t overhead = hybiBaseFramingOverhead + hybiMaskingKeyLength; 580 if (payloadSize >= minimumPayloadSizeWithEightByteExtendedPayloadLength) 581 overhead += 8; 582 else if (payloadSize >= minimumPayloadSizeWithTwoByteExtendedPayloadLength) 583 overhead += 2; 584 return overhead; 585} 586 587} // namespace WebCore 588 589#endif 590