WebSocket.cpp (89142B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "WebSocket.h" 8 9 #include "ErrorList.h" 10 #include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin 11 #include "jsapi.h" 12 #include "jsfriendapi.h" 13 #include "mozilla/Atomics.h" 14 #include "mozilla/BasePrincipal.h" 15 #include "mozilla/DOMEventTargetHelper.h" 16 #include "mozilla/LoadInfo.h" 17 #include "mozilla/Preferences.h" 18 #include "mozilla/StaticPrefs_dom.h" 19 #include "mozilla/dom/CloseEvent.h" 20 #include "mozilla/dom/Document.h" 21 #include "mozilla/dom/File.h" 22 #include "mozilla/dom/MessageEvent.h" 23 #include "mozilla/dom/MessageEventBinding.h" 24 #include "mozilla/dom/ScriptSettings.h" 25 #include "mozilla/dom/SerializedStackHolder.h" 26 #include "mozilla/dom/TypedArray.h" 27 #include "mozilla/dom/UnionTypes.h" 28 #include "mozilla/dom/WebSocketBinding.h" 29 #include "mozilla/dom/WindowContext.h" 30 #include "mozilla/dom/WorkerPrivate.h" 31 #include "mozilla/dom/WorkerRef.h" 32 #include "mozilla/dom/WorkerRunnable.h" 33 #include "mozilla/dom/WorkerScope.h" 34 #include "mozilla/dom/nsCSPContext.h" 35 #include "mozilla/dom/nsCSPUtils.h" 36 #include "mozilla/dom/nsHTTPSOnlyUtils.h" 37 #include "mozilla/dom/nsMixedContentBlocker.h" 38 #include "mozilla/net/WebSocketChannel.h" 39 #include "mozilla/net/WebSocketEventService.h" 40 #include "nsContentPolicyUtils.h" 41 #include "nsContentUtils.h" 42 #include "nsError.h" 43 #include "nsGlobalWindowInner.h" 44 #include "nsIAuthPrompt.h" 45 #include "nsIAuthPrompt2.h" 46 #include "nsIConsoleService.h" 47 #include "nsICookieJarSettings.h" 48 #include "nsIEventTarget.h" 49 #include "nsIInterfaceRequestor.h" 50 #include "nsILoadGroup.h" 51 #include "nsIPrompt.h" 52 #include "nsIPromptFactory.h" 53 #include "nsIRequest.h" 54 #include "nsIScriptError.h" 55 #include "nsIScriptGlobalObject.h" 56 #include "nsIScriptObjectPrincipal.h" 57 #include "nsIStringBundle.h" 58 #include "nsIThreadRetargetableRequest.h" 59 #include "nsIURIMutator.h" 60 #include "nsIURL.h" 61 #include "nsIWebSocketChannel.h" 62 #include "nsIWebSocketImpl.h" 63 #include "nsIWebSocketListener.h" 64 #include "nsIWindowWatcher.h" 65 #include "nsJSUtils.h" 66 #include "nsNetUtil.h" 67 #include "nsProxyRelease.h" 68 #include "nsThreadUtils.h" 69 #include "nsWrapperCacheInlines.h" 70 #include "nsXPCOM.h" 71 #include "xpcpublic.h" 72 73 #define OPEN_EVENT_STRING u"open"_ns 74 #define MESSAGE_EVENT_STRING u"message"_ns 75 #define ERROR_EVENT_STRING u"error"_ns 76 #define CLOSE_EVENT_STRING u"close"_ns 77 78 using namespace mozilla::net; 79 80 namespace mozilla::dom { 81 82 class WebSocketImpl; 83 84 // This class is responsible for proxying nsIObserver and nsIWebSocketImpl 85 // interfaces to WebSocketImpl. WebSocketImplProxy should be only accessed on 86 // main thread, so we can let it support weak reference. 87 class WebSocketImplProxy final : public nsIWebSocketImpl, 88 public GlobalTeardownObserver, 89 public GlobalFreezeObserver { 90 public: 91 NS_DECL_ISUPPORTS 92 NS_DECL_NSIWEBSOCKETIMPL 93 94 explicit WebSocketImplProxy(WebSocketImpl* aOwner) : mOwner(aOwner) { 95 MOZ_ASSERT(NS_IsMainThread()); 96 } 97 98 void Disconnect() { 99 MOZ_ASSERT(NS_IsMainThread()); 100 101 mOwner = nullptr; 102 } 103 104 void BindToOwner(nsIGlobalObject* aOwner) { 105 GlobalTeardownObserver::BindToOwner(aOwner); 106 GlobalFreezeObserver::BindToOwner(aOwner); 107 } 108 109 void DisconnectFromOwner() override; 110 void FrozenCallback(nsIGlobalObject* aGlobal) override; 111 112 private: 113 ~WebSocketImplProxy() = default; 114 115 RefPtr<WebSocketImpl> mOwner; 116 }; 117 118 class WebSocketImpl final : public nsIInterfaceRequestor, 119 public nsIWebSocketListener, 120 public nsIRequest, 121 public nsISerialEventTarget, 122 public nsIWebSocketImpl, 123 public GlobalTeardownObserver, 124 public GlobalFreezeObserver { 125 public: 126 NS_DECL_NSIINTERFACEREQUESTOR 127 NS_DECL_NSIWEBSOCKETLISTENER 128 NS_DECL_NSIREQUEST 129 NS_DECL_THREADSAFE_ISUPPORTS 130 NS_DECL_NSIEVENTTARGET_FULL 131 NS_DECL_NSIWEBSOCKETIMPL 132 133 explicit WebSocketImpl(WebSocket* aWebSocket) 134 : mWebSocket(aWebSocket), 135 mIsServerSide(false), 136 mSecure(false), 137 mOnCloseScheduled(false), 138 mFailed(false), 139 mDisconnectingOrDisconnected(false), 140 mCloseEventWasClean(false), 141 mCloseEventCode(nsIWebSocketChannel::CLOSE_ABNORMAL), 142 mPort(0), 143 mScriptLine(0), 144 mScriptColumn(1), 145 mInnerWindowID(0), 146 mPrivateBrowsing(false), 147 mIsChromeContext(false), 148 mIsMainThread(true), 149 mMutex("WebSocketImpl::mMutex"), 150 mWorkerShuttingDown(false) { 151 if (!NS_IsMainThread()) { 152 mIsMainThread = false; 153 } 154 } 155 156 void AssertIsOnTargetThread() const { MOZ_ASSERT(IsTargetThread()); } 157 158 bool IsTargetThread() const; 159 160 nsresult Init(nsIGlobalObject* aWindowGlobal, JSContext* aCx, bool aIsSecure, 161 nsIPrincipal* aPrincipal, const Maybe<ClientInfo>& aClientInfo, 162 nsICSPEventListener* aCSPEventListener, bool aIsServerSide, 163 const nsAString& aURL, nsTArray<nsString>& aProtocolArray, 164 const nsACString& aScriptFile, uint32_t aScriptLine, 165 uint32_t aScriptColumn); 166 167 nsresult AsyncOpen(nsIPrincipal* aPrincipal, uint64_t aInnerWindowID, 168 nsITransportProvider* aTransportProvider, 169 const nsACString& aNegotiatedExtensions, 170 UniquePtr<SerializedStackHolder> aOriginStack); 171 172 nsresult ParseURL(const nsAString& aURL, nsIURI* aBaseURI); 173 nsresult InitializeConnection(nsIPrincipal* aPrincipal, 174 nsICookieJarSettings* aCookieJarSettings); 175 176 // These methods when called can release the WebSocket object 177 void FailConnection(const RefPtr<WebSocketImpl>& aProofOfRef, 178 uint16_t reasonCode, 179 const nsACString& aReasonString = ""_ns); 180 nsresult CloseConnection(const RefPtr<WebSocketImpl>& aProofOfRef, 181 uint16_t reasonCode, 182 const nsACString& aReasonString = ""_ns); 183 void Disconnect(const RefPtr<WebSocketImpl>& aProofOfRef); 184 void DisconnectInternal(); 185 186 nsresult ConsoleError(); 187 void PrintErrorOnConsole(const char* aBundleURI, const char* aError, 188 nsTArray<nsString>&& aFormatStrings); 189 190 nsresult DoOnMessageAvailable(const nsACString& aMsg, bool isBinary) const; 191 192 // ConnectionCloseEvents: 'error' event if needed, then 'close' event. 193 nsresult ScheduleConnectionCloseEvents(nsISupports* aContext, 194 nsresult aStatusCode); 195 // 2nd half of ScheduleConnectionCloseEvents, run in its own event. 196 void DispatchConnectionCloseEvents(const RefPtr<WebSocketImpl>& aProofOfRef); 197 198 nsresult UpdateURI(); 199 200 void AddRefObject(); 201 void ReleaseObject(); 202 203 bool RegisterWorkerRef(WorkerPrivate* aWorkerPrivate); 204 void UnregisterWorkerRef(); 205 206 nsresult CancelInternal(); 207 208 nsresult IsSecure(bool* aValue); 209 210 void DisconnectFromOwner() override { 211 RefPtr<WebSocketImpl> self(this); 212 CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY); 213 } 214 void FrozenCallback(nsIGlobalObject* aGlobal) override { 215 RefPtr<WebSocketImpl> self(this); 216 CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY); 217 } 218 219 RefPtr<WebSocket> mWebSocket; 220 221 nsCOMPtr<nsIWebSocketChannel> mChannel; 222 223 bool mIsServerSide; // True if we're implementing the server side of a 224 // websocket connection 225 226 bool mSecure; // if true it is using SSL and the wss scheme, 227 // otherwise it is using the ws scheme with no SSL 228 229 bool mOnCloseScheduled; 230 bool mFailed; 231 Atomic<bool> mDisconnectingOrDisconnected; 232 233 // Set attributes of DOM 'onclose' message 234 bool mCloseEventWasClean; 235 nsString mCloseEventReason; 236 uint16_t mCloseEventCode; 237 238 nsCString mAsciiHost; // hostname 239 uint32_t mPort; 240 nsCString mResource; // [filepath[?query]] 241 nsString mUTF16Origin; 242 243 nsCString mURI; 244 nsCString mRequestedProtocolList; 245 246 WeakPtr<Document> mOriginDocument; 247 248 // Web Socket owner information: 249 // - the script file name, UTF8 encoded. 250 // - source code line number and 1-origin column number where the Web Socket 251 // object was constructed. 252 // - the ID of the Web Socket owner window. Note that this may not 253 // be the same as the inner window where the script lives. 254 // e.g within iframes 255 // These attributes are used for error reporting. 256 nsCString mScriptFile; 257 uint32_t mScriptLine; 258 uint32_t mScriptColumn; 259 uint64_t mInnerWindowID; 260 bool mPrivateBrowsing; 261 bool mIsChromeContext; 262 263 RefPtr<ThreadSafeWorkerRef> mWorkerRef; 264 265 nsWeakPtr mWeakLoadGroup; 266 267 bool mIsMainThread; 268 269 // This mutex protects mWorkerShuttingDown. 270 mozilla::Mutex mMutex; 271 bool mWorkerShuttingDown MOZ_GUARDED_BY(mMutex); 272 273 RefPtr<WebSocketEventService> mService; 274 nsCOMPtr<nsIPrincipal> mLoadingPrincipal; 275 276 RefPtr<WebSocketImplProxy> mImplProxy; 277 278 private: 279 ~WebSocketImpl() { 280 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread || 281 mDisconnectingOrDisconnected); 282 283 // If we threw during Init we never called disconnect 284 if (!mDisconnectingOrDisconnected) { 285 RefPtr<WebSocketImpl> self(this); 286 Disconnect(self); 287 } 288 } 289 }; 290 291 NS_IMPL_ISUPPORTS(WebSocketImplProxy, nsIWebSocketImpl) 292 293 void WebSocketImplProxy::DisconnectFromOwner() { 294 if (!mOwner) { 295 return; 296 } 297 298 mOwner->DisconnectFromOwner(); 299 GlobalTeardownObserver::DisconnectFromOwner(); 300 } 301 302 void WebSocketImplProxy::FrozenCallback(nsIGlobalObject* aGlobal) { 303 if (!mOwner) { 304 return; 305 } 306 307 mOwner->FrozenCallback(aGlobal); 308 } 309 310 NS_IMETHODIMP 311 WebSocketImplProxy::SendMessage(const nsAString& aMessage) { 312 if (!mOwner) { 313 return NS_OK; 314 } 315 316 return mOwner->SendMessage(aMessage); 317 } 318 319 NS_IMPL_ISUPPORTS(WebSocketImpl, nsIInterfaceRequestor, nsIWebSocketListener, 320 nsIRequest, nsIEventTarget, nsISerialEventTarget, 321 nsIWebSocketImpl) 322 323 class CallDispatchConnectionCloseEvents final : public DiscardableRunnable { 324 public: 325 explicit CallDispatchConnectionCloseEvents(WebSocketImpl* aWebSocketImpl) 326 : DiscardableRunnable("dom::CallDispatchConnectionCloseEvents"), 327 mWebSocketImpl(aWebSocketImpl) { 328 aWebSocketImpl->AssertIsOnTargetThread(); 329 } 330 331 NS_IMETHOD Run() override { 332 mWebSocketImpl->AssertIsOnTargetThread(); 333 mWebSocketImpl->DispatchConnectionCloseEvents(mWebSocketImpl); 334 return NS_OK; 335 } 336 337 private: 338 RefPtr<WebSocketImpl> mWebSocketImpl; 339 }; 340 341 //----------------------------------------------------------------------------- 342 // WebSocketImpl 343 //----------------------------------------------------------------------------- 344 345 namespace { 346 347 class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable { 348 public: 349 PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl, const char* aBundleURI, 350 const char* aError, 351 nsTArray<nsString>&& aFormatStrings) 352 : WorkerMainThreadRunnable(aImpl->mWorkerRef->Private(), 353 "WebSocket :: print error on console"_ns), 354 mImpl(aImpl), 355 mBundleURI(aBundleURI), 356 mError(aError), 357 mFormatStrings(std::move(aFormatStrings)) {} 358 359 bool MainThreadRun() override { 360 mImpl->PrintErrorOnConsole(mBundleURI, mError, std::move(mFormatStrings)); 361 return true; 362 } 363 364 private: 365 // Raw pointer because this runnable is sync. 366 WebSocketImpl* mImpl; 367 368 const char* mBundleURI; 369 const char* mError; 370 nsTArray<nsString> mFormatStrings; 371 }; 372 373 } // namespace 374 375 void WebSocketImpl::PrintErrorOnConsole(const char* aBundleURI, 376 const char* aError, 377 nsTArray<nsString>&& aFormatStrings) { 378 // This method must run on the main thread. 379 380 if (!NS_IsMainThread()) { 381 MOZ_ASSERT(mWorkerRef); 382 383 RefPtr<PrintErrorOnConsoleRunnable> runnable = 384 new PrintErrorOnConsoleRunnable(this, aBundleURI, aError, 385 std::move(aFormatStrings)); 386 ErrorResult rv; 387 runnable->Dispatch(mWorkerRef->Private(), Killing, rv); 388 // XXXbz this seems totally broken. We should be propagating this out, but 389 // none of our callers really propagate anything usefully. Come to think of 390 // it, why is this a syncrunnable anyway? Can't this be a fire-and-forget 391 // runnable?? 392 rv.SuppressException(); 393 return; 394 } 395 396 nsresult rv; 397 nsCOMPtr<nsIStringBundleService> bundleService = 398 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); 399 NS_ENSURE_SUCCESS_VOID(rv); 400 401 nsCOMPtr<nsIStringBundle> strBundle; 402 rv = bundleService->CreateBundle(aBundleURI, getter_AddRefs(strBundle)); 403 NS_ENSURE_SUCCESS_VOID(rv); 404 405 nsCOMPtr<nsIConsoleService> console( 406 do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv)); 407 NS_ENSURE_SUCCESS_VOID(rv); 408 409 nsCOMPtr<nsIScriptError> errorObject( 410 do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv)); 411 NS_ENSURE_SUCCESS_VOID(rv); 412 413 // Localize the error message 414 nsAutoString message; 415 if (!aFormatStrings.IsEmpty()) { 416 rv = strBundle->FormatStringFromName(aError, aFormatStrings, message); 417 } else { 418 rv = strBundle->GetStringFromName(aError, message); 419 } 420 NS_ENSURE_SUCCESS_VOID(rv); 421 422 if (mInnerWindowID) { 423 rv = errorObject->InitWithWindowID(message, mScriptFile, mScriptLine, 424 mScriptColumn, nsIScriptError::errorFlag, 425 "Web Socket"_ns, mInnerWindowID); 426 } else { 427 rv = errorObject->Init(message, mScriptFile, mScriptLine, mScriptColumn, 428 nsIScriptError::errorFlag, "Web Socket"_ns, 429 mPrivateBrowsing, mIsChromeContext); 430 } 431 432 NS_ENSURE_SUCCESS_VOID(rv); 433 434 // print the error message directly to the JS console 435 rv = console->LogMessage(errorObject); 436 NS_ENSURE_SUCCESS_VOID(rv); 437 } 438 439 namespace { 440 441 class CancelWebSocketRunnable final : public Runnable { 442 public: 443 CancelWebSocketRunnable(nsIWebSocketChannel* aChannel, uint16_t aReasonCode, 444 const nsACString& aReasonString) 445 : Runnable("dom::CancelWebSocketRunnable"), 446 mChannel(aChannel), 447 mReasonCode(aReasonCode), 448 mReasonString(aReasonString) {} 449 450 NS_IMETHOD Run() override { 451 nsresult rv = mChannel->Close(mReasonCode, mReasonString); 452 if (NS_FAILED(rv)) { 453 NS_WARNING("Failed to dispatch the close message"); 454 } 455 return NS_OK; 456 } 457 458 private: 459 nsCOMPtr<nsIWebSocketChannel> mChannel; 460 uint16_t mReasonCode; 461 nsCString mReasonString; 462 }; 463 464 class MOZ_STACK_CLASS MaybeDisconnect { 465 public: 466 explicit MaybeDisconnect(WebSocketImpl* aImpl) : mImpl(aImpl) {} 467 468 ~MaybeDisconnect() { 469 bool toDisconnect = false; 470 471 { 472 MutexAutoLock lock(mImpl->mMutex); 473 toDisconnect = mImpl->mWorkerShuttingDown; 474 } 475 476 if (toDisconnect) { 477 mImpl->Disconnect(mImpl); 478 } 479 } 480 481 private: 482 RefPtr<WebSocketImpl> mImpl; 483 }; 484 485 class CloseConnectionRunnable final : public Runnable { 486 public: 487 CloseConnectionRunnable(WebSocketImpl* aImpl, uint16_t aReasonCode, 488 const nsACString& aReasonString) 489 : Runnable("dom::CloseConnectionRunnable"), 490 mImpl(aImpl), 491 mReasonCode(aReasonCode), 492 mReasonString(aReasonString) {} 493 494 NS_IMETHOD Run() override { 495 return mImpl->CloseConnection(mImpl, mReasonCode, mReasonString); 496 } 497 498 private: 499 RefPtr<WebSocketImpl> mImpl; 500 uint16_t mReasonCode; 501 const nsCString mReasonString; 502 }; 503 504 } // namespace 505 506 nsresult WebSocketImpl::CloseConnection( 507 const RefPtr<WebSocketImpl>& aProofOfRef, uint16_t aReasonCode, 508 const nsACString& aReasonString) { 509 if (!IsTargetThread()) { 510 nsCOMPtr<nsIRunnable> runnable = 511 new CloseConnectionRunnable(this, aReasonCode, aReasonString); 512 return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); 513 } 514 515 AssertIsOnTargetThread(); 516 517 if (mDisconnectingOrDisconnected) { 518 return NS_OK; 519 } 520 521 // If this method is called because the worker is going away, we will not 522 // receive the OnStop() method and we have to disconnect the WebSocket and 523 // release the ThreadSafeWorkerRef. 524 MaybeDisconnect md(this); 525 526 uint16_t readyState = mWebSocket->ReadyState(); 527 if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) { 528 return NS_OK; 529 } 530 531 // The common case... 532 if (mChannel) { 533 mWebSocket->SetReadyState(WebSocket::CLOSING); 534 535 // The channel has to be closed on the main-thread. 536 537 if (NS_IsMainThread()) { 538 return mChannel->Close(aReasonCode, aReasonString); 539 } 540 541 RefPtr<CancelWebSocketRunnable> runnable = 542 new CancelWebSocketRunnable(mChannel, aReasonCode, aReasonString); 543 return NS_DispatchToMainThread(runnable); 544 } 545 546 // No channel, but not disconnected: canceled or failed early 547 MOZ_ASSERT(readyState == WebSocket::CONNECTING, 548 "Should only get here for early websocket cancel/error"); 549 550 // Server won't be sending us a close code, so use what's passed in here. 551 mCloseEventCode = aReasonCode; 552 CopyUTF8toUTF16(aReasonString, mCloseEventReason); 553 554 mWebSocket->SetReadyState(WebSocket::CLOSING); 555 556 ScheduleConnectionCloseEvents( 557 nullptr, (aReasonCode == nsIWebSocketChannel::CLOSE_NORMAL || 558 aReasonCode == nsIWebSocketChannel::CLOSE_GOING_AWAY) 559 ? NS_OK 560 : NS_ERROR_FAILURE); 561 562 return NS_OK; 563 } 564 565 nsresult WebSocketImpl::ConsoleError() { 566 AssertIsOnTargetThread(); 567 568 { 569 MutexAutoLock lock(mMutex); 570 if (mWorkerShuttingDown) { 571 // Too late to report anything, bail out. 572 return NS_OK; 573 } 574 } 575 576 nsTArray<nsString> formatStrings; 577 CopyUTF8toUTF16(mURI, *formatStrings.AppendElement()); 578 579 if (mWebSocket->ReadyState() < WebSocket::OPEN) { 580 PrintErrorOnConsole("chrome://global/locale/appstrings.properties", 581 "connectionFailure", std::move(formatStrings)); 582 } else { 583 PrintErrorOnConsole("chrome://global/locale/appstrings.properties", 584 "netInterrupt", std::move(formatStrings)); 585 } 586 /// todo some specific errors - like for message too large 587 return NS_OK; 588 } 589 590 void WebSocketImpl::FailConnection(const RefPtr<WebSocketImpl>& aProofOfRef, 591 uint16_t aReasonCode, 592 const nsACString& aReasonString) { 593 AssertIsOnTargetThread(); 594 595 if (mDisconnectingOrDisconnected) { 596 return; 597 } 598 599 ConsoleError(); 600 mFailed = true; 601 CloseConnection(aProofOfRef, aReasonCode, aReasonString); 602 603 if (NS_IsMainThread() && mImplProxy) { 604 mImplProxy->Disconnect(); 605 mImplProxy = nullptr; 606 } 607 } 608 609 namespace { 610 611 class DisconnectInternalRunnable final : public WorkerMainThreadRunnable { 612 public: 613 explicit DisconnectInternalRunnable(WebSocketImpl* aImpl) 614 : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(), 615 "WebSocket :: disconnect"_ns), 616 mImpl(aImpl) {} 617 618 bool MainThreadRun() override { 619 mImpl->DisconnectInternal(); 620 return true; 621 } 622 623 private: 624 // NOTE: WebSocketImpl may be it the middle of being destroyed. 625 // We can't just hold this as a RefPtr, since after the runnable ends 626 // the sync caller will be released, and can finish destroying WebSocketImpl 627 // before a ref here could be dropped. 628 WebSocketImpl* mImpl; 629 }; 630 631 } // namespace 632 633 void WebSocketImpl::Disconnect(const RefPtr<WebSocketImpl>& aProofOfRef) { 634 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread); 635 636 if (mDisconnectingOrDisconnected) { 637 return; 638 } 639 640 // DontKeepAliveAnyMore() and DisconnectInternal() can release the 641 // object. aProofOfRef ensures we're holding a reference to this until 642 // the end of the method. 643 644 // Disconnect can be called from some control event (such as a callback from 645 // StrongWorkerRef). This will be scheduled before any other sync/async 646 // runnable. In order to prevent some double Disconnect() calls, we use this 647 // boolean. 648 mDisconnectingOrDisconnected = true; 649 650 // DisconnectInternal touches observers and nsILoadGroup and it must run on 651 // the main thread. 652 653 if (NS_IsMainThread()) { 654 DisconnectInternal(); 655 } else { 656 RefPtr<DisconnectInternalRunnable> runnable = 657 new DisconnectInternalRunnable(this); 658 ErrorResult rv; 659 runnable->Dispatch(GetCurrentThreadWorkerPrivate(), Killing, rv); 660 // XXXbz this seems totally broken. We should be propagating this out, but 661 // where to, exactly? 662 rv.SuppressException(); 663 } 664 665 // If we haven't called WebSocket::DisconnectFromOwner yet, update 666 // web socket count here. 667 if (nsIGlobalObject* global = mWebSocket->GetOwnerGlobal()) { 668 global->UpdateWebSocketCount(-1); 669 } 670 671 NS_ReleaseOnMainThread("WebSocketImpl::mChannel", mChannel.forget()); 672 NS_ReleaseOnMainThread("WebSocketImpl::mService", mService.forget()); 673 674 mWebSocket->DontKeepAliveAnyMore(); 675 mWebSocket->mImpl = nullptr; 676 677 if (mWorkerRef) { 678 UnregisterWorkerRef(); 679 } 680 681 // We want to release the WebSocket in the correct thread. 682 mWebSocket = nullptr; 683 } 684 685 void WebSocketImpl::DisconnectInternal() { 686 AssertIsOnMainThread(); 687 688 nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup); 689 if (loadGroup) { 690 loadGroup->RemoveRequest(this, nullptr, NS_OK); 691 // mWeakLoadGroup has to be released on main-thread because WeakReferences 692 // are not thread-safe. 693 mWeakLoadGroup = nullptr; 694 } 695 696 if (!mWorkerRef) { 697 GlobalTeardownObserver::DisconnectFromOwner(); 698 DisconnectFreezeObserver(); 699 } 700 701 if (mImplProxy) { 702 mImplProxy->Disconnect(); 703 mImplProxy = nullptr; 704 } 705 } 706 707 //----------------------------------------------------------------------------- 708 // WebSocketImpl::nsIWebSocketImpl 709 //----------------------------------------------------------------------------- 710 711 NS_IMETHODIMP 712 WebSocketImpl::SendMessage(const nsAString& aMessage) { 713 nsString message(aMessage); 714 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( 715 "WebSocketImpl::SendMessage", 716 [self = RefPtr<WebSocketImpl>(this), message = std::move(message)]() { 717 ErrorResult IgnoredErrorResult; 718 self->mWebSocket->Send(message, IgnoredErrorResult); 719 }); 720 return Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); 721 } 722 723 //----------------------------------------------------------------------------- 724 // WebSocketImpl::nsIWebSocketListener methods: 725 //----------------------------------------------------------------------------- 726 727 nsresult WebSocketImpl::DoOnMessageAvailable(const nsACString& aMsg, 728 bool isBinary) const { 729 AssertIsOnTargetThread(); 730 731 if (mDisconnectingOrDisconnected) { 732 return NS_OK; 733 } 734 735 int16_t readyState = mWebSocket->ReadyState(); 736 if (readyState == WebSocket::CLOSED) { 737 NS_ERROR("Received message after CLOSED"); 738 return NS_ERROR_UNEXPECTED; 739 } 740 741 if (readyState == WebSocket::OPEN) { 742 // Dispatch New Message 743 nsresult rv = mWebSocket->CreateAndDispatchMessageEvent(aMsg, isBinary); 744 if (NS_FAILED(rv)) { 745 NS_WARNING("Failed to dispatch the message event"); 746 } 747 748 return NS_OK; 749 } 750 751 // CLOSING should be the only other state where it's possible to get msgs 752 // from channel: Spec says to drop them. 753 MOZ_ASSERT(readyState == WebSocket::CLOSING, 754 "Received message while CONNECTING or CLOSED"); 755 return NS_OK; 756 } 757 758 NS_IMETHODIMP 759 WebSocketImpl::OnMessageAvailable(nsISupports* aContext, 760 const nsACString& aMsg) { 761 AssertIsOnTargetThread(); 762 763 if (mDisconnectingOrDisconnected) { 764 return NS_OK; 765 } 766 767 return DoOnMessageAvailable(aMsg, false); 768 } 769 770 NS_IMETHODIMP 771 WebSocketImpl::OnBinaryMessageAvailable(nsISupports* aContext, 772 const nsACString& aMsg) { 773 AssertIsOnTargetThread(); 774 775 if (mDisconnectingOrDisconnected) { 776 return NS_OK; 777 } 778 779 return DoOnMessageAvailable(aMsg, true); 780 } 781 782 NS_IMETHODIMP 783 WebSocketImpl::OnStart(nsISupports* aContext) { 784 if (!IsTargetThread()) { 785 nsCOMPtr<nsISupports> context = aContext; 786 return Dispatch(NS_NewRunnableFunction("WebSocketImpl::OnStart", 787 [self = RefPtr{this}, context]() { 788 (void)self->OnStart(context); 789 }), 790 NS_DISPATCH_NORMAL); 791 } 792 793 AssertIsOnTargetThread(); 794 795 if (mDisconnectingOrDisconnected) { 796 return NS_OK; 797 } 798 799 int16_t readyState = mWebSocket->ReadyState(); 800 801 // This is the only function that sets OPEN, and should be called only once 802 MOZ_ASSERT(readyState != WebSocket::OPEN, 803 "readyState already OPEN! OnStart called twice?"); 804 805 // Nothing to do if we've already closed/closing 806 if (readyState != WebSocket::CONNECTING) { 807 return NS_OK; 808 } 809 810 // Attempt to kill "ghost" websocket: but usually too early for check to fail 811 nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness(); 812 if (NS_FAILED(rv)) { 813 RefPtr<WebSocketImpl> self(this); 814 CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY); 815 return rv; 816 } 817 818 if (!mRequestedProtocolList.IsEmpty()) { 819 rv = mChannel->GetProtocol(mWebSocket->mEstablishedProtocol); 820 MOZ_ASSERT(NS_SUCCEEDED(rv)); 821 } 822 823 rv = mChannel->GetExtensions(mWebSocket->mEstablishedExtensions); 824 MOZ_ASSERT(NS_SUCCEEDED(rv)); 825 UpdateURI(); 826 827 mWebSocket->SetReadyState(WebSocket::OPEN); 828 829 mService->WebSocketOpened( 830 mChannel->Serial(), mInnerWindowID, mWebSocket->mEffectiveURL, 831 mWebSocket->mEstablishedProtocol, mWebSocket->mEstablishedExtensions, 832 mChannel->HttpChannelId()); 833 834 // Let's keep the object alive because the webSocket can be CCed in the 835 // onopen callback 836 RefPtr<WebSocket> webSocket = mWebSocket; 837 838 // Call 'onopen' 839 rv = webSocket->CreateAndDispatchSimpleEvent(OPEN_EVENT_STRING); 840 if (NS_FAILED(rv)) { 841 NS_WARNING("Failed to dispatch the open event"); 842 } 843 844 webSocket->UpdateMustKeepAlive(); 845 return NS_OK; 846 } 847 848 NS_IMETHODIMP 849 WebSocketImpl::OnStop(nsISupports* aContext, nsresult aStatusCode) { 850 AssertIsOnTargetThread(); 851 852 if (mDisconnectingOrDisconnected) { 853 return NS_OK; 854 } 855 856 // We can be CONNECTING here if connection failed. 857 // We can be OPEN if we have encountered a fatal protocol error 858 // We can be CLOSING if close() was called and/or server initiated close. 859 MOZ_ASSERT(mWebSocket->ReadyState() != WebSocket::CLOSED, 860 "Shouldn't already be CLOSED when OnStop called"); 861 862 return ScheduleConnectionCloseEvents(aContext, aStatusCode); 863 } 864 865 nsresult WebSocketImpl::ScheduleConnectionCloseEvents(nsISupports* aContext, 866 nsresult aStatusCode) { 867 AssertIsOnTargetThread(); 868 869 // no-op if some other code has already initiated close event 870 if (!mOnCloseScheduled) { 871 mCloseEventWasClean = NS_SUCCEEDED(aStatusCode); 872 873 if (aStatusCode == NS_BASE_STREAM_CLOSED) { 874 // don't generate an error event just because of an unclean close 875 aStatusCode = NS_OK; 876 } 877 878 if (aStatusCode == NS_ERROR_NET_INADEQUATE_SECURITY) { 879 // TLS negotiation failed so we need to set status code to 1015. 880 mCloseEventCode = 1015; 881 } 882 883 if (NS_FAILED(aStatusCode)) { 884 ConsoleError(); 885 mFailed = true; 886 } 887 888 mOnCloseScheduled = true; 889 890 NS_DispatchToCurrentThread(new CallDispatchConnectionCloseEvents(this)); 891 } 892 893 return NS_OK; 894 } 895 896 NS_IMETHODIMP 897 WebSocketImpl::OnAcknowledge(nsISupports* aContext, uint32_t aSize) { 898 AssertIsOnTargetThread(); 899 900 if (mDisconnectingOrDisconnected) { 901 return NS_OK; 902 } 903 904 MOZ_RELEASE_ASSERT(mWebSocket->mOutgoingBufferedAmount.isValid()); 905 if (aSize > mWebSocket->mOutgoingBufferedAmount.value()) { 906 return NS_ERROR_UNEXPECTED; 907 } 908 909 CheckedUint64 outgoingBufferedAmount = mWebSocket->mOutgoingBufferedAmount; 910 outgoingBufferedAmount -= aSize; 911 if (!outgoingBufferedAmount.isValid()) { 912 return NS_ERROR_UNEXPECTED; 913 } 914 915 mWebSocket->mOutgoingBufferedAmount = outgoingBufferedAmount; 916 MOZ_RELEASE_ASSERT(mWebSocket->mOutgoingBufferedAmount.isValid()); 917 918 return NS_OK; 919 } 920 921 NS_IMETHODIMP 922 WebSocketImpl::OnServerClose(nsISupports* aContext, uint16_t aCode, 923 const nsACString& aReason) { 924 AssertIsOnTargetThread(); 925 926 if (mDisconnectingOrDisconnected) { 927 return NS_OK; 928 } 929 930 int16_t readyState = mWebSocket->ReadyState(); 931 932 MOZ_ASSERT(readyState != WebSocket::CONNECTING, 933 "Received server close before connected?"); 934 MOZ_ASSERT(readyState != WebSocket::CLOSED, 935 "Received server close after already closed!"); 936 937 // store code/string for onclose DOM event 938 mCloseEventCode = aCode; 939 CopyUTF8toUTF16(aReason, mCloseEventReason); 940 941 if (readyState == WebSocket::OPEN) { 942 // Server initiating close. 943 // RFC 6455, 5.5.1: "When sending a Close frame in response, the endpoint 944 // typically echos the status code it received". 945 // But never send certain codes, per section 7.4.1 946 RefPtr<WebSocketImpl> self(this); 947 if (aCode == 1005 || aCode == 1006 || aCode == 1015) { 948 CloseConnection(self, 0, ""_ns); 949 } else { 950 CloseConnection(self, aCode, aReason); 951 } 952 } else { 953 // We initiated close, and server has replied: OnStop does rest of the work. 954 MOZ_ASSERT(readyState == WebSocket::CLOSING, "unknown state"); 955 } 956 957 return NS_OK; 958 } 959 960 NS_IMETHODIMP 961 WebSocketImpl::OnError() { 962 if (!IsTargetThread()) { 963 return Dispatch( 964 NS_NewRunnableFunction("dom::FailConnectionRunnable", 965 [self = RefPtr{this}]() { 966 self->FailConnection( 967 self, nsIWebSocketChannel::CLOSE_ABNORMAL); 968 }), 969 NS_DISPATCH_NORMAL); 970 } 971 972 AssertIsOnTargetThread(); 973 RefPtr<WebSocketImpl> self(this); 974 FailConnection(self, nsIWebSocketChannel::CLOSE_ABNORMAL); 975 return NS_OK; 976 } 977 978 //----------------------------------------------------------------------------- 979 // WebSocketImpl::nsIInterfaceRequestor 980 //----------------------------------------------------------------------------- 981 982 NS_IMETHODIMP 983 WebSocketImpl::GetInterface(const nsIID& aIID, void** aResult) { 984 AssertIsOnMainThread(); 985 986 if (!mWebSocket || mWebSocket->ReadyState() == WebSocket::CLOSED) { 987 return NS_ERROR_FAILURE; 988 } 989 990 if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || 991 aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { 992 nsCOMPtr<nsPIDOMWindowInner> win = mWebSocket->GetWindowIfCurrent(); 993 if (!win) { 994 return NS_ERROR_NOT_AVAILABLE; 995 } 996 997 nsresult rv; 998 nsCOMPtr<nsIPromptFactory> wwatch = 999 do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); 1000 NS_ENSURE_SUCCESS(rv, rv); 1001 1002 nsCOMPtr<nsPIDOMWindowOuter> outerWindow = win->GetOuterWindow(); 1003 return wwatch->GetPrompt(outerWindow, aIID, aResult); 1004 } 1005 1006 return QueryInterface(aIID, aResult); 1007 } 1008 1009 //////////////////////////////////////////////////////////////////////////////// 1010 // WebSocket 1011 //////////////////////////////////////////////////////////////////////////////// 1012 1013 WebSocket::WebSocket(nsIGlobalObject* aGlobal) 1014 : DOMEventTargetHelper(aGlobal), 1015 mIsMainThread(true), 1016 mKeepingAlive(false), 1017 mCheckMustKeepAlive(true), 1018 mOutgoingBufferedAmount(0), 1019 mBinaryType(dom::BinaryType::Blob), 1020 mMutex("WebSocket::mMutex"), 1021 mReadyState(CONNECTING) { 1022 MOZ_ASSERT(aGlobal); 1023 1024 mImpl = new WebSocketImpl(this); 1025 mIsMainThread = mImpl->mIsMainThread; 1026 } 1027 1028 WebSocket::~WebSocket() = default; 1029 1030 mozilla::Maybe<EventCallbackDebuggerNotificationType> 1031 WebSocket::GetDebuggerNotificationType() const { 1032 return mozilla::Some(EventCallbackDebuggerNotificationType::Websocket); 1033 } 1034 1035 JSObject* WebSocket::WrapObject(JSContext* cx, 1036 JS::Handle<JSObject*> aGivenProto) { 1037 return WebSocket_Binding::Wrap(cx, this, aGivenProto); 1038 } 1039 1040 //--------------------------------------------------------------------------- 1041 // WebIDL 1042 //--------------------------------------------------------------------------- 1043 1044 // Constructor: 1045 already_AddRefed<WebSocket> WebSocket::Constructor( 1046 const GlobalObject& aGlobal, const nsAString& aUrl, 1047 const StringOrStringSequence& aProtocols, ErrorResult& aRv) { 1048 if (aProtocols.IsStringSequence()) { 1049 return WebSocket::ConstructorCommon( 1050 aGlobal, aUrl, aProtocols.GetAsStringSequence(), nullptr, ""_ns, aRv); 1051 } 1052 1053 Sequence<nsString> protocols; 1054 if (!protocols.AppendElement(aProtocols.GetAsString(), fallible)) { 1055 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 1056 return nullptr; 1057 } 1058 1059 return WebSocket::ConstructorCommon(aGlobal, aUrl, protocols, nullptr, ""_ns, 1060 aRv); 1061 } 1062 1063 already_AddRefed<WebSocket> WebSocket::CreateServerWebSocket( 1064 const GlobalObject& aGlobal, const nsAString& aUrl, 1065 const Sequence<nsString>& aProtocols, 1066 nsITransportProvider* aTransportProvider, 1067 const nsAString& aNegotiatedExtensions, ErrorResult& aRv) { 1068 return WebSocket::ConstructorCommon( 1069 aGlobal, aUrl, aProtocols, aTransportProvider, 1070 NS_ConvertUTF16toUTF8(aNegotiatedExtensions), aRv); 1071 } 1072 1073 namespace { 1074 1075 // This class is used to clear any exception. 1076 class MOZ_STACK_CLASS ClearException { 1077 public: 1078 explicit ClearException(JSContext* aCx) : mCx(aCx) {} 1079 1080 ~ClearException() { JS_ClearPendingException(mCx); } 1081 1082 private: 1083 JSContext* mCx; 1084 }; 1085 1086 class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable { 1087 public: 1088 WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate, 1089 const nsACString& aTelemetryKey) 1090 : WorkerMainThreadRunnable(aWorkerPrivate, aTelemetryKey) { 1091 MOZ_ASSERT(aWorkerPrivate); 1092 aWorkerPrivate->AssertIsOnWorkerThread(); 1093 } 1094 1095 bool MainThreadRun() override { 1096 AssertIsOnMainThread(); 1097 MOZ_ASSERT(mWorkerRef); 1098 1099 // Walk up to our containing page 1100 WorkerPrivate* wp = mWorkerRef->Private()->GetTopLevelWorker(); 1101 1102 nsPIDOMWindowInner* window = wp->GetWindow(); 1103 if (window) { 1104 return InitWithWindow(window); 1105 } 1106 1107 return InitWindowless(wp); 1108 } 1109 1110 protected: 1111 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) = 0; 1112 1113 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) = 0; 1114 }; 1115 1116 class InitRunnable final : public WebSocketMainThreadRunnable { 1117 public: 1118 InitRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl, 1119 const Maybe<mozilla::dom::ClientInfo>& aClientInfo, 1120 bool aIsServerSide, const nsAString& aURL, 1121 nsTArray<nsString>& aProtocolArray, 1122 const nsACString& aScriptFile, uint32_t aScriptLine, 1123 uint32_t aScriptColumn) 1124 : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns), 1125 mImpl(aImpl), 1126 mClientInfo(aClientInfo), 1127 mIsServerSide(aIsServerSide), 1128 mURL(aURL), 1129 mProtocolArray(aProtocolArray), 1130 mScriptFile(aScriptFile), 1131 mScriptLine(aScriptLine), 1132 mScriptColumn(aScriptColumn), 1133 mErrorCode(NS_OK) { 1134 aWorkerPrivate->AssertIsOnWorkerThread(); 1135 } 1136 1137 nsresult ErrorCode() const { return mErrorCode; } 1138 1139 protected: 1140 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override { 1141 AutoJSAPI jsapi; 1142 if (NS_WARN_IF(!jsapi.Init(aWindow))) { 1143 mErrorCode = NS_ERROR_FAILURE; 1144 return true; 1145 } 1146 1147 ClearException ce(jsapi.cx()); 1148 1149 Document* doc = aWindow->GetExtantDoc(); 1150 if (!doc) { 1151 mErrorCode = NS_ERROR_FAILURE; 1152 return true; 1153 } 1154 1155 MOZ_ASSERT(mWorkerRef); 1156 1157 nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal(); 1158 mErrorCode = mImpl->Init( 1159 nullptr, jsapi.cx(), principal->SchemeIs("https"), principal, 1160 mClientInfo, mWorkerRef->Private()->CSPEventListener(), mIsServerSide, 1161 mURL, mProtocolArray, mScriptFile, mScriptLine, mScriptColumn); 1162 return true; 1163 } 1164 1165 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override { 1166 MOZ_ASSERT(NS_IsMainThread()); 1167 MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow()); 1168 MOZ_ASSERT(mWorkerRef); 1169 1170 WorkerPrivate* workerPrivate = mWorkerRef->Private(); 1171 1172 mErrorCode = mImpl->Init( 1173 nullptr, nullptr, workerPrivate->GetPrincipal()->SchemeIs("https"), 1174 aTopLevelWorkerPrivate->GetPrincipal(), mClientInfo, 1175 workerPrivate->CSPEventListener(), mIsServerSide, mURL, mProtocolArray, 1176 mScriptFile, mScriptLine, mScriptColumn); 1177 return true; 1178 } 1179 1180 // Raw pointer. This worker runnable runs synchronously. 1181 WebSocketImpl* mImpl; 1182 1183 Maybe<ClientInfo> mClientInfo; 1184 bool mIsServerSide; 1185 const nsAString& mURL; 1186 nsTArray<nsString>& mProtocolArray; 1187 nsCString mScriptFile; 1188 uint32_t mScriptLine; 1189 uint32_t mScriptColumn; 1190 nsresult mErrorCode; 1191 }; 1192 1193 class ConnectRunnable final : public WebSocketMainThreadRunnable { 1194 public: 1195 ConnectRunnable(WorkerPrivate* aWorkerPrivate, WebSocketImpl* aImpl) 1196 : WebSocketMainThreadRunnable(aWorkerPrivate, "WebSocket :: init"_ns), 1197 mImpl(aImpl), 1198 mConnectionFailed(true) { 1199 MOZ_ASSERT(aWorkerPrivate); 1200 aWorkerPrivate->AssertIsOnWorkerThread(); 1201 } 1202 1203 bool ConnectionFailed() const { return mConnectionFailed; } 1204 1205 protected: 1206 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override { 1207 MOZ_ASSERT(mWorkerRef); 1208 1209 Document* doc = aWindow->GetExtantDoc(); 1210 if (!doc) { 1211 return true; 1212 } 1213 1214 mConnectionFailed = NS_FAILED(mImpl->InitializeConnection( 1215 doc->NodePrincipal(), mWorkerRef->Private()->CookieJarSettings())); 1216 return true; 1217 } 1218 1219 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override { 1220 MOZ_ASSERT(NS_IsMainThread()); 1221 MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow()); 1222 MOZ_ASSERT(mWorkerRef); 1223 1224 mConnectionFailed = NS_FAILED(mImpl->InitializeConnection( 1225 aTopLevelWorkerPrivate->GetPrincipal(), 1226 mWorkerRef->Private()->CookieJarSettings())); 1227 return true; 1228 } 1229 1230 // Raw pointer. This worker runnable runs synchronously. 1231 WebSocketImpl* mImpl; 1232 1233 bool mConnectionFailed; 1234 }; 1235 1236 class AsyncOpenRunnable final : public WebSocketMainThreadRunnable { 1237 public: 1238 explicit AsyncOpenRunnable(WebSocketImpl* aImpl, 1239 UniquePtr<SerializedStackHolder> aOriginStack) 1240 : WebSocketMainThreadRunnable(aImpl->mWorkerRef->Private(), 1241 "WebSocket :: AsyncOpen"_ns), 1242 mImpl(aImpl), 1243 mOriginStack(std::move(aOriginStack)), 1244 mErrorCode(NS_OK) { 1245 MOZ_ASSERT(aImpl->mWorkerRef); 1246 aImpl->mWorkerRef->Private()->AssertIsOnWorkerThread(); 1247 } 1248 1249 nsresult ErrorCode() const { return mErrorCode; } 1250 1251 protected: 1252 virtual bool InitWithWindow(nsPIDOMWindowInner* aWindow) override { 1253 AssertIsOnMainThread(); 1254 MOZ_ASSERT(aWindow); 1255 1256 Document* doc = aWindow->GetExtantDoc(); 1257 if (!doc) { 1258 mErrorCode = NS_ERROR_FAILURE; 1259 return true; 1260 } 1261 1262 nsCOMPtr<nsIPrincipal> principal = doc->PartitionedPrincipal(); 1263 if (!principal) { 1264 mErrorCode = NS_ERROR_FAILURE; 1265 return true; 1266 } 1267 1268 uint64_t windowID = 0; 1269 if (WindowContext* wc = aWindow->GetWindowContext()) { 1270 windowID = wc->InnerWindowId(); 1271 } 1272 1273 mErrorCode = mImpl->AsyncOpen(principal, windowID, nullptr, ""_ns, 1274 std::move(mOriginStack)); 1275 return true; 1276 } 1277 1278 virtual bool InitWindowless(WorkerPrivate* aTopLevelWorkerPrivate) override { 1279 MOZ_ASSERT(NS_IsMainThread()); 1280 MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow()); 1281 1282 mErrorCode = 1283 mImpl->AsyncOpen(aTopLevelWorkerPrivate->GetPartitionedPrincipal(), 0, 1284 nullptr, ""_ns, nullptr); 1285 return true; 1286 } 1287 1288 private: 1289 // Raw pointer. This worker runs synchronously. 1290 WebSocketImpl* mImpl; 1291 1292 UniquePtr<SerializedStackHolder> mOriginStack; 1293 1294 nsresult mErrorCode; 1295 }; 1296 1297 } // namespace 1298 1299 // Check a protocol entry contains only valid characters 1300 bool WebSocket::IsValidProtocolString(const nsString& aValue) { 1301 // RFC 6455 (4.1): "not including separator characters as defined in RFC 2616" 1302 const char16_t illegalCharacters[] = {0x28, 0x29, 0x3C, 0x3E, 0x40, 0x2C, 1303 0x3B, 0x3A, 0x5C, 0x22, 0x2F, 0x5B, 1304 0x5D, 0x3F, 0x3D, 0x7B, 0x7D}; 1305 1306 // Cannot be empty string 1307 if (aValue.IsEmpty()) { 1308 return false; 1309 } 1310 1311 const auto* start = aValue.BeginReading(); 1312 const auto* end = aValue.EndReading(); 1313 1314 auto charFilter = [&](char16_t c) { 1315 // RFC 6455 (4.1 P18): "in the range U+0021 to U+007E" 1316 if (c < 0x21 || c > 0x7E) { 1317 return true; 1318 } 1319 1320 return std::find(std::begin(illegalCharacters), std::end(illegalCharacters), 1321 c) != std::end(illegalCharacters); 1322 }; 1323 1324 return std::find_if(start, end, charFilter) == end; 1325 } 1326 1327 already_AddRefed<WebSocket> WebSocket::ConstructorCommon( 1328 const GlobalObject& aGlobal, const nsAString& aUrl, 1329 const Sequence<nsString>& aProtocols, 1330 nsITransportProvider* aTransportProvider, 1331 const nsACString& aNegotiatedExtensions, ErrorResult& aRv) { 1332 MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty()); 1333 nsCOMPtr<nsIPrincipal> principal; 1334 nsCOMPtr<nsIPrincipal> partitionedPrincipal; 1335 1336 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 1337 if (NS_WARN_IF(!global)) { 1338 aRv.Throw(NS_ERROR_FAILURE); 1339 return nullptr; 1340 } 1341 1342 if (NS_IsMainThread()) { 1343 nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal = 1344 do_QueryInterface(aGlobal.GetAsSupports()); 1345 if (!scriptPrincipal) { 1346 aRv.Throw(NS_ERROR_FAILURE); 1347 return nullptr; 1348 } 1349 1350 principal = scriptPrincipal->GetPrincipal(); 1351 partitionedPrincipal = scriptPrincipal->PartitionedPrincipal(); 1352 if (!principal || !partitionedPrincipal) { 1353 aRv.Throw(NS_ERROR_FAILURE); 1354 return nullptr; 1355 } 1356 } 1357 1358 nsTArray<nsString> protocolArray; 1359 1360 for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) { 1361 const nsString& protocolElement = aProtocols[index]; 1362 1363 // Repeated protocols are not allowed 1364 if (protocolArray.Contains(protocolElement)) { 1365 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); 1366 return nullptr; 1367 } 1368 1369 // Protocol string value must match constraints 1370 if (!IsValidProtocolString(protocolElement)) { 1371 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); 1372 return nullptr; 1373 } 1374 1375 protocolArray.AppendElement(protocolElement); 1376 } 1377 1378 RefPtr<WebSocket> webSocket = new WebSocket(global); 1379 RefPtr<WebSocketImpl> webSocketImpl = webSocket->mImpl; 1380 1381 bool connectionFailed = true; 1382 1383 global->UpdateWebSocketCount(1); 1384 1385 if (NS_IsMainThread()) { 1386 // We're keeping track of all main thread web sockets to be able to 1387 // avoid throttling timeouts when we have active web sockets. 1388 1389 bool isSecure = principal->SchemeIs("https"); 1390 aRv = webSocketImpl->IsSecure(&isSecure); 1391 if (NS_WARN_IF(aRv.Failed())) { 1392 return nullptr; 1393 } 1394 1395 aRv = webSocketImpl->Init(global, aGlobal.Context(), isSecure, principal, 1396 Nothing(), nullptr, !!aTransportProvider, aUrl, 1397 protocolArray, ""_ns, 0, 0); 1398 1399 if (NS_WARN_IF(aRv.Failed())) { 1400 return nullptr; 1401 } 1402 1403 nsCOMPtr<Document> doc = webSocket->GetDocumentIfCurrent(); 1404 1405 // the constructor should throw a SYNTAX_ERROR only if it fails to parse the 1406 // url parameter, so don't throw if InitializeConnection fails, and call 1407 // onerror/onclose asynchronously 1408 connectionFailed = NS_FAILED(webSocketImpl->InitializeConnection( 1409 principal, doc ? doc->CookieJarSettings() : nullptr)); 1410 } else { 1411 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 1412 MOZ_ASSERT(workerPrivate); 1413 1414 uint32_t lineno; 1415 JS::ColumnNumberOneOrigin column; 1416 JS::AutoFilename file; 1417 if (!JS::DescribeScriptedCaller(&file, aGlobal.Context(), &lineno, 1418 &column)) { 1419 NS_WARNING("Failed to get line number and filename in workers."); 1420 } 1421 1422 RefPtr<InitRunnable> runnable = new InitRunnable( 1423 workerPrivate, webSocketImpl, 1424 workerPrivate->GlobalScope()->GetClientInfo(), !!aTransportProvider, 1425 aUrl, protocolArray, nsDependentCString(file.get()), lineno, 1426 column.oneOriginValue()); 1427 runnable->Dispatch(workerPrivate, Canceling, aRv); 1428 if (NS_WARN_IF(aRv.Failed())) { 1429 return nullptr; 1430 } 1431 1432 aRv = runnable->ErrorCode(); 1433 if (NS_WARN_IF(aRv.Failed())) { 1434 return nullptr; 1435 } 1436 1437 if (NS_WARN_IF(!webSocketImpl->RegisterWorkerRef(workerPrivate))) { 1438 // The worker is shutting down. 1439 aRv.Throw(NS_ERROR_FAILURE); 1440 return nullptr; 1441 } 1442 1443 RefPtr<ConnectRunnable> connectRunnable = 1444 new ConnectRunnable(workerPrivate, webSocketImpl); 1445 connectRunnable->Dispatch(workerPrivate, Canceling, aRv); 1446 if (NS_WARN_IF(aRv.Failed())) { 1447 return nullptr; 1448 } 1449 1450 connectionFailed = connectRunnable->ConnectionFailed(); 1451 } 1452 1453 // It can be that we have been already disconnected because the WebSocket is 1454 // gone away while we where initializing the webSocket. 1455 if (!webSocket->mImpl) { 1456 aRv.Throw(NS_ERROR_FAILURE); 1457 return nullptr; 1458 } 1459 1460 // We don't return an error if the connection just failed. Instead we dispatch 1461 // an event. 1462 if (connectionFailed) { 1463 webSocketImpl->FailConnection(webSocketImpl, 1464 nsIWebSocketChannel::CLOSE_ABNORMAL); 1465 } 1466 1467 // If we don't have a channel, the connection is failed and onerror() will be 1468 // called asynchrounsly. 1469 if (!webSocket->mImpl->mChannel) { 1470 return webSocket.forget(); 1471 } 1472 1473 class MOZ_STACK_CLASS ClearWebSocket { 1474 public: 1475 explicit ClearWebSocket(WebSocketImpl* aWebSocketImpl) 1476 : mWebSocketImpl(aWebSocketImpl), mDone(false) {} 1477 1478 void Done() { mDone = true; } 1479 1480 ~ClearWebSocket() { 1481 if (!mDone) { 1482 mWebSocketImpl->mChannel = nullptr; 1483 mWebSocketImpl->FailConnection(mWebSocketImpl, 1484 nsIWebSocketChannel::CLOSE_ABNORMAL); 1485 } 1486 } 1487 1488 RefPtr<WebSocketImpl> mWebSocketImpl; 1489 bool mDone; 1490 }; 1491 1492 ClearWebSocket cws(webSocket->mImpl); 1493 1494 // This operation must be done on the correct thread. The rest must run on the 1495 // main-thread. 1496 aRv = webSocket->mImpl->mChannel->SetNotificationCallbacks(webSocket->mImpl); 1497 if (NS_WARN_IF(aRv.Failed())) { 1498 return nullptr; 1499 } 1500 1501 if (NS_IsMainThread()) { 1502 MOZ_ASSERT(principal); 1503 MOZ_ASSERT(partitionedPrincipal); 1504 1505 nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(global); 1506 1507 UniquePtr<SerializedStackHolder> stack; 1508 uint64_t windowID = 0; 1509 1510 if (ownerWindow) { 1511 BrowsingContext* browsingContext = ownerWindow->GetBrowsingContext(); 1512 if (browsingContext && browsingContext->WatchedByDevTools()) { 1513 stack = GetCurrentStackForNetMonitor(aGlobal.Context()); 1514 } 1515 1516 if (WindowContext* wc = ownerWindow->GetWindowContext()) { 1517 windowID = wc->InnerWindowId(); 1518 } 1519 } 1520 1521 aRv = webSocket->mImpl->AsyncOpen(partitionedPrincipal, windowID, 1522 aTransportProvider, aNegotiatedExtensions, 1523 std::move(stack)); 1524 } else { 1525 MOZ_ASSERT(!aTransportProvider && aNegotiatedExtensions.IsEmpty(), 1526 "not yet implemented"); 1527 1528 UniquePtr<SerializedStackHolder> stack; 1529 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 1530 if (workerPrivate->IsWatchedByDevTools()) { 1531 stack = GetCurrentStackForNetMonitor(aGlobal.Context()); 1532 } 1533 1534 RefPtr<AsyncOpenRunnable> runnable = 1535 new AsyncOpenRunnable(webSocket->mImpl, std::move(stack)); 1536 runnable->Dispatch(webSocket->mImpl->mWorkerRef->Private(), Canceling, aRv); 1537 if (NS_WARN_IF(aRv.Failed())) { 1538 return nullptr; 1539 } 1540 1541 aRv = runnable->ErrorCode(); 1542 } 1543 1544 if (NS_WARN_IF(aRv.Failed())) { 1545 return nullptr; 1546 } 1547 1548 // It can be that we have been already disconnected because the WebSocket is 1549 // gone away while we where initializing the webSocket. 1550 if (!webSocket->mImpl) { 1551 aRv.Throw(NS_ERROR_FAILURE); 1552 return nullptr; 1553 } 1554 1555 // Let's inform devtools about this new active WebSocket. 1556 webSocket->mImpl->mService->WebSocketCreated( 1557 webSocket->mImpl->mChannel->Serial(), webSocket->mImpl->mInnerWindowID, 1558 webSocket->mURI, webSocket->mImpl->mRequestedProtocolList); 1559 cws.Done(); 1560 1561 return webSocket.forget(); 1562 } 1563 1564 NS_IMPL_CYCLE_COLLECTION_CLASS(WebSocket) 1565 1566 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WebSocket, 1567 DOMEventTargetHelper) 1568 if (tmp->mImpl) { 1569 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImpl->mChannel) 1570 } 1571 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 1572 1573 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WebSocket, DOMEventTargetHelper) 1574 if (tmp->mImpl) { 1575 NS_IMPL_CYCLE_COLLECTION_UNLINK(mImpl->mChannel) 1576 RefPtr<WebSocketImpl> pin(tmp->mImpl); 1577 pin->Disconnect(pin); 1578 MOZ_ASSERT(!tmp->mImpl); 1579 } 1580 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 1581 1582 bool WebSocket::IsCertainlyAliveForCC() const { return mKeepingAlive; } 1583 1584 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebSocket) 1585 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) 1586 1587 NS_IMPL_ADDREF_INHERITED(WebSocket, DOMEventTargetHelper) 1588 NS_IMPL_RELEASE_INHERITED(WebSocket, DOMEventTargetHelper) 1589 1590 void WebSocket::DisconnectFromOwner() { 1591 // If we haven't called WebSocketImpl::Disconnect yet, update web 1592 // socket count here. 1593 if (mImpl && !mImpl->mDisconnectingOrDisconnected) { 1594 GetOwnerGlobal()->UpdateWebSocketCount(-1); 1595 } 1596 1597 DOMEventTargetHelper::DisconnectFromOwner(); 1598 1599 if (mImpl) { 1600 RefPtr<WebSocketImpl> pin(mImpl); 1601 pin->CloseConnection(pin, nsIWebSocketChannel::CLOSE_GOING_AWAY); 1602 } 1603 1604 DontKeepAliveAnyMore(); 1605 } 1606 1607 //----------------------------------------------------------------------------- 1608 // WebSocketImpl:: initialization 1609 //----------------------------------------------------------------------------- 1610 1611 nsresult WebSocketImpl::Init(nsIGlobalObject* aWindowGlobal, JSContext* aCx, 1612 bool aIsSecure, nsIPrincipal* aPrincipal, 1613 const Maybe<ClientInfo>& aClientInfo, 1614 nsICSPEventListener* aCSPEventListener, 1615 bool aIsServerSide, const nsAString& aURL, 1616 nsTArray<nsString>& aProtocolArray, 1617 const nsACString& aScriptFile, 1618 uint32_t aScriptLine, uint32_t aScriptColumn) { 1619 AssertIsOnMainThread(); 1620 MOZ_ASSERT(aPrincipal); 1621 1622 mService = WebSocketEventService::GetOrCreate(); 1623 1624 // We need to keep the implementation alive in case the init disconnects it 1625 // because of some error. 1626 RefPtr<WebSocketImpl> kungfuDeathGrip = this; 1627 1628 // Attempt to kill "ghost" websocket: but usually too early for check to fail 1629 nsresult rv = mWebSocket->CheckCurrentGlobalCorrectness(); 1630 NS_ENSURE_SUCCESS(rv, rv); 1631 1632 // Assign the sub protocol list and scan it for illegal values 1633 for (uint32_t index = 0; index < aProtocolArray.Length(); ++index) { 1634 if (!WebSocket::IsValidProtocolString(aProtocolArray[index])) { 1635 return NS_ERROR_DOM_SYNTAX_ERR; 1636 } 1637 1638 if (!mRequestedProtocolList.IsEmpty()) { 1639 mRequestedProtocolList.AppendLiteral(", "); 1640 } 1641 1642 AppendUTF16toUTF8(aProtocolArray[index], mRequestedProtocolList); 1643 } 1644 1645 // Shut down websocket if window is frozen or destroyed (only needed for 1646 // "ghost" websockets--see bug 696085) 1647 RefPtr<WebSocketImplProxy> proxy; 1648 if (mIsMainThread) { 1649 proxy = new WebSocketImplProxy(this); 1650 proxy->BindToOwner(aWindowGlobal); 1651 } 1652 1653 if (!mIsMainThread) { 1654 mScriptFile = aScriptFile; 1655 mScriptLine = aScriptLine; 1656 mScriptColumn = aScriptColumn; 1657 } else { 1658 MOZ_ASSERT(aCx); 1659 1660 uint32_t lineno; 1661 JS::ColumnNumberOneOrigin column; 1662 JS::AutoFilename file; 1663 if (JS::DescribeScriptedCaller(&file, aCx, &lineno, &column)) { 1664 mScriptFile = file.get(); 1665 mScriptLine = lineno; 1666 mScriptColumn = column.oneOriginValue(); 1667 } 1668 } 1669 1670 mIsServerSide = aIsServerSide; 1671 1672 // If we don't have aCx, we are window-less, so we don't have a 1673 // inner-windowID. This can happen in sharedWorkers and ServiceWorkers or in 1674 // DedicateWorkers created by JSM. 1675 if (aCx) { 1676 if (nsPIDOMWindowInner* ownerWindow = mWebSocket->GetOwnerWindow()) { 1677 mInnerWindowID = ownerWindow->WindowID(); 1678 } 1679 } 1680 1681 mPrivateBrowsing = aPrincipal->OriginAttributesRef().IsPrivateBrowsing(); 1682 mIsChromeContext = aPrincipal->IsSystemPrincipal(); 1683 1684 // parses the url 1685 nsCOMPtr<nsIURI> baseURI = aPrincipal->GetURI(); 1686 rv = ParseURL(aURL, baseURI); 1687 NS_ENSURE_SUCCESS(rv, rv); 1688 1689 nsCOMPtr<Document> originDoc = mWebSocket->GetDocumentIfCurrent(); 1690 if (!originDoc) { 1691 rv = mWebSocket->CheckCurrentGlobalCorrectness(); 1692 NS_ENSURE_SUCCESS(rv, rv); 1693 } 1694 mOriginDocument = originDoc; 1695 1696 if (!mIsServerSide) { 1697 nsCOMPtr<nsIURI> uri; 1698 { 1699 nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI); 1700 1701 // We crash here because we are sure that mURI is a valid URI, so either 1702 // we are OOM'ing or something else bad is happening. 1703 if (NS_WARN_IF(NS_FAILED(rv))) { 1704 MOZ_CRASH(); 1705 } 1706 } 1707 1708 // The 'real' nsHttpChannel of the websocket gets opened in the parent. 1709 // Since we don't serialize the CSP within child and parent and also not 1710 // the context, we have to perform content policy checks here instead of 1711 // AsyncOpen(). 1712 // Please note that websockets can't follow redirects, hence there is no 1713 // need to perform a CSP check after redirects. 1714 nsCOMPtr<nsILoadInfo> secCheckLoadInfo = MOZ_TRY(net::LoadInfo::Create( 1715 aPrincipal, // loading principal 1716 aPrincipal, // triggering principal 1717 originDoc, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, 1718 nsIContentPolicy::TYPE_WEBSOCKET, aClientInfo)); 1719 1720 if (aCSPEventListener) { 1721 secCheckLoadInfo->SetCspEventListener(aCSPEventListener); 1722 } 1723 1724 int16_t shouldLoad = nsIContentPolicy::ACCEPT; 1725 rv = NS_CheckContentLoadPolicy(uri, secCheckLoadInfo, &shouldLoad, 1726 nsContentUtils::GetContentPolicy()); 1727 NS_ENSURE_SUCCESS(rv, rv); 1728 1729 if (NS_CP_REJECTED(shouldLoad)) { 1730 // Disallowed by content policy 1731 return NS_ERROR_CONTENT_BLOCKED; 1732 } 1733 1734 // If the HTTPS-Only mode is enabled, we need to upgrade the websocket 1735 // connection from ws:// to wss:// and mark it as secure. 1736 if (!mSecure && originDoc && 1737 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL( 1738 originDoc->GetDocumentURI())) { 1739 nsCOMPtr<nsIURI> uri; 1740 nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI); 1741 NS_ENSURE_SUCCESS(rv, rv); 1742 1743 // secCheckLoadInfo is only used for the triggering principal, so this 1744 // is okay. 1745 if (nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(uri, secCheckLoadInfo)) { 1746 mURI.ReplaceSubstring("ws://", "wss://"); 1747 if (NS_WARN_IF(mURI.Find("wss://") != 0)) { 1748 return NS_OK; 1749 } 1750 mSecure = true; 1751 } 1752 } 1753 } 1754 1755 // Potentially the page uses the CSP directive 'upgrade-insecure-requests'. 1756 // In such a case we have to upgrade ws: to wss: and also update mSecure 1757 // to reflect that upgrade. Please note that we can not upgrade from ws: 1758 // to wss: before performing content policy checks because CSP needs to 1759 // send reports in case the scheme is about to be upgraded. 1760 if (!mIsServerSide && !mSecure && originDoc && 1761 originDoc->GetUpgradeInsecureRequests(false) && 1762 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL( 1763 originDoc->GetDocumentURI())) { 1764 // let's use the old specification before the upgrade for logging 1765 AutoTArray<nsString, 2> params; 1766 CopyUTF8toUTF16(mURI, *params.AppendElement()); 1767 1768 // upgrade the request from ws:// to wss:// and mark as secure 1769 mURI.ReplaceSubstring("ws://", "wss://"); 1770 if (NS_WARN_IF(mURI.Find("wss://") != 0)) { 1771 return NS_OK; 1772 } 1773 mSecure = true; 1774 1775 params.AppendElement(u"wss"_ns); 1776 CSP_LogLocalizedStr("upgradeInsecureRequest", params, 1777 ""_ns, // aSourceFile 1778 u""_ns, // aScriptSample 1779 0, // aLineNumber 1780 1, // aColumnNumber 1781 nsIScriptError::warningFlag, 1782 "upgradeInsecureRequest"_ns, mInnerWindowID, 1783 mPrivateBrowsing); 1784 } 1785 1786 // Don't allow https:// to open ws:// 1787 // Check that we aren't a server side websocket or set to be upgraded to wss 1788 // or allowing ws from https or a local websocket 1789 if (!mIsServerSide && !mSecure && aIsSecure && 1790 !Preferences::GetBool("network.websocket.allowInsecureFromHTTPS", 1791 false) && 1792 !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost( 1793 mAsciiHost)) { 1794 nsCOMPtr<nsIURI> uri; 1795 nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI); 1796 NS_ENSURE_SUCCESS(rv, rv); 1797 if (!nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(uri)) { 1798 return NS_ERROR_DOM_SECURITY_ERR; 1799 } 1800 1801 // Obtain the precursor's URI for the loading principal if it exists 1802 // otherwise use the loading principal's URI 1803 nsCOMPtr<nsIPrincipal> precursorPrincipal = 1804 aPrincipal->GetPrecursorPrincipal(); 1805 nsCOMPtr<nsIURI> precursorOrLoadingURI = precursorPrincipal 1806 ? precursorPrincipal->GetURI() 1807 : aPrincipal->GetURI(); 1808 1809 // Check if the parent was loaded securely if we have one 1810 if (precursorOrLoadingURI) { 1811 nsCOMPtr<nsIURI> precursorOrLoadingInnermostURI = 1812 NS_GetInnermostURI(precursorOrLoadingURI); 1813 // If the parent was loaded securely then disallow loading ws 1814 if (precursorOrLoadingInnermostURI && 1815 precursorOrLoadingInnermostURI->SchemeIs("https")) { 1816 return NS_ERROR_DOM_SECURITY_ERR; 1817 } 1818 } 1819 } 1820 1821 if (mIsMainThread) { 1822 mImplProxy = std::move(proxy); 1823 } 1824 return NS_OK; 1825 } 1826 1827 nsresult WebSocketImpl::AsyncOpen( 1828 nsIPrincipal* aPrincipal, uint64_t aInnerWindowID, 1829 nsITransportProvider* aTransportProvider, 1830 const nsACString& aNegotiatedExtensions, 1831 UniquePtr<SerializedStackHolder> aOriginStack) { 1832 MOZ_ASSERT(NS_IsMainThread(), "Not running on main thread"); 1833 MOZ_ASSERT_IF(!aTransportProvider, aNegotiatedExtensions.IsEmpty()); 1834 1835 nsCString webExposedOriginSerialization; 1836 nsresult rv = aPrincipal->GetWebExposedOriginSerialization( 1837 webExposedOriginSerialization); 1838 if (NS_FAILED(rv)) { 1839 webExposedOriginSerialization.AssignLiteral("null"); 1840 } 1841 1842 if (aTransportProvider) { 1843 rv = mChannel->SetServerParameters(aTransportProvider, 1844 aNegotiatedExtensions); 1845 NS_ENSURE_SUCCESS(rv, rv); 1846 } 1847 1848 ToLowerCase(webExposedOriginSerialization); 1849 1850 nsCOMPtr<nsIURI> uri; 1851 if (!aTransportProvider) { 1852 rv = NS_NewURI(getter_AddRefs(uri), mURI); 1853 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1854 } 1855 1856 rv = mChannel->AsyncOpenNative(uri, webExposedOriginSerialization, 1857 aPrincipal->OriginAttributesRef(), 1858 aInnerWindowID, this, nullptr); 1859 if (NS_WARN_IF(NS_FAILED(rv))) { 1860 return NS_ERROR_CONTENT_BLOCKED; 1861 } 1862 1863 NotifyNetworkMonitorAlternateStack(mChannel, std::move(aOriginStack)); 1864 1865 mInnerWindowID = aInnerWindowID; 1866 1867 return NS_OK; 1868 } 1869 1870 //----------------------------------------------------------------------------- 1871 // WebSocketImpl methods: 1872 //----------------------------------------------------------------------------- 1873 1874 class nsAutoCloseWS final { 1875 public: 1876 explicit nsAutoCloseWS(WebSocketImpl* aWebSocketImpl) 1877 : mWebSocketImpl(aWebSocketImpl) {} 1878 1879 ~nsAutoCloseWS() { 1880 if (!mWebSocketImpl->mChannel) { 1881 mWebSocketImpl->CloseConnection( 1882 mWebSocketImpl, nsIWebSocketChannel::CLOSE_INTERNAL_ERROR); 1883 } 1884 } 1885 1886 private: 1887 RefPtr<WebSocketImpl> mWebSocketImpl; 1888 }; 1889 1890 nsresult WebSocketImpl::InitializeConnection( 1891 nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings) { 1892 AssertIsOnMainThread(); 1893 MOZ_ASSERT(!mChannel, "mChannel should be null"); 1894 1895 nsCOMPtr<nsIWebSocketChannel> wsChannel; 1896 nsAutoCloseWS autoClose(this); 1897 nsresult rv; 1898 1899 if (mSecure) { 1900 wsChannel = 1901 do_CreateInstance("@mozilla.org/network/protocol;1?name=wss", &rv); 1902 } else { 1903 wsChannel = 1904 do_CreateInstance("@mozilla.org/network/protocol;1?name=ws", &rv); 1905 } 1906 NS_ENSURE_SUCCESS(rv, rv); 1907 1908 // add ourselves to the document's load group and 1909 // provide the http stack the loadgroup info too 1910 nsCOMPtr<nsILoadGroup> loadGroup; 1911 rv = GetLoadGroup(getter_AddRefs(loadGroup)); 1912 if (loadGroup) { 1913 rv = wsChannel->SetLoadGroup(loadGroup); 1914 NS_ENSURE_SUCCESS(rv, rv); 1915 rv = loadGroup->AddRequest(this, nullptr); 1916 NS_ENSURE_SUCCESS(rv, rv); 1917 1918 mWeakLoadGroup = do_GetWeakReference(loadGroup); 1919 } 1920 1921 // manually adding loadinfo to the channel since it 1922 // was not set during channel creation. 1923 nsCOMPtr<Document> doc(mOriginDocument); 1924 1925 // mOriginDocument has to be release on main-thread because WeakReferences 1926 // are not thread-safe. 1927 mOriginDocument = nullptr; 1928 1929 // The TriggeringPrincipal for websockets must always be a script. 1930 // Let's make sure that the doc's principal (if a doc exists) 1931 // and aPrincipal are same origin. 1932 MOZ_ASSERT(!doc || doc->NodePrincipal()->Equals(aPrincipal)); 1933 1934 rv = wsChannel->InitLoadInfoNative( 1935 doc, doc ? doc->NodePrincipal() : aPrincipal, aPrincipal, 1936 aCookieJarSettings, 1937 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, 1938 nsIContentPolicy::TYPE_WEBSOCKET, 0); 1939 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1940 1941 if (!mRequestedProtocolList.IsEmpty()) { 1942 rv = wsChannel->SetProtocol(mRequestedProtocolList); 1943 NS_ENSURE_SUCCESS(rv, rv); 1944 } 1945 1946 nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(wsChannel); 1947 NS_ENSURE_TRUE(rr, NS_ERROR_FAILURE); 1948 1949 rv = rr->RetargetDeliveryTo(this); 1950 NS_ENSURE_SUCCESS(rv, rv); 1951 1952 mChannel = wsChannel; 1953 1954 if (mIsMainThread) { 1955 MOZ_ASSERT(mImplProxy); 1956 mService->AssociateWebSocketImplWithSerialID(mImplProxy, 1957 mChannel->Serial()); 1958 } 1959 1960 return NS_OK; 1961 } 1962 1963 void WebSocketImpl::DispatchConnectionCloseEvents( 1964 const RefPtr<WebSocketImpl>& aProofOfRef) { 1965 AssertIsOnTargetThread(); 1966 1967 if (mDisconnectingOrDisconnected) { 1968 return; 1969 } 1970 1971 mWebSocket->SetReadyState(WebSocket::CLOSED); 1972 1973 // Let's keep the object alive because the webSocket can be CCed in the 1974 // onerror or in the onclose callback 1975 RefPtr<WebSocket> webSocket = mWebSocket; 1976 1977 // Call 'onerror' if needed 1978 if (mFailed) { 1979 nsresult rv = webSocket->CreateAndDispatchSimpleEvent(ERROR_EVENT_STRING); 1980 if (NS_FAILED(rv)) { 1981 NS_WARNING("Failed to dispatch the error event"); 1982 } 1983 } 1984 1985 nsresult rv = webSocket->CreateAndDispatchCloseEvent( 1986 mCloseEventWasClean, mCloseEventCode, mCloseEventReason); 1987 if (NS_FAILED(rv)) { 1988 NS_WARNING("Failed to dispatch the close event"); 1989 } 1990 1991 webSocket->UpdateMustKeepAlive(); 1992 Disconnect(aProofOfRef); 1993 } 1994 1995 nsresult WebSocket::CreateAndDispatchSimpleEvent(const nsAString& aName) { 1996 MOZ_ASSERT(mImpl); 1997 AssertIsOnTargetThread(); 1998 1999 nsresult rv = CheckCurrentGlobalCorrectness(); 2000 if (NS_FAILED(rv)) { 2001 return NS_OK; 2002 } 2003 2004 RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); 2005 2006 // it doesn't bubble, and it isn't cancelable 2007 event->InitEvent(aName, false, false); 2008 event->SetTrusted(true); 2009 2010 ErrorResult err; 2011 DispatchEvent(*event, err); 2012 return err.StealNSResult(); 2013 } 2014 2015 nsresult WebSocket::CreateAndDispatchMessageEvent(const nsACString& aData, 2016 bool aIsBinary) { 2017 MOZ_ASSERT(mImpl); 2018 AssertIsOnTargetThread(); 2019 2020 AutoJSAPI jsapi; 2021 if (NS_WARN_IF(!jsapi.Init(GetOwnerGlobal()))) { 2022 return NS_ERROR_FAILURE; 2023 } 2024 2025 JSContext* cx = jsapi.cx(); 2026 2027 nsresult rv = CheckCurrentGlobalCorrectness(); 2028 if (NS_FAILED(rv)) { 2029 return NS_OK; 2030 } 2031 2032 uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING; 2033 2034 // Create appropriate JS object for message 2035 JS::Rooted<JS::Value> jsData(cx); 2036 if (aIsBinary) { 2037 if (mBinaryType == dom::BinaryType::Blob) { 2038 messageType = nsIWebSocketEventListener::TYPE_BLOB; 2039 2040 RefPtr<Blob> blob = 2041 Blob::CreateStringBlob(GetOwnerGlobal(), aData, u""_ns); 2042 if (NS_WARN_IF(!blob)) { 2043 return NS_ERROR_FAILURE; 2044 } 2045 2046 if (!ToJSValue(cx, blob, &jsData)) { 2047 return NS_ERROR_FAILURE; 2048 } 2049 2050 } else if (mBinaryType == dom::BinaryType::Arraybuffer) { 2051 messageType = nsIWebSocketEventListener::TYPE_ARRAYBUFFER; 2052 2053 ErrorResult rv; 2054 JS::Rooted<JSObject*> arrayBuf(cx, ArrayBuffer::Create(cx, aData, rv)); 2055 RETURN_NSRESULT_ON_FAILURE(rv); 2056 jsData.setObject(*arrayBuf); 2057 } else { 2058 MOZ_CRASH("Unknown binary type!"); 2059 return NS_ERROR_UNEXPECTED; 2060 } 2061 } else { 2062 // JS string 2063 nsAutoString utf16Data; 2064 if (!AppendUTF8toUTF16(aData, utf16Data, mozilla::fallible)) { 2065 return NS_ERROR_OUT_OF_MEMORY; 2066 } 2067 JSString* jsString; 2068 jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length()); 2069 NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE); 2070 2071 jsData.setString(jsString); 2072 } 2073 2074 mImpl->mService->WebSocketMessageAvailable( 2075 mImpl->mChannel->Serial(), mImpl->mInnerWindowID, aData, messageType); 2076 2077 // create an event that uses the MessageEvent interface, 2078 // which does not bubble, is not cancelable, and has no default action 2079 2080 RefPtr<MessageEvent> event = new MessageEvent(this, nullptr, nullptr); 2081 2082 event->InitMessageEvent(nullptr, MESSAGE_EVENT_STRING, CanBubble::eNo, 2083 Cancelable::eNo, jsData, mImpl->mUTF16Origin, u""_ns, 2084 nullptr, Sequence<OwningNonNull<MessagePort>>()); 2085 event->SetTrusted(true); 2086 2087 ErrorResult err; 2088 DispatchEvent(*event, err); 2089 return err.StealNSResult(); 2090 } 2091 2092 nsresult WebSocket::CreateAndDispatchCloseEvent(bool aWasClean, uint16_t aCode, 2093 const nsAString& aReason) { 2094 AssertIsOnTargetThread(); 2095 2096 // This method is called by a runnable and it can happen that, in the 2097 // meantime, GC unlinked this object, so mImpl could be null. 2098 if (mImpl && mImpl->mChannel) { 2099 mImpl->mService->WebSocketClosed(mImpl->mChannel->Serial(), 2100 mImpl->mInnerWindowID, aWasClean, aCode, 2101 aReason); 2102 } 2103 2104 nsresult rv = CheckCurrentGlobalCorrectness(); 2105 if (NS_FAILED(rv)) { 2106 return NS_OK; 2107 } 2108 2109 CloseEventInit init; 2110 init.mBubbles = false; 2111 init.mCancelable = false; 2112 init.mWasClean = aWasClean; 2113 init.mCode = aCode; 2114 init.mReason = aReason; 2115 2116 RefPtr<CloseEvent> event = 2117 CloseEvent::Constructor(this, CLOSE_EVENT_STRING, init); 2118 event->SetTrusted(true); 2119 2120 ErrorResult err; 2121 DispatchEvent(*event, err); 2122 return err.StealNSResult(); 2123 } 2124 2125 nsresult WebSocketImpl::ParseURL(const nsAString& aURL, nsIURI* aBaseURI) { 2126 AssertIsOnMainThread(); 2127 NS_ENSURE_TRUE(!aURL.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR); 2128 2129 if (mIsServerSide) { 2130 mWebSocket->mURI = aURL; 2131 CopyUTF16toUTF8(mWebSocket->mURI, mURI); 2132 2133 return NS_OK; 2134 } 2135 2136 nsCOMPtr<nsIURI> uri; 2137 nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, aBaseURI); 2138 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); 2139 2140 nsCOMPtr<nsIURL> parsedURL = do_QueryInterface(uri, &rv); 2141 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); 2142 2143 nsAutoCString scheme; 2144 rv = parsedURL->GetScheme(scheme); 2145 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !scheme.IsEmpty(), 2146 NS_ERROR_DOM_SYNTAX_ERR); 2147 2148 // If |urlRecord|'s [=url/scheme=] is "`http`", then set |urlRecord|'s 2149 // [=url/scheme=] to "`ws`". Otherwise, if |urlRecord|'s [=url/scheme=] is 2150 // "`https`", set |urlRecord|'s [=url/scheme=] to "`wss`". 2151 // https://websockets.spec.whatwg.org/#dom-websocket-websocket 2152 2153 if (scheme == "http" || scheme == "https") { 2154 scheme = scheme == "https" ? "wss"_ns : "ws"_ns; 2155 2156 NS_MutateURI mutator(parsedURL); 2157 mutator.SetScheme(scheme); 2158 rv = mutator.Finalize(parsedURL); 2159 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); 2160 } 2161 2162 bool hasRef; 2163 rv = parsedURL->GetHasRef(&hasRef); 2164 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !hasRef, NS_ERROR_DOM_SYNTAX_ERR); 2165 2166 nsAutoCString host; 2167 rv = parsedURL->GetAsciiHost(host); 2168 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && !host.IsEmpty(), NS_ERROR_DOM_SYNTAX_ERR); 2169 2170 int32_t port; 2171 rv = parsedURL->GetPort(&port); 2172 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); 2173 2174 nsAutoCString filePath; 2175 rv = parsedURL->GetFilePath(filePath); 2176 if (filePath.IsEmpty()) { 2177 filePath.Assign('/'); 2178 } 2179 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); 2180 2181 nsAutoCString query; 2182 rv = parsedURL->GetQuery(query); 2183 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); 2184 2185 if (scheme.LowerCaseEqualsLiteral("ws")) { 2186 mSecure = false; 2187 mPort = (port == -1) ? DEFAULT_WS_SCHEME_PORT : port; 2188 } else if (scheme.LowerCaseEqualsLiteral("wss")) { 2189 mSecure = true; 2190 mPort = (port == -1) ? DEFAULT_WSS_SCHEME_PORT : port; 2191 } else { 2192 return NS_ERROR_DOM_SYNTAX_ERR; 2193 } 2194 2195 rv = 2196 nsContentUtils::GetWebExposedOriginSerialization(parsedURL, mUTF16Origin); 2197 NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_SYNTAX_ERR); 2198 2199 mAsciiHost = host; 2200 ToLowerCase(mAsciiHost); 2201 2202 mResource = filePath; 2203 if (!query.IsEmpty()) { 2204 mResource.Append('?'); 2205 mResource.Append(query); 2206 } 2207 uint32_t length = mResource.Length(); 2208 uint32_t i; 2209 for (i = 0; i < length; ++i) { 2210 if (mResource[i] < static_cast<char16_t>(0x0021) || 2211 mResource[i] > static_cast<char16_t>(0x007E)) { 2212 return NS_ERROR_DOM_SYNTAX_ERR; 2213 } 2214 } 2215 2216 rv = parsedURL->GetSpec(mURI); 2217 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2218 2219 CopyUTF8toUTF16(mURI, mWebSocket->mURI); 2220 return NS_OK; 2221 } 2222 2223 //----------------------------------------------------------------------------- 2224 // Methods that keep alive the WebSocket object when: 2225 // 1. the object has registered event listeners that can be triggered 2226 // ("strong event listeners"); 2227 // 2. there are outgoing not sent messages. 2228 //----------------------------------------------------------------------------- 2229 2230 void WebSocket::UpdateMustKeepAlive() { 2231 // Here we could not have mImpl. 2232 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread); 2233 2234 if (!mCheckMustKeepAlive || !mImpl) { 2235 return; 2236 } 2237 2238 bool shouldKeepAlive = false; 2239 uint16_t readyState = ReadyState(); 2240 2241 if (mListenerManager) { 2242 switch (readyState) { 2243 case CONNECTING: { 2244 if (mListenerManager->HasListenersFor(OPEN_EVENT_STRING) || 2245 mListenerManager->HasListenersFor(MESSAGE_EVENT_STRING) || 2246 mListenerManager->HasListenersFor(ERROR_EVENT_STRING) || 2247 mListenerManager->HasListenersFor(CLOSE_EVENT_STRING)) { 2248 shouldKeepAlive = true; 2249 } 2250 } break; 2251 2252 case OPEN: 2253 case CLOSING: { 2254 if (mListenerManager->HasListenersFor(MESSAGE_EVENT_STRING) || 2255 mListenerManager->HasListenersFor(ERROR_EVENT_STRING) || 2256 mListenerManager->HasListenersFor(CLOSE_EVENT_STRING) || 2257 mOutgoingBufferedAmount.value() != 0) { 2258 shouldKeepAlive = true; 2259 } 2260 } break; 2261 2262 case CLOSED: { 2263 shouldKeepAlive = false; 2264 } 2265 } 2266 } 2267 2268 if (mKeepingAlive && !shouldKeepAlive) { 2269 mKeepingAlive = false; 2270 mImpl->ReleaseObject(); 2271 // Note that this could be made 'alive' again if another listener is 2272 // added. 2273 } else if (!mKeepingAlive && shouldKeepAlive) { 2274 mKeepingAlive = true; 2275 mImpl->AddRefObject(); 2276 } 2277 } 2278 2279 void WebSocket::DontKeepAliveAnyMore() { 2280 // Here we could not have mImpl. 2281 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread); 2282 2283 if (mKeepingAlive) { 2284 MOZ_ASSERT(mImpl); 2285 2286 mKeepingAlive = false; 2287 mImpl->ReleaseObject(); 2288 } 2289 2290 mCheckMustKeepAlive = false; 2291 } 2292 2293 void WebSocketImpl::AddRefObject() { 2294 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread); 2295 AddRef(); 2296 } 2297 2298 void WebSocketImpl::ReleaseObject() { 2299 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread); 2300 Release(); 2301 } 2302 2303 bool WebSocketImpl::RegisterWorkerRef(WorkerPrivate* aWorkerPrivate) { 2304 MOZ_ASSERT(aWorkerPrivate); 2305 2306 RefPtr<WebSocketImpl> self(this); 2307 2308 // In workers we have to keep the worker alive using a strong reference in 2309 // order to dispatch messages correctly. 2310 RefPtr<StrongWorkerRef> workerRef = 2311 StrongWorkerRef::Create(aWorkerPrivate, "WebSocketImpl", [self]() { 2312 { 2313 MutexAutoLock lock(self->mMutex); 2314 self->mWorkerShuttingDown = true; 2315 } 2316 2317 self->CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY, 2318 ""_ns); 2319 }); 2320 if (NS_WARN_IF(!workerRef)) { 2321 return false; 2322 } 2323 2324 mWorkerRef = new ThreadSafeWorkerRef(workerRef); 2325 MOZ_ASSERT(mWorkerRef); 2326 2327 return true; 2328 } 2329 2330 void WebSocketImpl::UnregisterWorkerRef() { 2331 MOZ_ASSERT(mDisconnectingOrDisconnected); 2332 MOZ_ASSERT(mWorkerRef); 2333 mWorkerRef->Private()->AssertIsOnWorkerThread(); 2334 2335 { 2336 MutexAutoLock lock(mMutex); 2337 mWorkerShuttingDown = true; 2338 } 2339 2340 // The DTOR of this StrongWorkerRef will release the worker for us. 2341 mWorkerRef = nullptr; 2342 } 2343 2344 nsresult WebSocketImpl::UpdateURI() { 2345 AssertIsOnTargetThread(); 2346 2347 // Check for Redirections 2348 RefPtr<BaseWebSocketChannel> channel; 2349 channel = static_cast<BaseWebSocketChannel*>(mChannel.get()); 2350 MOZ_ASSERT(channel); 2351 2352 channel->GetEffectiveURL(mWebSocket->mEffectiveURL); 2353 mSecure = channel->IsEncrypted(); 2354 2355 return NS_OK; 2356 } 2357 2358 void WebSocket::EventListenerAdded(nsAtom* aType) { 2359 AssertIsOnTargetThread(); 2360 UpdateMustKeepAlive(); 2361 } 2362 2363 void WebSocket::EventListenerRemoved(nsAtom* aType) { 2364 AssertIsOnTargetThread(); 2365 UpdateMustKeepAlive(); 2366 } 2367 2368 //----------------------------------------------------------------------------- 2369 // WebSocket - methods 2370 //----------------------------------------------------------------------------- 2371 2372 // webIDL: readonly attribute unsigned short readyState; 2373 uint16_t WebSocket::ReadyState() { 2374 MutexAutoLock lock(mMutex); 2375 return mReadyState; 2376 } 2377 2378 void WebSocket::SetReadyState(uint16_t aReadyState) { 2379 MutexAutoLock lock(mMutex); 2380 mReadyState = aReadyState; 2381 } 2382 2383 // webIDL: readonly attribute unsigned long long bufferedAmount; 2384 uint64_t WebSocket::BufferedAmount() const { 2385 AssertIsOnTargetThread(); 2386 MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid()); 2387 return mOutgoingBufferedAmount.value(); 2388 } 2389 2390 // webIDL: attribute BinaryType binaryType; 2391 dom::BinaryType WebSocket::BinaryType() const { 2392 AssertIsOnTargetThread(); 2393 return mBinaryType; 2394 } 2395 2396 // webIDL: attribute BinaryType binaryType; 2397 void WebSocket::SetBinaryType(dom::BinaryType aData) { 2398 AssertIsOnTargetThread(); 2399 mBinaryType = aData; 2400 } 2401 2402 // webIDL: readonly attribute DOMString url 2403 void WebSocket::GetUrl(nsAString& aURL) { 2404 AssertIsOnTargetThread(); 2405 2406 if (mEffectiveURL.IsEmpty()) { 2407 aURL = mURI; 2408 } else { 2409 aURL = mEffectiveURL; 2410 } 2411 } 2412 2413 // webIDL: readonly attribute DOMString extensions; 2414 void WebSocket::GetExtensions(nsAString& aExtensions) { 2415 AssertIsOnTargetThread(); 2416 CopyUTF8toUTF16(mEstablishedExtensions, aExtensions); 2417 } 2418 2419 // webIDL: readonly attribute DOMString protocol; 2420 void WebSocket::GetProtocol(nsAString& aProtocol) { 2421 AssertIsOnTargetThread(); 2422 CopyUTF8toUTF16(mEstablishedProtocol, aProtocol); 2423 } 2424 2425 // webIDL: void send(DOMString data); 2426 void WebSocket::Send(const nsAString& aData, ErrorResult& aRv) { 2427 AssertIsOnTargetThread(); 2428 2429 nsAutoCString msgString; 2430 if (!AppendUTF16toUTF8(aData, msgString, mozilla::fallible_t())) { 2431 aRv.Throw(NS_ERROR_FILE_TOO_BIG); 2432 return; 2433 } 2434 Send(nullptr, msgString, msgString.Length(), false, aRv); 2435 } 2436 2437 void WebSocket::Send(Blob& aData, ErrorResult& aRv) { 2438 AssertIsOnTargetThread(); 2439 2440 nsCOMPtr<nsIInputStream> msgStream; 2441 aData.CreateInputStream(getter_AddRefs(msgStream), aRv); 2442 if (NS_WARN_IF(aRv.Failed())) { 2443 return; 2444 } 2445 2446 uint64_t msgLength = aData.GetSize(aRv); 2447 if (NS_WARN_IF(aRv.Failed())) { 2448 return; 2449 } 2450 2451 if (msgLength > UINT32_MAX) { 2452 aRv.Throw(NS_ERROR_FILE_TOO_BIG); 2453 return; 2454 } 2455 2456 Send(msgStream, ""_ns, msgLength, true, aRv); 2457 } 2458 2459 void WebSocket::Send(const ArrayBuffer& aData, ErrorResult& aRv) { 2460 AssertIsOnTargetThread(); 2461 2462 static_assert( 2463 sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1, 2464 "byte-sized data required"); 2465 2466 nsCString msgString; 2467 if (!aData.AppendDataTo(msgString)) { 2468 aRv.Throw(NS_ERROR_FILE_TOO_BIG); 2469 return; 2470 } 2471 Send(nullptr, msgString, msgString.Length(), true, aRv); 2472 } 2473 2474 void WebSocket::Send(const ArrayBufferView& aData, ErrorResult& aRv) { 2475 AssertIsOnTargetThread(); 2476 2477 static_assert( 2478 sizeof(std::remove_reference_t<decltype(aData)>::element_type) == 1, 2479 "byte-sized data required"); 2480 2481 nsCString msgString; 2482 if (!aData.AppendDataTo(msgString)) { 2483 aRv.Throw(NS_ERROR_FILE_TOO_BIG); 2484 return; 2485 } 2486 Send(nullptr, msgString, msgString.Length(), true, aRv); 2487 } 2488 2489 void WebSocket::Send(nsIInputStream* aMsgStream, const nsACString& aMsgString, 2490 uint32_t aMsgLength, bool aIsBinary, ErrorResult& aRv) { 2491 AssertIsOnTargetThread(); 2492 2493 int64_t readyState = ReadyState(); 2494 if (readyState == CONNECTING) { 2495 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); 2496 return; 2497 } 2498 2499 CheckedUint64 outgoingBufferedAmount = mOutgoingBufferedAmount; 2500 outgoingBufferedAmount += aMsgLength; 2501 if (!outgoingBufferedAmount.isValid()) { 2502 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 2503 return; 2504 } 2505 2506 // Always increment outgoing buffer len, even if closed 2507 mOutgoingBufferedAmount = outgoingBufferedAmount; 2508 MOZ_RELEASE_ASSERT(mOutgoingBufferedAmount.isValid()); 2509 2510 if (readyState == CLOSING || readyState == CLOSED) { 2511 return; 2512 } 2513 2514 // We must have mImpl when connected. 2515 MOZ_ASSERT(mImpl); 2516 MOZ_ASSERT(readyState == OPEN, "Unknown state in WebSocket::Send"); 2517 2518 nsresult rv; 2519 if (aMsgStream) { 2520 rv = mImpl->mChannel->SendBinaryStream(aMsgStream, aMsgLength); 2521 } else { 2522 if (aIsBinary) { 2523 rv = mImpl->mChannel->SendBinaryMsg(aMsgString); 2524 } else { 2525 rv = mImpl->mChannel->SendMsg(aMsgString); 2526 } 2527 } 2528 2529 if (NS_FAILED(rv)) { 2530 aRv.Throw(rv); 2531 return; 2532 } 2533 2534 UpdateMustKeepAlive(); 2535 } 2536 2537 // webIDL: void close(optional unsigned short code, optional DOMString reason): 2538 void WebSocket::Close(const Optional<uint16_t>& aCode, 2539 const Optional<nsAString>& aReason, ErrorResult& aRv) { 2540 MOZ_RELEASE_ASSERT(NS_IsMainThread() == mIsMainThread); 2541 2542 // the reason code is optional, but if provided it must be in a specific range 2543 uint16_t closeCode = 0; 2544 if (aCode.WasPassed()) { 2545 if (aCode.Value() != 1000 && 2546 (aCode.Value() < 3000 || aCode.Value() > 4999)) { 2547 aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR); 2548 return; 2549 } 2550 closeCode = aCode.Value(); 2551 } 2552 2553 nsCString closeReason; 2554 if (aReason.WasPassed()) { 2555 CopyUTF16toUTF8(aReason.Value(), closeReason); 2556 2557 // The API requires the UTF-8 string to be 123 or less bytes 2558 if (closeReason.Length() > 123) { 2559 aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); 2560 return; 2561 } 2562 } 2563 2564 int64_t readyState = ReadyState(); 2565 if (readyState == CLOSING || readyState == CLOSED) { 2566 return; 2567 } 2568 2569 // If we don't have mImpl, we are in a shutting down worker where we are still 2570 // in CONNECTING state, but already disconnected internally. 2571 if (!mImpl) { 2572 MOZ_ASSERT(readyState == CONNECTING); 2573 SetReadyState(CLOSING); 2574 return; 2575 } 2576 2577 // These could cause the mImpl to be released (and so this to be 2578 // released); make sure it stays valid through the call 2579 RefPtr<WebSocketImpl> pin(mImpl); 2580 2581 if (readyState == CONNECTING) { 2582 pin->FailConnection(pin, closeCode, closeReason); 2583 return; 2584 } 2585 2586 MOZ_ASSERT(readyState == OPEN); 2587 pin->CloseConnection(pin, closeCode, closeReason); 2588 } 2589 2590 //----------------------------------------------------------------------------- 2591 // WebSocketImpl::nsIRequest 2592 //----------------------------------------------------------------------------- 2593 2594 NS_IMETHODIMP 2595 WebSocketImpl::GetName(nsACString& aName) { 2596 AssertIsOnMainThread(); 2597 2598 CopyUTF16toUTF8(mWebSocket->mURI, aName); 2599 return NS_OK; 2600 } 2601 2602 NS_IMETHODIMP 2603 WebSocketImpl::IsPending(bool* aValue) { 2604 AssertIsOnTargetThread(); 2605 2606 int64_t readyState = mWebSocket->ReadyState(); 2607 *aValue = (readyState != WebSocket::CLOSED); 2608 return NS_OK; 2609 } 2610 2611 NS_IMETHODIMP 2612 WebSocketImpl::GetStatus(nsresult* aStatus) { 2613 AssertIsOnTargetThread(); 2614 2615 *aStatus = NS_OK; 2616 return NS_OK; 2617 } 2618 2619 namespace { 2620 2621 class CancelRunnable final : public MainThreadWorkerRunnable { 2622 public: 2623 CancelRunnable(ThreadSafeWorkerRef* aWorkerRef, WebSocketImpl* aImpl) 2624 : MainThreadWorkerRunnable("CancelRunnable"), mImpl(aImpl) {} 2625 2626 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { 2627 aWorkerPrivate->AssertIsOnWorkerThread(); 2628 return !NS_FAILED(mImpl->CancelInternal()); 2629 } 2630 2631 private: 2632 RefPtr<WebSocketImpl> mImpl; 2633 }; 2634 2635 } // namespace 2636 2637 NS_IMETHODIMP WebSocketImpl::SetCanceledReason(const nsACString& aReason) { 2638 return SetCanceledReasonImpl(aReason); 2639 } 2640 2641 NS_IMETHODIMP WebSocketImpl::GetCanceledReason(nsACString& aReason) { 2642 return GetCanceledReasonImpl(aReason); 2643 } 2644 2645 NS_IMETHODIMP WebSocketImpl::CancelWithReason(nsresult aStatus, 2646 const nsACString& aReason) { 2647 return CancelWithReasonImpl(aStatus, aReason); 2648 } 2649 2650 // Window closed, stop/reload button pressed, user navigated away from page, 2651 // etc. 2652 NS_IMETHODIMP 2653 WebSocketImpl::Cancel(nsresult aStatus) { 2654 AssertIsOnMainThread(); 2655 2656 if (!mIsMainThread) { 2657 MOZ_ASSERT(mWorkerRef); 2658 RefPtr<CancelRunnable> runnable = new CancelRunnable(mWorkerRef, this); 2659 if (!runnable->Dispatch(mWorkerRef->Private())) { 2660 return NS_ERROR_FAILURE; 2661 } 2662 2663 return NS_OK; 2664 } 2665 2666 return CancelInternal(); 2667 } 2668 2669 nsresult WebSocketImpl::CancelInternal() { 2670 AssertIsOnTargetThread(); 2671 2672 // If CancelInternal is called by a runnable, we may already be disconnected 2673 // by the time it runs. 2674 if (mDisconnectingOrDisconnected) { 2675 return NS_OK; 2676 } 2677 2678 int64_t readyState = mWebSocket->ReadyState(); 2679 if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) { 2680 return NS_OK; 2681 } 2682 2683 RefPtr<WebSocketImpl> self(this); 2684 return CloseConnection(self, nsIWebSocketChannel::CLOSE_GOING_AWAY); 2685 } 2686 2687 NS_IMETHODIMP 2688 WebSocketImpl::Suspend() { 2689 AssertIsOnMainThread(); 2690 return NS_ERROR_NOT_IMPLEMENTED; 2691 } 2692 2693 NS_IMETHODIMP 2694 WebSocketImpl::Resume() { 2695 AssertIsOnMainThread(); 2696 return NS_ERROR_NOT_IMPLEMENTED; 2697 } 2698 2699 NS_IMETHODIMP 2700 WebSocketImpl::GetLoadGroup(nsILoadGroup** aLoadGroup) { 2701 AssertIsOnMainThread(); 2702 2703 *aLoadGroup = nullptr; 2704 2705 if (mIsMainThread) { 2706 nsCOMPtr<Document> doc = mWebSocket->GetDocumentIfCurrent(); 2707 if (doc) { 2708 *aLoadGroup = doc->GetDocumentLoadGroup().take(); 2709 } 2710 2711 return NS_OK; 2712 } 2713 2714 MOZ_ASSERT(mWorkerRef); 2715 2716 nsPIDOMWindowInner* window = mWorkerRef->Private()->GetAncestorWindow(); 2717 if (!window) { 2718 return NS_OK; 2719 } 2720 2721 Document* doc = window->GetExtantDoc(); 2722 if (doc) { 2723 *aLoadGroup = doc->GetDocumentLoadGroup().take(); 2724 } 2725 2726 return NS_OK; 2727 } 2728 2729 NS_IMETHODIMP 2730 WebSocketImpl::SetLoadGroup(nsILoadGroup* aLoadGroup) { 2731 AssertIsOnMainThread(); 2732 return NS_ERROR_UNEXPECTED; 2733 } 2734 2735 NS_IMETHODIMP 2736 WebSocketImpl::GetLoadFlags(nsLoadFlags* aLoadFlags) { 2737 AssertIsOnMainThread(); 2738 2739 *aLoadFlags = nsIRequest::LOAD_BACKGROUND; 2740 return NS_OK; 2741 } 2742 2743 NS_IMETHODIMP 2744 WebSocketImpl::SetLoadFlags(nsLoadFlags aLoadFlags) { 2745 AssertIsOnMainThread(); 2746 2747 // we won't change the load flags at all. 2748 return NS_OK; 2749 } 2750 2751 NS_IMETHODIMP 2752 WebSocketImpl::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { 2753 return GetTRRModeImpl(aTRRMode); 2754 } 2755 2756 NS_IMETHODIMP 2757 WebSocketImpl::SetTRRMode(nsIRequest::TRRMode aTRRMode) { 2758 return SetTRRModeImpl(aTRRMode); 2759 } 2760 2761 namespace { 2762 2763 class WorkerRunnableDispatcher final : public WorkerThreadRunnable { 2764 RefPtr<WebSocketImpl> mWebSocketImpl; 2765 2766 public: 2767 WorkerRunnableDispatcher(WebSocketImpl* aImpl, 2768 ThreadSafeWorkerRef* aWorkerRef, 2769 already_AddRefed<nsIRunnable> aEvent) 2770 : WorkerThreadRunnable("WorkerRunnableDispatcher"), 2771 mWebSocketImpl(aImpl), 2772 mEvent(std::move(aEvent)) {} 2773 2774 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { 2775 aWorkerPrivate->AssertIsOnWorkerThread(); 2776 2777 // No messages when disconnected. 2778 if (mWebSocketImpl->mDisconnectingOrDisconnected) { 2779 NS_WARNING("Dispatching a WebSocket event after the disconnection!"); 2780 return true; 2781 } 2782 2783 return !NS_FAILED(mEvent->Run()); 2784 } 2785 2786 void PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, 2787 bool aRunResult) override {} 2788 2789 bool PreDispatch(WorkerPrivate* aWorkerPrivate) override { 2790 // We don't call WorkerRunnable::PreDispatch because it would assert the 2791 // wrong thing about which thread we're on. We're on whichever thread the 2792 // channel implementation is running on (probably the main thread or socket 2793 // transport thread). 2794 return true; 2795 } 2796 2797 void PostDispatch(WorkerPrivate* aWorkerPrivate, 2798 bool aDispatchResult) override { 2799 // We don't call WorkerRunnable::PreDispatch because it would assert the 2800 // wrong thing about which thread we're on. We're on whichever thread the 2801 // channel implementation is running on (probably the main thread or socket 2802 // transport thread). 2803 } 2804 2805 private: 2806 nsCOMPtr<nsIRunnable> mEvent; 2807 }; 2808 2809 } // namespace 2810 2811 NS_IMETHODIMP 2812 WebSocketImpl::DispatchFromScript(nsIRunnable* aEvent, DispatchFlags aFlags) { 2813 return Dispatch(do_AddRef(aEvent), aFlags); 2814 } 2815 2816 NS_IMETHODIMP 2817 WebSocketImpl::Dispatch(already_AddRefed<nsIRunnable> aEvent, 2818 DispatchFlags aFlags) { 2819 // FIXME: This dispatch implementation is inconsistent about whether or not 2820 // failure causes a leak when the `NS_DISPATCH_FALLIBLE` flag is not set. 2821 nsCOMPtr<nsIRunnable> event_ref(aEvent); 2822 if (mIsMainThread) { 2823 nsISerialEventTarget* target = GetMainThreadSerialEventTarget(); 2824 NS_ENSURE_TRUE(target, NS_ERROR_FAILURE); 2825 return target->Dispatch(event_ref.forget(), aFlags); 2826 } 2827 2828 MutexAutoLock lock(mMutex); 2829 if (mWorkerShuttingDown) { 2830 return NS_OK; 2831 } 2832 2833 MOZ_DIAGNOSTIC_ASSERT(mWorkerRef); 2834 2835 // If the target is a worker, we have to use a custom WorkerRunnableDispatcher 2836 // runnable. 2837 RefPtr<WorkerRunnableDispatcher> event = 2838 new WorkerRunnableDispatcher(this, mWorkerRef, event_ref.forget()); 2839 2840 if (!event->Dispatch(mWorkerRef->Private())) { 2841 return NS_ERROR_FAILURE; 2842 } 2843 2844 return NS_OK; 2845 } 2846 2847 NS_IMETHODIMP 2848 WebSocketImpl::DelayedDispatch(already_AddRefed<nsIRunnable>, uint32_t) { 2849 return NS_ERROR_NOT_IMPLEMENTED; 2850 } 2851 2852 NS_IMETHODIMP 2853 WebSocketImpl::RegisterShutdownTask(nsITargetShutdownTask*) { 2854 return NS_ERROR_NOT_IMPLEMENTED; 2855 } 2856 2857 NS_IMETHODIMP 2858 WebSocketImpl::UnregisterShutdownTask(nsITargetShutdownTask*) { 2859 return NS_ERROR_NOT_IMPLEMENTED; 2860 } 2861 2862 NS_IMETHODIMP 2863 WebSocketImpl::IsOnCurrentThread(bool* aResult) { 2864 *aResult = IsTargetThread(); 2865 return NS_OK; 2866 } 2867 2868 NS_IMETHODIMP_(bool) 2869 WebSocketImpl::IsOnCurrentThreadInfallible() { return IsTargetThread(); } 2870 2871 bool WebSocketImpl::IsTargetThread() const { 2872 // FIXME: This should also check if we're on the worker thread. Code using 2873 // `IsOnCurrentThread` could easily misbehave here! 2874 return NS_IsMainThread() == mIsMainThread; 2875 } 2876 2877 void WebSocket::AssertIsOnTargetThread() const { 2878 MOZ_ASSERT(NS_IsMainThread() == mIsMainThread); 2879 } 2880 2881 nsresult WebSocketImpl::IsSecure(bool* aValue) { 2882 MOZ_ASSERT(NS_IsMainThread()); 2883 MOZ_ASSERT(mIsMainThread); 2884 2885 // Check the principal's uri to determine if we were loaded from https. 2886 nsCOMPtr<nsIGlobalObject> globalObject(GetEntryGlobal()); 2887 nsCOMPtr<nsIPrincipal> principal; 2888 2889 if (globalObject) { 2890 principal = globalObject->PrincipalOrNull(); 2891 } 2892 2893 nsCOMPtr<nsPIDOMWindowInner> innerWindow = do_QueryInterface(globalObject); 2894 if (!innerWindow) { 2895 // If we are in a XPConnect sandbox or in a JS component, 2896 // innerWindow will be null. There is nothing on top of this to be 2897 // considered. 2898 if (NS_WARN_IF(!principal)) { 2899 return NS_OK; 2900 } 2901 *aValue = principal->SchemeIs("https"); 2902 return NS_OK; 2903 } 2904 2905 RefPtr<WindowContext> windowContext = innerWindow->GetWindowContext(); 2906 if (NS_WARN_IF(!windowContext)) { 2907 return NS_ERROR_DOM_SECURITY_ERR; 2908 } 2909 2910 while (true) { 2911 if (windowContext->GetIsSecure()) { 2912 *aValue = true; 2913 return NS_OK; 2914 } 2915 2916 if (windowContext->IsTop()) { 2917 break; 2918 } else { 2919 // If we're not a top window get the parent window context instead. 2920 windowContext = windowContext->GetParentWindowContext(); 2921 } 2922 2923 if (NS_WARN_IF(!windowContext)) { 2924 return NS_ERROR_DOM_SECURITY_ERR; 2925 } 2926 } 2927 2928 *aValue = windowContext->GetIsSecure(); 2929 return NS_OK; 2930 } 2931 2932 } // namespace mozilla::dom