WebSocketChannel.cpp (139831B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=8 et 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 <algorithm> 8 9 #include "WebSocketChannel.h" 10 11 #include "WebSocketConnectionBase.h" 12 #include "WebSocketFrame.h" 13 #include "WebSocketLog.h" 14 #include "mozilla/Atomics.h" 15 #include "mozilla/Attributes.h" 16 #include "mozilla/Base64.h" 17 #include "mozilla/Components.h" 18 #include "mozilla/EndianUtils.h" 19 #include "mozilla/MathAlgorithms.h" 20 #include "mozilla/ScopeExit.h" 21 #include "mozilla/StaticMutex.h" 22 #include "mozilla/StaticPrefs_privacy.h" 23 #include "mozilla/glean/NetwerkProtocolWebsocketMetrics.h" 24 #include "mozilla/TimeStamp.h" 25 #include "mozilla/Utf8.h" 26 #include "mozilla/net/WebSocketEventService.h" 27 #include "nsCRT.h" 28 #include "nsCharSeparatedTokenizer.h" 29 #include "nsComponentManagerUtils.h" 30 #include "nsError.h" 31 #include "nsIAsyncVerifyRedirectCallback.h" 32 #include "nsICancelable.h" 33 #include "nsIChannel.h" 34 #include "nsIClassOfService.h" 35 #include "nsICryptoHash.h" 36 #include "nsIDNSRecord.h" 37 #include "nsIDNSService.h" 38 #include "nsIDashboardEventNotifier.h" 39 #include "nsIEventTarget.h" 40 #include "nsIHttpChannel.h" 41 #include "nsIIOService.h" 42 #include "nsINSSErrorsService.h" 43 #include "nsINetworkLinkService.h" 44 #include "nsINode.h" 45 #include "nsIObserverService.h" 46 #include "nsIPrefBranch.h" 47 #include "nsIProtocolHandler.h" 48 #include "nsIProtocolProxyService.h" 49 #include "nsIProxiedChannel.h" 50 #include "nsIProxyInfo.h" 51 #include "nsIRandomGenerator.h" 52 #include "nsIRunnable.h" 53 #include "nsISocketTransport.h" 54 #include "nsITLSSocketControl.h" 55 #include "nsITransportProvider.h" 56 #include "nsITransportSecurityInfo.h" 57 #include "nsIURI.h" 58 #include "nsIURIMutator.h" 59 #include "nsNetCID.h" 60 #include "nsNetUtil.h" 61 #include "nsProxyRelease.h" 62 #include "nsServiceManagerUtils.h" 63 #include "nsSocketTransportService2.h" 64 #include "nsStringStream.h" 65 #include "nsThreadUtils.h" 66 #include "plbase64.h" 67 #include "prmem.h" 68 #include "prnetdb.h" 69 #include "zlib.h" 70 71 // rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just 72 // dupe one constant we need from it 73 #define CLOSE_GOING_AWAY 1001 74 75 using namespace mozilla; 76 using namespace mozilla::net; 77 78 namespace mozilla::net { 79 80 NS_IMPL_ISUPPORTS(WebSocketChannel, nsIWebSocketChannel, nsIHttpUpgradeListener, 81 nsIRequestObserver, nsIStreamListener, nsIProtocolHandler, 82 nsIInputStreamCallback, nsIOutputStreamCallback, 83 nsITimerCallback, nsIDNSListener, nsIProtocolProxyCallback, 84 nsIInterfaceRequestor, nsIChannelEventSink, 85 nsIThreadRetargetableRequest, nsIObserver, nsINamed) 86 87 // We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire. 88 #define SEC_WEBSOCKET_VERSION "13" 89 90 /* 91 * About SSL unsigned certificates 92 * 93 * wss will not work to a host using an unsigned certificate unless there 94 * is already an exception (i.e. it cannot popup a dialog asking for 95 * a security exception). This is similar to how an inlined img will 96 * fail without a dialog if fails for the same reason. This should not 97 * be a problem in practice as it is expected the websocket javascript 98 * is served from the same host as the websocket server (or of course, 99 * a valid cert could just be provided). 100 * 101 */ 102 103 // some helper classes 104 105 //----------------------------------------------------------------------------- 106 // FailDelayManager 107 // 108 // Stores entries (searchable by {host, port}) of connections that have recently 109 // failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3 110 //----------------------------------------------------------------------------- 111 112 // Initial reconnect delay is randomly chosen between 200-400 ms. 113 // This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests. 114 const uint32_t kWSReconnectInitialBaseDelay = 200; 115 const uint32_t kWSReconnectInitialRandomDelay = 200; 116 117 // Base lifetime (in ms) of a FailDelay: kept longer if more failures occur 118 const uint32_t kWSReconnectBaseLifeTime = 60 * 1000; 119 // Maximum reconnect delay (in ms) 120 const uint32_t kWSReconnectMaxDelay = 60 * 1000; 121 122 // hold record of failed connections, and calculates needed delay for reconnects 123 // to same host/path/port. 124 class FailDelay { 125 public: 126 FailDelay(nsCString address, nsCString path, int32_t port) 127 : mAddress(std::move(address)), mPath(std::move(path)), mPort(port) { 128 mLastFailure = TimeStamp::Now(); 129 mNextDelay = kWSReconnectInitialBaseDelay + 130 (rand() % kWSReconnectInitialRandomDelay); 131 } 132 133 // Called to update settings when connection fails again. 134 void FailedAgain() { 135 mLastFailure = TimeStamp::Now(); 136 // We use a truncated exponential backoff as suggested by RFC 6455, 137 // but multiply by 1.5 instead of 2 to be more gradual. 138 mNextDelay = static_cast<uint32_t>( 139 std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5)); 140 LOG( 141 ("WebSocket: FailedAgain: host=%s, path=%s, port=%d: incremented delay " 142 "to " 143 "%" PRIu32, 144 mAddress.get(), mPath.get(), mPort, mNextDelay)); 145 } 146 147 // returns 0 if there is no need to delay (i.e. delay interval is over) 148 uint32_t RemainingDelay(TimeStamp rightNow) { 149 TimeDuration dur = rightNow - mLastFailure; 150 uint32_t sinceFail = (uint32_t)dur.ToMilliseconds(); 151 if (sinceFail > mNextDelay) return 0; 152 153 return mNextDelay - sinceFail; 154 } 155 156 bool IsExpired(TimeStamp rightNow) { 157 return (mLastFailure + TimeDuration::FromMilliseconds( 158 kWSReconnectBaseLifeTime + mNextDelay)) <= 159 rightNow; 160 } 161 162 nsCString mAddress; // IP address (or hostname if using proxy) 163 nsCString mPath; 164 int32_t mPort; 165 166 private: 167 TimeStamp mLastFailure; // Time of last failed attempt 168 // mLastFailure + mNextDelay is the soonest we'll allow a reconnect 169 uint32_t mNextDelay; // milliseconds 170 }; 171 172 class FailDelayManager { 173 public: 174 FailDelayManager() { 175 MOZ_COUNT_CTOR(FailDelayManager); 176 177 mDelaysDisabled = false; 178 179 nsCOMPtr<nsIPrefBranch> prefService; 180 prefService = mozilla::components::Preferences::Service(); 181 if (!prefService) { 182 return; 183 } 184 bool boolpref = true; 185 nsresult rv; 186 rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects", 187 &boolpref); 188 if (NS_SUCCEEDED(rv) && !boolpref) { 189 mDelaysDisabled = true; 190 } 191 } 192 193 ~FailDelayManager() { MOZ_COUNT_DTOR(FailDelayManager); } 194 195 void Add(nsCString& address, nsCString& path, int32_t port) { 196 if (mDelaysDisabled) return; 197 198 UniquePtr<FailDelay> record(new FailDelay(address, path, port)); 199 mEntries.AppendElement(std::move(record)); 200 } 201 202 // Element returned may not be valid after next main thread event: don't keep 203 // pointer to it around 204 FailDelay* Lookup(nsCString& address, nsCString& path, int32_t port, 205 uint32_t* outIndex = nullptr) { 206 if (mDelaysDisabled) return nullptr; 207 208 FailDelay* result = nullptr; 209 TimeStamp rightNow = TimeStamp::Now(); 210 211 // We also remove expired entries during search: iterate from end to make 212 // indexing simpler 213 for (int32_t i = mEntries.Length() - 1; i >= 0; --i) { 214 FailDelay* fail = mEntries[i].get(); 215 if (fail->mAddress.Equals(address) && fail->mPath.Equals(path) && 216 fail->mPort == port) { 217 if (outIndex) *outIndex = i; 218 result = fail; 219 // break here: removing more entries would mess up *outIndex. 220 // Any remaining expired entries will be deleted next time Lookup 221 // finds nothing, which is the most common case anyway. 222 break; 223 } 224 if (fail->IsExpired(rightNow)) { 225 mEntries.RemoveElementAt(i); 226 } 227 } 228 return result; 229 } 230 231 // returns true if channel connects immediately, or false if it's delayed 232 void DelayOrBegin(WebSocketChannel* ws) { 233 if (!mDelaysDisabled) { 234 uint32_t failIndex = 0; 235 FailDelay* fail = Lookup(ws->mAddress, ws->mPath, ws->mPort, &failIndex); 236 237 if (fail) { 238 TimeStamp rightNow = TimeStamp::Now(); 239 240 uint32_t remainingDelay = fail->RemainingDelay(rightNow); 241 if (remainingDelay) { 242 // reconnecting within delay interval: delay by remaining time 243 nsresult rv; 244 MutexAutoLock lock(ws->mMutex); 245 rv = NS_NewTimerWithCallback(getter_AddRefs(ws->mReconnectDelayTimer), 246 ws, remainingDelay, 247 nsITimer::TYPE_ONE_SHOT); 248 if (NS_SUCCEEDED(rv)) { 249 LOG( 250 ("WebSocket: delaying websocket [this=%p] by %lu ms, changing" 251 " state to CONNECTING_DELAYED", 252 ws, (unsigned long)remainingDelay)); 253 ws->mConnecting = CONNECTING_DELAYED; 254 return; 255 } 256 // if timer fails (which is very unlikely), drop down to BeginOpen 257 // call 258 } else if (fail->IsExpired(rightNow)) { 259 mEntries.RemoveElementAt(failIndex); 260 } 261 } 262 } 263 264 // Delays disabled, or no previous failure, or we're reconnecting after 265 // scheduled delay interval has passed: connect. 266 ws->BeginOpen(true); 267 } 268 269 // Remove() also deletes all expired entries as it iterates: better for 270 // battery life than using a periodic timer. 271 void Remove(nsCString& address, nsCString& path, int32_t port) { 272 TimeStamp rightNow = TimeStamp::Now(); 273 274 // iterate from end, to make deletion indexing easier 275 for (int32_t i = mEntries.Length() - 1; i >= 0; --i) { 276 FailDelay* entry = mEntries[i].get(); 277 if ((entry->mAddress.Equals(address) && entry->mPath.Equals(path) && 278 entry->mPort == port) || 279 entry->IsExpired(rightNow)) { 280 mEntries.RemoveElementAt(i); 281 } 282 } 283 } 284 285 private: 286 nsTArray<UniquePtr<FailDelay>> mEntries; 287 bool mDelaysDisabled; 288 }; 289 290 //----------------------------------------------------------------------------- 291 // nsWSAdmissionManager 292 // 293 // 1) Ensures that only one websocket at a time is CONNECTING to a given IP 294 // address (or hostname, if using proxy), per RFC 6455 Section 4.1. 295 // 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3 296 //----------------------------------------------------------------------------- 297 298 class nsWSAdmissionManager { 299 public: 300 static void Init() { 301 StaticMutexAutoLock lock(sLock); 302 if (!sManager) { 303 sManager = new nsWSAdmissionManager(); 304 } 305 } 306 307 static void Shutdown() { 308 StaticMutexAutoLock lock(sLock); 309 delete sManager; 310 sManager = nullptr; 311 } 312 313 // Determine if we will open connection immediately (returns true), or 314 // delay/queue the connection (returns false) 315 static void ConditionallyConnect(WebSocketChannel* ws) { 316 LOG(("Websocket: ConditionallyConnect: [this=%p]", ws)); 317 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 318 MOZ_ASSERT(ws->mConnecting == NOT_CONNECTING, "opening state"); 319 320 StaticMutexAutoLock lock(sLock); 321 if (!sManager) { 322 return; 323 } 324 325 // If there is already another WS channel connecting to this IP address, 326 // defer BeginOpen and mark as waiting in queue. 327 bool hostFound = (sManager->IndexOf(ws->mAddress, ws->mOriginSuffix) >= 0); 328 329 uint32_t failIndex = 0; 330 FailDelay* fail = sManager->mFailures.Lookup(ws->mAddress, ws->mPath, 331 ws->mPort, &failIndex); 332 bool existingFail = fail != nullptr; 333 334 // Always add ourselves to queue, even if we'll connect immediately 335 UniquePtr<nsOpenConn> newdata( 336 new nsOpenConn(ws->mAddress, ws->mOriginSuffix, existingFail, ws)); 337 338 // If a connection has not previously failed then prioritize it over 339 // connections that have 340 if (existingFail) { 341 sManager->mQueue.AppendElement(std::move(newdata)); 342 } else { 343 uint32_t insertionIndex = sManager->IndexOfFirstFailure(); 344 MOZ_ASSERT(insertionIndex <= sManager->mQueue.Length(), 345 "Insertion index outside bounds"); 346 sManager->mQueue.InsertElementAt(insertionIndex, std::move(newdata)); 347 } 348 349 if (hostFound) { 350 LOG( 351 ("Websocket: some other channel is connecting, changing state to " 352 "CONNECTING_QUEUED")); 353 ws->mConnecting = CONNECTING_QUEUED; 354 } else { 355 sManager->mFailures.DelayOrBegin(ws); 356 } 357 } 358 359 static void OnConnected(WebSocketChannel* aChannel) { 360 LOG(("Websocket: OnConnected: [this=%p]", aChannel)); 361 362 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 363 MOZ_ASSERT(aChannel->mConnecting == CONNECTING_IN_PROGRESS, 364 "Channel completed connect, but not connecting?"); 365 366 StaticMutexAutoLock lock(sLock); 367 if (!sManager) { 368 return; 369 } 370 371 LOG(("Websocket: changing state to NOT_CONNECTING")); 372 aChannel->mConnecting = NOT_CONNECTING; 373 374 // Remove from queue 375 sManager->RemoveFromQueue(aChannel); 376 377 // Connection succeeded, so stop keeping track of any previous failures 378 sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPath, 379 aChannel->mPort); 380 381 // Check for queued connections to same host. 382 // Note: still need to check for failures, since next websocket with same 383 // host may have different port 384 sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix); 385 } 386 387 // Called every time a websocket channel ends its session (including going 388 // away w/o ever successfully creating a connection) 389 static void OnStopSession(WebSocketChannel* aChannel, nsresult aReason) { 390 LOG(("Websocket: OnStopSession: [this=%p, reason=0x%08" PRIx32 "]", 391 aChannel, static_cast<uint32_t>(aReason))); 392 393 StaticMutexAutoLock lock(sLock); 394 if (!sManager) { 395 return; 396 } 397 398 if (NS_FAILED(aReason)) { 399 // Have we seen this failure before? 400 FailDelay* knownFailure = sManager->mFailures.Lookup( 401 aChannel->mAddress, aChannel->mPath, aChannel->mPort); 402 if (knownFailure) { 403 if (aReason == NS_ERROR_NOT_CONNECTED) { 404 // Don't count close() before connection as a network error 405 LOG( 406 ("Websocket close() before connection to %s, %s, %d completed" 407 " [this=%p]", 408 aChannel->mAddress.get(), aChannel->mPath.get(), 409 (int)aChannel->mPort, aChannel)); 410 } else { 411 // repeated failure to connect: increase delay for next connection 412 knownFailure->FailedAgain(); 413 } 414 } else { 415 // new connection failure: record it. 416 LOG(("WebSocket: connection to %s, %s, %d failed: [this=%p]", 417 aChannel->mAddress.get(), aChannel->mPath.get(), 418 (int)aChannel->mPort, aChannel)); 419 sManager->mFailures.Add(aChannel->mAddress, aChannel->mPath, 420 aChannel->mPort); 421 } 422 } 423 424 if (NS_IsMainThread()) { 425 ContinueOnStopSession(aChannel, aReason); 426 } else { 427 NS_DispatchToMainThread(NS_NewRunnableFunction( 428 "nsWSAdmissionManager::ContinueOnStopSession", 429 [channel = RefPtr{aChannel}, reason = aReason]() { 430 StaticMutexAutoLock lock(sLock); 431 if (!sManager) { 432 return; 433 } 434 435 nsWSAdmissionManager::ContinueOnStopSession(channel, reason); 436 })); 437 } 438 } 439 440 static void ContinueOnStopSession(WebSocketChannel* aChannel, 441 nsresult aReason) { 442 sLock.AssertCurrentThreadOwns(); 443 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 444 445 if (!aChannel->mConnecting) { 446 return; 447 } 448 449 // Only way a connecting channel may get here w/o failing is if it 450 // was closed with GOING_AWAY (1001) because of navigation, tab 451 // close, etc. 452 #ifdef DEBUG 453 { 454 MutexAutoLock lock(aChannel->mMutex); 455 MOZ_ASSERT( 456 NS_FAILED(aReason) || aChannel->mScriptCloseCode == CLOSE_GOING_AWAY, 457 "websocket closed while connecting w/o failing?"); 458 } 459 #endif 460 (void)aReason; 461 462 sManager->RemoveFromQueue(aChannel); 463 464 bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED); 465 LOG(("Websocket: changing state to NOT_CONNECTING")); 466 aChannel->mConnecting = NOT_CONNECTING; 467 if (wasNotQueued) { 468 sManager->ConnectNext(aChannel->mAddress, aChannel->mOriginSuffix); 469 } 470 } 471 472 static void IncrementSessionCount() { 473 StaticMutexAutoLock lock(sLock); 474 if (!sManager) { 475 return; 476 } 477 sManager->mSessionCount++; 478 } 479 480 static void DecrementSessionCount() { 481 StaticMutexAutoLock lock(sLock); 482 if (!sManager) { 483 return; 484 } 485 sManager->mSessionCount--; 486 } 487 488 static void GetSessionCount(int32_t& aSessionCount) { 489 StaticMutexAutoLock lock(sLock); 490 if (!sManager) { 491 return; 492 } 493 aSessionCount = sManager->mSessionCount; 494 } 495 496 private: 497 nsWSAdmissionManager() : mSessionCount(0) { 498 MOZ_COUNT_CTOR(nsWSAdmissionManager); 499 } 500 501 ~nsWSAdmissionManager() { MOZ_COUNT_DTOR(nsWSAdmissionManager); } 502 503 class nsOpenConn { 504 public: 505 nsOpenConn(nsCString& addr, nsCString& originSuffix, bool failed, 506 WebSocketChannel* channel) 507 : mAddress(addr), 508 mOriginSuffix(originSuffix), 509 mFailed(failed), 510 mChannel(channel) { 511 MOZ_COUNT_CTOR(nsOpenConn); 512 } 513 MOZ_COUNTED_DTOR(nsOpenConn) 514 515 nsCString mAddress; 516 nsCString mOriginSuffix; 517 bool mFailed = false; 518 RefPtr<WebSocketChannel> mChannel; 519 }; 520 521 void ConnectNext(nsCString& hostName, nsCString& originSuffix) { 522 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 523 524 int32_t index = IndexOf(hostName, originSuffix); 525 if (index >= 0) { 526 WebSocketChannel* chan = mQueue[index]->mChannel; 527 528 MOZ_ASSERT(chan->mConnecting == CONNECTING_QUEUED, 529 "transaction not queued but in queue"); 530 LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan)); 531 532 mFailures.DelayOrBegin(chan); 533 } 534 } 535 536 void RemoveFromQueue(WebSocketChannel* aChannel) { 537 LOG(("Websocket: RemoveFromQueue: [this=%p]", aChannel)); 538 int32_t index = IndexOf(aChannel); 539 MOZ_ASSERT(index >= 0, "connection to remove not in queue"); 540 if (index >= 0) { 541 mQueue.RemoveElementAt(index); 542 } 543 } 544 545 int32_t IndexOf(nsCString& aAddress, nsCString& aOriginSuffix) { 546 for (uint32_t i = 0; i < mQueue.Length(); i++) { 547 if (aAddress == mQueue[i]->mAddress && 548 aOriginSuffix == mQueue[i]->mOriginSuffix) { 549 return i; 550 } 551 } 552 return -1; 553 } 554 555 int32_t IndexOf(WebSocketChannel* aChannel) { 556 for (uint32_t i = 0; i < mQueue.Length(); i++) { 557 if (aChannel == mQueue[i]->mChannel) { 558 return i; 559 } 560 } 561 return -1; 562 } 563 564 // Returns the index of the first entry that failed, or else the last entry if 565 // none found 566 uint32_t IndexOfFirstFailure() { 567 for (uint32_t i = 0; i < mQueue.Length(); i++) { 568 if (mQueue[i]->mFailed) return i; 569 } 570 return mQueue.Length(); 571 } 572 573 // SessionCount might be decremented from the main or the socket 574 // thread, so manage it with atomic counters 575 Atomic<int32_t> mSessionCount; 576 577 // Queue for websockets that have not completed connecting yet. 578 // The first nsOpenConn with a given address will be either be 579 // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED. Later ones with the same 580 // hostname must be CONNECTING_QUEUED. 581 // 582 // We could hash hostnames instead of using a single big vector here, but the 583 // dataset is expected to be small. 584 nsTArray<UniquePtr<nsOpenConn>> mQueue; 585 586 FailDelayManager mFailures; 587 588 static nsWSAdmissionManager* sManager MOZ_GUARDED_BY(sLock); 589 static StaticMutex sLock; 590 }; 591 592 nsWSAdmissionManager* nsWSAdmissionManager::sManager; 593 StaticMutex nsWSAdmissionManager::sLock; 594 595 //----------------------------------------------------------------------------- 596 // CallOnMessageAvailable 597 //----------------------------------------------------------------------------- 598 599 class CallOnMessageAvailable final : public Runnable { 600 public: 601 CallOnMessageAvailable(WebSocketChannel* aChannel, nsACString& aData, 602 int32_t aLen) 603 : Runnable("net::CallOnMessageAvailable"), 604 mChannel(aChannel), 605 mListenerMT(aChannel->mListenerMT), 606 mData(aData), 607 mLen(aLen) {} 608 609 NS_IMETHOD Run() override { 610 MOZ_ASSERT(mChannel->IsOnTargetThread()); 611 612 if (mListenerMT) { 613 nsresult rv; 614 if (mLen < 0) { 615 rv = mListenerMT->mListener->OnMessageAvailable(mListenerMT->mContext, 616 mData); 617 } else { 618 rv = mListenerMT->mListener->OnBinaryMessageAvailable( 619 mListenerMT->mContext, mData); 620 } 621 if (NS_FAILED(rv)) { 622 LOG( 623 ("OnMessageAvailable or OnBinaryMessageAvailable " 624 "failed with 0x%08" PRIx32, 625 static_cast<uint32_t>(rv))); 626 } 627 } 628 629 return NS_OK; 630 } 631 632 private: 633 ~CallOnMessageAvailable() = default; 634 635 RefPtr<WebSocketChannel> mChannel; 636 RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT; 637 nsCString mData; 638 int32_t mLen; 639 }; 640 641 //----------------------------------------------------------------------------- 642 // CallOnStop 643 //----------------------------------------------------------------------------- 644 645 class CallOnStop final : public Runnable { 646 public: 647 CallOnStop(WebSocketChannel* aChannel, nsresult aReason) 648 : Runnable("net::CallOnStop"), 649 mChannel(aChannel), 650 mListenerMT(mChannel->mListenerMT), 651 mReason(aReason) {} 652 653 NS_IMETHOD Run() override { 654 MOZ_ASSERT(mChannel->IsOnTargetThread()); 655 656 if (mListenerMT) { 657 nsresult rv = 658 mListenerMT->mListener->OnStop(mListenerMT->mContext, mReason); 659 if (NS_FAILED(rv)) { 660 LOG( 661 ("WebSocketChannel::CallOnStop " 662 "OnStop failed (%08" PRIx32 ")\n", 663 static_cast<uint32_t>(rv))); 664 } 665 mChannel->mListenerMT = nullptr; 666 } 667 668 return NS_OK; 669 } 670 671 private: 672 ~CallOnStop() = default; 673 674 RefPtr<WebSocketChannel> mChannel; 675 RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT; 676 nsresult mReason; 677 }; 678 679 //----------------------------------------------------------------------------- 680 // CallOnServerClose 681 //----------------------------------------------------------------------------- 682 683 class CallOnServerClose final : public Runnable { 684 public: 685 CallOnServerClose(WebSocketChannel* aChannel, uint16_t aCode, 686 nsACString& aReason) 687 : Runnable("net::CallOnServerClose"), 688 mChannel(aChannel), 689 mListenerMT(mChannel->mListenerMT), 690 mCode(aCode), 691 mReason(aReason) {} 692 693 NS_IMETHOD Run() override { 694 MOZ_ASSERT(mChannel->IsOnTargetThread()); 695 696 if (mListenerMT) { 697 nsresult rv = mListenerMT->mListener->OnServerClose(mListenerMT->mContext, 698 mCode, mReason); 699 if (NS_FAILED(rv)) { 700 LOG( 701 ("WebSocketChannel::CallOnServerClose " 702 "OnServerClose failed (%08" PRIx32 ")\n", 703 static_cast<uint32_t>(rv))); 704 } 705 } 706 return NS_OK; 707 } 708 709 private: 710 ~CallOnServerClose() = default; 711 712 RefPtr<WebSocketChannel> mChannel; 713 RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT; 714 uint16_t mCode; 715 nsCString mReason; 716 }; 717 718 //----------------------------------------------------------------------------- 719 // CallAcknowledge 720 //----------------------------------------------------------------------------- 721 722 class CallAcknowledge final : public Runnable { 723 public: 724 CallAcknowledge(WebSocketChannel* aChannel, uint32_t aSize) 725 : Runnable("net::CallAcknowledge"), 726 mChannel(aChannel), 727 mListenerMT(mChannel->mListenerMT), 728 mSize(aSize) {} 729 730 NS_IMETHOD Run() override { 731 MOZ_ASSERT(mChannel->IsOnTargetThread()); 732 733 LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize)); 734 if (mListenerMT) { 735 nsresult rv = 736 mListenerMT->mListener->OnAcknowledge(mListenerMT->mContext, mSize); 737 if (NS_FAILED(rv)) { 738 LOG(("WebSocketChannel::CallAcknowledge: Acknowledge failed (%08" PRIx32 739 ")\n", 740 static_cast<uint32_t>(rv))); 741 } 742 } 743 return NS_OK; 744 } 745 746 private: 747 ~CallAcknowledge() = default; 748 749 RefPtr<WebSocketChannel> mChannel; 750 RefPtr<BaseWebSocketChannel::ListenerAndContextContainer> mListenerMT; 751 uint32_t mSize; 752 }; 753 754 //----------------------------------------------------------------------------- 755 // CallOnTransportAvailable 756 //----------------------------------------------------------------------------- 757 758 class CallOnTransportAvailable final : public Runnable { 759 public: 760 CallOnTransportAvailable(WebSocketChannel* aChannel, 761 nsISocketTransport* aTransport, 762 nsIAsyncInputStream* aSocketIn, 763 nsIAsyncOutputStream* aSocketOut) 764 : Runnable("net::CallOnTransportAvailble"), 765 mChannel(aChannel), 766 mTransport(aTransport), 767 mSocketIn(aSocketIn), 768 mSocketOut(aSocketOut) {} 769 770 NS_IMETHOD Run() override { 771 LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this)); 772 return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut); 773 } 774 775 private: 776 ~CallOnTransportAvailable() = default; 777 778 RefPtr<WebSocketChannel> mChannel; 779 nsCOMPtr<nsISocketTransport> mTransport; 780 nsCOMPtr<nsIAsyncInputStream> mSocketIn; 781 nsCOMPtr<nsIAsyncOutputStream> mSocketOut; 782 }; 783 784 //----------------------------------------------------------------------------- 785 // PMCECompression 786 //----------------------------------------------------------------------------- 787 788 class PMCECompression { 789 public: 790 PMCECompression(bool aNoContextTakeover, int32_t aLocalMaxWindowBits, 791 int32_t aRemoteMaxWindowBits) 792 : mActive(false), 793 mNoContextTakeover(aNoContextTakeover), 794 mResetDeflater(false), 795 mMessageDeflated(false) { 796 this->mDeflater.next_in = nullptr; 797 this->mDeflater.avail_in = 0; 798 this->mDeflater.total_in = 0; 799 this->mDeflater.next_out = nullptr; 800 this->mDeflater.avail_out = 0; 801 this->mDeflater.total_out = 0; 802 this->mDeflater.msg = nullptr; 803 this->mDeflater.state = nullptr; 804 this->mDeflater.data_type = 0; 805 this->mDeflater.adler = 0; 806 this->mDeflater.reserved = 0; 807 this->mInflater.next_in = nullptr; 808 this->mInflater.avail_in = 0; 809 this->mInflater.total_in = 0; 810 this->mInflater.next_out = nullptr; 811 this->mInflater.avail_out = 0; 812 this->mInflater.total_out = 0; 813 this->mInflater.msg = nullptr; 814 this->mInflater.state = nullptr; 815 this->mInflater.data_type = 0; 816 this->mInflater.adler = 0; 817 this->mInflater.reserved = 0; 818 MOZ_COUNT_CTOR(PMCECompression); 819 820 mDeflater.zalloc = mInflater.zalloc = Z_NULL; 821 mDeflater.zfree = mInflater.zfree = Z_NULL; 822 mDeflater.opaque = mInflater.opaque = Z_NULL; 823 824 if (deflateInit2(&mDeflater, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 825 -aLocalMaxWindowBits, 8, Z_DEFAULT_STRATEGY) == Z_OK) { 826 if (inflateInit2(&mInflater, -aRemoteMaxWindowBits) == Z_OK) { 827 mActive = true; 828 } else { 829 deflateEnd(&mDeflater); 830 } 831 } 832 } 833 834 ~PMCECompression() { 835 MOZ_COUNT_DTOR(PMCECompression); 836 837 if (mActive) { 838 inflateEnd(&mInflater); 839 deflateEnd(&mDeflater); 840 } 841 } 842 843 bool Active() { return mActive; } 844 845 void SetMessageDeflated() { 846 MOZ_ASSERT(!mMessageDeflated); 847 mMessageDeflated = true; 848 } 849 bool IsMessageDeflated() { return mMessageDeflated; } 850 851 bool UsingContextTakeover() { return !mNoContextTakeover; } 852 853 nsresult Deflate(uint8_t* data, uint32_t dataLen, nsACString& _retval) { 854 if (mResetDeflater || mNoContextTakeover) { 855 if (deflateReset(&mDeflater) != Z_OK) { 856 return NS_ERROR_UNEXPECTED; 857 } 858 mResetDeflater = false; 859 } 860 861 mDeflater.avail_out = kBufferLen; 862 mDeflater.next_out = mBuffer; 863 mDeflater.avail_in = dataLen; 864 mDeflater.next_in = data; 865 866 while (true) { 867 int zerr = deflate(&mDeflater, Z_SYNC_FLUSH); 868 869 if (zerr != Z_OK) { 870 mResetDeflater = true; 871 return NS_ERROR_UNEXPECTED; 872 } 873 874 uint32_t deflated = kBufferLen - mDeflater.avail_out; 875 if (deflated > 0) { 876 _retval.Append(reinterpret_cast<char*>(mBuffer), deflated); 877 } 878 879 mDeflater.avail_out = kBufferLen; 880 mDeflater.next_out = mBuffer; 881 882 if (mDeflater.avail_in > 0) { 883 continue; // There is still some data to deflate 884 } 885 886 if (deflated == kBufferLen) { 887 continue; // There was not enough space in the buffer 888 } 889 890 break; 891 } 892 893 if (_retval.Length() < 4) { 894 MOZ_ASSERT(false, "Expected trailing not found in deflated data!"); 895 mResetDeflater = true; 896 return NS_ERROR_UNEXPECTED; 897 } 898 899 _retval.Truncate(_retval.Length() - 4); 900 901 return NS_OK; 902 } 903 904 nsresult Inflate(uint8_t* data, uint32_t dataLen, nsACString& _retval) { 905 mMessageDeflated = false; 906 907 Bytef trailingData[] = {0x00, 0x00, 0xFF, 0xFF}; 908 bool trailingDataUsed = false; 909 910 mInflater.avail_out = kBufferLen; 911 mInflater.next_out = mBuffer; 912 mInflater.avail_in = dataLen; 913 mInflater.next_in = data; 914 915 while (true) { 916 int zerr = inflate(&mInflater, Z_NO_FLUSH); 917 918 if (zerr == Z_STREAM_END) { 919 Bytef* saveNextIn = mInflater.next_in; 920 uint32_t saveAvailIn = mInflater.avail_in; 921 Bytef* saveNextOut = mInflater.next_out; 922 uint32_t saveAvailOut = mInflater.avail_out; 923 924 inflateReset(&mInflater); 925 926 mInflater.next_in = saveNextIn; 927 mInflater.avail_in = saveAvailIn; 928 mInflater.next_out = saveNextOut; 929 mInflater.avail_out = saveAvailOut; 930 } else if (zerr != Z_OK && zerr != Z_BUF_ERROR) { 931 return NS_ERROR_INVALID_CONTENT_ENCODING; 932 } 933 934 uint32_t inflated = kBufferLen - mInflater.avail_out; 935 if (inflated > 0) { 936 if (!_retval.Append(reinterpret_cast<char*>(mBuffer), inflated, 937 fallible)) { 938 return NS_ERROR_OUT_OF_MEMORY; 939 } 940 } 941 942 mInflater.avail_out = kBufferLen; 943 mInflater.next_out = mBuffer; 944 945 if (mInflater.avail_in > 0) { 946 continue; // There is still some data to inflate 947 } 948 949 if (inflated == kBufferLen) { 950 continue; // There was not enough space in the buffer 951 } 952 953 if (!trailingDataUsed) { 954 trailingDataUsed = true; 955 mInflater.avail_in = sizeof(trailingData); 956 mInflater.next_in = trailingData; 957 continue; 958 } 959 960 return NS_OK; 961 } 962 } 963 964 private: 965 bool mActive; 966 bool mNoContextTakeover; 967 bool mResetDeflater; 968 bool mMessageDeflated; 969 z_stream mDeflater{}; 970 z_stream mInflater{}; 971 const static uint32_t kBufferLen = 4096; 972 uint8_t mBuffer[kBufferLen]{0}; 973 }; 974 975 //----------------------------------------------------------------------------- 976 // OutboundMessage 977 //----------------------------------------------------------------------------- 978 979 enum WsMsgType { 980 kMsgTypeString = 0, 981 kMsgTypeBinaryString, 982 kMsgTypeStream, 983 kMsgTypePing, 984 kMsgTypePong, 985 kMsgTypeFin 986 }; 987 988 static const char* msgNames[] = {"text", "binaryString", "binaryStream", 989 "ping", "pong", "close"}; 990 991 class OutboundMessage { 992 public: 993 OutboundMessage(WsMsgType type, const nsACString& str) 994 : mMsg(mozilla::AsVariant(pString(str))), 995 mMsgType(type), 996 mDeflated(false) { 997 MOZ_COUNT_CTOR(OutboundMessage); 998 } 999 1000 OutboundMessage(nsIInputStream* stream, uint32_t length) 1001 : mMsg(mozilla::AsVariant(StreamWithLength(stream, length))), 1002 mMsgType(kMsgTypeStream), 1003 mDeflated(false) { 1004 MOZ_COUNT_CTOR(OutboundMessage); 1005 } 1006 1007 ~OutboundMessage() { 1008 MOZ_COUNT_DTOR(OutboundMessage); 1009 switch (mMsgType) { 1010 case kMsgTypeString: 1011 case kMsgTypeBinaryString: 1012 case kMsgTypePing: 1013 case kMsgTypePong: 1014 break; 1015 case kMsgTypeStream: 1016 // for now this only gets hit if msg deleted w/o being sent 1017 if (mMsg.as<StreamWithLength>().mStream) { 1018 mMsg.as<StreamWithLength>().mStream->Close(); 1019 } 1020 break; 1021 case kMsgTypeFin: 1022 break; // do-nothing: avoid compiler warning 1023 } 1024 } 1025 1026 WsMsgType GetMsgType() const { return mMsgType; } 1027 int32_t Length() { 1028 if (mMsg.is<pString>()) { 1029 return mMsg.as<pString>().mValue.Length(); 1030 } 1031 1032 return mMsg.as<StreamWithLength>().mLength; 1033 } 1034 int32_t OrigLength() { 1035 if (mMsg.is<pString>()) { 1036 pString& ref = mMsg.as<pString>(); 1037 return mDeflated ? ref.mOrigValue.Length() : ref.mValue.Length(); 1038 } 1039 1040 return mMsg.as<StreamWithLength>().mLength; 1041 } 1042 1043 uint8_t* BeginWriting() { 1044 MOZ_ASSERT(mMsgType != kMsgTypeStream, 1045 "Stream should have been converted to string by now"); 1046 if (!mMsg.as<pString>().mValue.IsVoid()) { 1047 return (uint8_t*)mMsg.as<pString>().mValue.BeginWriting(); 1048 } 1049 return nullptr; 1050 } 1051 1052 uint8_t* BeginReading() { 1053 MOZ_ASSERT(mMsgType != kMsgTypeStream, 1054 "Stream should have been converted to string by now"); 1055 if (!mMsg.as<pString>().mValue.IsVoid()) { 1056 return (uint8_t*)mMsg.as<pString>().mValue.BeginReading(); 1057 } 1058 return nullptr; 1059 } 1060 1061 uint8_t* BeginOrigReading() { 1062 MOZ_ASSERT(mMsgType != kMsgTypeStream, 1063 "Stream should have been converted to string by now"); 1064 if (!mDeflated) return BeginReading(); 1065 if (!mMsg.as<pString>().mOrigValue.IsVoid()) { 1066 return (uint8_t*)mMsg.as<pString>().mOrigValue.BeginReading(); 1067 } 1068 return nullptr; 1069 } 1070 1071 nsresult ConvertStreamToString() { 1072 MOZ_ASSERT(mMsgType == kMsgTypeStream, "Not a stream!"); 1073 nsAutoCString temp; 1074 { 1075 StreamWithLength& ref = mMsg.as<StreamWithLength>(); 1076 nsresult rv = NS_ReadInputStreamToString(ref.mStream, temp, ref.mLength); 1077 1078 NS_ENSURE_SUCCESS(rv, rv); 1079 if (temp.Length() != ref.mLength) { 1080 return NS_ERROR_UNEXPECTED; 1081 } 1082 ref.mStream->Close(); 1083 } 1084 1085 mMsg = mozilla::AsVariant(pString(temp)); 1086 mMsgType = kMsgTypeBinaryString; 1087 1088 return NS_OK; 1089 } 1090 1091 bool DeflatePayload(PMCECompression* aCompressor) { 1092 MOZ_ASSERT(mMsgType != kMsgTypeStream, 1093 "Stream should have been converted to string by now"); 1094 MOZ_ASSERT(!mDeflated); 1095 1096 nsresult rv; 1097 pString& ref = mMsg.as<pString>(); 1098 if (ref.mValue.Length() == 0) { 1099 // Empty message 1100 return false; 1101 } 1102 1103 nsAutoCString temp; 1104 rv = aCompressor->Deflate(BeginReading(), ref.mValue.Length(), temp); 1105 if (NS_FAILED(rv)) { 1106 LOG( 1107 ("WebSocketChannel::OutboundMessage: Deflating payload failed " 1108 "[rv=0x%08" PRIx32 "]\n", 1109 static_cast<uint32_t>(rv))); 1110 return false; 1111 } 1112 1113 if (!aCompressor->UsingContextTakeover() && 1114 temp.Length() > ref.mValue.Length()) { 1115 // When "<local>_no_context_takeover" was negotiated, do not send deflated 1116 // payload if it's larger that the original one. OTOH, it makes sense 1117 // to send the larger deflated payload when the sliding window is not 1118 // reset between messages because if we would skip some deflated block 1119 // we would need to empty the sliding window which could affect the 1120 // compression of the subsequent messages. 1121 LOG( 1122 ("WebSocketChannel::OutboundMessage: Not deflating message since the " 1123 "deflated payload is larger than the original one [deflated=%zd, " 1124 "original=%zd]", 1125 temp.Length(), ref.mValue.Length())); 1126 return false; 1127 } 1128 1129 mDeflated = true; 1130 mMsg.as<pString>().mOrigValue = mMsg.as<pString>().mValue; 1131 mMsg.as<pString>().mValue = temp; 1132 return true; 1133 } 1134 1135 private: 1136 struct pString { 1137 nsCString mValue; 1138 nsCString mOrigValue; 1139 explicit pString(const nsACString& value) 1140 : mValue(value), mOrigValue(VoidCString()) {} 1141 }; 1142 struct StreamWithLength { 1143 nsCOMPtr<nsIInputStream> mStream; 1144 uint32_t mLength; 1145 explicit StreamWithLength(nsIInputStream* stream, uint32_t Length) 1146 : mStream(stream), mLength(Length) {} 1147 }; 1148 mozilla::Variant<pString, StreamWithLength> mMsg; 1149 WsMsgType mMsgType; 1150 bool mDeflated; 1151 }; 1152 1153 //----------------------------------------------------------------------------- 1154 // OutboundEnqueuer 1155 //----------------------------------------------------------------------------- 1156 1157 class OutboundEnqueuer final : public Runnable { 1158 public: 1159 OutboundEnqueuer(WebSocketChannel* aChannel, OutboundMessage* aMsg) 1160 : Runnable("OutboundEnquerer"), mChannel(aChannel), mMessage(aMsg) {} 1161 1162 NS_IMETHOD Run() override { 1163 mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage); 1164 return NS_OK; 1165 } 1166 1167 private: 1168 ~OutboundEnqueuer() = default; 1169 1170 RefPtr<WebSocketChannel> mChannel; 1171 OutboundMessage* mMessage; 1172 }; 1173 1174 //----------------------------------------------------------------------------- 1175 // WebSocketChannel 1176 //----------------------------------------------------------------------------- 1177 1178 WebSocketChannel::WebSocketChannel() 1179 : mPort(0), 1180 mCloseTimeout(20000), 1181 mOpenTimeout(20000), 1182 mConnecting(NOT_CONNECTING), 1183 mMaxConcurrentConnections(200), 1184 mInnerWindowID(0), 1185 mGotUpgradeOK(0), 1186 mRecvdHttpUpgradeTransport(0), 1187 mPingOutstanding(0), 1188 mReleaseOnTransmit(0), 1189 mDataStarted(false), 1190 mRequestedClose(false), 1191 mClientClosed(false), 1192 mServerClosed(false), 1193 mStopped(false), 1194 mCalledOnStop(false), 1195 mTCPClosed(false), 1196 mOpenedHttpChannel(false), 1197 mIncrementedSessionCount(false), 1198 mDecrementedSessionCount(false), 1199 mMaxMessageSize(INT32_MAX), 1200 mStopOnClose(NS_OK), 1201 mServerCloseCode(CLOSE_ABNORMAL), 1202 mScriptCloseCode(0), 1203 mFragmentOpcode(nsIWebSocketFrame::OPCODE_CONTINUATION), 1204 mFragmentAccumulator(0), 1205 mBuffered(0), 1206 mBufferSize(kIncomingBufferInitialSize), 1207 mCurrentOut(nullptr), 1208 mCurrentOutSent(0), 1209 mHdrOutToSend(0), 1210 mHdrOut(nullptr), 1211 mCompressorMutex("WebSocketChannel::mCompressorMutex"), 1212 mDynamicOutputSize(0), 1213 mDynamicOutput(nullptr), 1214 mPrivateBrowsing(false), 1215 mConnectionLogService(nullptr), 1216 mMutex("WebSocketChannel::mMutex") { 1217 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 1218 1219 LOG(("WebSocketChannel::WebSocketChannel() %p\n", this)); 1220 1221 nsWSAdmissionManager::Init(); 1222 1223 mFramePtr = mBuffer = static_cast<uint8_t*>(moz_xmalloc(mBufferSize)); 1224 1225 nsresult rv; 1226 mConnectionLogService = mozilla::components::Dashboard::Service(&rv); 1227 if (NS_FAILED(rv)) LOG(("Failed to initiate dashboard service.")); 1228 1229 mService = WebSocketEventService::GetOrCreate(); 1230 } 1231 1232 WebSocketChannel::~WebSocketChannel() { 1233 LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this)); 1234 1235 if (mWasOpened) { 1236 MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called"); 1237 MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped"); 1238 } 1239 MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction"); 1240 MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor"); 1241 1242 free(mBuffer); 1243 free(mDynamicOutput); 1244 delete mCurrentOut; 1245 1246 while ((mCurrentOut = mOutgoingPingMessages.PopFront())) { 1247 delete mCurrentOut; 1248 } 1249 while ((mCurrentOut = mOutgoingPongMessages.PopFront())) { 1250 delete mCurrentOut; 1251 } 1252 while ((mCurrentOut = mOutgoingMessages.PopFront())) { 1253 delete mCurrentOut; 1254 } 1255 1256 mListenerMT = nullptr; 1257 1258 NS_ReleaseOnMainThread("WebSocketChannel::mService", mService.forget()); 1259 } 1260 1261 NS_IMETHODIMP 1262 WebSocketChannel::Observe(nsISupports* subject, const char* topic, 1263 const char16_t* data) { 1264 LOG(("WebSocketChannel::Observe [topic=\"%s\"]\n", topic)); 1265 1266 if (strcmp(topic, NS_NETWORK_LINK_TOPIC) == 0) { 1267 nsCString converted = NS_ConvertUTF16toUTF8(data); 1268 const char* state = converted.get(); 1269 1270 if (strcmp(state, NS_NETWORK_LINK_DATA_CHANGED) == 0) { 1271 LOG(("WebSocket: received network CHANGED event")); 1272 1273 if (!mIOThread) { 1274 // there has not been an asyncopen yet on the object and then we need 1275 // no ping. 1276 LOG(("WebSocket: early object, no ping needed")); 1277 } else { 1278 mIOThread->Dispatch( 1279 NewRunnableMethod("net::WebSocketChannel::OnNetworkChanged", this, 1280 &WebSocketChannel::OnNetworkChanged), 1281 NS_DISPATCH_NORMAL); 1282 } 1283 } 1284 } 1285 1286 return NS_OK; 1287 } 1288 1289 nsresult WebSocketChannel::OnNetworkChanged() { 1290 if (!mDataStarted) { 1291 LOG(("WebSocket: data not started yet, no ping needed")); 1292 return NS_OK; 1293 } 1294 1295 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 1296 1297 LOG(("WebSocketChannel::OnNetworkChanged() - on socket thread %p", this)); 1298 1299 if (mPingOutstanding) { 1300 // If there's an outstanding ping that's expected to get a pong back 1301 // we let that do its thing. 1302 LOG(("WebSocket: pong already pending")); 1303 return NS_OK; 1304 } 1305 1306 if (mPingForced) { 1307 // avoid more than one 1308 LOG(("WebSocket: forced ping timer already fired")); 1309 return NS_OK; 1310 } 1311 1312 LOG(("nsWebSocketChannel:: Generating Ping as network changed\n")); 1313 1314 if (!mPingTimer) { 1315 // The ping timer is only conditionally running already. If it wasn't 1316 // already created do it here. 1317 mPingTimer = NS_NewTimer(); 1318 if (!mPingTimer) { 1319 LOG(("WebSocket: unable to create ping timer!")); 1320 NS_WARNING("unable to create ping timer!"); 1321 return NS_ERROR_OUT_OF_MEMORY; 1322 } 1323 } 1324 // Trigger the ping timeout asap to fire off a new ping. Wait just 1325 // a little bit to better avoid multi-triggers. 1326 mPingForced = true; 1327 mPingTimer->InitWithCallback(this, 200, nsITimer::TYPE_ONE_SHOT); 1328 1329 return NS_OK; 1330 } 1331 1332 void WebSocketChannel::Shutdown() { nsWSAdmissionManager::Shutdown(); } 1333 1334 void WebSocketChannel::GetEffectiveURL(nsAString& aEffectiveURL) const { 1335 aEffectiveURL = mEffectiveURL; 1336 } 1337 1338 bool WebSocketChannel::IsEncrypted() const { return mEncrypted; } 1339 1340 void WebSocketChannel::BeginOpen(bool aCalledFromAdmissionManager) { 1341 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 1342 1343 LOG(("WebSocketChannel::BeginOpen() %p\n", this)); 1344 1345 // Important that we set CONNECTING_IN_PROGRESS before any call to 1346 // AbortSession here: ensures that any remaining queued connection(s) are 1347 // scheduled in OnStopSession 1348 LOG(("Websocket: changing state to CONNECTING_IN_PROGRESS")); 1349 mConnecting = CONNECTING_IN_PROGRESS; 1350 1351 if (aCalledFromAdmissionManager) { 1352 // When called from nsWSAdmissionManager post an event to avoid potential 1353 // re-entering of nsWSAdmissionManager and its lock. 1354 NS_DispatchToMainThread( 1355 NewRunnableMethod("net::WebSocketChannel::BeginOpenInternal", this, 1356 &WebSocketChannel::BeginOpenInternal), 1357 NS_DISPATCH_NORMAL); 1358 } else { 1359 BeginOpenInternal(); 1360 } 1361 } 1362 1363 // MainThread 1364 void WebSocketChannel::BeginOpenInternal() { 1365 LOG(("WebSocketChannel::BeginOpenInternal() %p\n", this)); 1366 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 1367 1368 nsresult rv; 1369 1370 if (mRedirectCallback) { 1371 LOG(("WebSocketChannel::BeginOpenInternal: Resuming Redirect\n")); 1372 rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK); 1373 mRedirectCallback = nullptr; 1374 return; 1375 } 1376 1377 nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv); 1378 if (NS_FAILED(rv)) { 1379 LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n")); 1380 AbortSession(NS_ERROR_UNEXPECTED); 1381 return; 1382 } 1383 1384 rv = localChannel->AsyncOpen(this); 1385 1386 if (NS_FAILED(rv)) { 1387 LOG(("WebSocketChannel::BeginOpenInternal: cannot async open\n")); 1388 AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED); 1389 return; 1390 } 1391 mOpenedHttpChannel = true; 1392 1393 rv = NS_NewTimerWithCallback(getter_AddRefs(mOpenTimer), this, mOpenTimeout, 1394 nsITimer::TYPE_ONE_SHOT); 1395 if (NS_FAILED(rv)) { 1396 LOG( 1397 ("WebSocketChannel::BeginOpenInternal: cannot initialize open " 1398 "timer\n")); 1399 AbortSession(NS_ERROR_UNEXPECTED); 1400 return; 1401 } 1402 } 1403 1404 bool WebSocketChannel::IsPersistentFramePtr() { 1405 return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize); 1406 } 1407 1408 // Extends the internal buffer by count and returns the total 1409 // amount of data available for read 1410 // 1411 // Accumulated fragment size is passed in instead of using the member 1412 // variable beacuse when transitioning from the stack to the persistent 1413 // read buffer we want to explicitly include them in the buffer instead 1414 // of as already existing data. 1415 bool WebSocketChannel::UpdateReadBuffer(uint8_t* buffer, uint32_t count, 1416 uint32_t accumulatedFragments, 1417 uint32_t* available) { 1418 LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n", this, buffer, 1419 count)); 1420 1421 if (!mBuffered) mFramePtr = mBuffer; 1422 1423 MOZ_ASSERT(IsPersistentFramePtr(), "update read buffer bad mFramePtr"); 1424 MOZ_ASSERT(mFramePtr - accumulatedFragments >= mBuffer, 1425 "reserved FramePtr bad"); 1426 1427 if (mBuffered + count <= mBufferSize) { 1428 // append to existing buffer 1429 LOG(("WebSocketChannel: update read buffer absorbed %u\n", count)); 1430 } else if (mBuffered + count - (mFramePtr - accumulatedFragments - mBuffer) <= 1431 mBufferSize) { 1432 // make room in existing buffer by shifting unused data to start 1433 mBuffered -= (mFramePtr - mBuffer - accumulatedFragments); 1434 LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered)); 1435 ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered); 1436 mFramePtr = mBuffer + accumulatedFragments; 1437 } else { 1438 // existing buffer is not sufficient, extend it 1439 uint32_t newBufferSize = mBufferSize; 1440 newBufferSize += count + 8192 + mBufferSize / 3; 1441 ptrdiff_t frameIndex = mFramePtr - mBuffer; 1442 LOG(("WebSocketChannel: update read buffer extended to %u\n", 1443 newBufferSize)); 1444 uint8_t* newBuffer = (uint8_t*)realloc(mBuffer, newBufferSize); 1445 if (!newBuffer) { 1446 // Reallocation failed. 1447 return false; 1448 } 1449 mBuffer = newBuffer; 1450 mBufferSize = newBufferSize; 1451 1452 // mBuffer was reallocated, so we need to update mFramePtr 1453 mFramePtr = mBuffer + frameIndex; 1454 } 1455 1456 ::memcpy(mBuffer + mBuffered, buffer, count); 1457 mBuffered += count; 1458 1459 if (available) *available = mBuffered - (mFramePtr - mBuffer); 1460 1461 return true; 1462 } 1463 1464 nsresult WebSocketChannel::ProcessInput(uint8_t* buffer, uint32_t count) { 1465 LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered)); 1466 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 1467 1468 nsresult rv; 1469 1470 // The purpose of ping/pong is to actively probe the peer so that an 1471 // unreachable peer is not mistaken for a period of idleness. This 1472 // implementation accepts any application level read activity as a sign of 1473 // life, it does not necessarily have to be a pong. 1474 ResetPingTimer(); 1475 1476 uint32_t avail; 1477 1478 if (!mBuffered) { 1479 // Most of the time we can process right off the stack buffer without 1480 // having to accumulate anything 1481 mFramePtr = buffer; 1482 avail = count; 1483 } else { 1484 if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) { 1485 return NS_ERROR_FILE_TOO_BIG; 1486 } 1487 } 1488 1489 uint8_t* payload; 1490 uint32_t totalAvail = avail; 1491 1492 while (avail >= 2) { 1493 int64_t payloadLength64 = mFramePtr[1] & kPayloadLengthBitsMask; 1494 uint8_t finBit = mFramePtr[0] & kFinalFragBit; 1495 uint8_t rsvBits = mFramePtr[0] & kRsvBitsMask; 1496 uint8_t rsvBit1 = mFramePtr[0] & kRsv1Bit; 1497 uint8_t rsvBit2 = mFramePtr[0] & kRsv2Bit; 1498 uint8_t rsvBit3 = mFramePtr[0] & kRsv3Bit; 1499 uint8_t opcode = mFramePtr[0] & kOpcodeBitsMask; 1500 uint8_t maskBit = mFramePtr[1] & kMaskBit; 1501 uint32_t mask = 0; 1502 1503 uint32_t framingLength = 2; 1504 if (maskBit) framingLength += 4; 1505 1506 if (payloadLength64 < 126) { 1507 if (avail < framingLength) break; 1508 } else if (payloadLength64 == 126) { 1509 // 16 bit length field 1510 framingLength += 2; 1511 if (avail < framingLength) break; 1512 1513 payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3]; 1514 1515 if (payloadLength64 < 126) { 1516 // Section 5.2 says that the minimal number of bytes MUST 1517 // be used to encode the length in all cases 1518 LOG(("WebSocketChannel:: non-minimal-encoded payload length")); 1519 return NS_ERROR_ILLEGAL_VALUE; 1520 } 1521 1522 } else { 1523 // 64 bit length 1524 framingLength += 8; 1525 if (avail < framingLength) break; 1526 1527 if (mFramePtr[2] & 0x80) { 1528 // Section 4.2 says that the most significant bit MUST be 1529 // 0. (i.e. this is really a 63 bit value) 1530 LOG(("WebSocketChannel:: high bit of 64 bit length set")); 1531 return NS_ERROR_ILLEGAL_VALUE; 1532 } 1533 1534 // copy this in case it is unaligned 1535 payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2); 1536 1537 if (payloadLength64 <= 0xffff) { 1538 // Section 5.2 says that the minimal number of bytes MUST 1539 // be used to encode the length in all cases 1540 LOG(("WebSocketChannel:: non-minimal-encoded payload length")); 1541 return NS_ERROR_ILLEGAL_VALUE; 1542 } 1543 } 1544 1545 payload = mFramePtr + framingLength; 1546 avail -= framingLength; 1547 1548 LOG(("WebSocketChannel::ProcessInput: payload %" PRId64 " avail %" PRIu32 1549 "\n", 1550 payloadLength64, avail)); 1551 1552 CheckedInt<int64_t> payloadLengthChecked(payloadLength64); 1553 payloadLengthChecked += mFragmentAccumulator; 1554 if (!payloadLengthChecked.isValid() || 1555 payloadLengthChecked.value() > mMaxMessageSize) { 1556 return NS_ERROR_FILE_TOO_BIG; 1557 } 1558 1559 uint32_t payloadLength = static_cast<uint32_t>(payloadLength64); 1560 1561 if (avail < payloadLength) break; 1562 1563 LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n", 1564 opcode)); 1565 1566 if (!maskBit && mIsServerSide) { 1567 LOG( 1568 ("WebSocketChannel::ProcessInput: unmasked frame received " 1569 "from client\n")); 1570 return NS_ERROR_ILLEGAL_VALUE; 1571 } 1572 1573 if (maskBit) { 1574 if (!mIsServerSide) { 1575 // The server should not be allowed to send masked frames to clients. 1576 // But we've been allowing it for some time, so this should be 1577 // deprecated with care. 1578 LOG(("WebSocketChannel:: Client RECEIVING masked frame.")); 1579 } 1580 1581 mask = NetworkEndian::readUint32(payload - 4); 1582 } 1583 1584 if (mask) { 1585 ApplyMask(mask, payload, payloadLength); 1586 } else if (mIsServerSide) { 1587 LOG( 1588 ("WebSocketChannel::ProcessInput: masked frame with mask 0 received" 1589 "from client\n")); 1590 return NS_ERROR_ILLEGAL_VALUE; 1591 } 1592 1593 // Control codes are required to have the fin bit set 1594 if (!finBit && (opcode & kControlFrameMask)) { 1595 LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode)); 1596 return NS_ERROR_ILLEGAL_VALUE; 1597 } 1598 1599 if (rsvBits) { 1600 // PMCE sets RSV1 bit in the first fragment when the non-control frame 1601 // is deflated 1602 MutexAutoLock lock(mCompressorMutex); 1603 if (mPMCECompressor && rsvBits == kRsv1Bit && mFragmentAccumulator == 0 && 1604 !(opcode & kControlFrameMask)) { 1605 mPMCECompressor->SetMessageDeflated(); 1606 LOG(("WebSocketChannel::ProcessInput: received deflated frame\n")); 1607 } else { 1608 LOG(("WebSocketChannel::ProcessInput: unexpected reserved bits %x\n", 1609 rsvBits)); 1610 return NS_ERROR_ILLEGAL_VALUE; 1611 } 1612 } 1613 1614 if (!finBit || opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) { 1615 // This is part of a fragment response 1616 1617 // Only the first frame has a non zero op code: Make sure we don't see a 1618 // first frame while some old fragments are open 1619 if ((mFragmentAccumulator != 0) && 1620 (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION)) { 1621 LOG(("WebSocketChannel:: nested fragments\n")); 1622 return NS_ERROR_ILLEGAL_VALUE; 1623 } 1624 1625 LOG(("WebSocketChannel:: Accumulating Fragment %" PRIu32 "\n", 1626 payloadLength)); 1627 1628 if (opcode == nsIWebSocketFrame::OPCODE_CONTINUATION) { 1629 // Make sure this continuation fragment isn't the first fragment 1630 if (mFragmentOpcode == nsIWebSocketFrame::OPCODE_CONTINUATION) { 1631 LOG(("WebSocketHeandler:: continuation code in first fragment\n")); 1632 return NS_ERROR_ILLEGAL_VALUE; 1633 } 1634 1635 // For frag > 1 move the data body back on top of the headers 1636 // so we have contiguous stream of data 1637 MOZ_ASSERT(mFramePtr + framingLength == payload, 1638 "payload offset from frameptr wrong"); 1639 ::memmove(mFramePtr, payload, avail); 1640 payload = mFramePtr; 1641 if (mBuffered) mBuffered -= framingLength; 1642 } else { 1643 mFragmentOpcode = opcode; 1644 } 1645 1646 if (finBit) { 1647 LOG(("WebSocketChannel:: Finalizing Fragment\n")); 1648 payload -= mFragmentAccumulator; 1649 payloadLength += mFragmentAccumulator; 1650 avail += mFragmentAccumulator; 1651 mFragmentAccumulator = 0; 1652 opcode = mFragmentOpcode; 1653 // reset to detect if next message illegally starts with continuation 1654 mFragmentOpcode = nsIWebSocketFrame::OPCODE_CONTINUATION; 1655 } else { 1656 opcode = nsIWebSocketFrame::OPCODE_CONTINUATION; 1657 mFragmentAccumulator += payloadLength; 1658 } 1659 } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) { 1660 // This frame is not part of a fragment sequence but we 1661 // have an open fragment.. it must be a control code or else 1662 // we have a problem 1663 LOG(("WebSocketChannel:: illegal fragment sequence\n")); 1664 return NS_ERROR_ILLEGAL_VALUE; 1665 } 1666 1667 if (mServerClosed) { 1668 LOG(("WebSocketChannel:: ignoring read frame code %d after close\n", 1669 opcode)); 1670 // nop 1671 } else if (mStopped) { 1672 LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n", 1673 opcode)); 1674 } else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) { 1675 if (mListenerMT) { 1676 nsCString utf8Data; 1677 { 1678 MutexAutoLock lock(mCompressorMutex); 1679 bool isDeflated = 1680 mPMCECompressor && mPMCECompressor->IsMessageDeflated(); 1681 LOG(("WebSocketChannel:: %stext frame received\n", 1682 isDeflated ? "deflated " : "")); 1683 1684 if (isDeflated) { 1685 rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data); 1686 if (NS_FAILED(rv)) { 1687 return rv; 1688 } 1689 LOG( 1690 ("WebSocketChannel:: message successfully inflated " 1691 "[origLength=%d, newLength=%zd]\n", 1692 payloadLength, utf8Data.Length())); 1693 } else { 1694 if (!utf8Data.Assign((const char*)payload, payloadLength, 1695 mozilla::fallible)) { 1696 return NS_ERROR_OUT_OF_MEMORY; 1697 } 1698 } 1699 } 1700 1701 // Section 8.1 says to fail connection if invalid utf-8 in text message 1702 if (!IsUtf8(utf8Data)) { 1703 LOG(("WebSocketChannel:: text frame invalid utf-8\n")); 1704 return NS_ERROR_CANNOT_CONVERT_DATA; 1705 } 1706 1707 RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded( 1708 finBit, rsvBit1, rsvBit2, rsvBit3, opcode, maskBit, mask, utf8Data); 1709 1710 if (frame) { 1711 mService->FrameReceived(mSerial, mInnerWindowID, frame.forget()); 1712 } 1713 1714 if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) { 1715 target->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1), 1716 NS_DISPATCH_NORMAL); 1717 } else { 1718 return NS_ERROR_UNEXPECTED; 1719 } 1720 if (mConnectionLogService && !mPrivateBrowsing) { 1721 mConnectionLogService->NewMsgReceived(mHost, mSerial, count); 1722 LOG(("Added new msg received for %s", mHost.get())); 1723 } 1724 } 1725 } else if (opcode & kControlFrameMask) { 1726 // control frames 1727 if (payloadLength > 125) { 1728 LOG(("WebSocketChannel:: bad control frame code %d length %d\n", opcode, 1729 payloadLength)); 1730 return NS_ERROR_ILLEGAL_VALUE; 1731 } 1732 1733 RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded( 1734 finBit, rsvBit1, rsvBit2, rsvBit3, opcode, maskBit, mask, payload, 1735 payloadLength); 1736 1737 if (opcode == nsIWebSocketFrame::OPCODE_CLOSE) { 1738 LOG(("WebSocketChannel:: close received\n")); 1739 mServerClosed = true; 1740 1741 mServerCloseCode = CLOSE_NO_STATUS; 1742 if (payloadLength >= 2) { 1743 mServerCloseCode = NetworkEndian::readUint16(payload); 1744 LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode)); 1745 uint16_t msglen = static_cast<uint16_t>(payloadLength - 2); 1746 if (msglen > 0) { 1747 mServerCloseReason.SetLength(msglen); 1748 memcpy(mServerCloseReason.BeginWriting(), (const char*)payload + 2, 1749 msglen); 1750 1751 // section 8.1 says to replace received non utf-8 sequences 1752 // (which are non-conformant to send) with u+fffd, 1753 // but secteam feels that silently rewriting messages is 1754 // inappropriate - so we will fail the connection instead. 1755 if (!IsUtf8(mServerCloseReason)) { 1756 LOG(("WebSocketChannel:: close frame invalid utf-8\n")); 1757 return NS_ERROR_CANNOT_CONVERT_DATA; 1758 } 1759 1760 LOG(("WebSocketChannel:: close msg %s\n", 1761 mServerCloseReason.get())); 1762 } 1763 } 1764 1765 if (mCloseTimer) { 1766 mCloseTimer->Cancel(); 1767 mCloseTimer = nullptr; 1768 } 1769 1770 if (frame) { 1771 // We send the frame immediately becuase we want to have it dispatched 1772 // before the CallOnServerClose. 1773 mService->FrameReceived(mSerial, mInnerWindowID, frame.forget()); 1774 frame = nullptr; 1775 } 1776 1777 if (mListenerMT) { 1778 if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) { 1779 target->Dispatch(new CallOnServerClose(this, mServerCloseCode, 1780 mServerCloseReason), 1781 NS_DISPATCH_NORMAL); 1782 } else { 1783 return NS_ERROR_UNEXPECTED; 1784 } 1785 } 1786 1787 if (mClientClosed) ReleaseSession(); 1788 } else if (opcode == nsIWebSocketFrame::OPCODE_PING) { 1789 LOG(("WebSocketChannel:: ping received\n")); 1790 GeneratePong(payload, payloadLength); 1791 } else if (opcode == nsIWebSocketFrame::OPCODE_PONG) { 1792 // opcode OPCODE_PONG: the mere act of receiving the packet is all we 1793 // need to do for the pong to trigger the activity timers 1794 LOG(("WebSocketChannel:: pong received\n")); 1795 } else { 1796 /* unknown control frame opcode */ 1797 LOG(("WebSocketChannel:: unknown control op code %d\n", opcode)); 1798 return NS_ERROR_ILLEGAL_VALUE; 1799 } 1800 1801 if (mFragmentAccumulator) { 1802 // Remove the control frame from the stream so we have a contiguous 1803 // data buffer of reassembled fragments 1804 LOG(("WebSocketChannel:: Removing Control From Read buffer\n")); 1805 MOZ_ASSERT(mFramePtr + framingLength == payload, 1806 "payload offset from frameptr wrong"); 1807 ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength); 1808 payload = mFramePtr; 1809 avail -= payloadLength; 1810 if (mBuffered) mBuffered -= framingLength + payloadLength; 1811 payloadLength = 0; 1812 } 1813 1814 if (frame) { 1815 mService->FrameReceived(mSerial, mInnerWindowID, frame.forget()); 1816 } 1817 } else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) { 1818 if (mListenerMT) { 1819 nsCString binaryData; 1820 { 1821 MutexAutoLock lock(mCompressorMutex); 1822 bool isDeflated = 1823 mPMCECompressor && mPMCECompressor->IsMessageDeflated(); 1824 LOG(("WebSocketChannel:: %sbinary frame received\n", 1825 isDeflated ? "deflated " : "")); 1826 1827 if (isDeflated) { 1828 rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData); 1829 if (NS_FAILED(rv)) { 1830 return rv; 1831 } 1832 LOG( 1833 ("WebSocketChannel:: message successfully inflated " 1834 "[origLength=%d, newLength=%zd]\n", 1835 payloadLength, binaryData.Length())); 1836 } else { 1837 if (!binaryData.Assign((const char*)payload, payloadLength, 1838 mozilla::fallible)) { 1839 return NS_ERROR_OUT_OF_MEMORY; 1840 } 1841 } 1842 } 1843 1844 RefPtr<WebSocketFrame> frame = 1845 mService->CreateFrameIfNeeded(finBit, rsvBit1, rsvBit2, rsvBit3, 1846 opcode, maskBit, mask, binaryData); 1847 if (frame) { 1848 mService->FrameReceived(mSerial, mInnerWindowID, frame.forget()); 1849 } 1850 1851 if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) { 1852 target->Dispatch( 1853 new CallOnMessageAvailable(this, binaryData, binaryData.Length()), 1854 NS_DISPATCH_NORMAL); 1855 } else { 1856 return NS_ERROR_UNEXPECTED; 1857 } 1858 // To add the header to 'Networking Dashboard' log 1859 if (mConnectionLogService && !mPrivateBrowsing) { 1860 mConnectionLogService->NewMsgReceived(mHost, mSerial, count); 1861 LOG(("Added new received msg for %s", mHost.get())); 1862 } 1863 } 1864 } else if (opcode != nsIWebSocketFrame::OPCODE_CONTINUATION) { 1865 /* unknown opcode */ 1866 LOG(("WebSocketChannel:: unknown op code %d\n", opcode)); 1867 return NS_ERROR_ILLEGAL_VALUE; 1868 } 1869 1870 mFramePtr = payload + payloadLength; 1871 avail -= payloadLength; 1872 totalAvail = avail; 1873 } 1874 1875 // Adjust the stateful buffer. If we were operating off the stack and 1876 // now have a partial message then transition to the buffer, or if 1877 // we were working off the buffer but no longer have any active state 1878 // then transition to the stack 1879 if (!IsPersistentFramePtr()) { 1880 mBuffered = 0; 1881 1882 if (mFragmentAccumulator) { 1883 LOG(("WebSocketChannel:: Setup Buffer due to fragment")); 1884 1885 if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator, 1886 totalAvail + mFragmentAccumulator, 0, nullptr)) { 1887 return NS_ERROR_FILE_TOO_BIG; 1888 } 1889 1890 // UpdateReadBuffer will reset the frameptr to the beginning 1891 // of new saved state, so we need to skip past processed framgents 1892 mFramePtr += mFragmentAccumulator; 1893 } else if (totalAvail) { 1894 LOG(("WebSocketChannel:: Setup Buffer due to partial frame")); 1895 if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) { 1896 return NS_ERROR_FILE_TOO_BIG; 1897 } 1898 } 1899 } else if (!mFragmentAccumulator && !totalAvail) { 1900 // If we were working off a saved buffer state and there is no partial 1901 // frame or fragment in process, then revert to stack behavior 1902 LOG(("WebSocketChannel:: Internal buffering not needed anymore")); 1903 mBuffered = 0; 1904 1905 // release memory if we've been processing a large message 1906 if (mBufferSize > kIncomingBufferStableSize) { 1907 mBufferSize = kIncomingBufferStableSize; 1908 free(mBuffer); 1909 mBuffer = (uint8_t*)moz_xmalloc(mBufferSize); 1910 } 1911 } 1912 return NS_OK; 1913 } 1914 1915 /* static */ 1916 void WebSocketChannel::ApplyMask(uint32_t mask, uint8_t* data, uint64_t len) { 1917 if (!data || len == 0) return; 1918 1919 // Optimally we want to apply the mask 32 bits at a time, 1920 // but the buffer might not be alligned. So we first deal with 1921 // 0 to 3 bytes of preamble individually 1922 1923 while (len && (reinterpret_cast<uintptr_t>(data) & 3)) { 1924 *data ^= mask >> 24; 1925 mask = RotateLeft(mask, 8); 1926 data++; 1927 len--; 1928 } 1929 1930 // perform mask on full words of data 1931 1932 uint32_t* iData = (uint32_t*)data; 1933 uint32_t* end = iData + (len / 4); 1934 NetworkEndian::writeUint32(&mask, mask); 1935 for (; iData < end; iData++) *iData ^= mask; 1936 mask = NetworkEndian::readUint32(&mask); 1937 data = (uint8_t*)iData; 1938 len = len % 4; 1939 1940 // There maybe up to 3 trailing bytes that need to be dealt with 1941 // individually 1942 1943 while (len) { 1944 *data ^= mask >> 24; 1945 mask = RotateLeft(mask, 8); 1946 data++; 1947 len--; 1948 } 1949 } 1950 1951 void WebSocketChannel::GeneratePing() { 1952 nsAutoCString buf; 1953 buf.AssignLiteral("PING"); 1954 EnqueueOutgoingMessage(mOutgoingPingMessages, 1955 new OutboundMessage(kMsgTypePing, buf)); 1956 } 1957 1958 void WebSocketChannel::GeneratePong(uint8_t* payload, uint32_t len) { 1959 nsAutoCString buf; 1960 buf.SetLength(len); 1961 if (buf.Length() < len) { 1962 LOG(("WebSocketChannel::GeneratePong Allocation Failure\n")); 1963 return; 1964 } 1965 1966 memcpy(buf.BeginWriting(), payload, len); 1967 EnqueueOutgoingMessage(mOutgoingPongMessages, 1968 new OutboundMessage(kMsgTypePong, buf)); 1969 } 1970 1971 void WebSocketChannel::EnqueueOutgoingMessage(nsDeque<OutboundMessage>& aQueue, 1972 OutboundMessage* aMsg) { 1973 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 1974 1975 LOG( 1976 ("WebSocketChannel::EnqueueOutgoingMessage %p " 1977 "queueing msg %p [type=%s len=%d]\n", 1978 this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length())); 1979 1980 aQueue.Push(aMsg); 1981 if (mSocketOut) { 1982 OnOutputStreamReady(mSocketOut); 1983 } else { 1984 DoEnqueueOutgoingMessage(); 1985 } 1986 } 1987 1988 uint16_t WebSocketChannel::ResultToCloseCode(nsresult resultCode) { 1989 if (NS_SUCCEEDED(resultCode)) return CLOSE_NORMAL; 1990 1991 switch (resultCode) { 1992 case NS_ERROR_FILE_TOO_BIG: 1993 case NS_ERROR_OUT_OF_MEMORY: 1994 return CLOSE_TOO_LARGE; 1995 case NS_ERROR_CANNOT_CONVERT_DATA: 1996 return CLOSE_INVALID_PAYLOAD; 1997 case NS_ERROR_UNEXPECTED: 1998 return CLOSE_INTERNAL_ERROR; 1999 default: 2000 return CLOSE_PROTOCOL_ERROR; 2001 } 2002 } 2003 2004 void WebSocketChannel::PrimeNewOutgoingMessage() { 2005 LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this)); 2006 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 2007 MOZ_ASSERT(!mCurrentOut, "Current message in progress"); 2008 2009 nsresult rv = NS_OK; 2010 2011 mCurrentOut = mOutgoingPongMessages.PopFront(); 2012 if (mCurrentOut) { 2013 MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePong, "Not pong message!"); 2014 } else { 2015 mCurrentOut = mOutgoingPingMessages.PopFront(); 2016 if (mCurrentOut) { 2017 MOZ_ASSERT(mCurrentOut->GetMsgType() == kMsgTypePing, 2018 "Not ping message!"); 2019 } else { 2020 mCurrentOut = mOutgoingMessages.PopFront(); 2021 } 2022 } 2023 2024 if (!mCurrentOut) return; 2025 2026 auto cleanupAfterFailure = 2027 MakeScopeExit([&] { DeleteCurrentOutGoingMessage(); }); 2028 2029 WsMsgType msgType = mCurrentOut->GetMsgType(); 2030 2031 LOG( 2032 ("WebSocketChannel::PrimeNewOutgoingMessage " 2033 "%p found queued msg %p [type=%s len=%d]\n", 2034 this, mCurrentOut, msgNames[msgType], mCurrentOut->Length())); 2035 2036 mCurrentOutSent = 0; 2037 mHdrOut = mOutHeader; 2038 2039 uint8_t maskBit = mIsServerSide ? 0 : kMaskBit; 2040 uint8_t maskSize = mIsServerSide ? 0 : 4; 2041 2042 uint8_t* payload = nullptr; 2043 2044 if (msgType == kMsgTypeFin) { 2045 // This is a demand to create a close message 2046 if (mClientClosed) { 2047 DeleteCurrentOutGoingMessage(); 2048 PrimeNewOutgoingMessage(); 2049 cleanupAfterFailure.release(); 2050 return; 2051 } 2052 2053 mClientClosed = true; 2054 mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_CLOSE; 2055 mOutHeader[1] = maskBit; 2056 2057 // payload is offset 2 plus size of the mask 2058 payload = mOutHeader + 2 + maskSize; 2059 2060 // The close reason code sits in the first 2 bytes of payload 2061 // If the channel user provided a code and reason during Close() 2062 // and there isn't an internal error, use that. 2063 if (NS_SUCCEEDED(mStopOnClose)) { 2064 MutexAutoLock lock(mMutex); 2065 if (mScriptCloseCode) { 2066 NetworkEndian::writeUint16(payload, mScriptCloseCode); 2067 mOutHeader[1] += 2; 2068 mHdrOutToSend = 4 + maskSize; 2069 if (!mScriptCloseReason.IsEmpty()) { 2070 MOZ_ASSERT(mScriptCloseReason.Length() <= 123, 2071 "Close Reason Too Long"); 2072 mOutHeader[1] += mScriptCloseReason.Length(); 2073 mHdrOutToSend += mScriptCloseReason.Length(); 2074 memcpy(payload + 2, mScriptCloseReason.BeginReading(), 2075 mScriptCloseReason.Length()); 2076 } 2077 } else { 2078 // No close code/reason, so payload length = 0. We must still send mask 2079 // even though it's not used. Keep payload offset so we write mask 2080 // below. 2081 mHdrOutToSend = 2 + maskSize; 2082 } 2083 } else { 2084 NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose)); 2085 mOutHeader[1] += 2; 2086 mHdrOutToSend = 4 + maskSize; 2087 } 2088 2089 if (mServerClosed) { 2090 /* bidi close complete */ 2091 mReleaseOnTransmit = 1; 2092 } else if (NS_FAILED(mStopOnClose)) { 2093 /* result of abort session - give up */ 2094 StopSession(mStopOnClose); 2095 } else { 2096 /* wait for reciprocal close from server */ 2097 rv = NS_NewTimerWithCallback(getter_AddRefs(mCloseTimer), this, 2098 mCloseTimeout, nsITimer::TYPE_ONE_SHOT); 2099 if (NS_FAILED(rv)) { 2100 StopSession(rv); 2101 } 2102 } 2103 } else { 2104 switch (msgType) { 2105 case kMsgTypePong: 2106 mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PONG; 2107 break; 2108 case kMsgTypePing: 2109 mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_PING; 2110 break; 2111 case kMsgTypeString: 2112 mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_TEXT; 2113 break; 2114 case kMsgTypeStream: 2115 // HACK ALERT: read in entire stream into string. 2116 // Will block socket transport thread if file is blocking. 2117 // TODO: bug 704447: don't block socket thread! 2118 rv = mCurrentOut->ConvertStreamToString(); 2119 if (NS_FAILED(rv)) { 2120 AbortSession(NS_ERROR_FILE_TOO_BIG); 2121 return; 2122 } 2123 // Now we're a binary string 2124 msgType = kMsgTypeBinaryString; 2125 2126 // no break: fall down into binary string case 2127 [[fallthrough]]; 2128 2129 case kMsgTypeBinaryString: 2130 mOutHeader[0] = kFinalFragBit | nsIWebSocketFrame::OPCODE_BINARY; 2131 break; 2132 case kMsgTypeFin: 2133 MOZ_ASSERT(false, "unreachable"); // avoid compiler warning 2134 break; 2135 } 2136 2137 // deflate the payload if PMCE is negotiated 2138 MutexAutoLock lock(mCompressorMutex); 2139 if (mPMCECompressor && 2140 (msgType == kMsgTypeString || msgType == kMsgTypeBinaryString)) { 2141 if (mCurrentOut->DeflatePayload(mPMCECompressor.get())) { 2142 // The payload was deflated successfully, set RSV1 bit 2143 mOutHeader[0] |= kRsv1Bit; 2144 2145 LOG( 2146 ("WebSocketChannel::PrimeNewOutgoingMessage %p current msg %p was " 2147 "deflated [origLength=%d, newLength=%d].\n", 2148 this, mCurrentOut, mCurrentOut->OrigLength(), 2149 mCurrentOut->Length())); 2150 } 2151 } 2152 2153 if (mCurrentOut->Length() < 126) { 2154 mOutHeader[1] = mCurrentOut->Length() | maskBit; 2155 mHdrOutToSend = 2 + maskSize; 2156 } else if (mCurrentOut->Length() <= 0xffff) { 2157 mOutHeader[1] = 126 | maskBit; 2158 NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t), 2159 mCurrentOut->Length()); 2160 mHdrOutToSend = 4 + maskSize; 2161 } else { 2162 mOutHeader[1] = 127 | maskBit; 2163 NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length()); 2164 mHdrOutToSend = 10 + maskSize; 2165 } 2166 payload = mOutHeader + mHdrOutToSend; 2167 } 2168 2169 MOZ_ASSERT(payload, "payload offset not found"); 2170 2171 uint32_t mask = 0; 2172 if (!mIsServerSide) { 2173 // Perform the sending mask. Never use a zero mask 2174 do { 2175 static_assert(4 == sizeof(mask), "Size of the mask should be equal to 4"); 2176 nsresult rv = mRandomGenerator->GenerateRandomBytesInto(mask); 2177 if (NS_FAILED(rv)) { 2178 LOG( 2179 ("WebSocketChannel::PrimeNewOutgoingMessage(): " 2180 "GenerateRandomBytes failure %" PRIx32 "\n", 2181 static_cast<uint32_t>(rv))); 2182 AbortSession(rv); 2183 return; 2184 } 2185 } while (!mask); 2186 NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask); 2187 } 2188 2189 LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask)); 2190 2191 // We don't mask the framing, but occasionally we stick a little payload 2192 // data in the buffer used for the framing. Close frames are the current 2193 // example. This data needs to be masked, but it is never more than a 2194 // handful of bytes and might rotate the mask, so we can just do it locally. 2195 // For real data frames we ship the bulk of the payload off to ApplyMask() 2196 2197 RefPtr<WebSocketFrame> frame = mService->CreateFrameIfNeeded( 2198 mOutHeader[0] & WebSocketChannel::kFinalFragBit, 2199 mOutHeader[0] & WebSocketChannel::kRsv1Bit, 2200 mOutHeader[0] & WebSocketChannel::kRsv2Bit, 2201 mOutHeader[0] & WebSocketChannel::kRsv3Bit, 2202 mOutHeader[0] & WebSocketChannel::kOpcodeBitsMask, 2203 mOutHeader[1] & WebSocketChannel::kMaskBit, mask, payload, 2204 mHdrOutToSend - (payload - mOutHeader), mCurrentOut->BeginOrigReading(), 2205 mCurrentOut->OrigLength()); 2206 2207 if (frame) { 2208 mService->FrameSent(mSerial, mInnerWindowID, frame.forget()); 2209 } 2210 2211 if (mask) { 2212 while (payload < (mOutHeader + mHdrOutToSend)) { 2213 *payload ^= mask >> 24; 2214 mask = RotateLeft(mask, 8); 2215 payload++; 2216 } 2217 2218 // Mask the real message payloads 2219 ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length()); 2220 } 2221 2222 int32_t len = mCurrentOut->Length(); 2223 2224 // for small frames, copy it all together for a contiguous write 2225 if (len && len <= kCopyBreak) { 2226 memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len); 2227 mHdrOutToSend += len; 2228 mCurrentOutSent = len; 2229 } 2230 2231 // Transmitting begins - mHdrOutToSend bytes from mOutHeader and 2232 // mCurrentOut->Length() bytes from mCurrentOut. The latter may be 2233 // coaleseced into the former for small messages or as the result of the 2234 // compression process. 2235 2236 cleanupAfterFailure.release(); 2237 } 2238 2239 void WebSocketChannel::DeleteCurrentOutGoingMessage() { 2240 delete mCurrentOut; 2241 mCurrentOut = nullptr; 2242 mCurrentOutSent = 0; 2243 } 2244 2245 void WebSocketChannel::EnsureHdrOut(uint32_t size) { 2246 LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size)); 2247 2248 if (mDynamicOutputSize < size) { 2249 mDynamicOutputSize = size; 2250 mDynamicOutput = (uint8_t*)moz_xrealloc(mDynamicOutput, mDynamicOutputSize); 2251 } 2252 2253 mHdrOut = mDynamicOutput; 2254 } 2255 2256 namespace { 2257 2258 class RemoveObserverRunnable : public Runnable { 2259 RefPtr<WebSocketChannel> mChannel; 2260 2261 public: 2262 explicit RemoveObserverRunnable(WebSocketChannel* aChannel) 2263 : Runnable("net::RemoveObserverRunnable"), mChannel(aChannel) {} 2264 2265 NS_IMETHOD Run() override { 2266 nsCOMPtr<nsIObserverService> observerService = 2267 mozilla::services::GetObserverService(); 2268 if (!observerService) { 2269 NS_WARNING("failed to get observer service"); 2270 return NS_OK; 2271 } 2272 2273 observerService->RemoveObserver(mChannel, NS_NETWORK_LINK_TOPIC); 2274 return NS_OK; 2275 } 2276 }; 2277 2278 } // namespace 2279 2280 void WebSocketChannel::CleanupConnection() { 2281 // normally this should be called on socket thread, but it may be called 2282 // on MainThread 2283 2284 LOG(("WebSocketChannel::CleanupConnection() %p", this)); 2285 // This needs to run on the IOThread so we don't need to lock a bunch of these 2286 if (!mIOThread->IsOnCurrentThread()) { 2287 mIOThread->Dispatch( 2288 NewRunnableMethod("net::WebSocketChannel::CleanupConnection", this, 2289 &WebSocketChannel::CleanupConnection), 2290 NS_DISPATCH_NORMAL); 2291 return; 2292 } 2293 2294 if (mLingeringCloseTimer) { 2295 mLingeringCloseTimer->Cancel(); 2296 mLingeringCloseTimer = nullptr; 2297 } 2298 2299 if (mSocketIn) { 2300 if (mDataStarted) { 2301 mSocketIn->AsyncWait(nullptr, 0, 0, nullptr); 2302 } 2303 mSocketIn = nullptr; 2304 } 2305 2306 if (mSocketOut) { 2307 mSocketOut->AsyncWait(nullptr, 0, 0, nullptr); 2308 mSocketOut = nullptr; 2309 } 2310 2311 if (mTransport) { 2312 mTransport->SetSecurityCallbacks(nullptr); 2313 mTransport->SetEventSink(nullptr, nullptr); 2314 mTransport->Close(NS_BASE_STREAM_CLOSED); 2315 mTransport = nullptr; 2316 } 2317 2318 if (mConnection) { 2319 mConnection->Close(); 2320 mConnection = nullptr; 2321 } 2322 2323 if (mConnectionLogService && !mPrivateBrowsing) { 2324 mConnectionLogService->RemoveHost(mHost, mSerial); 2325 } 2326 2327 // The observer has to be removed on the main-thread. 2328 NS_DispatchToMainThread(new RemoveObserverRunnable(this)); 2329 2330 DecrementSessionCount(); 2331 } 2332 2333 void WebSocketChannel::StopSession(nsresult reason) { 2334 LOG(("WebSocketChannel::StopSession() %p [%" PRIx32 "]\n", this, 2335 static_cast<uint32_t>(reason))); 2336 2337 { 2338 MutexAutoLock lock(mMutex); 2339 if (mStopped) { 2340 return; 2341 } 2342 mStopped = true; 2343 } 2344 2345 DoStopSession(reason); 2346 } 2347 2348 void WebSocketChannel::DoStopSession(nsresult reason) { 2349 LOG(("WebSocketChannel::DoStopSession() %p [%" PRIx32 "]\n", this, 2350 static_cast<uint32_t>(reason))); 2351 2352 // normally this should be called on socket thread, but it is ok to call it 2353 // from OnStartRequest before the socket thread machine has gotten underway. 2354 // If mDataStarted is false, this is called on MainThread for Close(). 2355 // Otherwise it should be called on the IO thread 2356 2357 MOZ_ASSERT(mStopped); 2358 MOZ_ASSERT(mIOThread->IsOnCurrentThread() || mTCPClosed || !mDataStarted); 2359 2360 if (!mOpenedHttpChannel) { 2361 // The HTTP channel information will never be used in this case 2362 NS_ReleaseOnMainThread("WebSocketChannel::mChannel", mChannel.forget()); 2363 NS_ReleaseOnMainThread("WebSocketChannel::mHttpChannel", 2364 mHttpChannel.forget()); 2365 NS_ReleaseOnMainThread("WebSocketChannel::mLoadGroup", mLoadGroup.forget()); 2366 NS_ReleaseOnMainThread("WebSocketChannel::mCallbacks", mCallbacks.forget()); 2367 } 2368 2369 if (mCloseTimer) { 2370 mCloseTimer->Cancel(); 2371 mCloseTimer = nullptr; 2372 } 2373 2374 // mOpenTimer must be null if mDataStarted is true and we're not on MainThread 2375 if (mOpenTimer) { 2376 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 2377 mOpenTimer->Cancel(); 2378 mOpenTimer = nullptr; 2379 } 2380 2381 { 2382 MutexAutoLock lock(mMutex); 2383 if (mReconnectDelayTimer) { 2384 mReconnectDelayTimer->Cancel(); 2385 NS_ReleaseOnMainThread("WebSocketChannel::mMutex", 2386 mReconnectDelayTimer.forget()); 2387 } 2388 } 2389 2390 if (mPingTimer) { 2391 mPingTimer->Cancel(); 2392 mPingTimer = nullptr; 2393 } 2394 2395 if (!mTCPClosed && mDataStarted) { 2396 if (mSocketIn) { 2397 // Drain, within reason, this socket. if we leave any data 2398 // unconsumed (including the tcp fin) a RST will be generated 2399 // The right thing to do here is shutdown(SHUT_WR) and then wait 2400 // a little while to see if any data comes in.. but there is no 2401 // reason to delay things for that when the websocket handshake 2402 // is supposed to guarantee a quiet connection except for that fin. 2403 2404 char buffer[512]; 2405 uint32_t count = 0; 2406 uint32_t total = 0; 2407 nsresult rv; 2408 do { 2409 total += count; 2410 rv = mSocketIn->Read(buffer, 512, &count); 2411 if (rv != NS_BASE_STREAM_WOULD_BLOCK && (NS_FAILED(rv) || count == 0)) { 2412 mTCPClosed = true; 2413 } 2414 } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000); 2415 } else if (mConnection) { 2416 mConnection->DrainSocketData(); 2417 } 2418 } 2419 2420 int32_t sessionCount = kLingeringCloseThreshold; 2421 nsWSAdmissionManager::GetSessionCount(sessionCount); 2422 2423 if (!mTCPClosed && (mTransport || mConnection) && 2424 sessionCount < kLingeringCloseThreshold) { 2425 // 7.1.1 says that the client SHOULD wait for the server to close the TCP 2426 // connection. This is so we can reuse port numbers before 2 MSL expires, 2427 // which is not really as much of a concern for us as the amount of state 2428 // that might be accrued by keeping this channel object around waiting for 2429 // the server. We handle the SHOULD by waiting a short time in the common 2430 // case, but not waiting in the case of high concurrency. 2431 // 2432 // Normally this will be taken care of in AbortSession() after mTCPClosed 2433 // is set when the server close arrives without waiting for the timeout to 2434 // expire. 2435 2436 LOG(("WebSocketChannel::DoStopSession: Wait for Server TCP close")); 2437 2438 nsresult rv; 2439 rv = NS_NewTimerWithCallback(getter_AddRefs(mLingeringCloseTimer), this, 2440 kLingeringCloseTimeout, 2441 nsITimer::TYPE_ONE_SHOT); 2442 if (NS_FAILED(rv)) CleanupConnection(); 2443 } else { 2444 CleanupConnection(); 2445 } 2446 2447 { 2448 MutexAutoLock lock(mMutex); 2449 if (mCancelable) { 2450 mCancelable->Cancel(NS_ERROR_UNEXPECTED); 2451 mCancelable = nullptr; 2452 } 2453 } 2454 2455 { 2456 MutexAutoLock lock(mCompressorMutex); 2457 mPMCECompressor = nullptr; 2458 } 2459 if (!mCalledOnStop) { 2460 mCalledOnStop = true; 2461 2462 nsWSAdmissionManager::OnStopSession(this, reason); 2463 2464 RefPtr<CallOnStop> runnable = new CallOnStop(this, reason); 2465 if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) { 2466 target->Dispatch(runnable, NS_DISPATCH_NORMAL); 2467 } 2468 } 2469 } 2470 2471 // Called from MainThread, and called from IOThread in 2472 // PrimeNewOutgoingMessage 2473 void WebSocketChannel::AbortSession(nsresult reason) { 2474 LOG(("WebSocketChannel::AbortSession() %p [reason %" PRIx32 2475 "] stopped = %d\n", 2476 this, static_cast<uint32_t>(reason), !!mStopped)); 2477 2478 MOZ_ASSERT(NS_FAILED(reason), "reason must be a failure!"); 2479 2480 // normally this should be called on socket thread, but it is ok to call it 2481 // from the main thread before StartWebsocketData() has completed 2482 MOZ_ASSERT(mIOThread->IsOnCurrentThread() || !mDataStarted); 2483 2484 // When we are failing we need to close the TCP connection immediately 2485 // as per 7.1.1 2486 mTCPClosed = true; 2487 2488 if (mLingeringCloseTimer) { 2489 MOZ_ASSERT(mStopped, "Lingering without Stop"); 2490 LOG(("WebSocketChannel:: Cleanup connection based on TCP Close")); 2491 CleanupConnection(); 2492 return; 2493 } 2494 2495 { 2496 MutexAutoLock lock(mMutex); 2497 if (mStopped) { 2498 return; 2499 } 2500 2501 if ((mTransport || mConnection) && reason != NS_BASE_STREAM_CLOSED && 2502 !mRequestedClose && !mClientClosed && !mServerClosed && mDataStarted) { 2503 mRequestedClose = true; 2504 mStopOnClose = reason; 2505 mIOThread->Dispatch( 2506 new OutboundEnqueuer(this, 2507 new OutboundMessage(kMsgTypeFin, VoidCString())), 2508 nsIEventTarget::DISPATCH_NORMAL); 2509 return; 2510 } 2511 2512 mStopped = true; 2513 } 2514 2515 DoStopSession(reason); 2516 } 2517 2518 // ReleaseSession is called on orderly shutdown 2519 void WebSocketChannel::ReleaseSession() { 2520 LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n", this, 2521 !!mStopped)); 2522 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 2523 2524 StopSession(NS_OK); 2525 } 2526 2527 void WebSocketChannel::IncrementSessionCount() { 2528 if (!mIncrementedSessionCount) { 2529 nsWSAdmissionManager::IncrementSessionCount(); 2530 mIncrementedSessionCount = true; 2531 } 2532 } 2533 2534 void WebSocketChannel::DecrementSessionCount() { 2535 // Make sure we decrement session count only once, and only if we incremented 2536 // it. This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount 2537 // is atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at 2538 // times when they'll never be a race condition for checking/setting them. 2539 if (mIncrementedSessionCount && !mDecrementedSessionCount) { 2540 nsWSAdmissionManager::DecrementSessionCount(); 2541 mDecrementedSessionCount = true; 2542 } 2543 } 2544 2545 namespace { 2546 enum ExtensionParseMode { eParseServerSide, eParseClientSide }; 2547 } 2548 2549 static nsresult ParseWebSocketExtension(const nsACString& aExtension, 2550 ExtensionParseMode aMode, 2551 bool& aClientNoContextTakeover, 2552 bool& aServerNoContextTakeover, 2553 int32_t& aClientMaxWindowBits, 2554 int32_t& aServerMaxWindowBits) { 2555 nsCCharSeparatedTokenizer tokens(aExtension, ';'); 2556 2557 if (!tokens.hasMoreTokens() || 2558 !tokens.nextToken().EqualsLiteral("permessage-deflate")) { 2559 LOG( 2560 ("WebSocketChannel::ParseWebSocketExtension: " 2561 "HTTP Sec-WebSocket-Extensions negotiated unknown value %s\n", 2562 PromiseFlatCString(aExtension).get())); 2563 return NS_ERROR_ILLEGAL_VALUE; 2564 } 2565 2566 aClientNoContextTakeover = aServerNoContextTakeover = false; 2567 aClientMaxWindowBits = aServerMaxWindowBits = -1; 2568 2569 while (tokens.hasMoreTokens()) { 2570 auto token = tokens.nextToken(); 2571 2572 int32_t nameEnd, valueStart; 2573 int32_t delimPos = token.FindChar('='); 2574 if (delimPos == kNotFound) { 2575 nameEnd = token.Length(); 2576 valueStart = token.Length(); 2577 } else { 2578 nameEnd = delimPos; 2579 valueStart = delimPos + 1; 2580 } 2581 2582 auto paramName = Substring(token, 0, nameEnd); 2583 auto paramValue = Substring(token, valueStart); 2584 2585 if (paramName.EqualsLiteral("client_no_context_takeover")) { 2586 if (!paramValue.IsEmpty()) { 2587 LOG( 2588 ("WebSocketChannel::ParseWebSocketExtension: parameter " 2589 "client_no_context_takeover must not have value, found %s\n", 2590 PromiseFlatCString(paramValue).get())); 2591 return NS_ERROR_ILLEGAL_VALUE; 2592 } 2593 if (aClientNoContextTakeover) { 2594 LOG( 2595 ("WebSocketChannel::ParseWebSocketExtension: found multiple " 2596 "parameters client_no_context_takeover\n")); 2597 return NS_ERROR_ILLEGAL_VALUE; 2598 } 2599 aClientNoContextTakeover = true; 2600 } else if (paramName.EqualsLiteral("server_no_context_takeover")) { 2601 if (!paramValue.IsEmpty()) { 2602 LOG( 2603 ("WebSocketChannel::ParseWebSocketExtension: parameter " 2604 "server_no_context_takeover must not have value, found %s\n", 2605 PromiseFlatCString(paramValue).get())); 2606 return NS_ERROR_ILLEGAL_VALUE; 2607 } 2608 if (aServerNoContextTakeover) { 2609 LOG( 2610 ("WebSocketChannel::ParseWebSocketExtension: found multiple " 2611 "parameters server_no_context_takeover\n")); 2612 return NS_ERROR_ILLEGAL_VALUE; 2613 } 2614 aServerNoContextTakeover = true; 2615 } else if (paramName.EqualsLiteral("client_max_window_bits")) { 2616 if (aClientMaxWindowBits != -1) { 2617 LOG( 2618 ("WebSocketChannel::ParseWebSocketExtension: found multiple " 2619 "parameters client_max_window_bits\n")); 2620 return NS_ERROR_ILLEGAL_VALUE; 2621 } 2622 2623 if (aMode == eParseServerSide && paramValue.IsEmpty()) { 2624 // Use -2 to indicate that "client_max_window_bits" has been parsed, 2625 // but had no value. 2626 aClientMaxWindowBits = -2; 2627 } else { 2628 nsresult errcode; 2629 aClientMaxWindowBits = 2630 PromiseFlatCString(paramValue).ToInteger(&errcode); 2631 if (NS_FAILED(errcode) || aClientMaxWindowBits < 8 || 2632 aClientMaxWindowBits > 15) { 2633 LOG( 2634 ("WebSocketChannel::ParseWebSocketExtension: found invalid " 2635 "parameter client_max_window_bits %s\n", 2636 PromiseFlatCString(paramValue).get())); 2637 return NS_ERROR_ILLEGAL_VALUE; 2638 } 2639 } 2640 } else if (paramName.EqualsLiteral("server_max_window_bits")) { 2641 if (aServerMaxWindowBits != -1) { 2642 LOG( 2643 ("WebSocketChannel::ParseWebSocketExtension: found multiple " 2644 "parameters server_max_window_bits\n")); 2645 return NS_ERROR_ILLEGAL_VALUE; 2646 } 2647 2648 nsresult errcode; 2649 aServerMaxWindowBits = PromiseFlatCString(paramValue).ToInteger(&errcode); 2650 if (NS_FAILED(errcode) || aServerMaxWindowBits < 8 || 2651 aServerMaxWindowBits > 15) { 2652 LOG( 2653 ("WebSocketChannel::ParseWebSocketExtension: found invalid " 2654 "parameter server_max_window_bits %s\n", 2655 PromiseFlatCString(paramValue).get())); 2656 return NS_ERROR_ILLEGAL_VALUE; 2657 } 2658 } else { 2659 LOG( 2660 ("WebSocketChannel::ParseWebSocketExtension: found unknown " 2661 "parameter %s\n", 2662 PromiseFlatCString(paramName).get())); 2663 return NS_ERROR_ILLEGAL_VALUE; 2664 } 2665 } 2666 2667 if (aClientMaxWindowBits == -2) { 2668 aClientMaxWindowBits = -1; 2669 } 2670 2671 return NS_OK; 2672 } 2673 2674 nsresult WebSocketChannel::HandleExtensions() { 2675 LOG(("WebSocketChannel::HandleExtensions() %p\n", this)); 2676 2677 nsresult rv; 2678 nsAutoCString extensions; 2679 2680 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 2681 2682 rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Extensions"_ns, 2683 extensions); 2684 extensions.CompressWhitespace(); 2685 if (extensions.IsEmpty()) { 2686 return NS_OK; 2687 } 2688 2689 LOG( 2690 ("WebSocketChannel::HandleExtensions: received " 2691 "Sec-WebSocket-Extensions header: %s\n", 2692 extensions.get())); 2693 2694 bool clientNoContextTakeover; 2695 bool serverNoContextTakeover; 2696 int32_t clientMaxWindowBits; 2697 int32_t serverMaxWindowBits; 2698 2699 rv = ParseWebSocketExtension(extensions, eParseClientSide, 2700 clientNoContextTakeover, serverNoContextTakeover, 2701 clientMaxWindowBits, serverMaxWindowBits); 2702 if (NS_FAILED(rv)) { 2703 AbortSession(rv); 2704 return rv; 2705 } 2706 2707 if (clientMaxWindowBits == -1) { 2708 clientMaxWindowBits = 15; 2709 } 2710 if (serverMaxWindowBits == -1) { 2711 serverMaxWindowBits = 15; 2712 } 2713 2714 MutexAutoLock lock(mCompressorMutex); 2715 mPMCECompressor = MakeUnique<PMCECompression>( 2716 clientNoContextTakeover, clientMaxWindowBits, serverMaxWindowBits); 2717 if (mPMCECompressor->Active()) { 2718 LOG( 2719 ("WebSocketChannel::HandleExtensions: PMCE negotiated, %susing " 2720 "context takeover, clientMaxWindowBits=%d, " 2721 "serverMaxWindowBits=%d\n", 2722 clientNoContextTakeover ? "NOT " : "", clientMaxWindowBits, 2723 serverMaxWindowBits)); 2724 2725 mNegotiatedExtensions = "permessage-deflate"; 2726 } else { 2727 LOG( 2728 ("WebSocketChannel::HandleExtensions: Cannot init PMCE " 2729 "compression object\n")); 2730 mPMCECompressor = nullptr; 2731 AbortSession(NS_ERROR_UNEXPECTED); 2732 return NS_ERROR_UNEXPECTED; 2733 } 2734 2735 return NS_OK; 2736 } 2737 2738 void ProcessServerWebSocketExtensions(const nsACString& aExtensions, 2739 nsACString& aNegotiatedExtensions) { 2740 aNegotiatedExtensions.Truncate(); 2741 2742 for (const auto& ext : 2743 nsCCharSeparatedTokenizer(aExtensions, ',').ToRange()) { 2744 bool clientNoContextTakeover; 2745 bool serverNoContextTakeover; 2746 int32_t clientMaxWindowBits; 2747 int32_t serverMaxWindowBits; 2748 2749 nsresult rv = ParseWebSocketExtension( 2750 ext, eParseServerSide, clientNoContextTakeover, serverNoContextTakeover, 2751 clientMaxWindowBits, serverMaxWindowBits); 2752 if (NS_FAILED(rv)) { 2753 // Ignore extensions that we can't parse 2754 continue; 2755 } 2756 2757 aNegotiatedExtensions.AssignLiteral("permessage-deflate"); 2758 if (clientNoContextTakeover) { 2759 aNegotiatedExtensions.AppendLiteral(";client_no_context_takeover"); 2760 } 2761 if (serverNoContextTakeover) { 2762 aNegotiatedExtensions.AppendLiteral(";server_no_context_takeover"); 2763 } 2764 if (clientMaxWindowBits != -1) { 2765 aNegotiatedExtensions.AppendLiteral(";client_max_window_bits="); 2766 aNegotiatedExtensions.AppendInt(clientMaxWindowBits); 2767 } 2768 if (serverMaxWindowBits != -1) { 2769 aNegotiatedExtensions.AppendLiteral(";server_max_window_bits="); 2770 aNegotiatedExtensions.AppendInt(serverMaxWindowBits); 2771 } 2772 2773 return; 2774 } 2775 } 2776 2777 nsresult CalculateWebSocketHashedSecret(const nsACString& aKey, 2778 nsACString& aHash) { 2779 nsresult rv; 2780 nsCString key = aKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"_ns; 2781 nsCOMPtr<nsICryptoHash> hasher = 2782 do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); 2783 NS_ENSURE_SUCCESS(rv, rv); 2784 rv = hasher->Init(nsICryptoHash::SHA1); 2785 NS_ENSURE_SUCCESS(rv, rv); 2786 rv = hasher->Update((const uint8_t*)key.BeginWriting(), key.Length()); 2787 NS_ENSURE_SUCCESS(rv, rv); 2788 return hasher->Finish(true, aHash); 2789 } 2790 2791 nsresult WebSocketChannel::SetupRequest() { 2792 LOG(("WebSocketChannel::SetupRequest() %p\n", this)); 2793 2794 nsresult rv; 2795 2796 if (mLoadGroup) { 2797 rv = mHttpChannel->SetLoadGroup(mLoadGroup); 2798 NS_ENSURE_SUCCESS(rv, rv); 2799 } 2800 2801 rv = mHttpChannel->SetLoadFlags( 2802 nsIRequest::LOAD_BACKGROUND | nsIRequest::INHIBIT_CACHING | 2803 nsIRequest::LOAD_BYPASS_CACHE | nsIChannel::LOAD_BYPASS_SERVICE_WORKER); 2804 NS_ENSURE_SUCCESS(rv, rv); 2805 2806 // we never let websockets be blocked by head CSS/JS loads to avoid 2807 // potential deadlock where server generation of CSS/JS requires 2808 // an XHR signal. 2809 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(mChannel)); 2810 if (cos) { 2811 cos->AddClassFlags(nsIClassOfService::Unblocked); 2812 } 2813 2814 // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket 2815 // in lower case, so go with that. It is technically case insensitive. 2816 rv = mChannel->HTTPUpgrade("websocket"_ns, this); 2817 NS_ENSURE_SUCCESS(rv, rv); 2818 2819 rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Version"_ns, 2820 nsLiteralCString(SEC_WEBSOCKET_VERSION), 2821 false); 2822 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2823 2824 if (!mOrigin.IsEmpty()) { 2825 rv = mHttpChannel->SetRequestHeader("Origin"_ns, mOrigin, false); 2826 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2827 } 2828 2829 if (!mProtocol.IsEmpty()) { 2830 rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Protocol"_ns, mProtocol, 2831 true); 2832 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2833 } 2834 2835 rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Extensions"_ns, 2836 "permessage-deflate"_ns, false); 2837 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2838 2839 uint8_t* secKey; 2840 nsAutoCString secKeyString; 2841 2842 rv = mRandomGenerator->GenerateRandomBytes(16, &secKey); 2843 NS_ENSURE_SUCCESS(rv, rv); 2844 rv = Base64Encode(reinterpret_cast<const char*>(secKey), 16, secKeyString); 2845 free(secKey); 2846 if (NS_FAILED(rv)) { 2847 return rv; 2848 } 2849 2850 rv = mHttpChannel->SetRequestHeader("Sec-WebSocket-Key"_ns, secKeyString, 2851 false); 2852 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2853 LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get())); 2854 2855 // prepare the value we expect to see in 2856 // the sec-websocket-accept response header 2857 rv = CalculateWebSocketHashedSecret(secKeyString, mHashedSecret); 2858 NS_ENSURE_SUCCESS(rv, rv); 2859 LOG(("WebSocketChannel::SetupRequest: expected server key %s\n", 2860 mHashedSecret.get())); 2861 2862 mHttpChannelId = mHttpChannel->ChannelId(); 2863 2864 return NS_OK; 2865 } 2866 2867 nsresult WebSocketChannel::DoAdmissionDNS() { 2868 nsresult rv; 2869 2870 nsCString hostName; 2871 rv = mURI->GetHost(hostName); 2872 NS_ENSURE_SUCCESS(rv, rv); 2873 mAddress = hostName; 2874 nsCString path; 2875 rv = mURI->GetFilePath(path); 2876 NS_ENSURE_SUCCESS(rv, rv); 2877 mPath = path; 2878 rv = mURI->GetPort(&mPort); 2879 NS_ENSURE_SUCCESS(rv, rv); 2880 if (mPort == -1) mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort); 2881 nsCOMPtr<nsIDNSService> dns; 2882 dns = mozilla::components::DNS::Service(&rv); 2883 NS_ENSURE_SUCCESS(rv, rv); 2884 nsCOMPtr<nsIEventTarget> main = GetMainThreadSerialEventTarget(); 2885 nsCOMPtr<nsICancelable> cancelable; 2886 rv = dns->AsyncResolveNative(hostName, nsIDNSService::RESOLVE_TYPE_DEFAULT, 2887 nsIDNSService::RESOLVE_DEFAULT_FLAGS, nullptr, 2888 this, main, mLoadInfo->GetOriginAttributes(), 2889 getter_AddRefs(cancelable)); 2890 if (NS_FAILED(rv)) { 2891 return rv; 2892 } 2893 2894 MutexAutoLock lock(mMutex); 2895 MOZ_ASSERT(!mCancelable); 2896 mCancelable = std::move(cancelable); 2897 return rv; 2898 } 2899 2900 nsresult WebSocketChannel::ApplyForAdmission() { 2901 LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this)); 2902 2903 // Websockets has a policy of 1 session at a time being allowed in the 2904 // CONNECTING state per server IP address (not hostname) 2905 2906 // Check to see if a proxy is being used before making DNS call 2907 nsCOMPtr<nsIProtocolProxyService> pps; 2908 pps = mozilla::components::ProtocolProxy::Service(); 2909 2910 if (!pps) { 2911 // go straight to DNS 2912 // expect the callback in ::OnLookupComplete 2913 LOG(( 2914 "WebSocketChannel::ApplyForAdmission: checking for concurrent open\n")); 2915 return DoAdmissionDNS(); 2916 } 2917 2918 nsresult rv; 2919 nsCOMPtr<nsICancelable> cancelable; 2920 rv = pps->AsyncResolve( 2921 mHttpChannel, 2922 nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY | 2923 nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | 2924 nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, 2925 this, nullptr, getter_AddRefs(cancelable)); 2926 2927 MutexAutoLock lock(mMutex); 2928 MOZ_ASSERT(!mCancelable); 2929 mCancelable = std::move(cancelable); 2930 return rv; 2931 } 2932 2933 // Called after both OnStartRequest and OnTransportAvailable have 2934 // executed. This essentially ends the handshake and starts the websockets 2935 // protocol state machine. 2936 nsresult WebSocketChannel::CallStartWebsocketData() { 2937 LOG(("WebSocketChannel::CallStartWebsocketData() %p", this)); 2938 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 2939 2940 if (mOpenTimer) { 2941 mOpenTimer->Cancel(); 2942 mOpenTimer = nullptr; 2943 } 2944 2945 nsCOMPtr<nsIEventTarget> target = GetTargetThread(); 2946 if (target && !target->IsOnCurrentThread()) { 2947 return target->Dispatch( 2948 NewRunnableMethod("net::WebSocketChannel::StartWebsocketData", this, 2949 &WebSocketChannel::StartWebsocketData), 2950 NS_DISPATCH_NORMAL); 2951 } 2952 2953 return StartWebsocketData(); 2954 } 2955 2956 nsresult WebSocketChannel::StartWebsocketData() { 2957 { 2958 MutexAutoLock lock(mMutex); 2959 LOG(("WebSocketChannel::StartWebsocketData() %p", this)); 2960 MOZ_ASSERT(!mDataStarted, "StartWebsocketData twice"); 2961 2962 if (mStopped) { 2963 LOG( 2964 ("WebSocketChannel::StartWebsocketData channel already closed, not " 2965 "starting data")); 2966 return NS_ERROR_NOT_AVAILABLE; 2967 } 2968 } 2969 2970 RefPtr<WebSocketChannel> self = this; 2971 mIOThread->Dispatch(NS_NewRunnableFunction( 2972 "WebSocketChannel::StartWebsocketData", [self{std::move(self)}] { 2973 LOG(("WebSocketChannel::DoStartWebsocketData() %p", self.get())); 2974 2975 NS_DispatchToMainThread( 2976 NewRunnableMethod("net::WebSocketChannel::NotifyOnStart", self, 2977 &WebSocketChannel::NotifyOnStart), 2978 NS_DISPATCH_NORMAL); 2979 2980 nsresult rv = self->mConnection ? self->mConnection->StartReading() 2981 : self->mSocketIn->AsyncWait( 2982 self, 0, 0, self->mIOThread); 2983 if (NS_FAILED(rv)) { 2984 self->AbortSession(rv); 2985 } 2986 2987 if (self->mPingInterval) { 2988 rv = self->StartPinging(); 2989 if (NS_FAILED(rv)) { 2990 LOG(( 2991 "WebSocketChannel::StartWebsocketData Could not start pinging, " 2992 "rv=0x%08" PRIx32, 2993 static_cast<uint32_t>(rv))); 2994 self->AbortSession(rv); 2995 } 2996 } 2997 })); 2998 2999 return NS_OK; 3000 } 3001 3002 void WebSocketChannel::NotifyOnStart() { 3003 LOG(("WebSocketChannel::NotifyOnStart Notifying Listener %p", 3004 mListenerMT ? mListenerMT->mListener.get() : nullptr)); 3005 mDataStarted = true; 3006 if (mListenerMT) { 3007 nsresult rv = mListenerMT->mListener->OnStart(mListenerMT->mContext); 3008 if (NS_FAILED(rv)) { 3009 LOG( 3010 ("WebSocketChannel::NotifyOnStart " 3011 "mListenerMT->mListener->OnStart() failed with error 0x%08" PRIx32, 3012 static_cast<uint32_t>(rv))); 3013 } 3014 } 3015 } 3016 3017 nsresult WebSocketChannel::StartPinging() { 3018 LOG(("WebSocketChannel::StartPinging() %p", this)); 3019 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 3020 MOZ_ASSERT(mPingInterval); 3021 MOZ_ASSERT(!mPingTimer); 3022 3023 nsresult rv; 3024 rv = NS_NewTimerWithCallback(getter_AddRefs(mPingTimer), this, mPingInterval, 3025 nsITimer::TYPE_ONE_SHOT); 3026 if (NS_SUCCEEDED(rv)) { 3027 LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n", 3028 (uint32_t)mPingInterval)); 3029 } else { 3030 NS_WARNING("unable to create ping timer. Carrying on."); 3031 } 3032 3033 return NS_OK; 3034 } 3035 3036 void WebSocketChannel::ReportConnectionTelemetry(nsresult aStatusCode) { 3037 // 3 bits are used. high bit is for wss, middle bit for failed, 3038 // and low bit for proxy.. 3039 // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy, 3040 // wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy 3041 3042 bool didProxy = false; 3043 3044 nsCOMPtr<nsIProxyInfo> pi; 3045 nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel); 3046 if (pc) pc->GetProxyInfo(getter_AddRefs(pi)); 3047 if (pi) { 3048 nsAutoCString proxyType; 3049 pi->GetType(proxyType); 3050 if (!proxyType.IsEmpty() && !proxyType.EqualsLiteral("direct")) { 3051 didProxy = true; 3052 } 3053 } 3054 3055 uint8_t value = 3056 (mEncrypted ? (1 << 2) : 0) | 3057 (!(mGotUpgradeOK && NS_SUCCEEDED(aStatusCode)) ? (1 << 1) : 0) | 3058 (didProxy ? (1 << 0) : 0); 3059 3060 LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value)); 3061 glean::websockets::handshake_type.AccumulateSingleSample(value); 3062 } 3063 3064 // nsIDNSListener 3065 3066 NS_IMETHODIMP 3067 WebSocketChannel::OnLookupComplete(nsICancelable* aRequest, 3068 nsIDNSRecord* aRecord, nsresult aStatus) { 3069 LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %" PRIx32 "]\n", this, 3070 aRequest, aRecord, static_cast<uint32_t>(aStatus))); 3071 3072 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3073 3074 { 3075 MutexAutoLock lock(mMutex); 3076 mCancelable = nullptr; 3077 } 3078 3079 if (mStopped) { 3080 LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n")); 3081 return NS_OK; 3082 } 3083 3084 // These failures are not fatal - we just use the hostname as the key 3085 if (NS_FAILED(aStatus)) { 3086 LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n")); 3087 3088 // set host in case we got here without calling DoAdmissionDNS() 3089 mURI->GetHost(mAddress); 3090 } else { 3091 nsCOMPtr<nsIDNSAddrRecord> record = do_QueryInterface(aRecord); 3092 MOZ_ASSERT(record); 3093 nsresult rv = record->GetNextAddrAsString(mAddress); 3094 if (NS_FAILED(rv)) { 3095 LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n")); 3096 } 3097 } 3098 3099 LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n")); 3100 nsWSAdmissionManager::ConditionallyConnect(this); 3101 3102 return NS_OK; 3103 } 3104 3105 // nsIProtocolProxyCallback 3106 NS_IMETHODIMP 3107 WebSocketChannel::OnProxyAvailable(nsICancelable* aRequest, 3108 nsIChannel* aChannel, nsIProxyInfo* pi, 3109 nsresult status) { 3110 { 3111 MutexAutoLock lock(mMutex); 3112 MOZ_ASSERT(!mCancelable || (aRequest == mCancelable)); 3113 mCancelable = nullptr; 3114 } 3115 3116 if (mStopped) { 3117 LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n", 3118 this)); 3119 return NS_OK; 3120 } 3121 3122 nsAutoCString type; 3123 if (NS_SUCCEEDED(status) && pi && NS_SUCCEEDED(pi->GetType(type)) && 3124 !type.EqualsLiteral("direct")) { 3125 LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n", 3126 this)); 3127 // call DNS callback directly without DNS resolver 3128 OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE); 3129 } else { 3130 LOG(("WebSocketChannel::OnProxyAvailable[%p] checking DNS resolution\n", 3131 this)); 3132 nsresult rv = DoAdmissionDNS(); 3133 if (NS_FAILED(rv)) { 3134 LOG(("WebSocket OnProxyAvailable [%p] DNS lookup failed\n", this)); 3135 // call DNS callback directly without DNS resolver 3136 OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE); 3137 } 3138 } 3139 3140 // notify listener of OnProxyAvailable 3141 LOG(("WebSocketChannel::OnProxyAvailable Notifying Listener %p", 3142 mListenerMT ? mListenerMT->mListener.get() : nullptr)); 3143 nsresult rv; 3144 nsCOMPtr<nsIProtocolProxyCallback> ppc( 3145 do_QueryInterface(mListenerMT->mListener, &rv)); 3146 if (NS_SUCCEEDED(rv)) { 3147 rv = ppc->OnProxyAvailable(aRequest, aChannel, pi, status); 3148 if (NS_FAILED(rv)) { 3149 LOG( 3150 ("WebSocketChannel::OnProxyAvailable notify" 3151 " failed with error 0x%08" PRIx32, 3152 static_cast<uint32_t>(rv))); 3153 } 3154 } 3155 3156 return NS_OK; 3157 } 3158 3159 // nsIInterfaceRequestor 3160 3161 NS_IMETHODIMP 3162 WebSocketChannel::GetInterface(const nsIID& iid, void** result) { 3163 LOG(("WebSocketChannel::GetInterface() %p\n", this)); 3164 3165 if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) { 3166 return QueryInterface(iid, result); 3167 } 3168 3169 if (mCallbacks) return mCallbacks->GetInterface(iid, result); 3170 3171 return NS_ERROR_NO_INTERFACE; 3172 } 3173 3174 // nsIChannelEventSink 3175 3176 NS_IMETHODIMP 3177 WebSocketChannel::AsyncOnChannelRedirect( 3178 nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags, 3179 nsIAsyncVerifyRedirectCallback* callback) { 3180 LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this)); 3181 3182 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3183 3184 nsresult rv; 3185 3186 nsCOMPtr<nsIURI> newuri; 3187 rv = newChannel->GetURI(getter_AddRefs(newuri)); 3188 NS_ENSURE_SUCCESS(rv, rv); 3189 3190 // newuri is expected to be http or https 3191 bool newuriIsHttps = newuri->SchemeIs("https"); 3192 3193 // allow insecure->secure redirects for HTTP Strict Transport Security (from 3194 // ws://FOO to https://FOO (mapped to wss://FOO) 3195 if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL | 3196 nsIChannelEventSink::REDIRECT_STS_UPGRADE))) { 3197 nsAutoCString newSpec; 3198 rv = newuri->GetSpec(newSpec); 3199 NS_ENSURE_SUCCESS(rv, rv); 3200 3201 LOG(("WebSocketChannel: Redirect to %s denied by configuration\n", 3202 newSpec.get())); 3203 return NS_ERROR_FAILURE; 3204 } 3205 3206 if (mEncrypted && !newuriIsHttps) { 3207 nsAutoCString spec; 3208 if (NS_SUCCEEDED(newuri->GetSpec(spec))) { 3209 LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n", 3210 spec.get())); 3211 } 3212 return NS_ERROR_FAILURE; 3213 } 3214 3215 nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv); 3216 if (NS_FAILED(rv)) { 3217 LOG(("WebSocketChannel: Redirect could not QI to HTTP\n")); 3218 return rv; 3219 } 3220 3221 nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel = 3222 do_QueryInterface(newChannel, &rv); 3223 3224 if (NS_FAILED(rv)) { 3225 LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n")); 3226 return rv; 3227 } 3228 3229 // The redirect is likely OK 3230 3231 newChannel->SetNotificationCallbacks(this); 3232 3233 mEncrypted = newuriIsHttps; 3234 rv = NS_MutateURI(newuri) 3235 .SetScheme(mEncrypted ? "wss"_ns : "ws"_ns) 3236 .Finalize(mURI); 3237 3238 if (NS_FAILED(rv)) { 3239 LOG(("WebSocketChannel: Could not set the proper scheme\n")); 3240 return rv; 3241 } 3242 3243 mHttpChannel = newHttpChannel; 3244 mChannel = newUpgradeChannel; 3245 rv = SetupRequest(); 3246 if (NS_FAILED(rv)) { 3247 LOG(("WebSocketChannel: Redirect could not SetupRequest()\n")); 3248 return rv; 3249 } 3250 3251 // Redirected-to URI may need to be delayed by 1-connecting-per-host and 3252 // delay-after-fail algorithms. So hold off calling OnRedirectVerifyCallback 3253 // until BeginOpen, when we know it's OK to proceed with new channel. 3254 mRedirectCallback = callback; 3255 3256 // Mark old channel as successfully connected so we'll clear any FailDelay 3257 // associated with the old URI. Note: no need to also call OnStopSession: 3258 // it's a no-op for successful, already-connected channels. 3259 nsWSAdmissionManager::OnConnected(this); 3260 3261 // ApplyForAdmission as if we were starting from fresh... 3262 mAddress.Truncate(); 3263 mOpenedHttpChannel = false; 3264 rv = ApplyForAdmission(); 3265 if (NS_FAILED(rv)) { 3266 LOG(("WebSocketChannel: Redirect failed due to DNS failure\n")); 3267 mRedirectCallback = nullptr; 3268 return rv; 3269 } 3270 3271 return NS_OK; 3272 } 3273 3274 // nsITimerCallback 3275 3276 NS_IMETHODIMP 3277 WebSocketChannel::Notify(nsITimer* timer) { 3278 LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer)); 3279 3280 if (timer == mCloseTimer) { 3281 MOZ_ASSERT(mClientClosed, "Close Timeout without local close"); 3282 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 3283 3284 mCloseTimer = nullptr; 3285 if (mStopped || mServerClosed) { /* no longer relevant */ 3286 return NS_OK; 3287 } 3288 3289 LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n")); 3290 AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL); 3291 } else if (timer == mOpenTimer) { 3292 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3293 3294 mOpenTimer = nullptr; 3295 LOG(("WebSocketChannel:: Connection Timed Out\n")); 3296 if (mStopped || mServerClosed) { /* no longer relevant */ 3297 return NS_OK; 3298 } 3299 3300 AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL); 3301 MOZ_PUSH_IGNORE_THREAD_SAFETY 3302 // mReconnectDelayTimer is only modified on MainThread, we can read it 3303 // without a lock, but ONLY if we're on MainThread! And if we're not 3304 // on MainThread, it can't be mReconnectDelayTimer 3305 } else if (NS_IsMainThread() && timer == mReconnectDelayTimer) { 3306 MOZ_POP_THREAD_SAFETY 3307 MOZ_ASSERT(mConnecting == CONNECTING_DELAYED, 3308 "woke up from delay w/o being delayed?"); 3309 3310 { 3311 MutexAutoLock lock(mMutex); 3312 mReconnectDelayTimer = nullptr; 3313 } 3314 LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this)); 3315 BeginOpen(false); 3316 } else if (timer == mPingTimer) { 3317 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 3318 3319 if (mClientClosed || mServerClosed || mRequestedClose) { 3320 // no point in worrying about ping now 3321 mPingTimer = nullptr; 3322 return NS_OK; 3323 } 3324 3325 if (!mPingOutstanding) { 3326 // Ping interval must be non-null or PING was forced by OnNetworkChanged() 3327 MOZ_ASSERT(mPingInterval || mPingForced); 3328 LOG(("nsWebSocketChannel:: Generating Ping\n")); 3329 mPingOutstanding = 1; 3330 mPingForced = false; 3331 mPingTimer->InitWithCallback(this, mPingResponseTimeout, 3332 nsITimer::TYPE_ONE_SHOT); 3333 GeneratePing(); 3334 } else { 3335 LOG(("nsWebSocketChannel:: Timed out Ping\n")); 3336 mPingTimer = nullptr; 3337 AbortSession(NS_ERROR_NET_TIMEOUT_EXTERNAL); 3338 } 3339 } else if (timer == mLingeringCloseTimer) { 3340 LOG(("WebSocketChannel:: Lingering Close Timer")); 3341 CleanupConnection(); 3342 } else { 3343 MOZ_ASSERT(0, "Unknown Timer"); 3344 } 3345 3346 return NS_OK; 3347 } 3348 3349 // nsINamed 3350 3351 NS_IMETHODIMP 3352 WebSocketChannel::GetName(nsACString& aName) { 3353 aName.AssignLiteral("WebSocketChannel"); 3354 return NS_OK; 3355 } 3356 3357 // nsIWebSocketChannel 3358 3359 NS_IMETHODIMP 3360 WebSocketChannel::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { 3361 LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this)); 3362 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3363 3364 *aSecurityInfo = nullptr; 3365 3366 if (mConnection) { 3367 nsresult rv = mConnection->GetSecurityInfo(aSecurityInfo); 3368 if (NS_FAILED(rv)) { 3369 return rv; 3370 } 3371 return NS_OK; 3372 } 3373 3374 if (mTransport) { 3375 nsCOMPtr<nsITLSSocketControl> tlsSocketControl; 3376 nsresult rv = 3377 mTransport->GetTlsSocketControl(getter_AddRefs(tlsSocketControl)); 3378 if (NS_FAILED(rv)) { 3379 return rv; 3380 } 3381 nsCOMPtr<nsITransportSecurityInfo> securityInfo( 3382 do_QueryInterface(tlsSocketControl)); 3383 if (securityInfo) { 3384 securityInfo.forget(aSecurityInfo); 3385 } 3386 } 3387 return NS_OK; 3388 } 3389 3390 NS_IMETHODIMP 3391 WebSocketChannel::AsyncOpen(nsIURI* aURI, const nsACString& aOrigin, 3392 JS::Handle<JS::Value> aOriginAttributes, 3393 uint64_t aInnerWindowID, 3394 nsIWebSocketListener* aListener, 3395 nsISupports* aContext, JSContext* aCx) { 3396 OriginAttributes attrs; 3397 if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { 3398 return NS_ERROR_INVALID_ARG; 3399 } 3400 return AsyncOpenNative(aURI, aOrigin, attrs, aInnerWindowID, aListener, 3401 aContext); 3402 } 3403 3404 NS_IMETHODIMP 3405 WebSocketChannel::AsyncOpenNative(nsIURI* aURI, const nsACString& aOrigin, 3406 const OriginAttributes& aOriginAttributes, 3407 uint64_t aInnerWindowID, 3408 nsIWebSocketListener* aListener, 3409 nsISupports* aContext) { 3410 LOG(("WebSocketChannel::AsyncOpen() %p\n", this)); 3411 3412 aOriginAttributes.CreateSuffix(mOriginSuffix); 3413 3414 if (!NS_IsMainThread()) { 3415 MOZ_ASSERT(false, "not main thread"); 3416 LOG(("WebSocketChannel::AsyncOpen() called off the main thread")); 3417 return NS_ERROR_UNEXPECTED; 3418 } 3419 3420 if ((!aURI && !mIsServerSide) || !aListener) { 3421 LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null")); 3422 return NS_ERROR_UNEXPECTED; 3423 } 3424 3425 if (mListenerMT || mWasOpened) return NS_ERROR_ALREADY_OPENED; 3426 3427 nsresult rv; 3428 3429 // Ensure target thread is set if RetargetDeliveryTo isn't called 3430 { 3431 auto lock = mTargetThread.Lock(); 3432 if (!lock.ref()) { 3433 lock.ref() = GetMainThreadSerialEventTarget(); 3434 } 3435 } 3436 3437 mIOThread = mozilla::components::SocketTransport::Service(&rv); 3438 if (NS_FAILED(rv)) { 3439 NS_WARNING("unable to continue without socket transport service"); 3440 return rv; 3441 } 3442 3443 nsCOMPtr<nsIPrefBranch> prefService; 3444 prefService = mozilla::components::Preferences::Service(); 3445 3446 if (prefService) { 3447 int32_t intpref; 3448 rv = 3449 prefService->GetIntPref("network.websocket.max-message-size", &intpref); 3450 if (NS_SUCCEEDED(rv)) { 3451 mMaxMessageSize = std::clamp(intpref, 1024, INT32_MAX); 3452 } 3453 rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref); 3454 if (NS_SUCCEEDED(rv)) { 3455 mCloseTimeout = std::clamp(intpref, 1, 1800) * 1000; 3456 } 3457 rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref); 3458 if (NS_SUCCEEDED(rv)) { 3459 mOpenTimeout = std::clamp(intpref, 1, 1800) * 1000; 3460 } 3461 rv = prefService->GetIntPref("network.websocket.timeout.ping.request", 3462 &intpref); 3463 if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) { 3464 mPingInterval = std::clamp(intpref, 0, 86400) * 1000; 3465 } 3466 rv = prefService->GetIntPref("network.websocket.timeout.ping.response", 3467 &intpref); 3468 if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) { 3469 mPingResponseTimeout = std::clamp(intpref, 1, 3600) * 1000; 3470 } 3471 rv = prefService->GetIntPref("network.websocket.max-connections", &intpref); 3472 if (NS_SUCCEEDED(rv)) { 3473 mMaxConcurrentConnections = std::clamp(intpref, 1, 0xffff); 3474 } 3475 } 3476 3477 int32_t sessionCount = -1; 3478 nsWSAdmissionManager::GetSessionCount(sessionCount); 3479 if (sessionCount >= 0) { 3480 LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this, 3481 sessionCount, mMaxConcurrentConnections)); 3482 } 3483 3484 if (sessionCount >= mMaxConcurrentConnections) { 3485 LOG(("WebSocketChannel: max concurrency %d exceeded (%d)", 3486 mMaxConcurrentConnections, sessionCount)); 3487 3488 // WebSocket connections are expected to be long lived, so return 3489 // an error here instead of queueing 3490 return NS_ERROR_SOCKET_CREATE_FAILED; 3491 } 3492 3493 mInnerWindowID = aInnerWindowID; 3494 mOriginalURI = aURI; 3495 mURI = mOriginalURI; 3496 mOrigin = aOrigin; 3497 3498 if (mIsServerSide) { 3499 // IncrementSessionCount(); 3500 mWasOpened = 1; 3501 mListenerMT = new ListenerAndContextContainer(aListener, aContext); 3502 rv = mServerTransportProvider->SetListener(this); 3503 MOZ_ASSERT(NS_SUCCEEDED(rv)); 3504 mServerTransportProvider = nullptr; 3505 3506 return NS_OK; 3507 } 3508 3509 mURI->GetHostPort(mHost); 3510 3511 mRandomGenerator = mozilla::components::RandomGenerator::Service(&rv); 3512 if (NS_FAILED(rv)) { 3513 NS_WARNING("unable to continue without random number generator"); 3514 return rv; 3515 } 3516 3517 nsCOMPtr<nsIURI> localURI; 3518 nsCOMPtr<nsIChannel> localChannel; 3519 3520 LOG(("WebSocketChannel::AsyncOpen uri=%s", mURI->GetSpecOrDefault().get())); 3521 3522 rv = NS_MutateURI(mURI) 3523 .SetScheme(mEncrypted ? "https"_ns : "http"_ns) 3524 .Finalize(localURI); 3525 NS_ENSURE_SUCCESS(rv, rv); 3526 3527 nsCOMPtr<nsIIOService> ioService; 3528 ioService = mozilla::components::IO::Service(&rv); 3529 if (NS_FAILED(rv)) { 3530 NS_WARNING("unable to continue without io service"); 3531 return rv; 3532 } 3533 3534 // Ideally we'd call newChannelFromURIWithLoadInfo here, but that doesn't 3535 // allow setting proxy uri/flags 3536 rv = ioService->NewChannelFromURIWithProxyFlags( 3537 localURI, mURI, 3538 nsIProtocolProxyService::RESOLVE_PREFER_SOCKS_PROXY | 3539 nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | 3540 nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, 3541 mLoadInfo->LoadingNode(), mLoadInfo->GetLoadingPrincipal(), 3542 mLoadInfo->TriggeringPrincipal(), mLoadInfo->GetSecurityFlags(), 3543 mLoadInfo->InternalContentPolicyType(), getter_AddRefs(localChannel)); 3544 NS_ENSURE_SUCCESS(rv, rv); 3545 3546 // Please note that we still call SetLoadInfo on the channel because 3547 // we want the same instance of the loadInfo to be set on the channel. 3548 rv = localChannel->SetLoadInfo(mLoadInfo); 3549 NS_ENSURE_SUCCESS(rv, rv); 3550 3551 // Pass most GetInterface() requests through to our instantiator, but handle 3552 // nsIChannelEventSink in this object in order to deal with redirects 3553 localChannel->SetNotificationCallbacks(this); 3554 3555 class MOZ_STACK_CLASS CleanUpOnFailure { 3556 public: 3557 explicit CleanUpOnFailure(WebSocketChannel* aWebSocketChannel) 3558 : mWebSocketChannel(aWebSocketChannel) {} 3559 3560 ~CleanUpOnFailure() { 3561 if (!mWebSocketChannel->mWasOpened) { 3562 mWebSocketChannel->mChannel = nullptr; 3563 mWebSocketChannel->mHttpChannel = nullptr; 3564 } 3565 } 3566 3567 WebSocketChannel* mWebSocketChannel; 3568 }; 3569 3570 CleanUpOnFailure cuof(this); 3571 3572 mChannel = do_QueryInterface(localChannel, &rv); 3573 NS_ENSURE_SUCCESS(rv, rv); 3574 3575 mHttpChannel = do_QueryInterface(localChannel, &rv); 3576 NS_ENSURE_SUCCESS(rv, rv); 3577 3578 rv = SetupRequest(); 3579 if (NS_FAILED(rv)) return rv; 3580 3581 mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel); 3582 3583 if (mConnectionLogService && !mPrivateBrowsing) { 3584 mConnectionLogService->AddHost(mHost, mSerial, 3585 BaseWebSocketChannel::mEncrypted); 3586 } 3587 3588 rv = ApplyForAdmission(); 3589 if (NS_FAILED(rv)) return rv; 3590 3591 // Register for prefs change notifications 3592 nsCOMPtr<nsIObserverService> observerService = 3593 mozilla::services::GetObserverService(); 3594 if (!observerService) { 3595 NS_WARNING("failed to get observer service"); 3596 return NS_ERROR_FAILURE; 3597 } 3598 3599 rv = observerService->AddObserver(this, NS_NETWORK_LINK_TOPIC, false); 3600 if (NS_WARN_IF(NS_FAILED(rv))) { 3601 return rv; 3602 } 3603 3604 // Only set these if the open was successful: 3605 // 3606 mWasOpened = 1; 3607 mListenerMT = new ListenerAndContextContainer(aListener, aContext); 3608 IncrementSessionCount(); 3609 3610 return rv; 3611 } 3612 3613 NS_IMETHODIMP 3614 WebSocketChannel::Close(uint16_t code, const nsACString& reason) { 3615 LOG(("WebSocketChannel::Close() %p\n", this)); 3616 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3617 3618 { 3619 MutexAutoLock lock(mMutex); 3620 3621 if (mRequestedClose) { 3622 return NS_OK; 3623 } 3624 3625 if (mStopped) { 3626 return NS_ERROR_NOT_AVAILABLE; 3627 } 3628 3629 // The API requires the UTF-8 string to be 123 or less bytes 3630 if (reason.Length() > 123) return NS_ERROR_ILLEGAL_VALUE; 3631 3632 mRequestedClose = true; 3633 mScriptCloseReason = reason; 3634 mScriptCloseCode = code; 3635 3636 if (mDataStarted) { 3637 return mIOThread->Dispatch( 3638 new OutboundEnqueuer(this, 3639 new OutboundMessage(kMsgTypeFin, VoidCString())), 3640 nsIEventTarget::DISPATCH_NORMAL); 3641 } 3642 3643 mStopped = true; 3644 } 3645 3646 nsresult rv; 3647 if (code == CLOSE_GOING_AWAY) { 3648 // Not an error: for example, tab has closed or navigated away 3649 LOG(("WebSocketChannel::Close() GOING_AWAY without transport.")); 3650 rv = NS_OK; 3651 } else { 3652 LOG(("WebSocketChannel::Close() without transport - error.")); 3653 rv = NS_ERROR_NOT_CONNECTED; 3654 } 3655 3656 DoStopSession(rv); 3657 return rv; 3658 } 3659 3660 NS_IMETHODIMP 3661 WebSocketChannel::SendMsg(const nsACString& aMsg) { 3662 LOG(("WebSocketChannel::SendMsg() %p\n", this)); 3663 3664 return SendMsgCommon(aMsg, false, aMsg.Length()); 3665 } 3666 3667 NS_IMETHODIMP 3668 WebSocketChannel::SendBinaryMsg(const nsACString& aMsg) { 3669 LOG(("WebSocketChannel::SendBinaryMsg() %p len=%zu\n", this, aMsg.Length())); 3670 return SendMsgCommon(aMsg, true, aMsg.Length()); 3671 } 3672 3673 NS_IMETHODIMP 3674 WebSocketChannel::SendBinaryStream(nsIInputStream* aStream, uint32_t aLength) { 3675 LOG(("WebSocketChannel::SendBinaryStream() %p\n", this)); 3676 3677 return SendMsgCommon(VoidCString(), true, aLength, aStream); 3678 } 3679 3680 nsresult WebSocketChannel::SendMsgCommon(const nsACString& aMsg, bool aIsBinary, 3681 uint32_t aLength, 3682 nsIInputStream* aStream) { 3683 MOZ_ASSERT(IsOnTargetThread(), "not target thread"); 3684 3685 if (!mDataStarted) { 3686 LOG(("WebSocketChannel:: Error: data not started yet\n")); 3687 return NS_ERROR_UNEXPECTED; 3688 } 3689 3690 if (mRequestedClose) { 3691 LOG(("WebSocketChannel:: Error: send when closed\n")); 3692 return NS_ERROR_UNEXPECTED; 3693 } 3694 3695 if (mStopped) { 3696 LOG(("WebSocketChannel:: Error: send when stopped\n")); 3697 return NS_ERROR_NOT_CONNECTED; 3698 } 3699 3700 MOZ_ASSERT(mMaxMessageSize >= 0, "max message size negative"); 3701 if (aLength > static_cast<uint32_t>(mMaxMessageSize)) { 3702 LOG(("WebSocketChannel:: Error: message too big\n")); 3703 return NS_ERROR_FILE_TOO_BIG; 3704 } 3705 3706 if (mConnectionLogService && !mPrivateBrowsing) { 3707 mConnectionLogService->NewMsgSent(mHost, mSerial, aLength); 3708 LOG(("Added new msg sent for %s", mHost.get())); 3709 } 3710 3711 return mIOThread->Dispatch( 3712 aStream 3713 ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength)) 3714 : new OutboundEnqueuer( 3715 this, 3716 new OutboundMessage( 3717 aIsBinary ? kMsgTypeBinaryString : kMsgTypeString, aMsg)), 3718 nsIEventTarget::DISPATCH_NORMAL); 3719 } 3720 3721 // nsIHttpUpgradeListener 3722 3723 NS_IMETHODIMP 3724 WebSocketChannel::OnTransportAvailable(nsISocketTransport* aTransport, 3725 nsIAsyncInputStream* aSocketIn, 3726 nsIAsyncOutputStream* aSocketOut) { 3727 if (!NS_IsMainThread()) { 3728 return NS_DispatchToMainThread( 3729 new CallOnTransportAvailable(this, aTransport, aSocketIn, aSocketOut)); 3730 } 3731 3732 LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n", 3733 this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK)); 3734 3735 if (mStopped) { 3736 LOG(("WebSocketChannel::OnTransportAvailable: Already stopped")); 3737 return NS_OK; 3738 } 3739 3740 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3741 MOZ_ASSERT(!mRecvdHttpUpgradeTransport, "OTA duplicated"); 3742 MOZ_ASSERT(aSocketIn, "OTA with invalid socketIn"); 3743 3744 mTransport = aTransport; 3745 mSocketIn = aSocketIn; 3746 mSocketOut = aSocketOut; 3747 3748 nsresult rv; 3749 rv = mTransport->SetEventSink(nullptr, nullptr); 3750 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 3751 rv = mTransport->SetSecurityCallbacks(this); 3752 if (NS_WARN_IF(NS_FAILED(rv))) return rv; 3753 3754 return OnTransportAvailableInternal(); 3755 } 3756 3757 NS_IMETHODIMP 3758 WebSocketChannel::OnWebSocketConnectionAvailable( 3759 WebSocketConnectionBase* aConnection) { 3760 if (!NS_IsMainThread()) { 3761 RefPtr<WebSocketChannel> self = this; 3762 RefPtr<WebSocketConnectionBase> connection = aConnection; 3763 return NS_DispatchToMainThread(NS_NewRunnableFunction( 3764 "WebSocketChannel::OnWebSocketConnectionAvailable", 3765 [self, connection]() { 3766 self->OnWebSocketConnectionAvailable(connection); 3767 })); 3768 } 3769 3770 LOG( 3771 ("WebSocketChannel::OnWebSocketConnectionAvailable %p [%p] " 3772 "rcvdonstart=%d\n", 3773 this, aConnection, mGotUpgradeOK)); 3774 3775 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3776 MOZ_ASSERT(!mRecvdHttpUpgradeTransport, 3777 "OnWebSocketConnectionAvailable duplicated"); 3778 MOZ_ASSERT(aConnection); 3779 3780 if (mStopped) { 3781 LOG(("WebSocketChannel::OnWebSocketConnectionAvailable: Already stopped")); 3782 aConnection->Close(); 3783 return NS_OK; 3784 } 3785 3786 nsresult rv = aConnection->Init(this); 3787 if (NS_FAILED(rv)) { 3788 return rv; 3789 } 3790 3791 mConnection = aConnection; 3792 // Note: mIOThread will be IPDL background thread. 3793 mConnection->GetIoTarget(getter_AddRefs(mIOThread)); 3794 return OnTransportAvailableInternal(); 3795 } 3796 3797 nsresult WebSocketChannel::OnTransportAvailableInternal() { 3798 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3799 MOZ_ASSERT(!mRecvdHttpUpgradeTransport, 3800 "OnWebSocketConnectionAvailable duplicated"); 3801 MOZ_ASSERT(mSocketIn || mConnection); 3802 3803 mRecvdHttpUpgradeTransport = 1; 3804 if (mGotUpgradeOK) { 3805 // We're now done CONNECTING, which means we can now open another, 3806 // perhaps parallel, connection to the same host if one 3807 // is pending 3808 nsWSAdmissionManager::OnConnected(this); 3809 3810 return CallStartWebsocketData(); 3811 } 3812 3813 if (mIsServerSide) { 3814 if (!mNegotiatedExtensions.IsEmpty()) { 3815 bool clientNoContextTakeover; 3816 bool serverNoContextTakeover; 3817 int32_t clientMaxWindowBits; 3818 int32_t serverMaxWindowBits; 3819 3820 nsresult rv = ParseWebSocketExtension( 3821 mNegotiatedExtensions, eParseServerSide, clientNoContextTakeover, 3822 serverNoContextTakeover, clientMaxWindowBits, serverMaxWindowBits); 3823 MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "illegal value provided by server"); 3824 3825 if (clientMaxWindowBits == -1) { 3826 clientMaxWindowBits = 15; 3827 } 3828 if (serverMaxWindowBits == -1) { 3829 serverMaxWindowBits = 15; 3830 } 3831 3832 MutexAutoLock lock(mCompressorMutex); 3833 mPMCECompressor = MakeUnique<PMCECompression>( 3834 serverNoContextTakeover, serverMaxWindowBits, clientMaxWindowBits); 3835 if (mPMCECompressor->Active()) { 3836 LOG( 3837 ("WebSocketChannel::OnTransportAvailable: PMCE negotiated, %susing " 3838 "context takeover, serverMaxWindowBits=%d, " 3839 "clientMaxWindowBits=%d\n", 3840 serverNoContextTakeover ? "NOT " : "", serverMaxWindowBits, 3841 clientMaxWindowBits)); 3842 3843 mNegotiatedExtensions = "permessage-deflate"; 3844 } else { 3845 LOG( 3846 ("WebSocketChannel::OnTransportAvailable: Cannot init PMCE " 3847 "compression object\n")); 3848 mPMCECompressor = nullptr; 3849 AbortSession(NS_ERROR_UNEXPECTED); 3850 return NS_ERROR_UNEXPECTED; 3851 } 3852 } 3853 3854 return CallStartWebsocketData(); 3855 } 3856 3857 return NS_OK; 3858 } 3859 3860 NS_IMETHODIMP 3861 WebSocketChannel::OnUpgradeFailed(nsresult aErrorCode) { 3862 // When socket process is enabled, this could be called on background thread. 3863 3864 LOG(("WebSocketChannel::OnUpgradeFailed() %p [aErrorCode %" PRIx32 "]", this, 3865 static_cast<uint32_t>(aErrorCode))); 3866 3867 if (mStopped) { 3868 LOG(("WebSocketChannel::OnUpgradeFailed: Already stopped")); 3869 return NS_OK; 3870 } 3871 3872 MOZ_ASSERT(!mRecvdHttpUpgradeTransport, "OTA already called"); 3873 3874 AbortSession(aErrorCode); 3875 return NS_OK; 3876 } 3877 3878 // nsIRequestObserver (from nsIStreamListener) 3879 3880 NS_IMETHODIMP 3881 WebSocketChannel::OnStartRequest(nsIRequest* aRequest) { 3882 LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n", 3883 this, aRequest, mHttpChannel.get(), mRecvdHttpUpgradeTransport)); 3884 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 3885 MOZ_ASSERT(!mGotUpgradeOK, "OTA duplicated"); 3886 3887 if (mStopped) { 3888 LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n")); 3889 AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED); 3890 return NS_ERROR_WEBSOCKET_CONNECTION_REFUSED; 3891 } 3892 3893 nsresult rv; 3894 uint32_t status; 3895 char *val, *token; 3896 3897 rv = mHttpChannel->GetResponseStatus(&status); 3898 if (NS_FAILED(rv)) { 3899 nsresult httpStatus; 3900 rv = NS_ERROR_WEBSOCKET_CONNECTION_REFUSED; 3901 3902 // If we failed to connect due to unsuccessful TLS handshake, we must 3903 // propagate a specific error to mozilla::dom::WebSocketImpl so it can set 3904 // status code to 1015. Otherwise return 3905 // NS_ERROR_WEBSOCKET_CONNECTION_REFUSED. 3906 if (NS_SUCCEEDED(mHttpChannel->GetStatus(&httpStatus))) { 3907 uint32_t errorClass; 3908 nsCOMPtr<nsINSSErrorsService> errSvc; 3909 errSvc = mozilla::components::NSSErrors::Service(); 3910 // If GetErrorClass succeeds httpStatus is TLS related failure. 3911 if (errSvc && 3912 NS_SUCCEEDED(errSvc->GetErrorClass(httpStatus, &errorClass))) { 3913 rv = NS_ERROR_NET_INADEQUATE_SECURITY; 3914 } 3915 } 3916 3917 LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n")); 3918 AbortSession(rv); 3919 return rv; 3920 } 3921 3922 LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status)); 3923 nsCOMPtr<nsIHttpChannelInternal> internalChannel = 3924 do_QueryInterface(mHttpChannel); 3925 uint32_t versionMajor, versionMinor; 3926 rv = internalChannel->GetResponseVersion(&versionMajor, &versionMinor); 3927 if (NS_FAILED(rv) || 3928 !((versionMajor == 1 && versionMinor != 0) || versionMajor == 2) || 3929 (versionMajor == 1 && status != 101) || 3930 (versionMajor == 2 && status != 200)) { 3931 AbortSession(NS_ERROR_WEBSOCKET_CONNECTION_REFUSED); 3932 return NS_ERROR_WEBSOCKET_CONNECTION_REFUSED; 3933 } 3934 3935 if (versionMajor == 1) { 3936 // These are only present on http/1.x websocket upgrades 3937 nsAutoCString respUpgrade; 3938 rv = mHttpChannel->GetResponseHeader("Upgrade"_ns, respUpgrade); 3939 3940 if (NS_SUCCEEDED(rv)) { 3941 rv = NS_ERROR_ILLEGAL_VALUE; 3942 if (!respUpgrade.IsEmpty()) { 3943 val = respUpgrade.BeginWriting(); 3944 while ((token = nsCRT::strtok(val, ", \t", &val))) { 3945 if (nsCRT::strcasecmp(token, "Websocket") == 0) { 3946 rv = NS_OK; 3947 break; 3948 } 3949 } 3950 } 3951 } 3952 3953 if (NS_FAILED(rv)) { 3954 LOG( 3955 ("WebSocketChannel::OnStartRequest: " 3956 "HTTP response header Upgrade: websocket not found\n")); 3957 AbortSession(NS_ERROR_ILLEGAL_VALUE); 3958 return rv; 3959 } 3960 3961 nsAutoCString respConnection; 3962 rv = mHttpChannel->GetResponseHeader("Connection"_ns, respConnection); 3963 3964 if (NS_SUCCEEDED(rv)) { 3965 rv = NS_ERROR_ILLEGAL_VALUE; 3966 if (!respConnection.IsEmpty()) { 3967 val = respConnection.BeginWriting(); 3968 while ((token = nsCRT::strtok(val, ", \t", &val))) { 3969 if (nsCRT::strcasecmp(token, "Upgrade") == 0) { 3970 rv = NS_OK; 3971 break; 3972 } 3973 } 3974 } 3975 } 3976 3977 if (NS_FAILED(rv)) { 3978 LOG( 3979 ("WebSocketChannel::OnStartRequest: " 3980 "HTTP response header 'Connection: Upgrade' not found\n")); 3981 AbortSession(NS_ERROR_ILLEGAL_VALUE); 3982 return rv; 3983 } 3984 3985 nsAutoCString respAccept; 3986 rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Accept"_ns, respAccept); 3987 3988 if (NS_FAILED(rv) || respAccept.IsEmpty() || 3989 !respAccept.Equals(mHashedSecret)) { 3990 LOG( 3991 ("WebSocketChannel::OnStartRequest: " 3992 "HTTP response header Sec-WebSocket-Accept check failed\n")); 3993 LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n", 3994 mHashedSecret.get(), respAccept.get())); 3995 #ifdef FUZZING 3996 if (NS_FAILED(rv) || respAccept.IsEmpty()) { 3997 #endif 3998 AbortSession(NS_ERROR_ILLEGAL_VALUE); 3999 return NS_ERROR_ILLEGAL_VALUE; 4000 #ifdef FUZZING 4001 } 4002 #endif 4003 } 4004 } 4005 4006 // If we sent a sub protocol header, verify the response matches. 4007 // If response contains protocol that was not in request, fail. 4008 // If response contained no protocol header, set to "" so the protocol 4009 // attribute of the WebSocket JS object reflects that 4010 if (!mProtocol.IsEmpty()) { 4011 nsAutoCString respProtocol; 4012 rv = mHttpChannel->GetResponseHeader("Sec-WebSocket-Protocol"_ns, 4013 respProtocol); 4014 if (NS_SUCCEEDED(rv)) { 4015 rv = NS_ERROR_ILLEGAL_VALUE; 4016 val = mProtocol.BeginWriting(); 4017 while ((token = nsCRT::strtok(val, ", \t", &val))) { 4018 if (strcmp(token, respProtocol.get()) == 0) { 4019 rv = NS_OK; 4020 break; 4021 } 4022 } 4023 4024 if (NS_SUCCEEDED(rv)) { 4025 LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed", 4026 respProtocol.get())); 4027 mProtocol = respProtocol; 4028 } else { 4029 LOG( 4030 ("WebsocketChannel::OnStartRequest: " 4031 "Server replied with non-matching subprotocol [%s]: aborting", 4032 respProtocol.get())); 4033 mProtocol.Truncate(); 4034 AbortSession(NS_ERROR_ILLEGAL_VALUE); 4035 return NS_ERROR_ILLEGAL_VALUE; 4036 } 4037 } else { 4038 LOG( 4039 ("WebsocketChannel::OnStartRequest " 4040 "subprotocol [%s] not found - none returned", 4041 mProtocol.get())); 4042 mProtocol.Truncate(); 4043 } 4044 } 4045 4046 rv = HandleExtensions(); 4047 if (NS_FAILED(rv)) return rv; 4048 4049 // Update mEffectiveURL for off main thread URI access. 4050 nsCOMPtr<nsIURI> uri = mURI ? mURI : mOriginalURI; 4051 nsAutoCString spec; 4052 rv = uri->GetSpec(spec); 4053 MOZ_ASSERT(NS_SUCCEEDED(rv)); 4054 CopyUTF8toUTF16(spec, mEffectiveURL); 4055 4056 mGotUpgradeOK = 1; 4057 if (mRecvdHttpUpgradeTransport) { 4058 // We're now done CONNECTING, which means we can now open another, 4059 // perhaps parallel, connection to the same host if one 4060 // is pending 4061 nsWSAdmissionManager::OnConnected(this); 4062 4063 return CallStartWebsocketData(); 4064 } 4065 4066 return NS_OK; 4067 } 4068 4069 NS_IMETHODIMP 4070 WebSocketChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { 4071 LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %" PRIx32 "]\n", this, 4072 aRequest, mHttpChannel.get(), static_cast<uint32_t>(aStatusCode))); 4073 MOZ_ASSERT(NS_IsMainThread(), "not main thread"); 4074 4075 // OnTransportAvailable won't be called if the request is stopped with 4076 // an error. Abort the session now instead of waiting for timeout. 4077 if (NS_FAILED(aStatusCode) && !mRecvdHttpUpgradeTransport) { 4078 AbortSession(aStatusCode); 4079 } 4080 4081 ReportConnectionTelemetry(aStatusCode); 4082 4083 // This is the end of the HTTP upgrade transaction, the 4084 // upgraded streams live on 4085 4086 mChannel = nullptr; 4087 mHttpChannel = nullptr; 4088 mLoadGroup = nullptr; 4089 mCallbacks = nullptr; 4090 4091 return NS_OK; 4092 } 4093 4094 // nsIInputStreamCallback 4095 4096 NS_IMETHODIMP 4097 WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream* aStream) { 4098 LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this)); 4099 MOZ_DIAGNOSTIC_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 4100 4101 if (!mSocketIn) { // did we we clean up the socket after scheduling 4102 // InputReady? 4103 return NS_OK; 4104 } 4105 4106 // this is after the http upgrade - so we are speaking websockets 4107 char buffer[2048]; 4108 uint32_t count; 4109 nsresult rv; 4110 4111 do { 4112 rv = mSocketIn->Read((char*)buffer, sizeof(buffer), &count); 4113 LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %" PRIx32 "\n", 4114 count, static_cast<uint32_t>(rv))); 4115 4116 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 4117 mSocketIn->AsyncWait(this, 0, 0, mIOThread); 4118 return NS_OK; 4119 } 4120 4121 if (NS_FAILED(rv)) { 4122 AbortSession(rv); 4123 return rv; 4124 } 4125 4126 if (count == 0) { 4127 AbortSession(NS_BASE_STREAM_CLOSED); 4128 return NS_OK; 4129 } 4130 4131 if (mStopped) { 4132 continue; 4133 } 4134 4135 rv = ProcessInput((uint8_t*)buffer, count); 4136 if (NS_FAILED(rv)) { 4137 AbortSession(rv); 4138 return rv; 4139 } 4140 } while (NS_SUCCEEDED(rv) && mSocketIn); 4141 4142 return NS_OK; 4143 } 4144 4145 // nsIOutputStreamCallback 4146 4147 NS_IMETHODIMP 4148 WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream* aStream) { 4149 LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this)); 4150 MOZ_DIAGNOSTIC_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 4151 nsresult rv; 4152 4153 if (!mCurrentOut) PrimeNewOutgoingMessage(); 4154 4155 while (mCurrentOut && mSocketOut) { 4156 const char* sndBuf; 4157 uint32_t toSend; 4158 uint32_t amtSent; 4159 4160 if (mHdrOut) { 4161 sndBuf = (const char*)mHdrOut; 4162 toSend = mHdrOutToSend; 4163 LOG( 4164 ("WebSocketChannel::OnOutputStreamReady: " 4165 "Try to send %u of hdr/copybreak\n", 4166 toSend)); 4167 } else { 4168 sndBuf = (char*)mCurrentOut->BeginReading() + mCurrentOutSent; 4169 toSend = mCurrentOut->Length() - mCurrentOutSent; 4170 if (toSend > 0) { 4171 LOG( 4172 ("WebSocketChannel::OnOutputStreamReady [%p]: " 4173 "Try to send %u of data\n", 4174 this, toSend)); 4175 } 4176 } 4177 4178 if (toSend == 0) { 4179 amtSent = 0; 4180 } else { 4181 rv = mSocketOut->Write(sndBuf, toSend, &amtSent); 4182 LOG(("WebSocketChannel::OnOutputStreamReady [%p]: write %u rv %" PRIx32 4183 "\n", 4184 this, amtSent, static_cast<uint32_t>(rv))); 4185 4186 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 4187 mSocketOut->AsyncWait(this, 0, 0, mIOThread); 4188 return NS_OK; 4189 } 4190 4191 if (NS_FAILED(rv)) { 4192 AbortSession(rv); 4193 return NS_OK; 4194 } 4195 } 4196 4197 if (mHdrOut) { 4198 if (amtSent == toSend) { 4199 mHdrOut = nullptr; 4200 mHdrOutToSend = 0; 4201 } else { 4202 mHdrOut += amtSent; 4203 mHdrOutToSend -= amtSent; 4204 mSocketOut->AsyncWait(this, 0, 0, mIOThread); 4205 } 4206 } else { 4207 if (amtSent == toSend) { 4208 if (!mStopped) { 4209 if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) { 4210 target->Dispatch( 4211 new CallAcknowledge(this, mCurrentOut->OrigLength()), 4212 NS_DISPATCH_NORMAL); 4213 } else { 4214 return NS_ERROR_UNEXPECTED; 4215 } 4216 } 4217 DeleteCurrentOutGoingMessage(); 4218 PrimeNewOutgoingMessage(); 4219 } else { 4220 mCurrentOutSent += amtSent; 4221 mSocketOut->AsyncWait(this, 0, 0, mIOThread); 4222 } 4223 } 4224 } 4225 4226 if (mReleaseOnTransmit) ReleaseSession(); 4227 return NS_OK; 4228 } 4229 4230 // nsIStreamListener 4231 4232 NS_IMETHODIMP 4233 WebSocketChannel::OnDataAvailable(nsIRequest* aRequest, 4234 nsIInputStream* aInputStream, 4235 uint64_t aOffset, uint32_t aCount) { 4236 LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %" PRIu64 " %u]\n", 4237 this, aRequest, mHttpChannel.get(), aInputStream, aOffset, aCount)); 4238 4239 // This is the HTTP OnDataAvailable Method, which means this is http data in 4240 // response to the upgrade request and there should be no http response body 4241 // if the upgrade succeeded. This generally should be caught by a non 101 4242 // response code in OnStartRequest().. so we can ignore the data here 4243 4244 LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n", 4245 aCount)); 4246 4247 return NS_OK; 4248 } 4249 4250 void WebSocketChannel::DoEnqueueOutgoingMessage() { 4251 LOG(("WebSocketChannel::DoEnqueueOutgoingMessage() %p\n", this)); 4252 MOZ_ASSERT(mIOThread->IsOnCurrentThread(), "not on right thread"); 4253 4254 if (!mCurrentOut) { 4255 PrimeNewOutgoingMessage(); 4256 } 4257 4258 while (mCurrentOut && mConnection) { 4259 nsresult rv = NS_OK; 4260 if (mCurrentOut->Length() - mCurrentOutSent == 0) { 4261 LOG( 4262 ("WebSocketChannel::DoEnqueueOutgoingMessage: " 4263 "Try to send %u of hdr/copybreak\n", 4264 mHdrOutToSend)); 4265 rv = mConnection->WriteOutputData(mOutHeader, mHdrOutToSend, nullptr, 0); 4266 } else { 4267 LOG( 4268 ("WebSocketChannel::DoEnqueueOutgoingMessage: " 4269 "Try to send %u of hdr and %u of data\n", 4270 mHdrOutToSend, mCurrentOut->Length())); 4271 rv = mConnection->WriteOutputData(mOutHeader, mHdrOutToSend, 4272 (uint8_t*)mCurrentOut->BeginReading(), 4273 mCurrentOut->Length()); 4274 } 4275 4276 LOG(("WebSocketChannel::DoEnqueueOutgoingMessage: rv %" PRIx32 "\n", 4277 static_cast<uint32_t>(rv))); 4278 if (NS_FAILED(rv)) { 4279 AbortSession(rv); 4280 return; 4281 } 4282 4283 if (!mStopped) { 4284 // TODO: Currently, we assume that data is completely written to the 4285 // socket after sending it to socket process, but it's not true. The data 4286 // could be queued in socket process and waiting for the socket to be able 4287 // to write. We should implement flow control for this in bug 1726552. 4288 if (nsCOMPtr<nsIEventTarget> target = GetTargetThread()) { 4289 target->Dispatch(new CallAcknowledge(this, mCurrentOut->OrigLength()), 4290 NS_DISPATCH_NORMAL); 4291 } else { 4292 AbortSession(NS_ERROR_UNEXPECTED); 4293 return; 4294 } 4295 } 4296 DeleteCurrentOutGoingMessage(); 4297 PrimeNewOutgoingMessage(); 4298 } 4299 4300 if (mReleaseOnTransmit) { 4301 ReleaseSession(); 4302 } 4303 } 4304 4305 void WebSocketChannel::OnError(nsresult aStatus) { AbortSession(aStatus); } 4306 4307 void WebSocketChannel::OnTCPClosed() { mTCPClosed = true; } 4308 4309 nsresult WebSocketChannel::OnDataReceived(uint8_t* aData, uint32_t aCount) { 4310 return ProcessInput(aData, aCount); 4311 } 4312 4313 } // namespace mozilla::net 4314 4315 #undef CLOSE_GOING_AWAY