Coverage Report

Created: 2018-09-25 14:53

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