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