tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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