netwerk/protocol/websocket/WebSocketChannel.cpp

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

mercurial