1/*
2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 * * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#include "config.h"
33#include "WebSocket.h"
34
35#include "Blob.h"
36#include "CloseEvent.h"
37#include "ContentSecurityPolicy.h"
38#include "DOMWindow.h"
39#include "Document.h"
40#include "Event.h"
41#include "EventListener.h"
42#include "EventNames.h"
43#include "Frame.h"
44#include "FrameLoader.h"
45#include "Logging.h"
46#include "MessageEvent.h"
47#include "ResourceLoadObserver.h"
48#include "ScriptController.h"
49#include "ScriptExecutionContext.h"
50#include "SecurityOrigin.h"
51#include "SocketProvider.h"
52#include "ThreadableWebSocketChannel.h"
53#include "WebSocketChannel.h"
54#include <JavaScriptCore/ArrayBuffer.h>
55#include <JavaScriptCore/ArrayBufferView.h>
56#include <JavaScriptCore/ScriptCallStack.h>
57#include <wtf/HashSet.h>
58#include <wtf/HexNumber.h>
59#include <wtf/IsoMallocInlines.h>
60#include <wtf/NeverDestroyed.h>
61#include <wtf/RunLoop.h>
62#include <wtf/StdLibExtras.h>
63#include <wtf/text/CString.h>
64#include <wtf/text/StringBuilder.h>
65
66#if USE(WEB_THREAD)
67#include "WebCoreThreadRun.h"
68#endif
69
70namespace WebCore {
71
72WTF_MAKE_ISO_ALLOCATED_IMPL(WebSocket);
73
74const size_t maxReasonSizeInBytes = 123;
75
76static inline bool isValidProtocolCharacter(UChar character)
77{
78 // Hybi-10 says "(Subprotocol string must consist of) characters in the range U+0021 to U+007E not including
79 // separator characters as defined in [RFC2616]."
80 const UChar minimumProtocolCharacter = '!'; // U+0021.
81 const UChar maximumProtocolCharacter = '~'; // U+007E.
82 return character >= minimumProtocolCharacter && character <= maximumProtocolCharacter
83 && character != '"' && character != '(' && character != ')' && character != ',' && character != '/'
84 && !(character >= ':' && character <= '@') // U+003A - U+0040 (':', ';', '<', '=', '>', '?', '@').
85 && !(character >= '[' && character <= ']') // U+005B - U+005D ('[', '\\', ']').
86 && character != '{' && character != '}';
87}
88
89static bool isValidProtocolString(StringView protocol)
90{
91 if (protocol.isEmpty())
92 return false;
93 for (auto codeUnit : protocol.codeUnits()) {
94 if (!isValidProtocolCharacter(codeUnit))
95 return false;
96 }
97 return true;
98}
99
100static String encodeProtocolString(const String& protocol)
101{
102 StringBuilder builder;
103 for (size_t i = 0; i < protocol.length(); i++) {
104 if (protocol[i] < 0x20 || protocol[i] > 0x7E) {
105 builder.appendLiteral("\\u");
106 appendUnsignedAsHexFixedSize(protocol[i], builder, 4);
107 } else if (protocol[i] == 0x5c)
108 builder.appendLiteral("\\\\");
109 else
110 builder.append(protocol[i]);
111 }
112 return builder.toString();
113}
114
115static String joinStrings(const Vector<String>& strings, const char* separator)
116{
117 StringBuilder builder;
118 for (size_t i = 0; i < strings.size(); ++i) {
119 if (i)
120 builder.append(separator);
121 builder.append(strings[i]);
122 }
123 return builder.toString();
124}
125
126static unsigned saturateAdd(unsigned a, unsigned b)
127{
128 if (std::numeric_limits<unsigned>::max() - a < b)
129 return std::numeric_limits<unsigned>::max();
130 return a + b;
131}
132
133const char* WebSocket::subprotocolSeparator()
134{
135 return ", ";
136}
137
138WebSocket::WebSocket(ScriptExecutionContext& context)
139 : ActiveDOMObject(&context)
140 , m_subprotocol(emptyString())
141 , m_extensions(emptyString())
142 , m_resumeTimer(*this, &WebSocket::resumeTimerFired)
143{
144 LockHolder lock(allActiveWebSocketsMutex());
145
146 allActiveWebSockets(lock).add(this);
147}
148
149WebSocket::~WebSocket()
150{
151 {
152 LockHolder lock(allActiveWebSocketsMutex());
153
154 allActiveWebSockets(lock).remove(this);
155 }
156
157 if (m_channel)
158 m_channel->disconnect();
159}
160
161ExceptionOr<Ref<WebSocket>> WebSocket::create(ScriptExecutionContext& context, const String& url)
162{
163 return create(context, url, Vector<String> { });
164}
165
166ExceptionOr<Ref<WebSocket>> WebSocket::create(ScriptExecutionContext& context, const String& url, const Vector<String>& protocols)
167{
168 if (url.isNull())
169 return Exception { SyntaxError };
170
171 auto socket = adoptRef(*new WebSocket(context));
172 socket->suspendIfNeeded();
173
174 auto result = socket->connect(context.completeURL(url), protocols);
175 if (result.hasException())
176 return result.releaseException();
177
178 return socket;
179}
180
181ExceptionOr<Ref<WebSocket>> WebSocket::create(ScriptExecutionContext& context, const String& url, const String& protocol)
182{
183 return create(context, url, Vector<String> { 1, protocol });
184}
185
186HashSet<WebSocket*>& WebSocket::allActiveWebSockets(const LockHolder&)
187{
188 static NeverDestroyed<HashSet<WebSocket*>> activeWebSockets;
189 return activeWebSockets;
190}
191
192Lock& WebSocket::allActiveWebSocketsMutex()
193{
194 static Lock mutex;
195 return mutex;
196}
197
198ExceptionOr<void> WebSocket::connect(const String& url)
199{
200 return connect(url, Vector<String> { });
201}
202
203ExceptionOr<void> WebSocket::connect(const String& url, const String& protocol)
204{
205 return connect(url, Vector<String> { 1, protocol });
206}
207
208ExceptionOr<void> WebSocket::connect(const String& url, const Vector<String>& protocols)
209{
210 LOG(Network, "WebSocket %p connect() url='%s'", this, url.utf8().data());
211 m_url = URL(URL(), url);
212
213 ASSERT(scriptExecutionContext());
214 auto& context = *scriptExecutionContext();
215
216 if (!m_url.isValid()) {
217 context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Invalid url for WebSocket " + m_url.stringCenterEllipsizedToLength());
218 m_state = CLOSED;
219 return Exception { SyntaxError };
220 }
221
222 if (!m_url.protocolIs("ws") && !m_url.protocolIs("wss")) {
223 context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Wrong url scheme for WebSocket " + m_url.stringCenterEllipsizedToLength());
224 m_state = CLOSED;
225 return Exception { SyntaxError };
226 }
227 if (m_url.hasFragmentIdentifier()) {
228 context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, "URL has fragment component " + m_url.stringCenterEllipsizedToLength());
229 m_state = CLOSED;
230 return Exception { SyntaxError };
231 }
232
233 ASSERT(context.contentSecurityPolicy());
234 auto& contentSecurityPolicy = *context.contentSecurityPolicy();
235
236 contentSecurityPolicy.upgradeInsecureRequestIfNeeded(m_url, ContentSecurityPolicy::InsecureRequestType::Load);
237
238 if (!portAllowed(m_url)) {
239 String message;
240 if (m_url.port())
241 message = makeString("WebSocket port ", static_cast<unsigned>(m_url.port().value()), " blocked");
242 else
243 message = "WebSocket without port blocked"_s;
244 context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, message);
245 m_state = CLOSED;
246 return Exception { SecurityError };
247 }
248
249 // FIXME: Convert this to check the isolated world's Content Security Policy once webkit.org/b/104520 is solved.
250 if (!context.shouldBypassMainWorldContentSecurityPolicy() && !contentSecurityPolicy.allowConnectToSource(m_url)) {
251 m_state = CLOSED;
252
253 // FIXME: Should this be throwing an exception?
254 return Exception { SecurityError };
255 }
256
257 if (auto* provider = context.socketProvider())
258 m_channel = ThreadableWebSocketChannel::create(*scriptExecutionContext(), *this, *provider);
259
260 // Every ScriptExecutionContext should have a SocketProvider.
261 RELEASE_ASSERT(m_channel);
262
263 // FIXME: There is a disagreement about restriction of subprotocols between WebSocket API and hybi-10 protocol
264 // draft. The former simply says "only characters in the range U+0021 to U+007E are allowed," while the latter
265 // imposes a stricter rule: "the elements MUST be non-empty strings with characters as defined in [RFC2616],
266 // and MUST all be unique strings."
267 //
268 // Here, we throw SyntaxError if the given protocols do not meet the latter criteria. This behavior does not
269 // comply with WebSocket API specification, but it seems to be the only reasonable way to handle this conflict.
270 for (auto& protocol : protocols) {
271 if (!isValidProtocolString(protocol)) {
272 context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, "Wrong protocol for WebSocket '" + encodeProtocolString(protocol) + "'");
273 m_state = CLOSED;
274 return Exception { SyntaxError };
275 }
276 }
277 HashSet<String> visited;
278 for (auto& protocol : protocols) {
279 if (!visited.add(protocol).isNewEntry) {
280 context.addConsoleMessage(MessageSource::JS, MessageLevel::Error, "WebSocket protocols contain duplicates: '" + encodeProtocolString(protocol) + "'");
281 m_state = CLOSED;
282 return Exception { SyntaxError };
283 }
284 }
285
286 RunLoop::main().dispatch([targetURL = m_url.isolatedCopy(), mainFrameURL = context.url().isolatedCopy(), sessionID = context.sessionID()]() {
287 ResourceLoadObserver::shared().logWebSocketLoading(targetURL, mainFrameURL, sessionID);
288 });
289
290 if (is<Document>(context)) {
291 Document& document = downcast<Document>(context);
292 RefPtr<Frame> frame = document.frame();
293 if (!frame || !frame->loader().mixedContentChecker().canRunInsecureContent(document.securityOrigin(), m_url)) {
294 m_pendingActivity = makePendingActivity(*this);
295
296 // We must block this connection. Instead of throwing an exception, we indicate this
297 // using the error event. But since this code executes as part of the WebSocket's
298 // constructor, we have to wait until the constructor has completed before firing the
299 // event; otherwise, users can't connect to the event.
300
301 document.postTask([this, protectedThis = makeRef(*this)](auto&) {
302 this->dispatchOrQueueErrorEvent();
303 this->stop();
304 });
305
306 return { };
307 }
308 }
309
310 String protocolString;
311 if (!protocols.isEmpty())
312 protocolString = joinStrings(protocols, subprotocolSeparator());
313
314 m_channel->connect(m_url, protocolString);
315 m_pendingActivity = makePendingActivity(*this);
316
317 return { };
318}
319
320ExceptionOr<void> WebSocket::send(const String& message)
321{
322 LOG(Network, "WebSocket %p send() Sending String '%s'", this, message.utf8().data());
323 if (m_state == CONNECTING)
324 return Exception { InvalidStateError };
325 // No exception is raised if the connection was once established but has subsequently been closed.
326 if (m_state == CLOSING || m_state == CLOSED) {
327 size_t payloadSize = message.utf8().length();
328 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize);
329 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize));
330 return { };
331 }
332 ASSERT(m_channel);
333 m_channel->send(message);
334 return { };
335}
336
337ExceptionOr<void> WebSocket::send(ArrayBuffer& binaryData)
338{
339 LOG(Network, "WebSocket %p send() Sending ArrayBuffer %p", this, &binaryData);
340 if (m_state == CONNECTING)
341 return Exception { InvalidStateError };
342 if (m_state == CLOSING || m_state == CLOSED) {
343 unsigned payloadSize = binaryData.byteLength();
344 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize);
345 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize));
346 return { };
347 }
348 ASSERT(m_channel);
349 m_channel->send(binaryData, 0, binaryData.byteLength());
350 return { };
351}
352
353ExceptionOr<void> WebSocket::send(ArrayBufferView& arrayBufferView)
354{
355 LOG(Network, "WebSocket %p send() Sending ArrayBufferView %p", this, &arrayBufferView);
356
357 if (m_state == CONNECTING)
358 return Exception { InvalidStateError };
359 if (m_state == CLOSING || m_state == CLOSED) {
360 unsigned payloadSize = arrayBufferView.byteLength();
361 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize);
362 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize));
363 return { };
364 }
365 ASSERT(m_channel);
366 m_channel->send(*arrayBufferView.unsharedBuffer(), arrayBufferView.byteOffset(), arrayBufferView.byteLength());
367 return { };
368}
369
370ExceptionOr<void> WebSocket::send(Blob& binaryData)
371{
372 LOG(Network, "WebSocket %p send() Sending Blob '%s'", this, binaryData.url().stringCenterEllipsizedToLength().utf8().data());
373 if (m_state == CONNECTING)
374 return Exception { InvalidStateError };
375 if (m_state == CLOSING || m_state == CLOSED) {
376 unsigned payloadSize = static_cast<unsigned>(binaryData.size());
377 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, payloadSize);
378 m_bufferedAmountAfterClose = saturateAdd(m_bufferedAmountAfterClose, getFramingOverhead(payloadSize));
379 return { };
380 }
381 ASSERT(m_channel);
382 m_channel->send(binaryData);
383 return { };
384}
385
386ExceptionOr<void> WebSocket::close(Optional<unsigned short> optionalCode, const String& reason)
387{
388 int code = optionalCode ? optionalCode.value() : static_cast<int>(WebSocketChannel::CloseEventCodeNotSpecified);
389 if (code == WebSocketChannel::CloseEventCodeNotSpecified)
390 LOG(Network, "WebSocket %p close() without code and reason", this);
391 else {
392 LOG(Network, "WebSocket %p close() code=%d reason='%s'", this, code, reason.utf8().data());
393 if (!(code == WebSocketChannel::CloseEventCodeNormalClosure || (WebSocketChannel::CloseEventCodeMinimumUserDefined <= code && code <= WebSocketChannel::CloseEventCodeMaximumUserDefined)))
394 return Exception { InvalidAccessError };
395 CString utf8 = reason.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD);
396 if (utf8.length() > maxReasonSizeInBytes) {
397 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "WebSocket close message is too long."_s);
398 return Exception { SyntaxError };
399 }
400 }
401
402 if (m_state == CLOSING || m_state == CLOSED)
403 return { };
404 if (m_state == CONNECTING) {
405 m_state = CLOSING;
406 m_channel->fail("WebSocket is closed before the connection is established.");
407 return { };
408 }
409 m_state = CLOSING;
410 if (m_channel)
411 m_channel->close(code, reason);
412 return { };
413}
414
415RefPtr<ThreadableWebSocketChannel> WebSocket::channel() const
416{
417 return m_channel;
418}
419
420const URL& WebSocket::url() const
421{
422 return m_url;
423}
424
425WebSocket::State WebSocket::readyState() const
426{
427 return m_state;
428}
429
430unsigned WebSocket::bufferedAmount() const
431{
432 return saturateAdd(m_bufferedAmount, m_bufferedAmountAfterClose);
433}
434
435String WebSocket::protocol() const
436{
437 return m_subprotocol;
438}
439
440String WebSocket::extensions() const
441{
442 return m_extensions;
443}
444
445String WebSocket::binaryType() const
446{
447 switch (m_binaryType) {
448 case BinaryType::Blob:
449 return "blob"_s;
450 case BinaryType::ArrayBuffer:
451 return "arraybuffer"_s;
452 }
453 ASSERT_NOT_REACHED();
454 return String();
455}
456
457ExceptionOr<void> WebSocket::setBinaryType(const String& binaryType)
458{
459 if (binaryType == "blob") {
460 m_binaryType = BinaryType::Blob;
461 return { };
462 }
463 if (binaryType == "arraybuffer") {
464 m_binaryType = BinaryType::ArrayBuffer;
465 return { };
466 }
467 scriptExecutionContext()->addConsoleMessage(MessageSource::JS, MessageLevel::Error, "'" + binaryType + "' is not a valid value for binaryType; binaryType remains unchanged.");
468 return Exception { SyntaxError };
469}
470
471EventTargetInterface WebSocket::eventTargetInterface() const
472{
473 return WebSocketEventTargetInterfaceType;
474}
475
476ScriptExecutionContext* WebSocket::scriptExecutionContext() const
477{
478 return ActiveDOMObject::scriptExecutionContext();
479}
480
481void WebSocket::contextDestroyed()
482{
483 LOG(Network, "WebSocket %p contextDestroyed()", this);
484 ASSERT(!m_channel);
485 ASSERT(m_state == CLOSED);
486 ActiveDOMObject::contextDestroyed();
487}
488
489bool WebSocket::canSuspendForDocumentSuspension() const
490{
491 return true;
492}
493
494void WebSocket::suspend(ReasonForSuspension reason)
495{
496 if (m_resumeTimer.isActive())
497 m_resumeTimer.stop();
498
499 m_shouldDelayEventFiring = true;
500
501 if (m_channel) {
502 if (reason == ReasonForSuspension::PageCache) {
503 // This will cause didClose() to be called.
504 m_channel->fail("WebSocket is closed due to suspension.");
505 } else
506 m_channel->suspend();
507 }
508}
509
510void WebSocket::resume()
511{
512 if (m_channel)
513 m_channel->resume();
514 else if (!m_pendingEvents.isEmpty() && !m_resumeTimer.isActive()) {
515 // Fire the pending events in a timer as we are not allowed to execute arbitrary JS from resume().
516 m_resumeTimer.startOneShot(0_s);
517 }
518
519 m_shouldDelayEventFiring = false;
520}
521
522void WebSocket::resumeTimerFired()
523{
524 Ref<WebSocket> protectedThis(*this);
525
526 ASSERT(!m_pendingEvents.isEmpty());
527
528 // Check m_shouldDelayEventFiring when iterating in case firing an event causes
529 // suspend() to be called.
530 while (!m_pendingEvents.isEmpty() && !m_shouldDelayEventFiring)
531 dispatchEvent(m_pendingEvents.takeFirst());
532}
533
534void WebSocket::stop()
535{
536 if (m_channel)
537 m_channel->disconnect();
538 m_channel = nullptr;
539 m_state = CLOSED;
540 m_pendingEvents.clear();
541 ActiveDOMObject::stop();
542 m_pendingActivity = nullptr;
543}
544
545const char* WebSocket::activeDOMObjectName() const
546{
547 return "WebSocket";
548}
549
550void WebSocket::didConnect()
551{
552 LOG(Network, "WebSocket %p didConnect()", this);
553 if (m_state != CONNECTING) {
554 didClose(0, ClosingHandshakeIncomplete, WebSocketChannel::CloseEventCodeAbnormalClosure, emptyString());
555 return;
556 }
557 ASSERT(scriptExecutionContext());
558 m_state = OPEN;
559 m_subprotocol = m_channel->subprotocol();
560 m_extensions = m_channel->extensions();
561 dispatchEvent(Event::create(eventNames().openEvent, Event::CanBubble::No, Event::IsCancelable::No));
562}
563
564void WebSocket::didReceiveMessage(const String& msg)
565{
566 LOG(Network, "WebSocket %p didReceiveMessage() Text message '%s'", this, msg.utf8().data());
567 if (m_state != OPEN)
568 return;
569 ASSERT(scriptExecutionContext());
570 dispatchEvent(MessageEvent::create(msg, SecurityOrigin::create(m_url)->toString()));
571}
572
573void WebSocket::didReceiveBinaryData(Vector<uint8_t>&& binaryData)
574{
575 LOG(Network, "WebSocket %p didReceiveBinaryData() %u byte binary message", this, static_cast<unsigned>(binaryData.size()));
576 switch (m_binaryType) {
577 case BinaryType::Blob:
578 // FIXME: We just received the data from NetworkProcess, and are sending it back. This is inefficient.
579 dispatchEvent(MessageEvent::create(Blob::create(WTFMove(binaryData), emptyString()), SecurityOrigin::create(m_url)->toString()));
580 break;
581 case BinaryType::ArrayBuffer:
582 dispatchEvent(MessageEvent::create(ArrayBuffer::create(binaryData.data(), binaryData.size()), SecurityOrigin::create(m_url)->toString()));
583 break;
584 }
585}
586
587void WebSocket::didReceiveMessageError()
588{
589 LOG(Network, "WebSocket %p didReceiveErrorMessage()", this);
590 m_state = CLOSED;
591 ASSERT(scriptExecutionContext());
592 dispatchOrQueueErrorEvent();
593}
594
595void WebSocket::didUpdateBufferedAmount(unsigned bufferedAmount)
596{
597 LOG(Network, "WebSocket %p didUpdateBufferedAmount() New bufferedAmount is %u", this, bufferedAmount);
598 if (m_state == CLOSED)
599 return;
600 m_bufferedAmount = bufferedAmount;
601}
602
603void WebSocket::didStartClosingHandshake()
604{
605 LOG(Network, "WebSocket %p didStartClosingHandshake()", this);
606 m_state = CLOSING;
607}
608
609void WebSocket::didClose(unsigned unhandledBufferedAmount, ClosingHandshakeCompletionStatus closingHandshakeCompletion, unsigned short code, const String& reason)
610{
611 LOG(Network, "WebSocket %p didClose()", this);
612 if (!m_channel)
613 return;
614 bool wasClean = m_state == CLOSING && !unhandledBufferedAmount && closingHandshakeCompletion == ClosingHandshakeComplete && code != WebSocketChannel::CloseEventCodeAbnormalClosure;
615 m_state = CLOSED;
616 m_bufferedAmount = unhandledBufferedAmount;
617 ASSERT(scriptExecutionContext());
618
619 dispatchOrQueueEvent(CloseEvent::create(wasClean, code, reason));
620
621 if (m_channel) {
622 m_channel->disconnect();
623 m_channel = nullptr;
624 }
625 m_pendingActivity = nullptr;
626}
627
628void WebSocket::didUpgradeURL()
629{
630 ASSERT(m_url.protocolIs("ws"));
631 m_url.setProtocol("wss");
632}
633
634size_t WebSocket::getFramingOverhead(size_t payloadSize)
635{
636 static const size_t hybiBaseFramingOverhead = 2; // Every frame has at least two-byte header.
637 static const size_t hybiMaskingKeyLength = 4; // Every frame from client must have masking key.
638 static const size_t minimumPayloadSizeWithTwoByteExtendedPayloadLength = 126;
639 static const size_t minimumPayloadSizeWithEightByteExtendedPayloadLength = 0x10000;
640 size_t overhead = hybiBaseFramingOverhead + hybiMaskingKeyLength;
641 if (payloadSize >= minimumPayloadSizeWithEightByteExtendedPayloadLength)
642 overhead += 8;
643 else if (payloadSize >= minimumPayloadSizeWithTwoByteExtendedPayloadLength)
644 overhead += 2;
645 return overhead;
646}
647
648void WebSocket::dispatchOrQueueErrorEvent()
649{
650 if (m_dispatchedErrorEvent)
651 return;
652
653 m_dispatchedErrorEvent = true;
654 dispatchOrQueueEvent(Event::create(eventNames().errorEvent, Event::CanBubble::No, Event::IsCancelable::No));
655}
656
657void WebSocket::dispatchOrQueueEvent(Ref<Event>&& event)
658{
659 if (m_shouldDelayEventFiring)
660 m_pendingEvents.append(WTFMove(event));
661 else
662 dispatchEvent(event);
663}
664
665} // namespace WebCore
666