netwerk/protocol/websocket/WebSocketChannel.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

     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/. */
     7 #include "WebSocketLog.h"
     8 #include "WebSocketChannel.h"
    10 #include "mozilla/Atomics.h"
    11 #include "mozilla/Attributes.h"
    12 #include "mozilla/Endian.h"
    13 #include "mozilla/MathAlgorithms.h"
    15 #include "nsIURI.h"
    16 #include "nsIChannel.h"
    17 #include "nsICryptoHash.h"
    18 #include "nsIRunnable.h"
    19 #include "nsIPrefBranch.h"
    20 #include "nsIPrefService.h"
    21 #include "nsICancelable.h"
    22 #include "nsIDNSRecord.h"
    23 #include "nsIDNSService.h"
    24 #include "nsIStreamConverterService.h"
    25 #include "nsIIOService2.h"
    26 #include "nsIProtocolProxyService.h"
    27 #include "nsIProxyInfo.h"
    28 #include "nsIProxiedChannel.h"
    29 #include "nsIAsyncVerifyRedirectCallback.h"
    30 #include "nsIDashboardEventNotifier.h"
    31 #include "nsIEventTarget.h"
    32 #include "nsIHttpChannel.h"
    33 #include "nsILoadGroup.h"
    34 #include "nsIProtocolHandler.h"
    35 #include "nsIRandomGenerator.h"
    36 #include "nsISocketTransport.h"
    37 #include "nsThreadUtils.h"
    39 #include "nsAutoPtr.h"
    40 #include "nsNetCID.h"
    41 #include "nsServiceManagerUtils.h"
    42 #include "nsCRT.h"
    43 #include "nsThreadUtils.h"
    44 #include "nsError.h"
    45 #include "nsStringStream.h"
    46 #include "nsAlgorithm.h"
    47 #include "nsProxyRelease.h"
    48 #include "nsNetUtil.h"
    49 #include "mozilla/StaticMutex.h"
    50 #include "mozilla/Telemetry.h"
    51 #include "mozilla/TimeStamp.h"
    53 #include "plbase64.h"
    54 #include "prmem.h"
    55 #include "prnetdb.h"
    56 #include "zlib.h"
    57 #include <algorithm>
    59 #ifdef MOZ_WIDGET_GONK
    60 #include "NetStatistics.h"
    61 #endif
    63 // rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just
    64 // dupe one constant we need from it
    65 #define CLOSE_GOING_AWAY 1001
    67 extern PRThread *gSocketThread;
    69 using namespace mozilla;
    70 using namespace mozilla::net;
    72 namespace mozilla {
    73 namespace net {
    75 NS_IMPL_ISUPPORTS(WebSocketChannel,
    76                   nsIWebSocketChannel,
    77                   nsIHttpUpgradeListener,
    78                   nsIRequestObserver,
    79                   nsIStreamListener,
    80                   nsIProtocolHandler,
    81                   nsIInputStreamCallback,
    82                   nsIOutputStreamCallback,
    83                   nsITimerCallback,
    84                   nsIDNSListener,
    85                   nsIProtocolProxyCallback,
    86                   nsIInterfaceRequestor,
    87                   nsIChannelEventSink,
    88                   nsIThreadRetargetableRequest)
    90 // We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire.
    91 #define SEC_WEBSOCKET_VERSION "13"
    93 /*
    94  * About SSL unsigned certificates
    95  *
    96  * wss will not work to a host using an unsigned certificate unless there
    97  * is already an exception (i.e. it cannot popup a dialog asking for
    98  * a security exception). This is similar to how an inlined img will
    99  * fail without a dialog if fails for the same reason. This should not
   100  * be a problem in practice as it is expected the websocket javascript
   101  * is served from the same host as the websocket server (or of course,
   102  * a valid cert could just be provided).
   103  *
   104  */
   106 // some helper classes
   108 //-----------------------------------------------------------------------------
   109 // FailDelayManager
   110 //
   111 // Stores entries (searchable by {host, port}) of connections that have recently
   112 // failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3
   113 //-----------------------------------------------------------------------------
   116 // Initial reconnect delay is randomly chosen between 200-400 ms.
   117 // This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests.
   118 const uint32_t kWSReconnectInitialBaseDelay     = 200;
   119 const uint32_t kWSReconnectInitialRandomDelay   = 200;
   121 // Base lifetime (in ms) of a FailDelay: kept longer if more failures occur
   122 const uint32_t kWSReconnectBaseLifeTime         = 60 * 1000;
   123 // Maximum reconnect delay (in ms)
   124 const uint32_t kWSReconnectMaxDelay             = 60 * 1000;
   126 // hold record of failed connections, and calculates needed delay for reconnects
   127 // to same host/port.
   128 class FailDelay
   129 {
   130 public:
   131   FailDelay(nsCString address, int32_t port)
   132     : mAddress(address), mPort(port)
   133   {
   134     mLastFailure = TimeStamp::Now();
   135     mNextDelay = kWSReconnectInitialBaseDelay +
   136                  (rand() % kWSReconnectInitialRandomDelay);
   137   }
   139   // Called to update settings when connection fails again.
   140   void FailedAgain()
   141   {
   142     mLastFailure = TimeStamp::Now();
   143     // We use a truncated exponential backoff as suggested by RFC 6455,
   144     // but multiply by 1.5 instead of 2 to be more gradual.
   145     mNextDelay = static_cast<uint32_t>(
   146       std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5));
   147     LOG(("WebSocket: FailedAgain: host=%s, port=%d: incremented delay to %lu",
   148          mAddress.get(), mPort, mNextDelay));
   149   }
   151   // returns 0 if there is no need to delay (i.e. delay interval is over)
   152   uint32_t RemainingDelay(TimeStamp rightNow)
   153   {
   154     TimeDuration dur = rightNow - mLastFailure;
   155     uint32_t sinceFail = (uint32_t) dur.ToMilliseconds();
   156     if (sinceFail > mNextDelay)
   157       return 0;
   159     return mNextDelay - sinceFail;
   160   }
   162   bool IsExpired(TimeStamp rightNow)
   163   {
   164     return (mLastFailure +
   165             TimeDuration::FromMilliseconds(kWSReconnectBaseLifeTime + mNextDelay))
   166             <= rightNow;
   167   }
   169   nsCString  mAddress;     // IP address (or hostname if using proxy)
   170   int32_t    mPort;
   172 private:
   173   TimeStamp  mLastFailure; // Time of last failed attempt
   174   // mLastFailure + mNextDelay is the soonest we'll allow a reconnect
   175   uint32_t   mNextDelay;   // milliseconds
   176 };
   178 class FailDelayManager
   179 {
   180 public:
   181   FailDelayManager()
   182   {
   183     MOZ_COUNT_CTOR(FailDelayManager);
   185     mDelaysDisabled = false;
   187     nsCOMPtr<nsIPrefBranch> prefService =
   188       do_GetService(NS_PREFSERVICE_CONTRACTID);
   189     bool boolpref = true;
   190     nsresult rv;
   191     rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects",
   192                                   &boolpref);
   193     if (NS_SUCCEEDED(rv) && !boolpref) {
   194       mDelaysDisabled = true;
   195     }
   196   }
   198   ~FailDelayManager()
   199   {
   200     MOZ_COUNT_DTOR(FailDelayManager);
   201     for (uint32_t i = 0; i < mEntries.Length(); i++) {
   202       delete mEntries[i];
   203     }
   204   }
   206   void Add(nsCString &address, int32_t port)
   207   {
   208     NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   210     if (mDelaysDisabled)
   211       return;
   213     FailDelay *record = new FailDelay(address, port);
   214     mEntries.AppendElement(record);
   215   }
   217   // Element returned may not be valid after next main thread event: don't keep
   218   // pointer to it around
   219   FailDelay* Lookup(nsCString &address, int32_t port,
   220                     uint32_t *outIndex = nullptr)
   221   {
   222     NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   224     if (mDelaysDisabled)
   225       return nullptr;
   227     FailDelay *result = nullptr;
   228     TimeStamp rightNow = TimeStamp::Now();
   230     // We also remove expired entries during search: iterate from end to make
   231     // indexing simpler
   232     for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
   233       FailDelay *fail = mEntries[i];
   234       if (fail->mAddress.Equals(address) && fail->mPort == port) {
   235         if (outIndex)
   236           *outIndex = i;
   237         result = fail;
   238         // break here: removing more entries would mess up *outIndex.
   239         // Any remaining expired entries will be deleted next time Lookup
   240         // finds nothing, which is the most common case anyway.
   241         break;
   242       } else if (fail->IsExpired(rightNow)) {
   243         mEntries.RemoveElementAt(i);
   244         delete fail;
   245       }
   246     }
   247     return result;
   248   }
   250   // returns true if channel connects immediately, or false if it's delayed
   251   void DelayOrBegin(WebSocketChannel *ws)
   252   {
   253     if (!mDelaysDisabled) {
   254       uint32_t failIndex = 0;
   255       FailDelay *fail = Lookup(ws->mAddress, ws->mPort, &failIndex);
   257       if (fail) {
   258         TimeStamp rightNow = TimeStamp::Now();
   260         uint32_t remainingDelay = fail->RemainingDelay(rightNow);
   261         if (remainingDelay) {
   262           // reconnecting within delay interval: delay by remaining time
   263           nsresult rv;
   264           ws->mReconnectDelayTimer =
   265             do_CreateInstance("@mozilla.org/timer;1", &rv);
   266           if (NS_SUCCEEDED(rv)) {
   267             rv = ws->mReconnectDelayTimer->InitWithCallback(
   268                           ws, remainingDelay, nsITimer::TYPE_ONE_SHOT);
   269             if (NS_SUCCEEDED(rv)) {
   270               LOG(("WebSocket: delaying websocket [this=%p] by %lu ms",
   271                    ws, (unsigned long)remainingDelay));
   272               ws->mConnecting = CONNECTING_DELAYED;
   273               return;
   274             }
   275           }
   276           // if timer fails (which is very unlikely), drop down to BeginOpen call
   277         } else if (fail->IsExpired(rightNow)) {
   278           mEntries.RemoveElementAt(failIndex);
   279           delete fail;
   280         }
   281       }
   282     }
   284     // Delays disabled, or no previous failure, or we're reconnecting after scheduled
   285     // delay interval has passed: connect.
   286     ws->BeginOpen();
   287   }
   289   // Remove() also deletes all expired entries as it iterates: better for
   290   // battery life than using a periodic timer.
   291   void Remove(nsCString &address, int32_t port)
   292   {
   293     NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   295     TimeStamp rightNow = TimeStamp::Now();
   297     // iterate from end, to make deletion indexing easier
   298     for (int32_t i = mEntries.Length() - 1; i >= 0; --i) {
   299       FailDelay *entry = mEntries[i];
   300       if ((entry->mAddress.Equals(address) && entry->mPort == port) ||
   301           entry->IsExpired(rightNow)) {
   302         mEntries.RemoveElementAt(i);
   303         delete entry;
   304       }
   305     }
   306   }
   308 private:
   309   nsTArray<FailDelay *> mEntries;
   310   bool                  mDelaysDisabled;
   311 };
   313 //-----------------------------------------------------------------------------
   314 // nsWSAdmissionManager
   315 //
   316 // 1) Ensures that only one websocket at a time is CONNECTING to a given IP
   317 //    address (or hostname, if using proxy), per RFC 6455 Section 4.1.
   318 // 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3
   319 //-----------------------------------------------------------------------------
   321 class nsWSAdmissionManager
   322 {
   323 public:
   324   static void Init()
   325   {
   326     StaticMutexAutoLock lock(sLock);
   327     if (!sManager) {
   328       sManager = new nsWSAdmissionManager();
   329     }
   330   }
   332   static void Shutdown()
   333   {
   334     StaticMutexAutoLock lock(sLock);
   335     delete sManager;
   336     sManager = nullptr;
   337   }
   339   // Determine if we will open connection immediately (returns true), or
   340   // delay/queue the connection (returns false)
   341   static void ConditionallyConnect(WebSocketChannel *ws)
   342   {
   343     NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   344     NS_ABORT_IF_FALSE(ws->mConnecting == NOT_CONNECTING, "opening state");
   346     StaticMutexAutoLock lock(sLock);
   347     if (!sManager) {
   348       return;
   349     }
   351     // If there is already another WS channel connecting to this IP address,
   352     // defer BeginOpen and mark as waiting in queue.
   353     bool found = (sManager->IndexOf(ws->mAddress) >= 0);
   355     // Always add ourselves to queue, even if we'll connect immediately
   356     nsOpenConn *newdata = new nsOpenConn(ws->mAddress, ws);
   357     sManager->mQueue.AppendElement(newdata);
   359     if (found) {
   360       ws->mConnecting = CONNECTING_QUEUED;
   361     } else {
   362       sManager->mFailures.DelayOrBegin(ws);
   363     }
   364   }
   366   static void OnConnected(WebSocketChannel *aChannel)
   367   {
   368     NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   369     NS_ABORT_IF_FALSE(aChannel->mConnecting == CONNECTING_IN_PROGRESS,
   370                       "Channel completed connect, but not connecting?");
   372     StaticMutexAutoLock lock(sLock);
   373     if (!sManager) {
   374       return;
   375     }
   377     aChannel->mConnecting = NOT_CONNECTING;
   379     // Remove from queue
   380     sManager->RemoveFromQueue(aChannel);
   382     // Connection succeeded, so stop keeping track of any previous failures
   383     sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPort);
   385     // Check for queued connections to same host.
   386     // Note: still need to check for failures, since next websocket with same
   387     // host may have different port
   388     sManager->ConnectNext(aChannel->mAddress);
   389   }
   391   // Called every time a websocket channel ends its session (including going away
   392   // w/o ever successfully creating a connection)
   393   static void OnStopSession(WebSocketChannel *aChannel, nsresult aReason)
   394   {
   395     NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   397     StaticMutexAutoLock lock(sLock);
   398     if (!sManager) {
   399       return;
   400     }
   402     if (NS_FAILED(aReason)) {
   403       // Have we seen this failure before?
   404       FailDelay *knownFailure = sManager->mFailures.Lookup(aChannel->mAddress,
   405                                                            aChannel->mPort);
   406       if (knownFailure) {
   407         if (aReason == NS_ERROR_NOT_CONNECTED) {
   408           // Don't count close() before connection as a network error
   409           LOG(("Websocket close() before connection to %s, %d completed"
   410                " [this=%p]", aChannel->mAddress.get(), (int)aChannel->mPort,
   411                aChannel));
   412         } else {
   413           // repeated failure to connect: increase delay for next connection
   414           knownFailure->FailedAgain();
   415         }
   416       } else {
   417         // new connection failure: record it.
   418         LOG(("WebSocket: connection to %s, %d failed: [this=%p]",
   419               aChannel->mAddress.get(), (int)aChannel->mPort, aChannel));
   420         sManager->mFailures.Add(aChannel->mAddress, aChannel->mPort);
   421       }
   422     }
   424     if (aChannel->mConnecting) {
   425       // Only way a connecting channel may get here w/o failing is if it was
   426       // closed with GOING_AWAY (1001) because of navigation, tab close, etc.
   427       NS_ABORT_IF_FALSE(NS_FAILED(aReason) ||
   428                         aChannel->mScriptCloseCode == CLOSE_GOING_AWAY,
   429                         "websocket closed while connecting w/o failing?");
   431       sManager->RemoveFromQueue(aChannel);
   433       bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED);
   434       aChannel->mConnecting = NOT_CONNECTING;
   435       if (wasNotQueued) {
   436         sManager->ConnectNext(aChannel->mAddress);
   437       }
   438     }
   439   }
   441   static void IncrementSessionCount()
   442   {
   443     StaticMutexAutoLock lock(sLock);
   444     if (!sManager) {
   445       return;
   446     }
   447     sManager->mSessionCount++;
   448   }
   450   static void DecrementSessionCount()
   451   {
   452     StaticMutexAutoLock lock(sLock);
   453     if (!sManager) {
   454       return;
   455     }
   456     sManager->mSessionCount--;
   457   }
   459   static void GetSessionCount(int32_t &aSessionCount)
   460   {
   461     StaticMutexAutoLock lock(sLock);
   462     if (!sManager) {
   463       return;
   464     }
   465     aSessionCount = sManager->mSessionCount;
   466   }
   468 private:
   469   nsWSAdmissionManager() : mSessionCount(0)
   470   {
   471     MOZ_COUNT_CTOR(nsWSAdmissionManager);
   472   }
   474   ~nsWSAdmissionManager()
   475   {
   476     MOZ_COUNT_DTOR(nsWSAdmissionManager);
   477     for (uint32_t i = 0; i < mQueue.Length(); i++)
   478       delete mQueue[i];
   479   }
   481   class nsOpenConn
   482   {
   483   public:
   484     nsOpenConn(nsCString &addr, WebSocketChannel *channel)
   485       : mAddress(addr), mChannel(channel) { MOZ_COUNT_CTOR(nsOpenConn); }
   486     ~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); }
   488     nsCString mAddress;
   489     WebSocketChannel *mChannel;
   490   };
   492   void ConnectNext(nsCString &hostName)
   493   {
   494     int32_t index = IndexOf(hostName);
   495     if (index >= 0) {
   496       WebSocketChannel *chan = mQueue[index]->mChannel;
   498       NS_ABORT_IF_FALSE(chan->mConnecting == CONNECTING_QUEUED,
   499                         "transaction not queued but in queue");
   500       LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan));
   502       mFailures.DelayOrBegin(chan);
   503     }
   504   }
   506   void RemoveFromQueue(WebSocketChannel *aChannel)
   507   {
   508     int32_t index = IndexOf(aChannel);
   509     NS_ABORT_IF_FALSE(index >= 0, "connection to remove not in queue");
   510     if (index >= 0) {
   511       nsOpenConn *olddata = mQueue[index];
   512       mQueue.RemoveElementAt(index);
   513       delete olddata;
   514     }
   515   }
   517   int32_t IndexOf(nsCString &aStr)
   518   {
   519     for (uint32_t i = 0; i < mQueue.Length(); i++)
   520       if (aStr == (mQueue[i])->mAddress)
   521         return i;
   522     return -1;
   523   }
   525   int32_t IndexOf(WebSocketChannel *aChannel)
   526   {
   527     for (uint32_t i = 0; i < mQueue.Length(); i++)
   528       if (aChannel == (mQueue[i])->mChannel)
   529         return i;
   530     return -1;
   531   }
   533   // SessionCount might be decremented from the main or the socket
   534   // thread, so manage it with atomic counters
   535   Atomic<int32_t>               mSessionCount;
   537   // Queue for websockets that have not completed connecting yet.
   538   // The first nsOpenConn with a given address will be either be
   539   // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED.  Later ones with the same
   540   // hostname must be CONNECTING_QUEUED.
   541   //
   542   // We could hash hostnames instead of using a single big vector here, but the
   543   // dataset is expected to be small.
   544   nsTArray<nsOpenConn *> mQueue;
   546   FailDelayManager       mFailures;
   548   static nsWSAdmissionManager *sManager;
   549   static StaticMutex           sLock;
   550 };
   552 nsWSAdmissionManager *nsWSAdmissionManager::sManager;
   553 StaticMutex           nsWSAdmissionManager::sLock;
   555 //-----------------------------------------------------------------------------
   556 // CallOnMessageAvailable
   557 //-----------------------------------------------------------------------------
   559 class CallOnMessageAvailable MOZ_FINAL : public nsIRunnable
   560 {
   561 public:
   562   NS_DECL_THREADSAFE_ISUPPORTS
   564   CallOnMessageAvailable(WebSocketChannel *aChannel,
   565                          nsCString        &aData,
   566                          int32_t           aLen)
   567     : mChannel(aChannel),
   568       mData(aData),
   569       mLen(aLen) {}
   571   NS_IMETHOD Run()
   572   {
   573     MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
   575     if (mLen < 0)
   576       mChannel->mListener->OnMessageAvailable(mChannel->mContext, mData);
   577     else
   578       mChannel->mListener->OnBinaryMessageAvailable(mChannel->mContext, mData);
   579     return NS_OK;
   580   }
   582 private:
   583   ~CallOnMessageAvailable() {}
   585   nsRefPtr<WebSocketChannel>        mChannel;
   586   nsCString                         mData;
   587   int32_t                           mLen;
   588 };
   589 NS_IMPL_ISUPPORTS(CallOnMessageAvailable, nsIRunnable)
   591 //-----------------------------------------------------------------------------
   592 // CallOnStop
   593 //-----------------------------------------------------------------------------
   595 class CallOnStop MOZ_FINAL : public nsIRunnable
   596 {
   597 public:
   598   NS_DECL_THREADSAFE_ISUPPORTS
   600   CallOnStop(WebSocketChannel *aChannel,
   601              nsresult          aReason)
   602     : mChannel(aChannel),
   603       mReason(aReason) {}
   605   NS_IMETHOD Run()
   606   {
   607     MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
   609     nsWSAdmissionManager::OnStopSession(mChannel, mReason);
   611     if (mChannel->mListener) {
   612       mChannel->mListener->OnStop(mChannel->mContext, mReason);
   613       mChannel->mListener = nullptr;
   614       mChannel->mContext = nullptr;
   615     }
   616     return NS_OK;
   617   }
   619 private:
   620   ~CallOnStop() {}
   622   nsRefPtr<WebSocketChannel>        mChannel;
   623   nsresult                          mReason;
   624 };
   625 NS_IMPL_ISUPPORTS(CallOnStop, nsIRunnable)
   627 //-----------------------------------------------------------------------------
   628 // CallOnServerClose
   629 //-----------------------------------------------------------------------------
   631 class CallOnServerClose MOZ_FINAL : public nsIRunnable
   632 {
   633 public:
   634   NS_DECL_THREADSAFE_ISUPPORTS
   636   CallOnServerClose(WebSocketChannel *aChannel,
   637                     uint16_t          aCode,
   638                     nsCString        &aReason)
   639     : mChannel(aChannel),
   640       mCode(aCode),
   641       mReason(aReason) {}
   643   NS_IMETHOD Run()
   644   {
   645     MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
   647     mChannel->mListener->OnServerClose(mChannel->mContext, mCode, mReason);
   648     return NS_OK;
   649   }
   651 private:
   652   ~CallOnServerClose() {}
   654   nsRefPtr<WebSocketChannel>        mChannel;
   655   uint16_t                          mCode;
   656   nsCString                         mReason;
   657 };
   658 NS_IMPL_ISUPPORTS(CallOnServerClose, nsIRunnable)
   660 //-----------------------------------------------------------------------------
   661 // CallAcknowledge
   662 //-----------------------------------------------------------------------------
   664 class CallAcknowledge MOZ_FINAL : public nsIRunnable
   665 {
   666 public:
   667   NS_DECL_THREADSAFE_ISUPPORTS
   669   CallAcknowledge(WebSocketChannel *aChannel,
   670                   uint32_t          aSize)
   671     : mChannel(aChannel),
   672       mSize(aSize) {}
   674   NS_IMETHOD Run()
   675   {
   676     MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread);
   678     LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize));
   679     mChannel->mListener->OnAcknowledge(mChannel->mContext, mSize);
   680     return NS_OK;
   681   }
   683 private:
   684   ~CallAcknowledge() {}
   686   nsRefPtr<WebSocketChannel>        mChannel;
   687   uint32_t                          mSize;
   688 };
   689 NS_IMPL_ISUPPORTS(CallAcknowledge, nsIRunnable)
   691 //-----------------------------------------------------------------------------
   692 // CallOnTransportAvailable
   693 //-----------------------------------------------------------------------------
   695 class CallOnTransportAvailable MOZ_FINAL : public nsIRunnable
   696 {
   697 public:
   698   NS_DECL_THREADSAFE_ISUPPORTS
   700   CallOnTransportAvailable(WebSocketChannel *aChannel,
   701                            nsISocketTransport *aTransport,
   702                            nsIAsyncInputStream *aSocketIn,
   703                            nsIAsyncOutputStream *aSocketOut)
   704     : mChannel(aChannel),
   705       mTransport(aTransport),
   706       mSocketIn(aSocketIn),
   707       mSocketOut(aSocketOut) {}
   709   NS_IMETHOD Run()
   710   {
   711     LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this));
   712     return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
   713   }
   715 private:
   716   ~CallOnTransportAvailable() {}
   718   nsRefPtr<WebSocketChannel>     mChannel;
   719   nsCOMPtr<nsISocketTransport>   mTransport;
   720   nsCOMPtr<nsIAsyncInputStream>  mSocketIn;
   721   nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
   722 };
   723 NS_IMPL_ISUPPORTS(CallOnTransportAvailable, nsIRunnable)
   725 //-----------------------------------------------------------------------------
   726 // OutboundMessage
   727 //-----------------------------------------------------------------------------
   729 enum WsMsgType {
   730   kMsgTypeString = 0,
   731   kMsgTypeBinaryString,
   732   kMsgTypeStream,
   733   kMsgTypePing,
   734   kMsgTypePong,
   735   kMsgTypeFin
   736 };
   738 static const char* msgNames[] = {
   739   "text",
   740   "binaryString",
   741   "binaryStream",
   742   "ping",
   743   "pong",
   744   "close"
   745 };
   747 class OutboundMessage
   748 {
   749 public:
   750   OutboundMessage(WsMsgType type, nsCString *str)
   751     : mMsgType(type)
   752   {
   753     MOZ_COUNT_CTOR(OutboundMessage);
   754     mMsg.pString = str;
   755     mLength = str ? str->Length() : 0;
   756   }
   758   OutboundMessage(nsIInputStream *stream, uint32_t length)
   759     : mMsgType(kMsgTypeStream), mLength(length)
   760   {
   761     MOZ_COUNT_CTOR(OutboundMessage);
   762     mMsg.pStream = stream;
   763     mMsg.pStream->AddRef();
   764   }
   766  ~OutboundMessage() {
   767     MOZ_COUNT_DTOR(OutboundMessage);
   768     switch (mMsgType) {
   769       case kMsgTypeString:
   770       case kMsgTypeBinaryString:
   771       case kMsgTypePing:
   772       case kMsgTypePong:
   773         delete mMsg.pString;
   774         break;
   775       case kMsgTypeStream:
   776         // for now this only gets hit if msg deleted w/o being sent
   777         if (mMsg.pStream) {
   778           mMsg.pStream->Close();
   779           mMsg.pStream->Release();
   780         }
   781         break;
   782       case kMsgTypeFin:
   783         break;    // do-nothing: avoid compiler warning
   784     }
   785   }
   787   WsMsgType GetMsgType() const { return mMsgType; }
   788   int32_t Length() const { return mLength; }
   790   uint8_t* BeginWriting() {
   791     NS_ABORT_IF_FALSE(mMsgType != kMsgTypeStream,
   792                       "Stream should have been converted to string by now");
   793     return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginWriting() : nullptr);
   794   }
   796   uint8_t* BeginReading() {
   797     NS_ABORT_IF_FALSE(mMsgType != kMsgTypeStream,
   798                       "Stream should have been converted to string by now");
   799     return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginReading() : nullptr);
   800   }
   802   nsresult ConvertStreamToString()
   803   {
   804     NS_ABORT_IF_FALSE(mMsgType == kMsgTypeStream, "Not a stream!");
   806 #ifdef DEBUG
   807     // Make sure we got correct length from Blob
   808     uint64_t bytes;
   809     mMsg.pStream->Available(&bytes);
   810     NS_ASSERTION(bytes == mLength, "Stream length != blob length!");
   811 #endif
   813     nsAutoPtr<nsCString> temp(new nsCString());
   814     nsresult rv = NS_ReadInputStreamToString(mMsg.pStream, *temp, mLength);
   816     NS_ENSURE_SUCCESS(rv, rv);
   818     mMsg.pStream->Close();
   819     mMsg.pStream->Release();
   820     mMsg.pString = temp.forget();
   821     mMsgType = kMsgTypeBinaryString;
   823     return NS_OK;
   824   }
   826 private:
   827   union {
   828     nsCString      *pString;
   829     nsIInputStream *pStream;
   830   }                           mMsg;
   831   WsMsgType                   mMsgType;
   832   uint32_t                    mLength;
   833 };
   835 //-----------------------------------------------------------------------------
   836 // OutboundEnqueuer
   837 //-----------------------------------------------------------------------------
   839 class OutboundEnqueuer MOZ_FINAL : public nsIRunnable
   840 {
   841 public:
   842   NS_DECL_THREADSAFE_ISUPPORTS
   844   OutboundEnqueuer(WebSocketChannel *aChannel, OutboundMessage *aMsg)
   845     : mChannel(aChannel), mMessage(aMsg) {}
   847   NS_IMETHOD Run()
   848   {
   849     mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage);
   850     return NS_OK;
   851   }
   853 private:
   854   ~OutboundEnqueuer() {}
   856   nsRefPtr<WebSocketChannel>  mChannel;
   857   OutboundMessage            *mMessage;
   858 };
   859 NS_IMPL_ISUPPORTS(OutboundEnqueuer, nsIRunnable)
   861 //-----------------------------------------------------------------------------
   862 // nsWSCompression
   863 //
   864 // similar to nsDeflateConverter except for the mandatory FLUSH calls
   865 // required by websocket and the absence of the deflate termination
   866 // block which is appropriate because it would create data bytes after
   867 // sending the websockets CLOSE message.
   868 //-----------------------------------------------------------------------------
   870 class nsWSCompression
   871 {
   872 public:
   873   nsWSCompression(nsIStreamListener *aListener,
   874                   nsISupports *aContext)
   875     : mActive(false),
   876       mContext(aContext),
   877       mListener(aListener)
   878   {
   879     MOZ_COUNT_CTOR(nsWSCompression);
   881     mZlib.zalloc = allocator;
   882     mZlib.zfree = destructor;
   883     mZlib.opaque = Z_NULL;
   885     // Initialize the compressor - these are all the normal zlib
   886     // defaults except window size is set to -15 instead of +15.
   887     // This is the zlib way of specifying raw RFC 1951 output instead
   888     // of the zlib rfc 1950 format which has a 2 byte header and
   889     // adler checksum as a trailer
   891     nsresult rv;
   892     mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
   893     if (NS_SUCCEEDED(rv) && aContext && aListener &&
   894       deflateInit2(&mZlib, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8,
   895                    Z_DEFAULT_STRATEGY) == Z_OK) {
   896       mActive = true;
   897     }
   898   }
   900   ~nsWSCompression()
   901   {
   902     MOZ_COUNT_DTOR(nsWSCompression);
   904     if (mActive)
   905       deflateEnd(&mZlib);
   906   }
   908   bool Active()
   909   {
   910     return mActive;
   911   }
   913   nsresult Deflate(uint8_t *buf1, uint32_t buf1Len,
   914                    uint8_t *buf2, uint32_t buf2Len)
   915   {
   916     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
   917                           "not socket thread");
   918     NS_ABORT_IF_FALSE(mActive, "not active");
   920     mZlib.avail_out = kBufferLen;
   921     mZlib.next_out = mBuffer;
   922     mZlib.avail_in = buf1Len;
   923     mZlib.next_in = buf1;
   925     nsresult rv;
   927     while (mZlib.avail_in > 0) {
   928       deflate(&mZlib, (buf2Len > 0) ? Z_NO_FLUSH : Z_SYNC_FLUSH);
   929       rv = PushData();
   930       if (NS_FAILED(rv))
   931         return rv;
   932       mZlib.avail_out = kBufferLen;
   933       mZlib.next_out = mBuffer;
   934     }
   936     mZlib.avail_in = buf2Len;
   937     mZlib.next_in = buf2;
   939     while (mZlib.avail_in > 0) {
   940       deflate(&mZlib, Z_SYNC_FLUSH);
   941       rv = PushData();
   942       if (NS_FAILED(rv))
   943         return rv;
   944       mZlib.avail_out = kBufferLen;
   945       mZlib.next_out = mBuffer;
   946     }
   948     return NS_OK;
   949   }
   951 private:
   953   // use zlib data types
   954   static void *allocator(void *opaque, uInt items, uInt size)
   955   {
   956     return moz_xmalloc(items * size);
   957   }
   959   static void destructor(void *opaque, void *addr)
   960   {
   961     moz_free(addr);
   962   }
   964   nsresult PushData()
   965   {
   966     uint32_t bytesToWrite = kBufferLen - mZlib.avail_out;
   967     if (bytesToWrite > 0) {
   968       mStream->ShareData(reinterpret_cast<char *>(mBuffer), bytesToWrite);
   969       nsresult rv =
   970         mListener->OnDataAvailable(nullptr, mContext, mStream, 0, bytesToWrite);
   971       if (NS_FAILED(rv))
   972         return rv;
   973     }
   974     return NS_OK;
   975   }
   977   bool                            mActive;
   978   z_stream                        mZlib;
   979   nsCOMPtr<nsIStringInputStream>  mStream;
   981   nsISupports                    *mContext;     /* weak ref */
   982   nsIStreamListener              *mListener;    /* weak ref */
   984   const static int32_t            kBufferLen = 4096;
   985   uint8_t                         mBuffer[kBufferLen];
   986 };
   988 //-----------------------------------------------------------------------------
   989 // WebSocketChannel
   990 //-----------------------------------------------------------------------------
   992 uint32_t WebSocketChannel::sSerialSeed = 0;
   994 WebSocketChannel::WebSocketChannel() :
   995   mPort(0),
   996   mCloseTimeout(20000),
   997   mOpenTimeout(20000),
   998   mConnecting(NOT_CONNECTING),
   999   mMaxConcurrentConnections(200),
  1000   mGotUpgradeOK(0),
  1001   mRecvdHttpUpgradeTransport(0),
  1002   mRequestedClose(0),
  1003   mClientClosed(0),
  1004   mServerClosed(0),
  1005   mStopped(0),
  1006   mCalledOnStop(0),
  1007   mPingOutstanding(0),
  1008   mAllowCompression(1),
  1009   mAutoFollowRedirects(0),
  1010   mReleaseOnTransmit(0),
  1011   mTCPClosed(0),
  1012   mOpenedHttpChannel(0),
  1013   mDataStarted(0),
  1014   mIncrementedSessionCount(0),
  1015   mDecrementedSessionCount(0),
  1016   mMaxMessageSize(INT32_MAX),
  1017   mStopOnClose(NS_OK),
  1018   mServerCloseCode(CLOSE_ABNORMAL),
  1019   mScriptCloseCode(0),
  1020   mFragmentOpcode(kContinuation),
  1021   mFragmentAccumulator(0),
  1022   mBuffered(0),
  1023   mBufferSize(kIncomingBufferInitialSize),
  1024   mCurrentOut(nullptr),
  1025   mCurrentOutSent(0),
  1026   mCompressor(nullptr),
  1027   mDynamicOutputSize(0),
  1028   mDynamicOutput(nullptr),
  1029   mPrivateBrowsing(false),
  1030   mConnectionLogService(nullptr),
  1031   mCountRecv(0),
  1032   mCountSent(0),
  1033   mAppId(NECKO_NO_APP_ID)
  1035   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1037   LOG(("WebSocketChannel::WebSocketChannel() %p\n", this));
  1039   nsWSAdmissionManager::Init();
  1041   mFramePtr = mBuffer = static_cast<uint8_t *>(moz_xmalloc(mBufferSize));
  1043   nsresult rv;
  1044   mConnectionLogService = do_GetService("@mozilla.org/network/dashboard;1",&rv);
  1045   if (NS_FAILED(rv))
  1046     LOG(("Failed to initiate dashboard service."));
  1048   mSerial = sSerialSeed++;
  1051 WebSocketChannel::~WebSocketChannel()
  1053   LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this));
  1055   if (mWasOpened) {
  1056     MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called");
  1057     MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped");
  1059   MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction");
  1060   MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor");
  1062   moz_free(mBuffer);
  1063   moz_free(mDynamicOutput);
  1064   delete mCompressor;
  1065   delete mCurrentOut;
  1067   while ((mCurrentOut = (OutboundMessage *) mOutgoingPingMessages.PopFront()))
  1068     delete mCurrentOut;
  1069   while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront()))
  1070     delete mCurrentOut;
  1071   while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront()))
  1072     delete mCurrentOut;
  1074   nsCOMPtr<nsIThread> mainThread;
  1075   nsIURI *forgettable;
  1076   NS_GetMainThread(getter_AddRefs(mainThread));
  1078   if (mURI) {
  1079     mURI.forget(&forgettable);
  1080     NS_ProxyRelease(mainThread, forgettable, false);
  1083   if (mOriginalURI) {
  1084     mOriginalURI.forget(&forgettable);
  1085     NS_ProxyRelease(mainThread, forgettable, false);
  1088   if (mListener) {
  1089     nsIWebSocketListener *forgettableListener;
  1090     mListener.forget(&forgettableListener);
  1091     NS_ProxyRelease(mainThread, forgettableListener, false);
  1094   if (mContext) {
  1095     nsISupports *forgettableContext;
  1096     mContext.forget(&forgettableContext);
  1097     NS_ProxyRelease(mainThread, forgettableContext, false);
  1100   if (mLoadGroup) {
  1101     nsILoadGroup *forgettableGroup;
  1102     mLoadGroup.forget(&forgettableGroup);
  1103     NS_ProxyRelease(mainThread, forgettableGroup, false);
  1107 void
  1108 WebSocketChannel::Shutdown()
  1110   nsWSAdmissionManager::Shutdown();
  1113 void
  1114 WebSocketChannel::BeginOpen()
  1116   LOG(("WebSocketChannel::BeginOpen() %p\n", this));
  1117   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  1119   nsresult rv;
  1121   // Important that we set CONNECTING_IN_PROGRESS before any call to
  1122   // AbortSession here: ensures that any remaining queued connection(s) are
  1123   // scheduled in OnStopSession
  1124   mConnecting = CONNECTING_IN_PROGRESS;
  1126   if (mRedirectCallback) {
  1127     LOG(("WebSocketChannel::BeginOpen: Resuming Redirect\n"));
  1128     rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK);
  1129     mRedirectCallback = nullptr;
  1130     return;
  1133   nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv);
  1134   if (NS_FAILED(rv)) {
  1135     LOG(("WebSocketChannel::BeginOpen: cannot async open\n"));
  1136     AbortSession(NS_ERROR_UNEXPECTED);
  1137     return;
  1140   if (localChannel) {
  1141     bool isInBrowser;
  1142     NS_GetAppInfo(localChannel, &mAppId, &isInBrowser);
  1145 #ifdef MOZ_WIDGET_GONK
  1146   if (mAppId != NECKO_NO_APP_ID) {
  1147     nsCOMPtr<nsINetworkInterface> activeNetwork;
  1148     GetActiveNetworkInterface(activeNetwork);
  1149     mActiveNetwork =
  1150       new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork);
  1152 #endif
  1154   rv = localChannel->AsyncOpen(this, mHttpChannel);
  1155   if (NS_FAILED(rv)) {
  1156     LOG(("WebSocketChannel::BeginOpen: cannot async open\n"));
  1157     AbortSession(NS_ERROR_CONNECTION_REFUSED);
  1158     return;
  1160   mOpenedHttpChannel = 1;
  1162   mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1163   if (NS_FAILED(rv)) {
  1164     LOG(("WebSocketChannel::BeginOpen: cannot create open timer\n"));
  1165     AbortSession(NS_ERROR_UNEXPECTED);
  1166     return;
  1169   rv = mOpenTimer->InitWithCallback(this, mOpenTimeout,
  1170                                     nsITimer::TYPE_ONE_SHOT);
  1171   if (NS_FAILED(rv)) {
  1172     LOG(("WebSocketChannel::BeginOpen: cannot initialize open timer\n"));
  1173     AbortSession(NS_ERROR_UNEXPECTED);
  1174     return;
  1178 bool
  1179 WebSocketChannel::IsPersistentFramePtr()
  1181   return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize);
  1184 // Extends the internal buffer by count and returns the total
  1185 // amount of data available for read
  1186 //
  1187 // Accumulated fragment size is passed in instead of using the member
  1188 // variable beacuse when transitioning from the stack to the persistent
  1189 // read buffer we want to explicitly include them in the buffer instead
  1190 // of as already existing data.
  1191 bool
  1192 WebSocketChannel::UpdateReadBuffer(uint8_t *buffer, uint32_t count,
  1193                                    uint32_t accumulatedFragments,
  1194                                    uint32_t *available)
  1196   LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n",
  1197          this, buffer, count));
  1199   if (!mBuffered)
  1200     mFramePtr = mBuffer;
  1202   NS_ABORT_IF_FALSE(IsPersistentFramePtr(), "update read buffer bad mFramePtr");
  1203   NS_ABORT_IF_FALSE(mFramePtr - accumulatedFragments >= mBuffer,
  1204                     "reserved FramePtr bad");
  1206   if (mBuffered + count <= mBufferSize) {
  1207     // append to existing buffer
  1208     LOG(("WebSocketChannel: update read buffer absorbed %u\n", count));
  1209   } else if (mBuffered + count - 
  1210              (mFramePtr - accumulatedFragments - mBuffer) <= mBufferSize) {
  1211     // make room in existing buffer by shifting unused data to start
  1212     mBuffered -= (mFramePtr - mBuffer - accumulatedFragments);
  1213     LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered));
  1214     ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered);
  1215     mFramePtr = mBuffer + accumulatedFragments;
  1216   } else {
  1217     // existing buffer is not sufficient, extend it
  1218     mBufferSize += count + 8192 + mBufferSize/3;
  1219     LOG(("WebSocketChannel: update read buffer extended to %u\n", mBufferSize));
  1220     uint8_t *old = mBuffer;
  1221     mBuffer = (uint8_t *)moz_realloc(mBuffer, mBufferSize);
  1222     if (!mBuffer) {
  1223       mBuffer = old;
  1224       return false;
  1226     mFramePtr = mBuffer + (mFramePtr - old);
  1229   ::memcpy(mBuffer + mBuffered, buffer, count);
  1230   mBuffered += count;
  1232   if (available)
  1233     *available = mBuffered - (mFramePtr - mBuffer);
  1235   return true;
  1238 nsresult
  1239 WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
  1241   LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
  1242   NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1244   // The purpose of ping/pong is to actively probe the peer so that an
  1245   // unreachable peer is not mistaken for a period of idleness. This
  1246   // implementation accepts any application level read activity as a sign of
  1247   // life, it does not necessarily have to be a pong.
  1248   ResetPingTimer();
  1250   uint32_t avail;
  1252   if (!mBuffered) {
  1253     // Most of the time we can process right off the stack buffer without
  1254     // having to accumulate anything
  1255     mFramePtr = buffer;
  1256     avail = count;
  1257   } else {
  1258     if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) {
  1259       return NS_ERROR_FILE_TOO_BIG;
  1263   uint8_t *payload;
  1264   uint32_t totalAvail = avail;
  1266   while (avail >= 2) {
  1267     int64_t payloadLength64 = mFramePtr[1] & 0x7F;
  1268     uint8_t finBit  = mFramePtr[0] & kFinalFragBit;
  1269     uint8_t rsvBits = mFramePtr[0] & 0x70;
  1270     uint8_t maskBit = mFramePtr[1] & kMaskBit;
  1271     uint8_t opcode  = mFramePtr[0] & 0x0F;
  1273     uint32_t framingLength = 2;
  1274     if (maskBit)
  1275       framingLength += 4;
  1277     if (payloadLength64 < 126) {
  1278       if (avail < framingLength)
  1279         break;
  1280     } else if (payloadLength64 == 126) {
  1281       // 16 bit length field
  1282       framingLength += 2;
  1283       if (avail < framingLength)
  1284         break;
  1286       payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3];
  1287     } else {
  1288       // 64 bit length
  1289       framingLength += 8;
  1290       if (avail < framingLength)
  1291         break;
  1293       if (mFramePtr[2] & 0x80) {
  1294         // Section 4.2 says that the most significant bit MUST be
  1295         // 0. (i.e. this is really a 63 bit value)
  1296         LOG(("WebSocketChannel:: high bit of 64 bit length set"));
  1297         return NS_ERROR_ILLEGAL_VALUE;
  1300       // copy this in case it is unaligned
  1301       payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2);
  1304     payload = mFramePtr + framingLength;
  1305     avail -= framingLength;
  1307     LOG(("WebSocketChannel::ProcessInput: payload %lld avail %lu\n",
  1308          payloadLength64, avail));
  1310     if (payloadLength64 + mFragmentAccumulator > mMaxMessageSize) {
  1311       return NS_ERROR_FILE_TOO_BIG;
  1313     uint32_t payloadLength = static_cast<uint32_t>(payloadLength64);
  1315     if (avail < payloadLength)
  1316       break;
  1318     LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n",
  1319          opcode));
  1321     if (maskBit) {
  1322       // This is unexpected - the server does not generally send masked
  1323       // frames to the client, but it is allowed
  1324       LOG(("WebSocketChannel:: Client RECEIVING masked frame."));
  1326       uint32_t mask = NetworkEndian::readUint32(payload - 4);
  1327       ApplyMask(mask, payload, payloadLength);
  1330     // Control codes are required to have the fin bit set
  1331     if (!finBit && (opcode & kControlFrameMask)) {
  1332       LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode));
  1333       return NS_ERROR_ILLEGAL_VALUE;
  1336     if (rsvBits) {
  1337       LOG(("WebSocketChannel:: unexpected reserved bits %x\n", rsvBits));
  1338       return NS_ERROR_ILLEGAL_VALUE;
  1341     if (!finBit || opcode == kContinuation) {
  1342       // This is part of a fragment response
  1344       // Only the first frame has a non zero op code: Make sure we don't see a
  1345       // first frame while some old fragments are open
  1346       if ((mFragmentAccumulator != 0) && (opcode != kContinuation)) {
  1347         LOG(("WebSocketChannel:: nested fragments\n"));
  1348         return NS_ERROR_ILLEGAL_VALUE;
  1351       LOG(("WebSocketChannel:: Accumulating Fragment %ld\n", payloadLength));
  1353       if (opcode == kContinuation) {
  1355         // Make sure this continuation fragment isn't the first fragment
  1356         if (mFragmentOpcode == kContinuation) {
  1357           LOG(("WebSocketHeandler:: continuation code in first fragment\n"));
  1358           return NS_ERROR_ILLEGAL_VALUE;
  1361         // For frag > 1 move the data body back on top of the headers
  1362         // so we have contiguous stream of data
  1363         NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload,
  1364                           "payload offset from frameptr wrong");
  1365         ::memmove(mFramePtr, payload, avail);
  1366         payload = mFramePtr;
  1367         if (mBuffered)
  1368           mBuffered -= framingLength;
  1369       } else {
  1370         mFragmentOpcode = opcode;
  1373       if (finBit) {
  1374         LOG(("WebSocketChannel:: Finalizing Fragment\n"));
  1375         payload -= mFragmentAccumulator;
  1376         payloadLength += mFragmentAccumulator;
  1377         avail += mFragmentAccumulator;
  1378         mFragmentAccumulator = 0;
  1379         opcode = mFragmentOpcode;
  1380         // reset to detect if next message illegally starts with continuation
  1381         mFragmentOpcode = kContinuation;
  1382       } else {
  1383         opcode = kContinuation;
  1384         mFragmentAccumulator += payloadLength;
  1386     } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) {
  1387       // This frame is not part of a fragment sequence but we
  1388       // have an open fragment.. it must be a control code or else
  1389       // we have a problem
  1390       LOG(("WebSocketChannel:: illegal fragment sequence\n"));
  1391       return NS_ERROR_ILLEGAL_VALUE;
  1394     if (mServerClosed) {
  1395       LOG(("WebSocketChannel:: ignoring read frame code %d after close\n",
  1396                  opcode));
  1397       // nop
  1398     } else if (mStopped) {
  1399       LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n",
  1400            opcode));
  1401     } else if (opcode == kText) {
  1402       LOG(("WebSocketChannel:: text frame received\n"));
  1403       if (mListener) {
  1404         nsCString utf8Data;
  1405         if (!utf8Data.Assign((const char *)payload, payloadLength,
  1406                              mozilla::fallible_t()))
  1407           return NS_ERROR_OUT_OF_MEMORY;
  1409         // Section 8.1 says to fail connection if invalid utf-8 in text message
  1410         if (!IsUTF8(utf8Data, false)) {
  1411           LOG(("WebSocketChannel:: text frame invalid utf-8\n"));
  1412           return NS_ERROR_CANNOT_CONVERT_DATA;
  1415         mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1),
  1416                                 NS_DISPATCH_NORMAL);
  1417         if (mConnectionLogService && !mPrivateBrowsing) {
  1418           mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
  1419           LOG(("Added new msg received for %s", mHost.get()));
  1422     } else if (opcode & kControlFrameMask) {
  1423       // control frames
  1424       if (payloadLength > 125) {
  1425         LOG(("WebSocketChannel:: bad control frame code %d length %d\n",
  1426              opcode, payloadLength));
  1427         return NS_ERROR_ILLEGAL_VALUE;
  1430       if (opcode == kClose) {
  1431         LOG(("WebSocketChannel:: close received\n"));
  1432         mServerClosed = 1;
  1434         mServerCloseCode = CLOSE_NO_STATUS;
  1435         if (payloadLength >= 2) {
  1436           mServerCloseCode = NetworkEndian::readUint16(payload);
  1437           LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode));
  1438           uint16_t msglen = static_cast<uint16_t>(payloadLength - 2);
  1439           if (msglen > 0) {
  1440             mServerCloseReason.SetLength(msglen);
  1441             memcpy(mServerCloseReason.BeginWriting(),
  1442                    (const char *)payload + 2, msglen);
  1444             // section 8.1 says to replace received non utf-8 sequences
  1445             // (which are non-conformant to send) with u+fffd,
  1446             // but secteam feels that silently rewriting messages is
  1447             // inappropriate - so we will fail the connection instead.
  1448             if (!IsUTF8(mServerCloseReason, false)) {
  1449               LOG(("WebSocketChannel:: close frame invalid utf-8\n"));
  1450               return NS_ERROR_CANNOT_CONVERT_DATA;
  1453             LOG(("WebSocketChannel:: close msg %s\n",
  1454                  mServerCloseReason.get()));
  1458         if (mCloseTimer) {
  1459           mCloseTimer->Cancel();
  1460           mCloseTimer = nullptr;
  1462         if (mListener) {
  1463           mTargetThread->Dispatch(new CallOnServerClose(this, mServerCloseCode,
  1464                                                         mServerCloseReason),
  1465                                   NS_DISPATCH_NORMAL);
  1468         if (mClientClosed)
  1469           ReleaseSession();
  1470       } else if (opcode == kPing) {
  1471         LOG(("WebSocketChannel:: ping received\n"));
  1472         GeneratePong(payload, payloadLength);
  1473       } else if (opcode == kPong) {
  1474         // opcode kPong: the mere act of receiving the packet is all we need
  1475         // to do for the pong to trigger the activity timers
  1476         LOG(("WebSocketChannel:: pong received\n"));
  1477       } else {
  1478         /* unknown control frame opcode */
  1479         LOG(("WebSocketChannel:: unknown control op code %d\n", opcode));
  1480         return NS_ERROR_ILLEGAL_VALUE;
  1483       if (mFragmentAccumulator) {
  1484         // Remove the control frame from the stream so we have a contiguous
  1485         // data buffer of reassembled fragments
  1486         LOG(("WebSocketChannel:: Removing Control From Read buffer\n"));
  1487         NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload,
  1488                           "payload offset from frameptr wrong");
  1489         ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength);
  1490         payload = mFramePtr;
  1491         avail -= payloadLength;
  1492         if (mBuffered)
  1493           mBuffered -= framingLength + payloadLength;
  1494         payloadLength = 0;
  1496     } else if (opcode == kBinary) {
  1497       LOG(("WebSocketChannel:: binary frame received\n"));
  1498       if (mListener) {
  1499         nsCString binaryData((const char *)payload, payloadLength);
  1500         mTargetThread->Dispatch(new CallOnMessageAvailable(this, binaryData,
  1501                                                            payloadLength),
  1502                                 NS_DISPATCH_NORMAL);
  1503         // To add the header to 'Networking Dashboard' log
  1504         if (mConnectionLogService && !mPrivateBrowsing) {
  1505           mConnectionLogService->NewMsgReceived(mHost, mSerial, count);
  1506           LOG(("Added new received msg for %s", mHost.get()));
  1509     } else if (opcode != kContinuation) {
  1510       /* unknown opcode */
  1511       LOG(("WebSocketChannel:: unknown op code %d\n", opcode));
  1512       return NS_ERROR_ILLEGAL_VALUE;
  1515     mFramePtr = payload + payloadLength;
  1516     avail -= payloadLength;
  1517     totalAvail = avail;
  1520   // Adjust the stateful buffer. If we were operating off the stack and
  1521   // now have a partial message then transition to the buffer, or if
  1522   // we were working off the buffer but no longer have any active state
  1523   // then transition to the stack
  1524   if (!IsPersistentFramePtr()) {
  1525     mBuffered = 0;
  1527     if (mFragmentAccumulator) {
  1528       LOG(("WebSocketChannel:: Setup Buffer due to fragment"));
  1530       if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator,
  1531                             totalAvail + mFragmentAccumulator, 0, nullptr)) {
  1532         return NS_ERROR_FILE_TOO_BIG;
  1535       // UpdateReadBuffer will reset the frameptr to the beginning
  1536       // of new saved state, so we need to skip past processed framgents
  1537       mFramePtr += mFragmentAccumulator;
  1538     } else if (totalAvail) {
  1539       LOG(("WebSocketChannel:: Setup Buffer due to partial frame"));
  1540       if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) {
  1541         return NS_ERROR_FILE_TOO_BIG;
  1544   } else if (!mFragmentAccumulator && !totalAvail) {
  1545     // If we were working off a saved buffer state and there is no partial
  1546     // frame or fragment in process, then revert to stack behavior
  1547     LOG(("WebSocketChannel:: Internal buffering not needed anymore"));
  1548     mBuffered = 0;
  1550     // release memory if we've been processing a large message
  1551     if (mBufferSize > kIncomingBufferStableSize) {
  1552       mBufferSize = kIncomingBufferStableSize;
  1553       moz_free(mBuffer);
  1554       mBuffer = (uint8_t *)moz_xmalloc(mBufferSize);
  1557   return NS_OK;
  1560 void
  1561 WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len)
  1563   if (!data || len == 0)
  1564     return;
  1566   // Optimally we want to apply the mask 32 bits at a time,
  1567   // but the buffer might not be alligned. So we first deal with
  1568   // 0 to 3 bytes of preamble individually
  1570   while (len && (reinterpret_cast<uintptr_t>(data) & 3)) {
  1571     *data ^= mask >> 24;
  1572     mask = RotateLeft(mask, 8);
  1573     data++;
  1574     len--;
  1577   // perform mask on full words of data
  1579   uint32_t *iData = (uint32_t *) data;
  1580   uint32_t *end = iData + (len / 4);
  1581   NetworkEndian::writeUint32(&mask, mask);
  1582   for (; iData < end; iData++)
  1583     *iData ^= mask;
  1584   mask = NetworkEndian::readUint32(&mask);
  1585   data = (uint8_t *)iData;
  1586   len  = len % 4;
  1588   // There maybe up to 3 trailing bytes that need to be dealt with
  1589   // individually 
  1591   while (len) {
  1592     *data ^= mask >> 24;
  1593     mask = RotateLeft(mask, 8);
  1594     data++;
  1595     len--;
  1599 void
  1600 WebSocketChannel::GeneratePing()
  1602   nsCString *buf = new nsCString();
  1603   buf->Assign("PING");
  1604   EnqueueOutgoingMessage(mOutgoingPingMessages,
  1605                          new OutboundMessage(kMsgTypePing, buf));
  1608 void
  1609 WebSocketChannel::GeneratePong(uint8_t *payload, uint32_t len)
  1611   nsCString *buf = new nsCString();
  1612   buf->SetLength(len);
  1613   if (buf->Length() < len) {
  1614     LOG(("WebSocketChannel::GeneratePong Allocation Failure\n"));
  1615     delete buf;
  1616     return;
  1619   memcpy(buf->BeginWriting(), payload, len);
  1620   EnqueueOutgoingMessage(mOutgoingPongMessages,
  1621                          new OutboundMessage(kMsgTypePong, buf));
  1624 void
  1625 WebSocketChannel::EnqueueOutgoingMessage(nsDeque &aQueue,
  1626                                          OutboundMessage *aMsg)
  1628   NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1630   LOG(("WebSocketChannel::EnqueueOutgoingMessage %p "
  1631        "queueing msg %p [type=%s len=%d]\n",
  1632        this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length()));
  1634   aQueue.Push(aMsg);
  1635   OnOutputStreamReady(mSocketOut);
  1639 uint16_t
  1640 WebSocketChannel::ResultToCloseCode(nsresult resultCode)
  1642   if (NS_SUCCEEDED(resultCode))
  1643     return CLOSE_NORMAL;
  1645   switch (resultCode) {
  1646     case NS_ERROR_FILE_TOO_BIG:
  1647     case NS_ERROR_OUT_OF_MEMORY:
  1648       return CLOSE_TOO_LARGE;
  1649     case NS_ERROR_CANNOT_CONVERT_DATA:
  1650       return CLOSE_INVALID_PAYLOAD;
  1651     case NS_ERROR_UNEXPECTED:
  1652       return CLOSE_INTERNAL_ERROR;
  1653     default:
  1654       return CLOSE_PROTOCOL_ERROR;
  1658 void
  1659 WebSocketChannel::PrimeNewOutgoingMessage()
  1661   LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this));
  1662   NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  1663   NS_ABORT_IF_FALSE(!mCurrentOut, "Current message in progress");
  1665   nsresult rv = NS_OK;
  1667   mCurrentOut = (OutboundMessage *)mOutgoingPongMessages.PopFront();
  1668   if (mCurrentOut) {
  1669     NS_ABORT_IF_FALSE(mCurrentOut->GetMsgType() == kMsgTypePong,
  1670                      "Not pong message!");
  1671   } else {
  1672     mCurrentOut = (OutboundMessage *)mOutgoingPingMessages.PopFront();
  1673     if (mCurrentOut)
  1674       NS_ABORT_IF_FALSE(mCurrentOut->GetMsgType() == kMsgTypePing,
  1675                         "Not ping message!");
  1676     else
  1677       mCurrentOut = (OutboundMessage *)mOutgoingMessages.PopFront();
  1680   if (!mCurrentOut)
  1681     return;
  1683   WsMsgType msgType = mCurrentOut->GetMsgType();
  1685   LOG(("WebSocketChannel::PrimeNewOutgoingMessage "
  1686        "%p found queued msg %p [type=%s len=%d]\n",
  1687        this, mCurrentOut, msgNames[msgType], mCurrentOut->Length()));
  1689   mCurrentOutSent = 0;
  1690   mHdrOut = mOutHeader;
  1692   uint8_t *payload = nullptr;
  1694   if (msgType == kMsgTypeFin) {
  1695     // This is a demand to create a close message
  1696     if (mClientClosed) {
  1697       DeleteCurrentOutGoingMessage();
  1698       PrimeNewOutgoingMessage();
  1699       return;
  1702     mClientClosed = 1;
  1703     mOutHeader[0] = kFinalFragBit | kClose;
  1704     mOutHeader[1] = kMaskBit;
  1706     // payload is offset 6 including 4 for the mask
  1707     payload = mOutHeader + 6;
  1709     // The close reason code sits in the first 2 bytes of payload
  1710     // If the channel user provided a code and reason during Close()
  1711     // and there isn't an internal error, use that.
  1712     if (NS_SUCCEEDED(mStopOnClose)) {
  1713       if (mScriptCloseCode) {
  1714         NetworkEndian::writeUint16(payload, mScriptCloseCode);
  1715         mOutHeader[1] += 2;
  1716         mHdrOutToSend = 8;
  1717         if (!mScriptCloseReason.IsEmpty()) {
  1718           NS_ABORT_IF_FALSE(mScriptCloseReason.Length() <= 123,
  1719                             "Close Reason Too Long");
  1720           mOutHeader[1] += mScriptCloseReason.Length();
  1721           mHdrOutToSend += mScriptCloseReason.Length();
  1722           memcpy (payload + 2,
  1723                   mScriptCloseReason.BeginReading(),
  1724                   mScriptCloseReason.Length());
  1726       } else {
  1727         // No close code/reason, so payload length = 0.  We must still send mask
  1728         // even though it's not used.  Keep payload offset so we write mask
  1729         // below.
  1730         mHdrOutToSend = 6;
  1732     } else {
  1733       NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose));
  1734       mOutHeader[1] += 2;
  1735       mHdrOutToSend = 8;
  1738     if (mServerClosed) {
  1739       /* bidi close complete */
  1740       mReleaseOnTransmit = 1;
  1741     } else if (NS_FAILED(mStopOnClose)) {
  1742       /* result of abort session - give up */
  1743       StopSession(mStopOnClose);
  1744     } else {
  1745       /* wait for reciprocal close from server */
  1746       mCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  1747       if (NS_SUCCEEDED(rv)) {
  1748         mCloseTimer->InitWithCallback(this, mCloseTimeout,
  1749                                       nsITimer::TYPE_ONE_SHOT);
  1750       } else {
  1751         StopSession(rv);
  1754   } else {
  1755     switch (msgType) {
  1756     case kMsgTypePong:
  1757       mOutHeader[0] = kFinalFragBit | kPong;
  1758       break;
  1759     case kMsgTypePing:
  1760       mOutHeader[0] = kFinalFragBit | kPing;
  1761       break;
  1762     case kMsgTypeString:
  1763       mOutHeader[0] = kFinalFragBit | kText;
  1764       break;
  1765     case kMsgTypeStream:
  1766       // HACK ALERT:  read in entire stream into string.
  1767       // Will block socket transport thread if file is blocking.
  1768       // TODO: bug 704447:  don't block socket thread!
  1769       rv = mCurrentOut->ConvertStreamToString();
  1770       if (NS_FAILED(rv)) {
  1771         AbortSession(NS_ERROR_FILE_TOO_BIG);
  1772         return;
  1774       // Now we're a binary string
  1775       msgType = kMsgTypeBinaryString;
  1777       // no break: fall down into binary string case
  1779     case kMsgTypeBinaryString:
  1780       mOutHeader[0] = kFinalFragBit | kBinary;
  1781       break;
  1782     case kMsgTypeFin:
  1783       NS_ABORT_IF_FALSE(false, "unreachable");  // avoid compiler warning
  1784       break;
  1787     if (mCurrentOut->Length() < 126) {
  1788       mOutHeader[1] = mCurrentOut->Length() | kMaskBit;
  1789       mHdrOutToSend = 6;
  1790     } else if (mCurrentOut->Length() <= 0xffff) {
  1791       mOutHeader[1] = 126 | kMaskBit;
  1792       NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t),
  1793                                  mCurrentOut->Length());
  1794       mHdrOutToSend = 8;
  1795     } else {
  1796       mOutHeader[1] = 127 | kMaskBit;
  1797       NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length());
  1798       mHdrOutToSend = 14;
  1800     payload = mOutHeader + mHdrOutToSend;
  1803   NS_ABORT_IF_FALSE(payload, "payload offset not found");
  1805   // Perform the sending mask. Never use a zero mask
  1806   uint32_t mask;
  1807   do {
  1808     uint8_t *buffer;
  1809     nsresult rv = mRandomGenerator->GenerateRandomBytes(4, &buffer);
  1810     if (NS_FAILED(rv)) {
  1811       LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): "
  1812            "GenerateRandomBytes failure %x\n", rv));
  1813       StopSession(rv);
  1814       return;
  1816     mask = * reinterpret_cast<uint32_t *>(buffer);
  1817     NS_Free(buffer);
  1818   } while (!mask);
  1819   NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask);
  1821   LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask));
  1823   // We don't mask the framing, but occasionally we stick a little payload
  1824   // data in the buffer used for the framing. Close frames are the current
  1825   // example. This data needs to be masked, but it is never more than a
  1826   // handful of bytes and might rotate the mask, so we can just do it locally.
  1827   // For real data frames we ship the bulk of the payload off to ApplyMask()
  1829   while (payload < (mOutHeader + mHdrOutToSend)) {
  1830     *payload ^= mask >> 24;
  1831     mask = RotateLeft(mask, 8);
  1832     payload++;
  1835   // Mask the real message payloads
  1837   ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length());
  1839   int32_t len = mCurrentOut->Length();
  1841   // for small frames, copy it all together for a contiguous write
  1842   if (len && len <= kCopyBreak) {
  1843     memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len);
  1844     mHdrOutToSend += len;
  1845     mCurrentOutSent = len;
  1848   if (len && mCompressor) {
  1849     // assume a 1/3 reduction in size for sizing the buffer
  1850     // the buffer is used multiple times if necessary
  1851     uint32_t currentHeaderSize = mHdrOutToSend;
  1852     mHdrOutToSend = 0;
  1854     EnsureHdrOut(32 + (currentHeaderSize + len - mCurrentOutSent) / 2 * 3);
  1855     mCompressor->Deflate(mOutHeader, currentHeaderSize,
  1856                          mCurrentOut->BeginReading() + mCurrentOutSent,
  1857                          len - mCurrentOutSent);
  1859     // All of the compressed data now resides in {mHdrOut, mHdrOutToSend}
  1860     // so do not send the body again
  1861     mCurrentOutSent = len;
  1864   // Transmitting begins - mHdrOutToSend bytes from mOutHeader and
  1865   // mCurrentOut->Length() bytes from mCurrentOut. The latter may be
  1866   // coaleseced into the former for small messages or as the result of the
  1867   // compression process,
  1870 void
  1871 WebSocketChannel::DeleteCurrentOutGoingMessage()
  1873   delete mCurrentOut;
  1874   mCurrentOut = nullptr;
  1875   mCurrentOutSent = 0;
  1878 void
  1879 WebSocketChannel::EnsureHdrOut(uint32_t size)
  1881   LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size));
  1883   if (mDynamicOutputSize < size) {
  1884     mDynamicOutputSize = size;
  1885     mDynamicOutput =
  1886       (uint8_t *) moz_xrealloc(mDynamicOutput, mDynamicOutputSize);
  1889   mHdrOut = mDynamicOutput;
  1892 void
  1893 WebSocketChannel::CleanupConnection()
  1895   LOG(("WebSocketChannel::CleanupConnection() %p", this));
  1897   if (mLingeringCloseTimer) {
  1898     mLingeringCloseTimer->Cancel();
  1899     mLingeringCloseTimer = nullptr;
  1902   if (mSocketIn) {
  1903     mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
  1904     mSocketIn = nullptr;
  1907   if (mSocketOut) {
  1908     mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
  1909     mSocketOut = nullptr;
  1912   if (mTransport) {
  1913     mTransport->SetSecurityCallbacks(nullptr);
  1914     mTransport->SetEventSink(nullptr, nullptr);
  1915     mTransport->Close(NS_BASE_STREAM_CLOSED);
  1916     mTransport = nullptr;
  1919   if (mConnectionLogService && !mPrivateBrowsing) {
  1920     mConnectionLogService->RemoveHost(mHost, mSerial);
  1923   DecrementSessionCount();
  1926 void
  1927 WebSocketChannel::StopSession(nsresult reason)
  1929   LOG(("WebSocketChannel::StopSession() %p [%x]\n", this, reason));
  1931   // normally this should be called on socket thread, but it is ok to call it
  1932   // from OnStartRequest before the socket thread machine has gotten underway
  1934   mStopped = 1;
  1936   if (!mOpenedHttpChannel) {
  1937     // The HTTP channel information will never be used in this case
  1938     mChannel = nullptr;
  1939     mHttpChannel = nullptr;
  1940     mLoadGroup = nullptr;
  1941     mCallbacks = nullptr;
  1944   if (mCloseTimer) {
  1945     mCloseTimer->Cancel();
  1946     mCloseTimer = nullptr;
  1949   if (mOpenTimer) {
  1950     mOpenTimer->Cancel();
  1951     mOpenTimer = nullptr;
  1954   if (mReconnectDelayTimer) {
  1955     mReconnectDelayTimer->Cancel();
  1956     mReconnectDelayTimer = nullptr;
  1959   if (mPingTimer) {
  1960     mPingTimer->Cancel();
  1961     mPingTimer = nullptr;
  1964   if (mSocketIn && !mTCPClosed) {
  1965     // Drain, within reason, this socket. if we leave any data
  1966     // unconsumed (including the tcp fin) a RST will be generated
  1967     // The right thing to do here is shutdown(SHUT_WR) and then wait
  1968     // a little while to see if any data comes in.. but there is no
  1969     // reason to delay things for that when the websocket handshake
  1970     // is supposed to guarantee a quiet connection except for that fin.
  1972     char     buffer[512];
  1973     uint32_t count = 0;
  1974     uint32_t total = 0;
  1975     nsresult rv;
  1976     do {
  1977       total += count;
  1978       rv = mSocketIn->Read(buffer, 512, &count);
  1979       if (rv != NS_BASE_STREAM_WOULD_BLOCK &&
  1980         (NS_FAILED(rv) || count == 0))
  1981         mTCPClosed = true;
  1982     } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000);
  1985   int32_t sessionCount = kLingeringCloseThreshold;
  1986   nsWSAdmissionManager::GetSessionCount(sessionCount);
  1988   if (!mTCPClosed && mTransport && sessionCount < kLingeringCloseThreshold) {
  1990     // 7.1.1 says that the client SHOULD wait for the server to close the TCP
  1991     // connection. This is so we can reuse port numbers before 2 MSL expires,
  1992     // which is not really as much of a concern for us as the amount of state
  1993     // that might be accrued by keeping this channel object around waiting for
  1994     // the server. We handle the SHOULD by waiting a short time in the common
  1995     // case, but not waiting in the case of high concurrency.
  1996     //
  1997     // Normally this will be taken care of in AbortSession() after mTCPClosed
  1998     // is set when the server close arrives without waiting for the timeout to
  1999     // expire.
  2001     LOG(("WebSocketChannel::StopSession: Wait for Server TCP close"));
  2003     nsresult rv;
  2004     mLingeringCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  2005     if (NS_SUCCEEDED(rv))
  2006       mLingeringCloseTimer->InitWithCallback(this, kLingeringCloseTimeout,
  2007                                              nsITimer::TYPE_ONE_SHOT);
  2008     else
  2009       CleanupConnection();
  2010   } else {
  2011     CleanupConnection();
  2014   if (mCancelable) {
  2015     mCancelable->Cancel(NS_ERROR_UNEXPECTED);
  2016     mCancelable = nullptr;
  2019   mInflateReader = nullptr;
  2020   mInflateStream = nullptr;
  2022   delete mCompressor;
  2023   mCompressor = nullptr;
  2025   if (!mCalledOnStop) {
  2026     mCalledOnStop = 1;
  2027     mTargetThread->Dispatch(new CallOnStop(this, reason),
  2028                             NS_DISPATCH_NORMAL);
  2031   return;
  2034 void
  2035 WebSocketChannel::AbortSession(nsresult reason)
  2037   LOG(("WebSocketChannel::AbortSession() %p [reason %x] stopped = %d\n",
  2038        this, reason, mStopped));
  2040   // normally this should be called on socket thread, but it is ok to call it
  2041   // from the main thread before StartWebsocketData() has completed
  2043   // When we are failing we need to close the TCP connection immediately
  2044   // as per 7.1.1
  2045   mTCPClosed = true;
  2047   if (mLingeringCloseTimer) {
  2048     NS_ABORT_IF_FALSE(mStopped, "Lingering without Stop");
  2049     LOG(("WebSocketChannel:: Cleanup connection based on TCP Close"));
  2050     CleanupConnection();
  2051     return;
  2054   if (mStopped)
  2055     return;
  2056   mStopped = 1;
  2058   if (mTransport && reason != NS_BASE_STREAM_CLOSED &&
  2059       !mRequestedClose && !mClientClosed && !mServerClosed) {
  2060     mRequestedClose = 1;
  2061     mStopOnClose = reason;
  2062     mSocketThread->Dispatch(
  2063       new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
  2064                            nsIEventTarget::DISPATCH_NORMAL);
  2065   } else {
  2066     StopSession(reason);
  2070 // ReleaseSession is called on orderly shutdown
  2071 void
  2072 WebSocketChannel::ReleaseSession()
  2074   LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n",
  2075        this, mStopped));
  2076   NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  2078   if (mStopped)
  2079     return;
  2080   StopSession(NS_OK);
  2083 void
  2084 WebSocketChannel::IncrementSessionCount()
  2086   if (!mIncrementedSessionCount) {
  2087     nsWSAdmissionManager::IncrementSessionCount();
  2088     mIncrementedSessionCount = 1;
  2092 void
  2093 WebSocketChannel::DecrementSessionCount()
  2095   // Make sure we decrement session count only once, and only if we incremented it.
  2096   // This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount is
  2097   // atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at
  2098   // times when they'll never be a race condition for checking/setting them.
  2099   if (mIncrementedSessionCount && !mDecrementedSessionCount) {
  2100     nsWSAdmissionManager::DecrementSessionCount();
  2101     mDecrementedSessionCount = 1;
  2105 nsresult
  2106 WebSocketChannel::HandleExtensions()
  2108   LOG(("WebSocketChannel::HandleExtensions() %p\n", this));
  2110   nsresult rv;
  2111   nsAutoCString extensions;
  2113   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  2115   rv = mHttpChannel->GetResponseHeader(
  2116     NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions);
  2117   if (NS_SUCCEEDED(rv)) {
  2118     if (!extensions.IsEmpty()) {
  2119       if (!extensions.Equals(NS_LITERAL_CSTRING("deflate-stream"))) {
  2120         LOG(("WebSocketChannel::OnStartRequest: "
  2121              "HTTP Sec-WebSocket-Exensions negotiated unknown value %s\n",
  2122              extensions.get()));
  2123         AbortSession(NS_ERROR_ILLEGAL_VALUE);
  2124         return NS_ERROR_ILLEGAL_VALUE;
  2127       if (!mAllowCompression) {
  2128         LOG(("WebSocketChannel::HandleExtensions: "
  2129              "Recvd Compression Extension that wasn't offered\n"));
  2130         AbortSession(NS_ERROR_ILLEGAL_VALUE);
  2131         return NS_ERROR_ILLEGAL_VALUE;
  2134       nsCOMPtr<nsIStreamConverterService> serv =
  2135         do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
  2136       if (NS_FAILED(rv)) {
  2137         LOG(("WebSocketChannel:: Cannot find compression service\n"));
  2138         AbortSession(NS_ERROR_UNEXPECTED);
  2139         return NS_ERROR_UNEXPECTED;
  2142       rv = serv->AsyncConvertData("deflate", "uncompressed", this, nullptr,
  2143                                   getter_AddRefs(mInflateReader));
  2145       if (NS_FAILED(rv)) {
  2146         LOG(("WebSocketChannel:: Cannot find inflate listener\n"));
  2147         AbortSession(NS_ERROR_UNEXPECTED);
  2148         return NS_ERROR_UNEXPECTED;
  2151       mInflateStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
  2153       if (NS_FAILED(rv)) {
  2154         LOG(("WebSocketChannel:: Cannot find inflate stream\n"));
  2155         AbortSession(NS_ERROR_UNEXPECTED);
  2156         return NS_ERROR_UNEXPECTED;
  2159       mCompressor = new nsWSCompression(this, mSocketOut);
  2160       if (!mCompressor->Active()) {
  2161         LOG(("WebSocketChannel:: Cannot init deflate object\n"));
  2162         delete mCompressor;
  2163         mCompressor = nullptr;
  2164         AbortSession(NS_ERROR_UNEXPECTED);
  2165         return NS_ERROR_UNEXPECTED;
  2167       mNegotiatedExtensions = extensions;
  2171   return NS_OK;
  2174 nsresult
  2175 WebSocketChannel::SetupRequest()
  2177   LOG(("WebSocketChannel::SetupRequest() %p\n", this));
  2179   nsresult rv;
  2181   if (mLoadGroup) {
  2182     rv = mHttpChannel->SetLoadGroup(mLoadGroup);
  2183     NS_ENSURE_SUCCESS(rv, rv);
  2186   rv = mHttpChannel->SetLoadFlags(nsIRequest::LOAD_BACKGROUND |
  2187                                   nsIRequest::INHIBIT_CACHING |
  2188                                   nsIRequest::LOAD_BYPASS_CACHE);
  2189   NS_ENSURE_SUCCESS(rv, rv);
  2191   // we never let websockets be blocked by head CSS/JS loads to avoid
  2192   // potential deadlock where server generation of CSS/JS requires
  2193   // an XHR signal.
  2194   rv = mChannel->SetLoadUnblocked(true);
  2195   NS_ENSURE_SUCCESS(rv, rv);
  2197   // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket
  2198   // in lower case, so go with that. It is technically case insensitive.
  2199   rv = mChannel->HTTPUpgrade(NS_LITERAL_CSTRING("websocket"), this);
  2200   NS_ENSURE_SUCCESS(rv, rv);
  2202   mHttpChannel->SetRequestHeader(
  2203     NS_LITERAL_CSTRING("Sec-WebSocket-Version"),
  2204     NS_LITERAL_CSTRING(SEC_WEBSOCKET_VERSION), false);
  2206   if (!mOrigin.IsEmpty())
  2207     mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), mOrigin,
  2208                                    false);
  2210   if (!mProtocol.IsEmpty())
  2211     mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"),
  2212                                    mProtocol, true);
  2214   if (mAllowCompression)
  2215     mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"),
  2216                                    NS_LITERAL_CSTRING("deflate-stream"),
  2217                                    false);
  2219   uint8_t      *secKey;
  2220   nsAutoCString secKeyString;
  2222   rv = mRandomGenerator->GenerateRandomBytes(16, &secKey);
  2223   NS_ENSURE_SUCCESS(rv, rv);
  2224   char* b64 = PL_Base64Encode((const char *)secKey, 16, nullptr);
  2225   NS_Free(secKey);
  2226   if (!b64)
  2227     return NS_ERROR_OUT_OF_MEMORY;
  2228   secKeyString.Assign(b64);
  2229   PR_Free(b64);
  2230   mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Key"),
  2231                                  secKeyString, false);
  2232   LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get()));
  2234   // prepare the value we expect to see in
  2235   // the sec-websocket-accept response header
  2236   secKeyString.AppendLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
  2237   nsCOMPtr<nsICryptoHash> hasher =
  2238     do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
  2239   NS_ENSURE_SUCCESS(rv, rv);
  2240   rv = hasher->Init(nsICryptoHash::SHA1);
  2241   NS_ENSURE_SUCCESS(rv, rv);
  2242   rv = hasher->Update((const uint8_t *) secKeyString.BeginWriting(),
  2243                       secKeyString.Length());
  2244   NS_ENSURE_SUCCESS(rv, rv);
  2245   rv = hasher->Finish(true, mHashedSecret);
  2246   NS_ENSURE_SUCCESS(rv, rv);
  2247   LOG(("WebSocketChannel::SetupRequest: expected server key %s\n",
  2248        mHashedSecret.get()));
  2250   return NS_OK;
  2253 nsresult
  2254 WebSocketChannel::DoAdmissionDNS()
  2256   nsresult rv;
  2258   nsCString hostName;
  2259   rv = mURI->GetHost(hostName);
  2260   NS_ENSURE_SUCCESS(rv, rv);
  2261   mAddress = hostName;
  2262   rv = mURI->GetPort(&mPort);
  2263   NS_ENSURE_SUCCESS(rv, rv);
  2264   if (mPort == -1)
  2265     mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort);
  2266   nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
  2267   NS_ENSURE_SUCCESS(rv, rv);
  2268   nsCOMPtr<nsIThread> mainThread;
  2269   NS_GetMainThread(getter_AddRefs(mainThread));
  2270   MOZ_ASSERT(!mCancelable);
  2271   return dns->AsyncResolve(hostName, 0, this, mainThread, getter_AddRefs(mCancelable));
  2274 nsresult
  2275 WebSocketChannel::ApplyForAdmission()
  2277   LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this));
  2279   // Websockets has a policy of 1 session at a time being allowed in the
  2280   // CONNECTING state per server IP address (not hostname)
  2282   // Check to see if a proxy is being used before making DNS call
  2283   nsCOMPtr<nsIProtocolProxyService> pps =
  2284     do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
  2286   if (!pps) {
  2287     // go straight to DNS
  2288     // expect the callback in ::OnLookupComplete
  2289     LOG(("WebSocketChannel::ApplyForAdmission: checking for concurrent open\n"));
  2290     return DoAdmissionDNS();
  2293   MOZ_ASSERT(!mCancelable);
  2295   return pps->AsyncResolve(mHttpChannel,
  2296                            nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
  2297                            nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
  2298                            this, getter_AddRefs(mCancelable));
  2301 // Called after both OnStartRequest and OnTransportAvailable have
  2302 // executed. This essentially ends the handshake and starts the websockets
  2303 // protocol state machine.
  2304 nsresult
  2305 WebSocketChannel::StartWebsocketData()
  2307   LOG(("WebSocketChannel::StartWebsocketData() %p", this));
  2308   NS_ABORT_IF_FALSE(!mDataStarted, "StartWebsocketData twice");
  2309   mDataStarted = 1;
  2311   // We're now done CONNECTING, which means we can now open another,
  2312   // perhaps parallel, connection to the same host if one
  2313   // is pending
  2314   nsWSAdmissionManager::OnConnected(this);
  2316   LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p\n",
  2317        mListener.get()));
  2319   if (mListener)
  2320     mListener->OnStart(mContext);
  2322   // Start keepalive ping timer, if we're using keepalive.
  2323   if (mPingInterval) {
  2324     nsresult rv;
  2325     mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
  2326     if (NS_FAILED(rv)) {
  2327       NS_WARNING("unable to create ping timer. Carrying on.");
  2328     } else {
  2329       LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
  2330            mPingInterval));
  2331       mPingTimer->SetTarget(mSocketThread);
  2332       mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT);
  2336   return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
  2339 void
  2340 WebSocketChannel::ReportConnectionTelemetry()
  2342   // 3 bits are used. high bit is for wss, middle bit for failed,
  2343   // and low bit for proxy..
  2344   // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy,
  2345   //         wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy
  2347   bool didProxy = false;
  2349   nsCOMPtr<nsIProxyInfo> pi;
  2350   nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel);
  2351   if (pc)
  2352     pc->GetProxyInfo(getter_AddRefs(pi));
  2353   if (pi) {
  2354     nsAutoCString proxyType;
  2355     pi->GetType(proxyType);
  2356     if (!proxyType.IsEmpty() &&
  2357         !proxyType.Equals(NS_LITERAL_CSTRING("direct")))
  2358       didProxy = true;
  2361   uint8_t value = (mEncrypted ? (1 << 2) : 0) | 
  2362     (!mGotUpgradeOK ? (1 << 1) : 0) |
  2363     (didProxy ? (1 << 0) : 0);
  2365   LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value));
  2366   Telemetry::Accumulate(Telemetry::WEBSOCKETS_HANDSHAKE_TYPE, value);
  2369 // nsIDNSListener
  2371 NS_IMETHODIMP
  2372 WebSocketChannel::OnLookupComplete(nsICancelable *aRequest,
  2373                                    nsIDNSRecord *aRecord,
  2374                                    nsresult aStatus)
  2376   LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %x]\n",
  2377        this, aRequest, aRecord, aStatus));
  2379   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  2381   if (mStopped) {
  2382     LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n"));
  2383     mCancelable = nullptr;
  2384     return NS_OK;
  2387   mCancelable = nullptr;
  2389   // These failures are not fatal - we just use the hostname as the key
  2390   if (NS_FAILED(aStatus)) {
  2391     LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n"));
  2393     // set host in case we got here without calling DoAdmissionDNS()
  2394     mURI->GetHost(mAddress);
  2395   } else {
  2396     nsresult rv = aRecord->GetNextAddrAsString(mAddress);
  2397     if (NS_FAILED(rv))
  2398       LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n"));
  2401   LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n"));
  2402   nsWSAdmissionManager::ConditionallyConnect(this);
  2404   return NS_OK;
  2407 // nsIProtocolProxyCallback
  2408 NS_IMETHODIMP
  2409 WebSocketChannel::OnProxyAvailable(nsICancelable *aRequest, nsIChannel *aChannel,
  2410                                    nsIProxyInfo *pi, nsresult status)
  2412   if (mStopped) {
  2413     LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n", this));
  2414     mCancelable = nullptr;
  2415     return NS_OK;
  2418   MOZ_ASSERT(aRequest == mCancelable);
  2419   mCancelable = nullptr;
  2421   nsAutoCString type;
  2422   if (NS_SUCCEEDED(status) && pi &&
  2423       NS_SUCCEEDED(pi->GetType(type)) &&
  2424       !type.EqualsLiteral("direct")) {
  2425     LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n", this));
  2426     // call DNS callback directly without DNS resolver
  2427     OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE);
  2428     return NS_OK;
  2431   LOG(("WebSocketChannel::OnProxyAvailable[%] checking DNS resolution\n", this));
  2432   DoAdmissionDNS();
  2433   return NS_OK;
  2436 // nsIInterfaceRequestor
  2438 NS_IMETHODIMP
  2439 WebSocketChannel::GetInterface(const nsIID & iid, void **result)
  2441   LOG(("WebSocketChannel::GetInterface() %p\n", this));
  2443   if (iid.Equals(NS_GET_IID(nsIChannelEventSink)))
  2444     return QueryInterface(iid, result);
  2446   if (mCallbacks)
  2447     return mCallbacks->GetInterface(iid, result);
  2449   return NS_ERROR_FAILURE;
  2452 // nsIChannelEventSink
  2454 NS_IMETHODIMP
  2455 WebSocketChannel::AsyncOnChannelRedirect(
  2456                     nsIChannel *oldChannel,
  2457                     nsIChannel *newChannel,
  2458                     uint32_t flags,
  2459                     nsIAsyncVerifyRedirectCallback *callback)
  2461   LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this));
  2462   nsresult rv;
  2464   nsCOMPtr<nsIURI> newuri;
  2465   rv = newChannel->GetURI(getter_AddRefs(newuri));
  2466   NS_ENSURE_SUCCESS(rv, rv);
  2468   // newuri is expected to be http or https
  2469   bool newuriIsHttps = false;
  2470   rv = newuri->SchemeIs("https", &newuriIsHttps);
  2471   NS_ENSURE_SUCCESS(rv, rv);
  2473   if (!mAutoFollowRedirects) {
  2474     // Even if redirects configured off, still allow them for HTTP Strict
  2475     // Transport Security (from ws://FOO to https://FOO (mapped to wss://FOO)
  2477     nsCOMPtr<nsIURI> clonedNewURI;
  2478     rv = newuri->Clone(getter_AddRefs(clonedNewURI));
  2479     NS_ENSURE_SUCCESS(rv, rv);
  2481     rv = clonedNewURI->SetScheme(NS_LITERAL_CSTRING("ws"));
  2482     NS_ENSURE_SUCCESS(rv, rv);
  2484     nsCOMPtr<nsIURI> currentURI;
  2485     rv = GetURI(getter_AddRefs(currentURI));
  2486     NS_ENSURE_SUCCESS(rv, rv);
  2488     // currentURI is expected to be ws or wss
  2489     bool currentIsHttps = false;
  2490     rv = currentURI->SchemeIs("wss", &currentIsHttps);
  2491     NS_ENSURE_SUCCESS(rv, rv);
  2493     bool uriEqual = false;
  2494     rv = clonedNewURI->Equals(currentURI, &uriEqual);
  2495     NS_ENSURE_SUCCESS(rv, rv);
  2497     // It's only a HSTS redirect if we started with non-secure, are going to
  2498     // secure, and the new URI is otherwise the same as the old one.
  2499     if (!(!currentIsHttps && newuriIsHttps && uriEqual)) {
  2500       nsAutoCString newSpec;
  2501       rv = newuri->GetSpec(newSpec);
  2502       NS_ENSURE_SUCCESS(rv, rv);
  2504       LOG(("WebSocketChannel: Redirect to %s denied by configuration\n",
  2505            newSpec.get()));
  2506       return NS_ERROR_FAILURE;
  2510   if (mEncrypted && !newuriIsHttps) {
  2511     nsAutoCString spec;
  2512     if (NS_SUCCEEDED(newuri->GetSpec(spec)))
  2513       LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n",
  2514            spec.get()));
  2515     return NS_ERROR_FAILURE;
  2518   nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv);
  2519   if (NS_FAILED(rv)) {
  2520     LOG(("WebSocketChannel: Redirect could not QI to HTTP\n"));
  2521     return rv;
  2524   nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel =
  2525     do_QueryInterface(newChannel, &rv);
  2527   if (NS_FAILED(rv)) {
  2528     LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n"));
  2529     return rv;
  2532   // The redirect is likely OK
  2534   newChannel->SetNotificationCallbacks(this);
  2536   mEncrypted = newuriIsHttps;
  2537   newuri->Clone(getter_AddRefs(mURI));
  2538   if (mEncrypted)
  2539     rv = mURI->SetScheme(NS_LITERAL_CSTRING("wss"));
  2540   else
  2541     rv = mURI->SetScheme(NS_LITERAL_CSTRING("ws"));
  2543   mHttpChannel = newHttpChannel;
  2544   mChannel = newUpgradeChannel;
  2545   rv = SetupRequest();
  2546   if (NS_FAILED(rv)) {
  2547     LOG(("WebSocketChannel: Redirect could not SetupRequest()\n"));
  2548     return rv;
  2551   // Redirected-to URI may need to be delayed by 1-connecting-per-host and
  2552   // delay-after-fail algorithms.  So hold off calling OnRedirectVerifyCallback
  2553   // until BeginOpen, when we know it's OK to proceed with new channel.
  2554   mRedirectCallback = callback;
  2556   // Mark old channel as successfully connected so we'll clear any FailDelay
  2557   // associated with the old URI.  Note: no need to also call OnStopSession:
  2558   // it's a no-op for successful, already-connected channels.
  2559   nsWSAdmissionManager::OnConnected(this);
  2561   // ApplyForAdmission as if we were starting from fresh...
  2562   mAddress.Truncate();
  2563   mOpenedHttpChannel = 0;
  2564   rv = ApplyForAdmission();
  2565   if (NS_FAILED(rv)) {
  2566     LOG(("WebSocketChannel: Redirect failed due to DNS failure\n"));
  2567     mRedirectCallback = nullptr;
  2568     return rv;
  2571   return NS_OK;
  2574 // nsITimerCallback
  2576 NS_IMETHODIMP
  2577 WebSocketChannel::Notify(nsITimer *timer)
  2579   LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer));
  2581   if (timer == mCloseTimer) {
  2582     NS_ABORT_IF_FALSE(mClientClosed, "Close Timeout without local close");
  2583     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
  2584                       "not socket thread");
  2586     mCloseTimer = nullptr;
  2587     if (mStopped || mServerClosed)                /* no longer relevant */
  2588       return NS_OK;
  2590     LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n"));
  2591     AbortSession(NS_ERROR_NET_TIMEOUT);
  2592   } else if (timer == mOpenTimer) {
  2593     NS_ABORT_IF_FALSE(!mGotUpgradeOK,
  2594                       "Open Timer after open complete");
  2595     NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  2597     mOpenTimer = nullptr;
  2598     LOG(("WebSocketChannel:: Connection Timed Out\n"));
  2599     if (mStopped || mServerClosed)                /* no longer relevant */
  2600       return NS_OK;
  2602     AbortSession(NS_ERROR_NET_TIMEOUT);
  2603   } else if (timer == mReconnectDelayTimer) {
  2604     NS_ABORT_IF_FALSE(mConnecting == CONNECTING_DELAYED,
  2605                       "woke up from delay w/o being delayed?");
  2607     mReconnectDelayTimer = nullptr;
  2608     LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this));
  2609     BeginOpen();
  2610   } else if (timer == mPingTimer) {
  2611     NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread,
  2612                       "not socket thread");
  2614     if (mClientClosed || mServerClosed || mRequestedClose) {
  2615       // no point in worrying about ping now
  2616       mPingTimer = nullptr;
  2617       return NS_OK;
  2620     if (!mPingOutstanding) {
  2621       LOG(("nsWebSocketChannel:: Generating Ping\n"));
  2622       mPingOutstanding = 1;
  2623       GeneratePing();
  2624       mPingTimer->InitWithCallback(this, mPingResponseTimeout,
  2625                                    nsITimer::TYPE_ONE_SHOT);
  2626     } else {
  2627       LOG(("nsWebSocketChannel:: Timed out Ping\n"));
  2628       mPingTimer = nullptr;
  2629       AbortSession(NS_ERROR_NET_TIMEOUT);
  2631   } else if (timer == mLingeringCloseTimer) {
  2632     LOG(("WebSocketChannel:: Lingering Close Timer"));
  2633     CleanupConnection();
  2634   } else {
  2635     NS_ABORT_IF_FALSE(0, "Unknown Timer");
  2638   return NS_OK;
  2641 // nsIWebSocketChannel
  2643 NS_IMETHODIMP
  2644 WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
  2646   LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this));
  2647   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  2649   if (mTransport) {
  2650     if (NS_FAILED(mTransport->GetSecurityInfo(aSecurityInfo)))
  2651       *aSecurityInfo = nullptr;
  2653   return NS_OK;
  2657 NS_IMETHODIMP
  2658 WebSocketChannel::AsyncOpen(nsIURI *aURI,
  2659                             const nsACString &aOrigin,
  2660                             nsIWebSocketListener *aListener,
  2661                             nsISupports *aContext)
  2663   LOG(("WebSocketChannel::AsyncOpen() %p\n", this));
  2665   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  2667   if (!aURI || !aListener) {
  2668     LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null"));
  2669     return NS_ERROR_UNEXPECTED;
  2672   if (mListener || mWasOpened)
  2673     return NS_ERROR_ALREADY_OPENED;
  2675   nsresult rv;
  2677   // Ensure target thread is set.
  2678   if (!mTargetThread) {
  2679     mTargetThread = do_GetMainThread();
  2682   mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
  2683   if (NS_FAILED(rv)) {
  2684     NS_WARNING("unable to continue without socket transport service");
  2685     return rv;
  2688   mRandomGenerator =
  2689     do_GetService("@mozilla.org/security/random-generator;1", &rv);
  2690   if (NS_FAILED(rv)) {
  2691     NS_WARNING("unable to continue without random number generator");
  2692     return rv;
  2695   nsCOMPtr<nsIPrefBranch> prefService;
  2696   prefService = do_GetService(NS_PREFSERVICE_CONTRACTID);
  2698   if (prefService) {
  2699     int32_t intpref;
  2700     bool boolpref;
  2701     rv = prefService->GetIntPref("network.websocket.max-message-size", 
  2702                                  &intpref);
  2703     if (NS_SUCCEEDED(rv)) {
  2704       mMaxMessageSize = clamped(intpref, 1024, INT32_MAX);
  2706     rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref);
  2707     if (NS_SUCCEEDED(rv)) {
  2708       mCloseTimeout = clamped(intpref, 1, 1800) * 1000;
  2710     rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref);
  2711     if (NS_SUCCEEDED(rv)) {
  2712       mOpenTimeout = clamped(intpref, 1, 1800) * 1000;
  2714     rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
  2715                                  &intpref);
  2716     if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
  2717       mPingInterval = clamped(intpref, 0, 86400) * 1000;
  2719     rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
  2720                                  &intpref);
  2721     if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
  2722       mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
  2724     rv = prefService->GetBoolPref("network.websocket.extensions.stream-deflate",
  2725                                   &boolpref);
  2726     if (NS_SUCCEEDED(rv)) {
  2727       mAllowCompression = boolpref ? 1 : 0;
  2729     rv = prefService->GetBoolPref("network.websocket.auto-follow-http-redirects",
  2730                                   &boolpref);
  2731     if (NS_SUCCEEDED(rv)) {
  2732       mAutoFollowRedirects = boolpref ? 1 : 0;
  2734     rv = prefService->GetIntPref
  2735       ("network.websocket.max-connections", &intpref);
  2736     if (NS_SUCCEEDED(rv)) {
  2737       mMaxConcurrentConnections = clamped(intpref, 1, 0xffff);
  2741   int32_t sessionCount = -1;
  2742   nsWSAdmissionManager::GetSessionCount(sessionCount);
  2743   if (sessionCount >= 0) {
  2744     LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this,
  2745          sessionCount, mMaxConcurrentConnections));
  2748   if (sessionCount >= mMaxConcurrentConnections) {
  2749     LOG(("WebSocketChannel: max concurrency %d exceeded (%d)",
  2750          mMaxConcurrentConnections,
  2751          sessionCount));
  2753     // WebSocket connections are expected to be long lived, so return
  2754     // an error here instead of queueing
  2755     return NS_ERROR_SOCKET_CREATE_FAILED;
  2758   mOriginalURI = aURI;
  2759   mURI = mOriginalURI;
  2760   mURI->GetHostPort(mHost);
  2761   mOrigin = aOrigin;
  2763   nsCOMPtr<nsIURI> localURI;
  2764   nsCOMPtr<nsIChannel> localChannel;
  2766   mURI->Clone(getter_AddRefs(localURI));
  2767   if (mEncrypted)
  2768     rv = localURI->SetScheme(NS_LITERAL_CSTRING("https"));
  2769   else
  2770     rv = localURI->SetScheme(NS_LITERAL_CSTRING("http"));
  2771   NS_ENSURE_SUCCESS(rv, rv);
  2773   nsCOMPtr<nsIIOService> ioService;
  2774   ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
  2775   if (NS_FAILED(rv)) {
  2776     NS_WARNING("unable to continue without io service");
  2777     return rv;
  2780   nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv);
  2781   if (NS_FAILED(rv)) {
  2782     NS_WARNING("WebSocketChannel: unable to continue without ioservice2");
  2783     return rv;
  2786   rv = io2->NewChannelFromURIWithProxyFlags(
  2787               localURI,
  2788               mURI,
  2789               nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
  2790               nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL,
  2791               getter_AddRefs(localChannel));
  2792   NS_ENSURE_SUCCESS(rv, rv);
  2794   // Pass most GetInterface() requests through to our instantiator, but handle
  2795   // nsIChannelEventSink in this object in order to deal with redirects
  2796   localChannel->SetNotificationCallbacks(this);
  2798   mChannel = do_QueryInterface(localChannel, &rv);
  2799   NS_ENSURE_SUCCESS(rv, rv);
  2801   mHttpChannel = do_QueryInterface(localChannel, &rv);
  2802   NS_ENSURE_SUCCESS(rv, rv);
  2804   rv = SetupRequest();
  2805   if (NS_FAILED(rv))
  2806     return rv;
  2808   mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel);
  2810   if (mConnectionLogService && !mPrivateBrowsing) {
  2811     mConnectionLogService->AddHost(mHost, mSerial,
  2812                                    BaseWebSocketChannel::mEncrypted);
  2815   rv = ApplyForAdmission();
  2816   if (NS_FAILED(rv))
  2817     return rv;
  2819   // Only set these if the open was successful:
  2820   //
  2821   mWasOpened = 1;
  2822   mListener = aListener;
  2823   mContext = aContext;
  2824   IncrementSessionCount();
  2826   return rv;
  2829 NS_IMETHODIMP
  2830 WebSocketChannel::Close(uint16_t code, const nsACString & reason)
  2832   LOG(("WebSocketChannel::Close() %p\n", this));
  2833   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  2835   // save the networkstats (bug 855949)
  2836   SaveNetworkStats(true);
  2838   if (mRequestedClose) {
  2839     return NS_OK;
  2842   // The API requires the UTF-8 string to be 123 or less bytes
  2843   if (reason.Length() > 123)
  2844     return NS_ERROR_ILLEGAL_VALUE;
  2846   mRequestedClose = 1;
  2847   mScriptCloseReason = reason;
  2848   mScriptCloseCode = code;
  2850   if (!mTransport) {
  2851     nsresult rv;
  2852     if (code == CLOSE_GOING_AWAY) {
  2853       // Not an error: for example, tab has closed or navigated away
  2854       LOG(("WebSocketChannel::Close() GOING_AWAY without transport."));
  2855       rv = NS_OK;
  2856     } else {
  2857       LOG(("WebSocketChannel::Close() without transport - error."));
  2858       rv = NS_ERROR_NOT_CONNECTED;
  2860     StopSession(rv);
  2861     return rv;
  2864   return mSocketThread->Dispatch(
  2865       new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)),
  2866                            nsIEventTarget::DISPATCH_NORMAL);
  2869 NS_IMETHODIMP
  2870 WebSocketChannel::SendMsg(const nsACString &aMsg)
  2872   LOG(("WebSocketChannel::SendMsg() %p\n", this));
  2874   return SendMsgCommon(&aMsg, false, aMsg.Length());
  2877 NS_IMETHODIMP
  2878 WebSocketChannel::SendBinaryMsg(const nsACString &aMsg)
  2880   LOG(("WebSocketChannel::SendBinaryMsg() %p len=%d\n", this, aMsg.Length()));
  2881   return SendMsgCommon(&aMsg, true, aMsg.Length());
  2884 NS_IMETHODIMP
  2885 WebSocketChannel::SendBinaryStream(nsIInputStream *aStream, uint32_t aLength)
  2887   LOG(("WebSocketChannel::SendBinaryStream() %p\n", this));
  2889   return SendMsgCommon(nullptr, true, aLength, aStream);
  2892 nsresult
  2893 WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary,
  2894                                 uint32_t aLength, nsIInputStream *aStream)
  2896   NS_ABORT_IF_FALSE(NS_GetCurrentThread() == mTargetThread, "not target thread");
  2898   if (mRequestedClose) {
  2899     LOG(("WebSocketChannel:: Error: send when closed\n"));
  2900     return NS_ERROR_UNEXPECTED;
  2903   if (mStopped) {
  2904     LOG(("WebSocketChannel:: Error: send when stopped\n"));
  2905     return NS_ERROR_NOT_CONNECTED;
  2908   NS_ABORT_IF_FALSE(mMaxMessageSize >= 0, "max message size negative");
  2909   if (aLength > static_cast<uint32_t>(mMaxMessageSize)) {
  2910     LOG(("WebSocketChannel:: Error: message too big\n"));
  2911     return NS_ERROR_FILE_TOO_BIG;
  2914   if (mConnectionLogService && !mPrivateBrowsing) {
  2915     mConnectionLogService->NewMsgSent(mHost, mSerial, aLength);
  2916     LOG(("Added new msg sent for %s", mHost.get()));
  2919   return mSocketThread->Dispatch(
  2920     aStream ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength))
  2921             : new OutboundEnqueuer(this,
  2922                      new OutboundMessage(aIsBinary ? kMsgTypeBinaryString
  2923                                                    : kMsgTypeString,
  2924                                          new nsCString(*aMsg))),
  2925     nsIEventTarget::DISPATCH_NORMAL);
  2928 // nsIHttpUpgradeListener
  2930 NS_IMETHODIMP
  2931 WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
  2932                                        nsIAsyncInputStream *aSocketIn,
  2933                                        nsIAsyncOutputStream *aSocketOut)
  2935   if (!NS_IsMainThread()) {
  2936     return NS_DispatchToMainThread(new CallOnTransportAvailable(this,
  2937                                                                 aTransport,
  2938                                                                 aSocketIn,
  2939                                                                 aSocketOut));
  2942   LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n",
  2943        this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK));
  2945   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  2946   NS_ABORT_IF_FALSE(!mRecvdHttpUpgradeTransport, "OTA duplicated");
  2947   NS_ABORT_IF_FALSE(aSocketIn, "OTA with invalid socketIn");
  2949   mTransport = aTransport;
  2950   mSocketIn = aSocketIn;
  2951   mSocketOut = aSocketOut;
  2953   nsresult rv;
  2954   rv = mTransport->SetEventSink(nullptr, nullptr);
  2955   if (NS_FAILED(rv)) return rv;
  2956   rv = mTransport->SetSecurityCallbacks(this);
  2957   if (NS_FAILED(rv)) return rv;
  2959   mRecvdHttpUpgradeTransport = 1;
  2960   if (mGotUpgradeOK)
  2961     return StartWebsocketData();
  2962   return NS_OK;
  2965 // nsIRequestObserver (from nsIStreamListener)
  2967 NS_IMETHODIMP
  2968 WebSocketChannel::OnStartRequest(nsIRequest *aRequest,
  2969                                  nsISupports *aContext)
  2971   LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n",
  2972        this, aRequest, aContext, mRecvdHttpUpgradeTransport));
  2973   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  2974   NS_ABORT_IF_FALSE(!mGotUpgradeOK, "OTA duplicated");
  2976   if (mOpenTimer) {
  2977     mOpenTimer->Cancel();
  2978     mOpenTimer = nullptr;
  2981   if (mStopped) {
  2982     LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n"));
  2983     AbortSession(NS_ERROR_CONNECTION_REFUSED);
  2984     return NS_ERROR_CONNECTION_REFUSED;
  2987   nsresult rv;
  2988   uint32_t status;
  2989   char *val, *token;
  2991   rv = mHttpChannel->GetResponseStatus(&status);
  2992   if (NS_FAILED(rv)) {
  2993     LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n"));
  2994     AbortSession(NS_ERROR_CONNECTION_REFUSED);
  2995     return NS_ERROR_CONNECTION_REFUSED;
  2998   LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status));
  2999   if (status != 101) {
  3000     AbortSession(NS_ERROR_CONNECTION_REFUSED);
  3001     return NS_ERROR_CONNECTION_REFUSED;
  3004   nsAutoCString respUpgrade;
  3005   rv = mHttpChannel->GetResponseHeader(
  3006     NS_LITERAL_CSTRING("Upgrade"), respUpgrade);
  3008   if (NS_SUCCEEDED(rv)) {
  3009     rv = NS_ERROR_ILLEGAL_VALUE;
  3010     if (!respUpgrade.IsEmpty()) {
  3011       val = respUpgrade.BeginWriting();
  3012       while ((token = nsCRT::strtok(val, ", \t", &val))) {
  3013         if (PL_strcasecmp(token, "Websocket") == 0) {
  3014           rv = NS_OK;
  3015           break;
  3021   if (NS_FAILED(rv)) {
  3022     LOG(("WebSocketChannel::OnStartRequest: "
  3023          "HTTP response header Upgrade: websocket not found\n"));
  3024     AbortSession(NS_ERROR_ILLEGAL_VALUE);
  3025     return rv;
  3028   nsAutoCString respConnection;
  3029   rv = mHttpChannel->GetResponseHeader(
  3030     NS_LITERAL_CSTRING("Connection"), respConnection);
  3032   if (NS_SUCCEEDED(rv)) {
  3033     rv = NS_ERROR_ILLEGAL_VALUE;
  3034     if (!respConnection.IsEmpty()) {
  3035       val = respConnection.BeginWriting();
  3036       while ((token = nsCRT::strtok(val, ", \t", &val))) {
  3037         if (PL_strcasecmp(token, "Upgrade") == 0) {
  3038           rv = NS_OK;
  3039           break;
  3045   if (NS_FAILED(rv)) {
  3046     LOG(("WebSocketChannel::OnStartRequest: "
  3047          "HTTP response header 'Connection: Upgrade' not found\n"));
  3048     AbortSession(NS_ERROR_ILLEGAL_VALUE);
  3049     return rv;
  3052   nsAutoCString respAccept;
  3053   rv = mHttpChannel->GetResponseHeader(
  3054                        NS_LITERAL_CSTRING("Sec-WebSocket-Accept"),
  3055                        respAccept);
  3057   if (NS_FAILED(rv) ||
  3058     respAccept.IsEmpty() || !respAccept.Equals(mHashedSecret)) {
  3059     LOG(("WebSocketChannel::OnStartRequest: "
  3060          "HTTP response header Sec-WebSocket-Accept check failed\n"));
  3061     LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n",
  3062          mHashedSecret.get(), respAccept.get()));
  3063     AbortSession(NS_ERROR_ILLEGAL_VALUE);
  3064     return NS_ERROR_ILLEGAL_VALUE;
  3067   // If we sent a sub protocol header, verify the response matches
  3068   // If it does not, set mProtocol to "" so the protocol attribute
  3069   // of the WebSocket JS object reflects that
  3070   if (!mProtocol.IsEmpty()) {
  3071     nsAutoCString respProtocol;
  3072     rv = mHttpChannel->GetResponseHeader(
  3073                          NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), 
  3074                          respProtocol);
  3075     if (NS_SUCCEEDED(rv)) {
  3076       rv = NS_ERROR_ILLEGAL_VALUE;
  3077       val = mProtocol.BeginWriting();
  3078       while ((token = nsCRT::strtok(val, ", \t", &val))) {
  3079         if (PL_strcasecmp(token, respProtocol.get()) == 0) {
  3080           rv = NS_OK;
  3081           break;
  3085       if (NS_SUCCEEDED(rv)) {
  3086         LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed",
  3087              respProtocol.get()));
  3088         mProtocol = respProtocol;
  3089       } else {
  3090         LOG(("WebsocketChannel::OnStartRequest: "
  3091              "subprotocol [%s] not found - %s returned",
  3092              mProtocol.get(), respProtocol.get()));
  3093         mProtocol.Truncate();
  3095     } else {
  3096       LOG(("WebsocketChannel::OnStartRequest "
  3097                  "subprotocol [%s] not found - none returned",
  3098                  mProtocol.get()));
  3099       mProtocol.Truncate();
  3103   rv = HandleExtensions();
  3104   if (NS_FAILED(rv))
  3105     return rv;
  3107   mGotUpgradeOK = 1;
  3108   if (mRecvdHttpUpgradeTransport)
  3109     return StartWebsocketData();
  3111   return NS_OK;
  3114 NS_IMETHODIMP
  3115 WebSocketChannel::OnStopRequest(nsIRequest *aRequest,
  3116                                   nsISupports *aContext,
  3117                                   nsresult aStatusCode)
  3119   LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %x]\n",
  3120        this, aRequest, aContext, aStatusCode));
  3121   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
  3123   ReportConnectionTelemetry();
  3125   // This is the end of the HTTP upgrade transaction, the
  3126   // upgraded streams live on
  3128   mChannel = nullptr;
  3129   mHttpChannel = nullptr;
  3130   mLoadGroup = nullptr;
  3131   mCallbacks = nullptr;
  3133   return NS_OK;
  3136 // nsIInputStreamCallback
  3138 NS_IMETHODIMP
  3139 WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream *aStream)
  3141   LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this));
  3142   NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  3144   if (!mSocketIn) // did we we clean up the socket after scheduling InputReady?
  3145     return NS_OK;
  3147   nsRefPtr<nsIStreamListener>    deleteProtector1(mInflateReader);
  3148   nsRefPtr<nsIStringInputStream> deleteProtector2(mInflateStream);
  3150   // this is after the  http upgrade - so we are speaking websockets
  3151   char  buffer[2048];
  3152   uint32_t count;
  3153   nsresult rv;
  3155   do {
  3156     rv = mSocketIn->Read((char *)buffer, 2048, &count);
  3157     LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %x\n", count, rv));
  3159     // accumulate received bytes
  3160     CountRecvBytes(count);
  3162     if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
  3163       mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
  3164       return NS_OK;
  3167     if (NS_FAILED(rv)) {
  3168       mTCPClosed = true;
  3169       AbortSession(rv);
  3170       return rv;
  3173     if (count == 0) {
  3174       mTCPClosed = true;
  3175       AbortSession(NS_BASE_STREAM_CLOSED);
  3176       return NS_OK;
  3179     if (mStopped) {
  3180       continue;
  3183     if (mInflateReader) {
  3184       mInflateStream->ShareData(buffer, count);
  3185       rv = mInflateReader->OnDataAvailable(nullptr, mSocketIn, mInflateStream, 
  3186                                            0, count);
  3187     } else {
  3188       rv = ProcessInput((uint8_t *)buffer, count);
  3191     if (NS_FAILED(rv)) {
  3192       AbortSession(rv);
  3193       return rv;
  3195   } while (NS_SUCCEEDED(rv) && mSocketIn);
  3197   return NS_OK;
  3201 // nsIOutputStreamCallback
  3203 NS_IMETHODIMP
  3204 WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream *aStream)
  3206   LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this));
  3207   NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
  3208   nsresult rv;
  3210   if (!mCurrentOut)
  3211     PrimeNewOutgoingMessage();
  3213   while (mCurrentOut && mSocketOut) {
  3214     const char *sndBuf;
  3215     uint32_t toSend;
  3216     uint32_t amtSent;
  3218     if (mHdrOut) {
  3219       sndBuf = (const char *)mHdrOut;
  3220       toSend = mHdrOutToSend;
  3221       LOG(("WebSocketChannel::OnOutputStreamReady: "
  3222            "Try to send %u of hdr/copybreak\n", toSend));
  3223     } else {
  3224       sndBuf = (char *) mCurrentOut->BeginReading() + mCurrentOutSent;
  3225       toSend = mCurrentOut->Length() - mCurrentOutSent;
  3226       if (toSend > 0) {
  3227         LOG(("WebSocketChannel::OnOutputStreamReady: "
  3228              "Try to send %u of data\n", toSend));
  3232     if (toSend == 0) {
  3233       amtSent = 0;
  3234     } else {
  3235       rv = mSocketOut->Write(sndBuf, toSend, &amtSent);
  3236       LOG(("WebSocketChannel::OnOutputStreamReady: write %u rv %x\n",
  3237            amtSent, rv));
  3239       // accumulate sent bytes
  3240       CountSentBytes(amtSent);
  3242       if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
  3243         mSocketOut->AsyncWait(this, 0, 0, mSocketThread);
  3244         return NS_OK;
  3247       if (NS_FAILED(rv)) {
  3248         AbortSession(rv);
  3249         return NS_OK;
  3253     if (mHdrOut) {
  3254       if (amtSent == toSend) {
  3255         mHdrOut = nullptr;
  3256         mHdrOutToSend = 0;
  3257       } else {
  3258         mHdrOut += amtSent;
  3259         mHdrOutToSend -= amtSent;
  3261     } else {
  3262       if (amtSent == toSend) {
  3263         if (!mStopped) {
  3264           mTargetThread->Dispatch(new CallAcknowledge(this,
  3265                                                       mCurrentOut->Length()),
  3266                                   NS_DISPATCH_NORMAL);
  3268         DeleteCurrentOutGoingMessage();
  3269         PrimeNewOutgoingMessage();
  3270       } else {
  3271         mCurrentOutSent += amtSent;
  3276   if (mReleaseOnTransmit)
  3277     ReleaseSession();
  3278   return NS_OK;
  3281 // nsIStreamListener
  3283 NS_IMETHODIMP
  3284 WebSocketChannel::OnDataAvailable(nsIRequest *aRequest,
  3285                                     nsISupports *aContext,
  3286                                     nsIInputStream *aInputStream,
  3287                                     uint64_t aOffset,
  3288                                     uint32_t aCount)
  3290   LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %llu %u]\n",
  3291          this, aRequest, aContext, aInputStream, aOffset, aCount));
  3293   if (aContext == mSocketIn) {
  3294     // This is the deflate decoder
  3296     LOG(("WebSocketChannel::OnDataAvailable: Deflate Data %u\n",
  3297              aCount));
  3299     uint8_t  buffer[2048];
  3300     uint32_t maxRead;
  3301     uint32_t count;
  3302     nsresult rv = NS_OK;  // aCount always > 0, so this just avoids warning
  3304     while (aCount > 0) {
  3305       if (mStopped)
  3306         return NS_BASE_STREAM_CLOSED;
  3308       maxRead = std::min(2048U, aCount);
  3309       rv = aInputStream->Read((char *)buffer, maxRead, &count);
  3310       LOG(("WebSocketChannel::OnDataAvailable: InflateRead read %u rv %x\n",
  3311            count, rv));
  3312       if (NS_FAILED(rv) || count == 0) {
  3313         AbortSession(NS_ERROR_UNEXPECTED);
  3314         break;
  3317       aCount -= count;
  3318       rv = ProcessInput(buffer, count);
  3319       if (NS_FAILED(rv)) {
  3320         AbortSession(rv);
  3321         break;
  3324     return rv;
  3327   if (aContext == mSocketOut) {
  3328     // This is the deflate encoder
  3330     uint32_t maxRead;
  3331     uint32_t count;
  3332     nsresult rv;
  3334     while (aCount > 0) {
  3335       if (mStopped)
  3336         return NS_BASE_STREAM_CLOSED;
  3338       maxRead = std::min(2048U, aCount);
  3339       EnsureHdrOut(mHdrOutToSend + aCount);
  3340       rv = aInputStream->Read((char *)mHdrOut + mHdrOutToSend, maxRead, &count);
  3341       LOG(("WebSocketChannel::OnDataAvailable: DeflateWrite read %u rv %x\n", 
  3342            count, rv));
  3343       if (NS_FAILED(rv) || count == 0) {
  3344         AbortSession(rv);
  3345         break;
  3348       mHdrOutToSend += count;
  3349       aCount -= count;
  3351     return NS_OK;
  3355   // Otherwise, this is the HTTP OnDataAvailable Method, which means
  3356   // this is http data in response to the upgrade request and
  3357   // there should be no http response body if the upgrade succeeded
  3359   // This generally should be caught by a non 101 response code in
  3360   // OnStartRequest().. so we can ignore the data here
  3362   LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n",
  3363          aCount));
  3365   return NS_OK;
  3368 nsresult
  3369 WebSocketChannel::SaveNetworkStats(bool enforce)
  3371 #ifdef MOZ_WIDGET_GONK
  3372   // Check if the active network and app id are valid.
  3373   if(!mActiveNetwork || mAppId == NECKO_NO_APP_ID) {
  3374     return NS_OK;
  3377   if (mCountRecv <= 0 && mCountSent <= 0) {
  3378     // There is no traffic, no need to save.
  3379     return NS_OK;
  3382   // If |enforce| is false, the traffic amount is saved
  3383   // only when the total amount exceeds the predefined
  3384   // threshold.
  3385   uint64_t totalBytes = mCountRecv + mCountSent;
  3386   if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
  3387     return NS_OK;
  3390   // Create the event to save the network statistics.
  3391   // the event is then dispathed to the main thread.
  3392   nsRefPtr<nsRunnable> event =
  3393     new SaveNetworkStatsEvent(mAppId, mActiveNetwork,
  3394                               mCountRecv, mCountSent, false);
  3395   NS_DispatchToMainThread(event);
  3397   // Reset the counters after saving.
  3398   mCountSent = 0;
  3399   mCountRecv = 0;
  3401   return NS_OK;
  3402 #else
  3403   return NS_ERROR_NOT_IMPLEMENTED;
  3404 #endif
  3407 } // namespace mozilla::net
  3408 } // namespace mozilla

mercurial