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