Add WTF::move()
[WebKit-https.git] / Source / WebCore / Modules / websockets / WebSocket.cpp
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
65 namespace WebCore {
66
67 const size_t maxReasonSizeInBytes = 123;
68
69 static 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
82 static 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
93 static 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
107 static 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
118 static 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
125 static bool webSocketsAvailable = true;
126
127 void WebSocket::setIsAvailable(bool available)
128 {
129     webSocketsAvailable = available;
130 }
131
132 bool WebSocket::isAvailable()
133 {
134     return webSocketsAvailable;
135 }
136
137 const char* WebSocket::subProtocolSeperator()
138 {
139     return ", ";
140 }
141
142 WebSocket::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
153 WebSocket::~WebSocket()
154 {
155     if (m_channel)
156         m_channel->disconnect();
157 }
158
159 PassRefPtr<WebSocket> WebSocket::create(ScriptExecutionContext& context)
160 {
161     RefPtr<WebSocket> webSocket(adoptRef(new WebSocket(context)));
162     webSocket->suspendIfNeeded();
163     return webSocket.release();
164 }
165
166 PassRefPtr<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
172 PassRefPtr<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
189 PassRefPtr<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
196 void WebSocket::connect(const String& url, ExceptionCode& ec)
197 {
198     Vector<String> protocols;
199     connect(url, protocols, ec);
200 }
201
202 void 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
209 void 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
289 void 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
312 void 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
330 void 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
349 void 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
366 void 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
396 const URL& WebSocket::url() const
397 {
398     return m_url;
399 }
400
401 WebSocket::State WebSocket::readyState() const
402 {
403     return m_state;
404 }
405
406 unsigned long WebSocket::bufferedAmount() const
407 {
408     return saturateAdd(m_bufferedAmount, m_bufferedAmountAfterClose);
409 }
410
411 String WebSocket::protocol() const
412 {
413     return m_subprotocol;
414 }
415
416 String WebSocket::extensions() const
417 {
418     return m_extensions;
419 }
420
421 String 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
433 void 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
446 EventTargetInterface WebSocket::eventTargetInterface() const
447 {
448     return WebSocketEventTargetInterfaceType;
449 }
450
451 ScriptExecutionContext* WebSocket::scriptExecutionContext() const
452 {
453     return ActiveDOMObject::scriptExecutionContext();
454 }
455
456 void WebSocket::contextDestroyed()
457 {
458     LOG(Network, "WebSocket %p contextDestroyed()", this);
459     ASSERT(!m_channel);
460     ASSERT(m_state == CLOSED);
461     ActiveDOMObject::contextDestroyed();
462 }
463
464 bool WebSocket::canSuspend() const
465 {
466     return !m_channel;
467 }
468
469 void WebSocket::suspend(ReasonForSuspension)
470 {
471     if (m_channel)
472         m_channel->suspend();
473 }
474
475 void WebSocket::resume()
476 {
477     if (m_channel)
478         m_channel->resume();
479 }
480
481 void 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
493 void 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
507 void 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
516 void 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
533 void WebSocket::didReceiveMessageError()
534 {
535     LOG(Network, "WebSocket %p didReceiveErrorMessage()", this);
536     ASSERT(scriptExecutionContext());
537     dispatchEvent(Event::create(eventNames().errorEvent, false, false));
538 }
539
540 void 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
548 void WebSocket::didStartClosingHandshake()
549 {
550     LOG(Network, "WebSocket %p didStartClosingHandshake()", this);
551     m_state = CLOSING;
552 }
553
554 void 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
573 size_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