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