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