1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,3408 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set sw=2 ts=8 et tw=80 : */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "WebSocketLog.h" 1.11 +#include "WebSocketChannel.h" 1.12 + 1.13 +#include "mozilla/Atomics.h" 1.14 +#include "mozilla/Attributes.h" 1.15 +#include "mozilla/Endian.h" 1.16 +#include "mozilla/MathAlgorithms.h" 1.17 + 1.18 +#include "nsIURI.h" 1.19 +#include "nsIChannel.h" 1.20 +#include "nsICryptoHash.h" 1.21 +#include "nsIRunnable.h" 1.22 +#include "nsIPrefBranch.h" 1.23 +#include "nsIPrefService.h" 1.24 +#include "nsICancelable.h" 1.25 +#include "nsIDNSRecord.h" 1.26 +#include "nsIDNSService.h" 1.27 +#include "nsIStreamConverterService.h" 1.28 +#include "nsIIOService2.h" 1.29 +#include "nsIProtocolProxyService.h" 1.30 +#include "nsIProxyInfo.h" 1.31 +#include "nsIProxiedChannel.h" 1.32 +#include "nsIAsyncVerifyRedirectCallback.h" 1.33 +#include "nsIDashboardEventNotifier.h" 1.34 +#include "nsIEventTarget.h" 1.35 +#include "nsIHttpChannel.h" 1.36 +#include "nsILoadGroup.h" 1.37 +#include "nsIProtocolHandler.h" 1.38 +#include "nsIRandomGenerator.h" 1.39 +#include "nsISocketTransport.h" 1.40 +#include "nsThreadUtils.h" 1.41 + 1.42 +#include "nsAutoPtr.h" 1.43 +#include "nsNetCID.h" 1.44 +#include "nsServiceManagerUtils.h" 1.45 +#include "nsCRT.h" 1.46 +#include "nsThreadUtils.h" 1.47 +#include "nsError.h" 1.48 +#include "nsStringStream.h" 1.49 +#include "nsAlgorithm.h" 1.50 +#include "nsProxyRelease.h" 1.51 +#include "nsNetUtil.h" 1.52 +#include "mozilla/StaticMutex.h" 1.53 +#include "mozilla/Telemetry.h" 1.54 +#include "mozilla/TimeStamp.h" 1.55 + 1.56 +#include "plbase64.h" 1.57 +#include "prmem.h" 1.58 +#include "prnetdb.h" 1.59 +#include "zlib.h" 1.60 +#include <algorithm> 1.61 + 1.62 +#ifdef MOZ_WIDGET_GONK 1.63 +#include "NetStatistics.h" 1.64 +#endif 1.65 + 1.66 +// rather than slurp up all of nsIWebSocket.idl, which lives outside necko, just 1.67 +// dupe one constant we need from it 1.68 +#define CLOSE_GOING_AWAY 1001 1.69 + 1.70 +extern PRThread *gSocketThread; 1.71 + 1.72 +using namespace mozilla; 1.73 +using namespace mozilla::net; 1.74 + 1.75 +namespace mozilla { 1.76 +namespace net { 1.77 + 1.78 +NS_IMPL_ISUPPORTS(WebSocketChannel, 1.79 + nsIWebSocketChannel, 1.80 + nsIHttpUpgradeListener, 1.81 + nsIRequestObserver, 1.82 + nsIStreamListener, 1.83 + nsIProtocolHandler, 1.84 + nsIInputStreamCallback, 1.85 + nsIOutputStreamCallback, 1.86 + nsITimerCallback, 1.87 + nsIDNSListener, 1.88 + nsIProtocolProxyCallback, 1.89 + nsIInterfaceRequestor, 1.90 + nsIChannelEventSink, 1.91 + nsIThreadRetargetableRequest) 1.92 + 1.93 +// We implement RFC 6455, which uses Sec-WebSocket-Version: 13 on the wire. 1.94 +#define SEC_WEBSOCKET_VERSION "13" 1.95 + 1.96 +/* 1.97 + * About SSL unsigned certificates 1.98 + * 1.99 + * wss will not work to a host using an unsigned certificate unless there 1.100 + * is already an exception (i.e. it cannot popup a dialog asking for 1.101 + * a security exception). This is similar to how an inlined img will 1.102 + * fail without a dialog if fails for the same reason. This should not 1.103 + * be a problem in practice as it is expected the websocket javascript 1.104 + * is served from the same host as the websocket server (or of course, 1.105 + * a valid cert could just be provided). 1.106 + * 1.107 + */ 1.108 + 1.109 +// some helper classes 1.110 + 1.111 +//----------------------------------------------------------------------------- 1.112 +// FailDelayManager 1.113 +// 1.114 +// Stores entries (searchable by {host, port}) of connections that have recently 1.115 +// failed, so we can do delay of reconnects per RFC 6455 Section 7.2.3 1.116 +//----------------------------------------------------------------------------- 1.117 + 1.118 + 1.119 +// Initial reconnect delay is randomly chosen between 200-400 ms. 1.120 +// This is a gentler backoff than the 0-5 seconds the spec offhandedly suggests. 1.121 +const uint32_t kWSReconnectInitialBaseDelay = 200; 1.122 +const uint32_t kWSReconnectInitialRandomDelay = 200; 1.123 + 1.124 +// Base lifetime (in ms) of a FailDelay: kept longer if more failures occur 1.125 +const uint32_t kWSReconnectBaseLifeTime = 60 * 1000; 1.126 +// Maximum reconnect delay (in ms) 1.127 +const uint32_t kWSReconnectMaxDelay = 60 * 1000; 1.128 + 1.129 +// hold record of failed connections, and calculates needed delay for reconnects 1.130 +// to same host/port. 1.131 +class FailDelay 1.132 +{ 1.133 +public: 1.134 + FailDelay(nsCString address, int32_t port) 1.135 + : mAddress(address), mPort(port) 1.136 + { 1.137 + mLastFailure = TimeStamp::Now(); 1.138 + mNextDelay = kWSReconnectInitialBaseDelay + 1.139 + (rand() % kWSReconnectInitialRandomDelay); 1.140 + } 1.141 + 1.142 + // Called to update settings when connection fails again. 1.143 + void FailedAgain() 1.144 + { 1.145 + mLastFailure = TimeStamp::Now(); 1.146 + // We use a truncated exponential backoff as suggested by RFC 6455, 1.147 + // but multiply by 1.5 instead of 2 to be more gradual. 1.148 + mNextDelay = static_cast<uint32_t>( 1.149 + std::min<double>(kWSReconnectMaxDelay, mNextDelay * 1.5)); 1.150 + LOG(("WebSocket: FailedAgain: host=%s, port=%d: incremented delay to %lu", 1.151 + mAddress.get(), mPort, mNextDelay)); 1.152 + } 1.153 + 1.154 + // returns 0 if there is no need to delay (i.e. delay interval is over) 1.155 + uint32_t RemainingDelay(TimeStamp rightNow) 1.156 + { 1.157 + TimeDuration dur = rightNow - mLastFailure; 1.158 + uint32_t sinceFail = (uint32_t) dur.ToMilliseconds(); 1.159 + if (sinceFail > mNextDelay) 1.160 + return 0; 1.161 + 1.162 + return mNextDelay - sinceFail; 1.163 + } 1.164 + 1.165 + bool IsExpired(TimeStamp rightNow) 1.166 + { 1.167 + return (mLastFailure + 1.168 + TimeDuration::FromMilliseconds(kWSReconnectBaseLifeTime + mNextDelay)) 1.169 + <= rightNow; 1.170 + } 1.171 + 1.172 + nsCString mAddress; // IP address (or hostname if using proxy) 1.173 + int32_t mPort; 1.174 + 1.175 +private: 1.176 + TimeStamp mLastFailure; // Time of last failed attempt 1.177 + // mLastFailure + mNextDelay is the soonest we'll allow a reconnect 1.178 + uint32_t mNextDelay; // milliseconds 1.179 +}; 1.180 + 1.181 +class FailDelayManager 1.182 +{ 1.183 +public: 1.184 + FailDelayManager() 1.185 + { 1.186 + MOZ_COUNT_CTOR(FailDelayManager); 1.187 + 1.188 + mDelaysDisabled = false; 1.189 + 1.190 + nsCOMPtr<nsIPrefBranch> prefService = 1.191 + do_GetService(NS_PREFSERVICE_CONTRACTID); 1.192 + bool boolpref = true; 1.193 + nsresult rv; 1.194 + rv = prefService->GetBoolPref("network.websocket.delay-failed-reconnects", 1.195 + &boolpref); 1.196 + if (NS_SUCCEEDED(rv) && !boolpref) { 1.197 + mDelaysDisabled = true; 1.198 + } 1.199 + } 1.200 + 1.201 + ~FailDelayManager() 1.202 + { 1.203 + MOZ_COUNT_DTOR(FailDelayManager); 1.204 + for (uint32_t i = 0; i < mEntries.Length(); i++) { 1.205 + delete mEntries[i]; 1.206 + } 1.207 + } 1.208 + 1.209 + void Add(nsCString &address, int32_t port) 1.210 + { 1.211 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.212 + 1.213 + if (mDelaysDisabled) 1.214 + return; 1.215 + 1.216 + FailDelay *record = new FailDelay(address, port); 1.217 + mEntries.AppendElement(record); 1.218 + } 1.219 + 1.220 + // Element returned may not be valid after next main thread event: don't keep 1.221 + // pointer to it around 1.222 + FailDelay* Lookup(nsCString &address, int32_t port, 1.223 + uint32_t *outIndex = nullptr) 1.224 + { 1.225 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.226 + 1.227 + if (mDelaysDisabled) 1.228 + return nullptr; 1.229 + 1.230 + FailDelay *result = nullptr; 1.231 + TimeStamp rightNow = TimeStamp::Now(); 1.232 + 1.233 + // We also remove expired entries during search: iterate from end to make 1.234 + // indexing simpler 1.235 + for (int32_t i = mEntries.Length() - 1; i >= 0; --i) { 1.236 + FailDelay *fail = mEntries[i]; 1.237 + if (fail->mAddress.Equals(address) && fail->mPort == port) { 1.238 + if (outIndex) 1.239 + *outIndex = i; 1.240 + result = fail; 1.241 + // break here: removing more entries would mess up *outIndex. 1.242 + // Any remaining expired entries will be deleted next time Lookup 1.243 + // finds nothing, which is the most common case anyway. 1.244 + break; 1.245 + } else if (fail->IsExpired(rightNow)) { 1.246 + mEntries.RemoveElementAt(i); 1.247 + delete fail; 1.248 + } 1.249 + } 1.250 + return result; 1.251 + } 1.252 + 1.253 + // returns true if channel connects immediately, or false if it's delayed 1.254 + void DelayOrBegin(WebSocketChannel *ws) 1.255 + { 1.256 + if (!mDelaysDisabled) { 1.257 + uint32_t failIndex = 0; 1.258 + FailDelay *fail = Lookup(ws->mAddress, ws->mPort, &failIndex); 1.259 + 1.260 + if (fail) { 1.261 + TimeStamp rightNow = TimeStamp::Now(); 1.262 + 1.263 + uint32_t remainingDelay = fail->RemainingDelay(rightNow); 1.264 + if (remainingDelay) { 1.265 + // reconnecting within delay interval: delay by remaining time 1.266 + nsresult rv; 1.267 + ws->mReconnectDelayTimer = 1.268 + do_CreateInstance("@mozilla.org/timer;1", &rv); 1.269 + if (NS_SUCCEEDED(rv)) { 1.270 + rv = ws->mReconnectDelayTimer->InitWithCallback( 1.271 + ws, remainingDelay, nsITimer::TYPE_ONE_SHOT); 1.272 + if (NS_SUCCEEDED(rv)) { 1.273 + LOG(("WebSocket: delaying websocket [this=%p] by %lu ms", 1.274 + ws, (unsigned long)remainingDelay)); 1.275 + ws->mConnecting = CONNECTING_DELAYED; 1.276 + return; 1.277 + } 1.278 + } 1.279 + // if timer fails (which is very unlikely), drop down to BeginOpen call 1.280 + } else if (fail->IsExpired(rightNow)) { 1.281 + mEntries.RemoveElementAt(failIndex); 1.282 + delete fail; 1.283 + } 1.284 + } 1.285 + } 1.286 + 1.287 + // Delays disabled, or no previous failure, or we're reconnecting after scheduled 1.288 + // delay interval has passed: connect. 1.289 + ws->BeginOpen(); 1.290 + } 1.291 + 1.292 + // Remove() also deletes all expired entries as it iterates: better for 1.293 + // battery life than using a periodic timer. 1.294 + void Remove(nsCString &address, int32_t port) 1.295 + { 1.296 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.297 + 1.298 + TimeStamp rightNow = TimeStamp::Now(); 1.299 + 1.300 + // iterate from end, to make deletion indexing easier 1.301 + for (int32_t i = mEntries.Length() - 1; i >= 0; --i) { 1.302 + FailDelay *entry = mEntries[i]; 1.303 + if ((entry->mAddress.Equals(address) && entry->mPort == port) || 1.304 + entry->IsExpired(rightNow)) { 1.305 + mEntries.RemoveElementAt(i); 1.306 + delete entry; 1.307 + } 1.308 + } 1.309 + } 1.310 + 1.311 +private: 1.312 + nsTArray<FailDelay *> mEntries; 1.313 + bool mDelaysDisabled; 1.314 +}; 1.315 + 1.316 +//----------------------------------------------------------------------------- 1.317 +// nsWSAdmissionManager 1.318 +// 1.319 +// 1) Ensures that only one websocket at a time is CONNECTING to a given IP 1.320 +// address (or hostname, if using proxy), per RFC 6455 Section 4.1. 1.321 +// 2) Delays reconnects to IP/host after connection failure, per Section 7.2.3 1.322 +//----------------------------------------------------------------------------- 1.323 + 1.324 +class nsWSAdmissionManager 1.325 +{ 1.326 +public: 1.327 + static void Init() 1.328 + { 1.329 + StaticMutexAutoLock lock(sLock); 1.330 + if (!sManager) { 1.331 + sManager = new nsWSAdmissionManager(); 1.332 + } 1.333 + } 1.334 + 1.335 + static void Shutdown() 1.336 + { 1.337 + StaticMutexAutoLock lock(sLock); 1.338 + delete sManager; 1.339 + sManager = nullptr; 1.340 + } 1.341 + 1.342 + // Determine if we will open connection immediately (returns true), or 1.343 + // delay/queue the connection (returns false) 1.344 + static void ConditionallyConnect(WebSocketChannel *ws) 1.345 + { 1.346 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.347 + NS_ABORT_IF_FALSE(ws->mConnecting == NOT_CONNECTING, "opening state"); 1.348 + 1.349 + StaticMutexAutoLock lock(sLock); 1.350 + if (!sManager) { 1.351 + return; 1.352 + } 1.353 + 1.354 + // If there is already another WS channel connecting to this IP address, 1.355 + // defer BeginOpen and mark as waiting in queue. 1.356 + bool found = (sManager->IndexOf(ws->mAddress) >= 0); 1.357 + 1.358 + // Always add ourselves to queue, even if we'll connect immediately 1.359 + nsOpenConn *newdata = new nsOpenConn(ws->mAddress, ws); 1.360 + sManager->mQueue.AppendElement(newdata); 1.361 + 1.362 + if (found) { 1.363 + ws->mConnecting = CONNECTING_QUEUED; 1.364 + } else { 1.365 + sManager->mFailures.DelayOrBegin(ws); 1.366 + } 1.367 + } 1.368 + 1.369 + static void OnConnected(WebSocketChannel *aChannel) 1.370 + { 1.371 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.372 + NS_ABORT_IF_FALSE(aChannel->mConnecting == CONNECTING_IN_PROGRESS, 1.373 + "Channel completed connect, but not connecting?"); 1.374 + 1.375 + StaticMutexAutoLock lock(sLock); 1.376 + if (!sManager) { 1.377 + return; 1.378 + } 1.379 + 1.380 + aChannel->mConnecting = NOT_CONNECTING; 1.381 + 1.382 + // Remove from queue 1.383 + sManager->RemoveFromQueue(aChannel); 1.384 + 1.385 + // Connection succeeded, so stop keeping track of any previous failures 1.386 + sManager->mFailures.Remove(aChannel->mAddress, aChannel->mPort); 1.387 + 1.388 + // Check for queued connections to same host. 1.389 + // Note: still need to check for failures, since next websocket with same 1.390 + // host may have different port 1.391 + sManager->ConnectNext(aChannel->mAddress); 1.392 + } 1.393 + 1.394 + // Called every time a websocket channel ends its session (including going away 1.395 + // w/o ever successfully creating a connection) 1.396 + static void OnStopSession(WebSocketChannel *aChannel, nsresult aReason) 1.397 + { 1.398 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.399 + 1.400 + StaticMutexAutoLock lock(sLock); 1.401 + if (!sManager) { 1.402 + return; 1.403 + } 1.404 + 1.405 + if (NS_FAILED(aReason)) { 1.406 + // Have we seen this failure before? 1.407 + FailDelay *knownFailure = sManager->mFailures.Lookup(aChannel->mAddress, 1.408 + aChannel->mPort); 1.409 + if (knownFailure) { 1.410 + if (aReason == NS_ERROR_NOT_CONNECTED) { 1.411 + // Don't count close() before connection as a network error 1.412 + LOG(("Websocket close() before connection to %s, %d completed" 1.413 + " [this=%p]", aChannel->mAddress.get(), (int)aChannel->mPort, 1.414 + aChannel)); 1.415 + } else { 1.416 + // repeated failure to connect: increase delay for next connection 1.417 + knownFailure->FailedAgain(); 1.418 + } 1.419 + } else { 1.420 + // new connection failure: record it. 1.421 + LOG(("WebSocket: connection to %s, %d failed: [this=%p]", 1.422 + aChannel->mAddress.get(), (int)aChannel->mPort, aChannel)); 1.423 + sManager->mFailures.Add(aChannel->mAddress, aChannel->mPort); 1.424 + } 1.425 + } 1.426 + 1.427 + if (aChannel->mConnecting) { 1.428 + // Only way a connecting channel may get here w/o failing is if it was 1.429 + // closed with GOING_AWAY (1001) because of navigation, tab close, etc. 1.430 + NS_ABORT_IF_FALSE(NS_FAILED(aReason) || 1.431 + aChannel->mScriptCloseCode == CLOSE_GOING_AWAY, 1.432 + "websocket closed while connecting w/o failing?"); 1.433 + 1.434 + sManager->RemoveFromQueue(aChannel); 1.435 + 1.436 + bool wasNotQueued = (aChannel->mConnecting != CONNECTING_QUEUED); 1.437 + aChannel->mConnecting = NOT_CONNECTING; 1.438 + if (wasNotQueued) { 1.439 + sManager->ConnectNext(aChannel->mAddress); 1.440 + } 1.441 + } 1.442 + } 1.443 + 1.444 + static void IncrementSessionCount() 1.445 + { 1.446 + StaticMutexAutoLock lock(sLock); 1.447 + if (!sManager) { 1.448 + return; 1.449 + } 1.450 + sManager->mSessionCount++; 1.451 + } 1.452 + 1.453 + static void DecrementSessionCount() 1.454 + { 1.455 + StaticMutexAutoLock lock(sLock); 1.456 + if (!sManager) { 1.457 + return; 1.458 + } 1.459 + sManager->mSessionCount--; 1.460 + } 1.461 + 1.462 + static void GetSessionCount(int32_t &aSessionCount) 1.463 + { 1.464 + StaticMutexAutoLock lock(sLock); 1.465 + if (!sManager) { 1.466 + return; 1.467 + } 1.468 + aSessionCount = sManager->mSessionCount; 1.469 + } 1.470 + 1.471 +private: 1.472 + nsWSAdmissionManager() : mSessionCount(0) 1.473 + { 1.474 + MOZ_COUNT_CTOR(nsWSAdmissionManager); 1.475 + } 1.476 + 1.477 + ~nsWSAdmissionManager() 1.478 + { 1.479 + MOZ_COUNT_DTOR(nsWSAdmissionManager); 1.480 + for (uint32_t i = 0; i < mQueue.Length(); i++) 1.481 + delete mQueue[i]; 1.482 + } 1.483 + 1.484 + class nsOpenConn 1.485 + { 1.486 + public: 1.487 + nsOpenConn(nsCString &addr, WebSocketChannel *channel) 1.488 + : mAddress(addr), mChannel(channel) { MOZ_COUNT_CTOR(nsOpenConn); } 1.489 + ~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); } 1.490 + 1.491 + nsCString mAddress; 1.492 + WebSocketChannel *mChannel; 1.493 + }; 1.494 + 1.495 + void ConnectNext(nsCString &hostName) 1.496 + { 1.497 + int32_t index = IndexOf(hostName); 1.498 + if (index >= 0) { 1.499 + WebSocketChannel *chan = mQueue[index]->mChannel; 1.500 + 1.501 + NS_ABORT_IF_FALSE(chan->mConnecting == CONNECTING_QUEUED, 1.502 + "transaction not queued but in queue"); 1.503 + LOG(("WebSocket: ConnectNext: found channel [this=%p] in queue", chan)); 1.504 + 1.505 + mFailures.DelayOrBegin(chan); 1.506 + } 1.507 + } 1.508 + 1.509 + void RemoveFromQueue(WebSocketChannel *aChannel) 1.510 + { 1.511 + int32_t index = IndexOf(aChannel); 1.512 + NS_ABORT_IF_FALSE(index >= 0, "connection to remove not in queue"); 1.513 + if (index >= 0) { 1.514 + nsOpenConn *olddata = mQueue[index]; 1.515 + mQueue.RemoveElementAt(index); 1.516 + delete olddata; 1.517 + } 1.518 + } 1.519 + 1.520 + int32_t IndexOf(nsCString &aStr) 1.521 + { 1.522 + for (uint32_t i = 0; i < mQueue.Length(); i++) 1.523 + if (aStr == (mQueue[i])->mAddress) 1.524 + return i; 1.525 + return -1; 1.526 + } 1.527 + 1.528 + int32_t IndexOf(WebSocketChannel *aChannel) 1.529 + { 1.530 + for (uint32_t i = 0; i < mQueue.Length(); i++) 1.531 + if (aChannel == (mQueue[i])->mChannel) 1.532 + return i; 1.533 + return -1; 1.534 + } 1.535 + 1.536 + // SessionCount might be decremented from the main or the socket 1.537 + // thread, so manage it with atomic counters 1.538 + Atomic<int32_t> mSessionCount; 1.539 + 1.540 + // Queue for websockets that have not completed connecting yet. 1.541 + // The first nsOpenConn with a given address will be either be 1.542 + // CONNECTING_IN_PROGRESS or CONNECTING_DELAYED. Later ones with the same 1.543 + // hostname must be CONNECTING_QUEUED. 1.544 + // 1.545 + // We could hash hostnames instead of using a single big vector here, but the 1.546 + // dataset is expected to be small. 1.547 + nsTArray<nsOpenConn *> mQueue; 1.548 + 1.549 + FailDelayManager mFailures; 1.550 + 1.551 + static nsWSAdmissionManager *sManager; 1.552 + static StaticMutex sLock; 1.553 +}; 1.554 + 1.555 +nsWSAdmissionManager *nsWSAdmissionManager::sManager; 1.556 +StaticMutex nsWSAdmissionManager::sLock; 1.557 + 1.558 +//----------------------------------------------------------------------------- 1.559 +// CallOnMessageAvailable 1.560 +//----------------------------------------------------------------------------- 1.561 + 1.562 +class CallOnMessageAvailable MOZ_FINAL : public nsIRunnable 1.563 +{ 1.564 +public: 1.565 + NS_DECL_THREADSAFE_ISUPPORTS 1.566 + 1.567 + CallOnMessageAvailable(WebSocketChannel *aChannel, 1.568 + nsCString &aData, 1.569 + int32_t aLen) 1.570 + : mChannel(aChannel), 1.571 + mData(aData), 1.572 + mLen(aLen) {} 1.573 + 1.574 + NS_IMETHOD Run() 1.575 + { 1.576 + MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread); 1.577 + 1.578 + if (mLen < 0) 1.579 + mChannel->mListener->OnMessageAvailable(mChannel->mContext, mData); 1.580 + else 1.581 + mChannel->mListener->OnBinaryMessageAvailable(mChannel->mContext, mData); 1.582 + return NS_OK; 1.583 + } 1.584 + 1.585 +private: 1.586 + ~CallOnMessageAvailable() {} 1.587 + 1.588 + nsRefPtr<WebSocketChannel> mChannel; 1.589 + nsCString mData; 1.590 + int32_t mLen; 1.591 +}; 1.592 +NS_IMPL_ISUPPORTS(CallOnMessageAvailable, nsIRunnable) 1.593 + 1.594 +//----------------------------------------------------------------------------- 1.595 +// CallOnStop 1.596 +//----------------------------------------------------------------------------- 1.597 + 1.598 +class CallOnStop MOZ_FINAL : public nsIRunnable 1.599 +{ 1.600 +public: 1.601 + NS_DECL_THREADSAFE_ISUPPORTS 1.602 + 1.603 + CallOnStop(WebSocketChannel *aChannel, 1.604 + nsresult aReason) 1.605 + : mChannel(aChannel), 1.606 + mReason(aReason) {} 1.607 + 1.608 + NS_IMETHOD Run() 1.609 + { 1.610 + MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread); 1.611 + 1.612 + nsWSAdmissionManager::OnStopSession(mChannel, mReason); 1.613 + 1.614 + if (mChannel->mListener) { 1.615 + mChannel->mListener->OnStop(mChannel->mContext, mReason); 1.616 + mChannel->mListener = nullptr; 1.617 + mChannel->mContext = nullptr; 1.618 + } 1.619 + return NS_OK; 1.620 + } 1.621 + 1.622 +private: 1.623 + ~CallOnStop() {} 1.624 + 1.625 + nsRefPtr<WebSocketChannel> mChannel; 1.626 + nsresult mReason; 1.627 +}; 1.628 +NS_IMPL_ISUPPORTS(CallOnStop, nsIRunnable) 1.629 + 1.630 +//----------------------------------------------------------------------------- 1.631 +// CallOnServerClose 1.632 +//----------------------------------------------------------------------------- 1.633 + 1.634 +class CallOnServerClose MOZ_FINAL : public nsIRunnable 1.635 +{ 1.636 +public: 1.637 + NS_DECL_THREADSAFE_ISUPPORTS 1.638 + 1.639 + CallOnServerClose(WebSocketChannel *aChannel, 1.640 + uint16_t aCode, 1.641 + nsCString &aReason) 1.642 + : mChannel(aChannel), 1.643 + mCode(aCode), 1.644 + mReason(aReason) {} 1.645 + 1.646 + NS_IMETHOD Run() 1.647 + { 1.648 + MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread); 1.649 + 1.650 + mChannel->mListener->OnServerClose(mChannel->mContext, mCode, mReason); 1.651 + return NS_OK; 1.652 + } 1.653 + 1.654 +private: 1.655 + ~CallOnServerClose() {} 1.656 + 1.657 + nsRefPtr<WebSocketChannel> mChannel; 1.658 + uint16_t mCode; 1.659 + nsCString mReason; 1.660 +}; 1.661 +NS_IMPL_ISUPPORTS(CallOnServerClose, nsIRunnable) 1.662 + 1.663 +//----------------------------------------------------------------------------- 1.664 +// CallAcknowledge 1.665 +//----------------------------------------------------------------------------- 1.666 + 1.667 +class CallAcknowledge MOZ_FINAL : public nsIRunnable 1.668 +{ 1.669 +public: 1.670 + NS_DECL_THREADSAFE_ISUPPORTS 1.671 + 1.672 + CallAcknowledge(WebSocketChannel *aChannel, 1.673 + uint32_t aSize) 1.674 + : mChannel(aChannel), 1.675 + mSize(aSize) {} 1.676 + 1.677 + NS_IMETHOD Run() 1.678 + { 1.679 + MOZ_ASSERT(NS_GetCurrentThread() == mChannel->mTargetThread); 1.680 + 1.681 + LOG(("WebSocketChannel::CallAcknowledge: Size %u\n", mSize)); 1.682 + mChannel->mListener->OnAcknowledge(mChannel->mContext, mSize); 1.683 + return NS_OK; 1.684 + } 1.685 + 1.686 +private: 1.687 + ~CallAcknowledge() {} 1.688 + 1.689 + nsRefPtr<WebSocketChannel> mChannel; 1.690 + uint32_t mSize; 1.691 +}; 1.692 +NS_IMPL_ISUPPORTS(CallAcknowledge, nsIRunnable) 1.693 + 1.694 +//----------------------------------------------------------------------------- 1.695 +// CallOnTransportAvailable 1.696 +//----------------------------------------------------------------------------- 1.697 + 1.698 +class CallOnTransportAvailable MOZ_FINAL : public nsIRunnable 1.699 +{ 1.700 +public: 1.701 + NS_DECL_THREADSAFE_ISUPPORTS 1.702 + 1.703 + CallOnTransportAvailable(WebSocketChannel *aChannel, 1.704 + nsISocketTransport *aTransport, 1.705 + nsIAsyncInputStream *aSocketIn, 1.706 + nsIAsyncOutputStream *aSocketOut) 1.707 + : mChannel(aChannel), 1.708 + mTransport(aTransport), 1.709 + mSocketIn(aSocketIn), 1.710 + mSocketOut(aSocketOut) {} 1.711 + 1.712 + NS_IMETHOD Run() 1.713 + { 1.714 + LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this)); 1.715 + return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut); 1.716 + } 1.717 + 1.718 +private: 1.719 + ~CallOnTransportAvailable() {} 1.720 + 1.721 + nsRefPtr<WebSocketChannel> mChannel; 1.722 + nsCOMPtr<nsISocketTransport> mTransport; 1.723 + nsCOMPtr<nsIAsyncInputStream> mSocketIn; 1.724 + nsCOMPtr<nsIAsyncOutputStream> mSocketOut; 1.725 +}; 1.726 +NS_IMPL_ISUPPORTS(CallOnTransportAvailable, nsIRunnable) 1.727 + 1.728 +//----------------------------------------------------------------------------- 1.729 +// OutboundMessage 1.730 +//----------------------------------------------------------------------------- 1.731 + 1.732 +enum WsMsgType { 1.733 + kMsgTypeString = 0, 1.734 + kMsgTypeBinaryString, 1.735 + kMsgTypeStream, 1.736 + kMsgTypePing, 1.737 + kMsgTypePong, 1.738 + kMsgTypeFin 1.739 +}; 1.740 + 1.741 +static const char* msgNames[] = { 1.742 + "text", 1.743 + "binaryString", 1.744 + "binaryStream", 1.745 + "ping", 1.746 + "pong", 1.747 + "close" 1.748 +}; 1.749 + 1.750 +class OutboundMessage 1.751 +{ 1.752 +public: 1.753 + OutboundMessage(WsMsgType type, nsCString *str) 1.754 + : mMsgType(type) 1.755 + { 1.756 + MOZ_COUNT_CTOR(OutboundMessage); 1.757 + mMsg.pString = str; 1.758 + mLength = str ? str->Length() : 0; 1.759 + } 1.760 + 1.761 + OutboundMessage(nsIInputStream *stream, uint32_t length) 1.762 + : mMsgType(kMsgTypeStream), mLength(length) 1.763 + { 1.764 + MOZ_COUNT_CTOR(OutboundMessage); 1.765 + mMsg.pStream = stream; 1.766 + mMsg.pStream->AddRef(); 1.767 + } 1.768 + 1.769 + ~OutboundMessage() { 1.770 + MOZ_COUNT_DTOR(OutboundMessage); 1.771 + switch (mMsgType) { 1.772 + case kMsgTypeString: 1.773 + case kMsgTypeBinaryString: 1.774 + case kMsgTypePing: 1.775 + case kMsgTypePong: 1.776 + delete mMsg.pString; 1.777 + break; 1.778 + case kMsgTypeStream: 1.779 + // for now this only gets hit if msg deleted w/o being sent 1.780 + if (mMsg.pStream) { 1.781 + mMsg.pStream->Close(); 1.782 + mMsg.pStream->Release(); 1.783 + } 1.784 + break; 1.785 + case kMsgTypeFin: 1.786 + break; // do-nothing: avoid compiler warning 1.787 + } 1.788 + } 1.789 + 1.790 + WsMsgType GetMsgType() const { return mMsgType; } 1.791 + int32_t Length() const { return mLength; } 1.792 + 1.793 + uint8_t* BeginWriting() { 1.794 + NS_ABORT_IF_FALSE(mMsgType != kMsgTypeStream, 1.795 + "Stream should have been converted to string by now"); 1.796 + return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginWriting() : nullptr); 1.797 + } 1.798 + 1.799 + uint8_t* BeginReading() { 1.800 + NS_ABORT_IF_FALSE(mMsgType != kMsgTypeStream, 1.801 + "Stream should have been converted to string by now"); 1.802 + return (uint8_t *)(mMsg.pString ? mMsg.pString->BeginReading() : nullptr); 1.803 + } 1.804 + 1.805 + nsresult ConvertStreamToString() 1.806 + { 1.807 + NS_ABORT_IF_FALSE(mMsgType == kMsgTypeStream, "Not a stream!"); 1.808 + 1.809 +#ifdef DEBUG 1.810 + // Make sure we got correct length from Blob 1.811 + uint64_t bytes; 1.812 + mMsg.pStream->Available(&bytes); 1.813 + NS_ASSERTION(bytes == mLength, "Stream length != blob length!"); 1.814 +#endif 1.815 + 1.816 + nsAutoPtr<nsCString> temp(new nsCString()); 1.817 + nsresult rv = NS_ReadInputStreamToString(mMsg.pStream, *temp, mLength); 1.818 + 1.819 + NS_ENSURE_SUCCESS(rv, rv); 1.820 + 1.821 + mMsg.pStream->Close(); 1.822 + mMsg.pStream->Release(); 1.823 + mMsg.pString = temp.forget(); 1.824 + mMsgType = kMsgTypeBinaryString; 1.825 + 1.826 + return NS_OK; 1.827 + } 1.828 + 1.829 +private: 1.830 + union { 1.831 + nsCString *pString; 1.832 + nsIInputStream *pStream; 1.833 + } mMsg; 1.834 + WsMsgType mMsgType; 1.835 + uint32_t mLength; 1.836 +}; 1.837 + 1.838 +//----------------------------------------------------------------------------- 1.839 +// OutboundEnqueuer 1.840 +//----------------------------------------------------------------------------- 1.841 + 1.842 +class OutboundEnqueuer MOZ_FINAL : public nsIRunnable 1.843 +{ 1.844 +public: 1.845 + NS_DECL_THREADSAFE_ISUPPORTS 1.846 + 1.847 + OutboundEnqueuer(WebSocketChannel *aChannel, OutboundMessage *aMsg) 1.848 + : mChannel(aChannel), mMessage(aMsg) {} 1.849 + 1.850 + NS_IMETHOD Run() 1.851 + { 1.852 + mChannel->EnqueueOutgoingMessage(mChannel->mOutgoingMessages, mMessage); 1.853 + return NS_OK; 1.854 + } 1.855 + 1.856 +private: 1.857 + ~OutboundEnqueuer() {} 1.858 + 1.859 + nsRefPtr<WebSocketChannel> mChannel; 1.860 + OutboundMessage *mMessage; 1.861 +}; 1.862 +NS_IMPL_ISUPPORTS(OutboundEnqueuer, nsIRunnable) 1.863 + 1.864 +//----------------------------------------------------------------------------- 1.865 +// nsWSCompression 1.866 +// 1.867 +// similar to nsDeflateConverter except for the mandatory FLUSH calls 1.868 +// required by websocket and the absence of the deflate termination 1.869 +// block which is appropriate because it would create data bytes after 1.870 +// sending the websockets CLOSE message. 1.871 +//----------------------------------------------------------------------------- 1.872 + 1.873 +class nsWSCompression 1.874 +{ 1.875 +public: 1.876 + nsWSCompression(nsIStreamListener *aListener, 1.877 + nsISupports *aContext) 1.878 + : mActive(false), 1.879 + mContext(aContext), 1.880 + mListener(aListener) 1.881 + { 1.882 + MOZ_COUNT_CTOR(nsWSCompression); 1.883 + 1.884 + mZlib.zalloc = allocator; 1.885 + mZlib.zfree = destructor; 1.886 + mZlib.opaque = Z_NULL; 1.887 + 1.888 + // Initialize the compressor - these are all the normal zlib 1.889 + // defaults except window size is set to -15 instead of +15. 1.890 + // This is the zlib way of specifying raw RFC 1951 output instead 1.891 + // of the zlib rfc 1950 format which has a 2 byte header and 1.892 + // adler checksum as a trailer 1.893 + 1.894 + nsresult rv; 1.895 + mStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); 1.896 + if (NS_SUCCEEDED(rv) && aContext && aListener && 1.897 + deflateInit2(&mZlib, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, 1.898 + Z_DEFAULT_STRATEGY) == Z_OK) { 1.899 + mActive = true; 1.900 + } 1.901 + } 1.902 + 1.903 + ~nsWSCompression() 1.904 + { 1.905 + MOZ_COUNT_DTOR(nsWSCompression); 1.906 + 1.907 + if (mActive) 1.908 + deflateEnd(&mZlib); 1.909 + } 1.910 + 1.911 + bool Active() 1.912 + { 1.913 + return mActive; 1.914 + } 1.915 + 1.916 + nsresult Deflate(uint8_t *buf1, uint32_t buf1Len, 1.917 + uint8_t *buf2, uint32_t buf2Len) 1.918 + { 1.919 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, 1.920 + "not socket thread"); 1.921 + NS_ABORT_IF_FALSE(mActive, "not active"); 1.922 + 1.923 + mZlib.avail_out = kBufferLen; 1.924 + mZlib.next_out = mBuffer; 1.925 + mZlib.avail_in = buf1Len; 1.926 + mZlib.next_in = buf1; 1.927 + 1.928 + nsresult rv; 1.929 + 1.930 + while (mZlib.avail_in > 0) { 1.931 + deflate(&mZlib, (buf2Len > 0) ? Z_NO_FLUSH : Z_SYNC_FLUSH); 1.932 + rv = PushData(); 1.933 + if (NS_FAILED(rv)) 1.934 + return rv; 1.935 + mZlib.avail_out = kBufferLen; 1.936 + mZlib.next_out = mBuffer; 1.937 + } 1.938 + 1.939 + mZlib.avail_in = buf2Len; 1.940 + mZlib.next_in = buf2; 1.941 + 1.942 + while (mZlib.avail_in > 0) { 1.943 + deflate(&mZlib, Z_SYNC_FLUSH); 1.944 + rv = PushData(); 1.945 + if (NS_FAILED(rv)) 1.946 + return rv; 1.947 + mZlib.avail_out = kBufferLen; 1.948 + mZlib.next_out = mBuffer; 1.949 + } 1.950 + 1.951 + return NS_OK; 1.952 + } 1.953 + 1.954 +private: 1.955 + 1.956 + // use zlib data types 1.957 + static void *allocator(void *opaque, uInt items, uInt size) 1.958 + { 1.959 + return moz_xmalloc(items * size); 1.960 + } 1.961 + 1.962 + static void destructor(void *opaque, void *addr) 1.963 + { 1.964 + moz_free(addr); 1.965 + } 1.966 + 1.967 + nsresult PushData() 1.968 + { 1.969 + uint32_t bytesToWrite = kBufferLen - mZlib.avail_out; 1.970 + if (bytesToWrite > 0) { 1.971 + mStream->ShareData(reinterpret_cast<char *>(mBuffer), bytesToWrite); 1.972 + nsresult rv = 1.973 + mListener->OnDataAvailable(nullptr, mContext, mStream, 0, bytesToWrite); 1.974 + if (NS_FAILED(rv)) 1.975 + return rv; 1.976 + } 1.977 + return NS_OK; 1.978 + } 1.979 + 1.980 + bool mActive; 1.981 + z_stream mZlib; 1.982 + nsCOMPtr<nsIStringInputStream> mStream; 1.983 + 1.984 + nsISupports *mContext; /* weak ref */ 1.985 + nsIStreamListener *mListener; /* weak ref */ 1.986 + 1.987 + const static int32_t kBufferLen = 4096; 1.988 + uint8_t mBuffer[kBufferLen]; 1.989 +}; 1.990 + 1.991 +//----------------------------------------------------------------------------- 1.992 +// WebSocketChannel 1.993 +//----------------------------------------------------------------------------- 1.994 + 1.995 +uint32_t WebSocketChannel::sSerialSeed = 0; 1.996 + 1.997 +WebSocketChannel::WebSocketChannel() : 1.998 + mPort(0), 1.999 + mCloseTimeout(20000), 1.1000 + mOpenTimeout(20000), 1.1001 + mConnecting(NOT_CONNECTING), 1.1002 + mMaxConcurrentConnections(200), 1.1003 + mGotUpgradeOK(0), 1.1004 + mRecvdHttpUpgradeTransport(0), 1.1005 + mRequestedClose(0), 1.1006 + mClientClosed(0), 1.1007 + mServerClosed(0), 1.1008 + mStopped(0), 1.1009 + mCalledOnStop(0), 1.1010 + mPingOutstanding(0), 1.1011 + mAllowCompression(1), 1.1012 + mAutoFollowRedirects(0), 1.1013 + mReleaseOnTransmit(0), 1.1014 + mTCPClosed(0), 1.1015 + mOpenedHttpChannel(0), 1.1016 + mDataStarted(0), 1.1017 + mIncrementedSessionCount(0), 1.1018 + mDecrementedSessionCount(0), 1.1019 + mMaxMessageSize(INT32_MAX), 1.1020 + mStopOnClose(NS_OK), 1.1021 + mServerCloseCode(CLOSE_ABNORMAL), 1.1022 + mScriptCloseCode(0), 1.1023 + mFragmentOpcode(kContinuation), 1.1024 + mFragmentAccumulator(0), 1.1025 + mBuffered(0), 1.1026 + mBufferSize(kIncomingBufferInitialSize), 1.1027 + mCurrentOut(nullptr), 1.1028 + mCurrentOutSent(0), 1.1029 + mCompressor(nullptr), 1.1030 + mDynamicOutputSize(0), 1.1031 + mDynamicOutput(nullptr), 1.1032 + mPrivateBrowsing(false), 1.1033 + mConnectionLogService(nullptr), 1.1034 + mCountRecv(0), 1.1035 + mCountSent(0), 1.1036 + mAppId(NECKO_NO_APP_ID) 1.1037 +{ 1.1038 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.1039 + 1.1040 + LOG(("WebSocketChannel::WebSocketChannel() %p\n", this)); 1.1041 + 1.1042 + nsWSAdmissionManager::Init(); 1.1043 + 1.1044 + mFramePtr = mBuffer = static_cast<uint8_t *>(moz_xmalloc(mBufferSize)); 1.1045 + 1.1046 + nsresult rv; 1.1047 + mConnectionLogService = do_GetService("@mozilla.org/network/dashboard;1",&rv); 1.1048 + if (NS_FAILED(rv)) 1.1049 + LOG(("Failed to initiate dashboard service.")); 1.1050 + 1.1051 + mSerial = sSerialSeed++; 1.1052 +} 1.1053 + 1.1054 +WebSocketChannel::~WebSocketChannel() 1.1055 +{ 1.1056 + LOG(("WebSocketChannel::~WebSocketChannel() %p\n", this)); 1.1057 + 1.1058 + if (mWasOpened) { 1.1059 + MOZ_ASSERT(mCalledOnStop, "WebSocket was opened but OnStop was not called"); 1.1060 + MOZ_ASSERT(mStopped, "WebSocket was opened but never stopped"); 1.1061 + } 1.1062 + MOZ_ASSERT(!mCancelable, "DNS/Proxy Request still alive at destruction"); 1.1063 + MOZ_ASSERT(!mConnecting, "Should not be connecting in destructor"); 1.1064 + 1.1065 + moz_free(mBuffer); 1.1066 + moz_free(mDynamicOutput); 1.1067 + delete mCompressor; 1.1068 + delete mCurrentOut; 1.1069 + 1.1070 + while ((mCurrentOut = (OutboundMessage *) mOutgoingPingMessages.PopFront())) 1.1071 + delete mCurrentOut; 1.1072 + while ((mCurrentOut = (OutboundMessage *) mOutgoingPongMessages.PopFront())) 1.1073 + delete mCurrentOut; 1.1074 + while ((mCurrentOut = (OutboundMessage *) mOutgoingMessages.PopFront())) 1.1075 + delete mCurrentOut; 1.1076 + 1.1077 + nsCOMPtr<nsIThread> mainThread; 1.1078 + nsIURI *forgettable; 1.1079 + NS_GetMainThread(getter_AddRefs(mainThread)); 1.1080 + 1.1081 + if (mURI) { 1.1082 + mURI.forget(&forgettable); 1.1083 + NS_ProxyRelease(mainThread, forgettable, false); 1.1084 + } 1.1085 + 1.1086 + if (mOriginalURI) { 1.1087 + mOriginalURI.forget(&forgettable); 1.1088 + NS_ProxyRelease(mainThread, forgettable, false); 1.1089 + } 1.1090 + 1.1091 + if (mListener) { 1.1092 + nsIWebSocketListener *forgettableListener; 1.1093 + mListener.forget(&forgettableListener); 1.1094 + NS_ProxyRelease(mainThread, forgettableListener, false); 1.1095 + } 1.1096 + 1.1097 + if (mContext) { 1.1098 + nsISupports *forgettableContext; 1.1099 + mContext.forget(&forgettableContext); 1.1100 + NS_ProxyRelease(mainThread, forgettableContext, false); 1.1101 + } 1.1102 + 1.1103 + if (mLoadGroup) { 1.1104 + nsILoadGroup *forgettableGroup; 1.1105 + mLoadGroup.forget(&forgettableGroup); 1.1106 + NS_ProxyRelease(mainThread, forgettableGroup, false); 1.1107 + } 1.1108 +} 1.1109 + 1.1110 +void 1.1111 +WebSocketChannel::Shutdown() 1.1112 +{ 1.1113 + nsWSAdmissionManager::Shutdown(); 1.1114 +} 1.1115 + 1.1116 +void 1.1117 +WebSocketChannel::BeginOpen() 1.1118 +{ 1.1119 + LOG(("WebSocketChannel::BeginOpen() %p\n", this)); 1.1120 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.1121 + 1.1122 + nsresult rv; 1.1123 + 1.1124 + // Important that we set CONNECTING_IN_PROGRESS before any call to 1.1125 + // AbortSession here: ensures that any remaining queued connection(s) are 1.1126 + // scheduled in OnStopSession 1.1127 + mConnecting = CONNECTING_IN_PROGRESS; 1.1128 + 1.1129 + if (mRedirectCallback) { 1.1130 + LOG(("WebSocketChannel::BeginOpen: Resuming Redirect\n")); 1.1131 + rv = mRedirectCallback->OnRedirectVerifyCallback(NS_OK); 1.1132 + mRedirectCallback = nullptr; 1.1133 + return; 1.1134 + } 1.1135 + 1.1136 + nsCOMPtr<nsIChannel> localChannel = do_QueryInterface(mChannel, &rv); 1.1137 + if (NS_FAILED(rv)) { 1.1138 + LOG(("WebSocketChannel::BeginOpen: cannot async open\n")); 1.1139 + AbortSession(NS_ERROR_UNEXPECTED); 1.1140 + return; 1.1141 + } 1.1142 + 1.1143 + if (localChannel) { 1.1144 + bool isInBrowser; 1.1145 + NS_GetAppInfo(localChannel, &mAppId, &isInBrowser); 1.1146 + } 1.1147 + 1.1148 +#ifdef MOZ_WIDGET_GONK 1.1149 + if (mAppId != NECKO_NO_APP_ID) { 1.1150 + nsCOMPtr<nsINetworkInterface> activeNetwork; 1.1151 + GetActiveNetworkInterface(activeNetwork); 1.1152 + mActiveNetwork = 1.1153 + new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork); 1.1154 + } 1.1155 +#endif 1.1156 + 1.1157 + rv = localChannel->AsyncOpen(this, mHttpChannel); 1.1158 + if (NS_FAILED(rv)) { 1.1159 + LOG(("WebSocketChannel::BeginOpen: cannot async open\n")); 1.1160 + AbortSession(NS_ERROR_CONNECTION_REFUSED); 1.1161 + return; 1.1162 + } 1.1163 + mOpenedHttpChannel = 1; 1.1164 + 1.1165 + mOpenTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); 1.1166 + if (NS_FAILED(rv)) { 1.1167 + LOG(("WebSocketChannel::BeginOpen: cannot create open timer\n")); 1.1168 + AbortSession(NS_ERROR_UNEXPECTED); 1.1169 + return; 1.1170 + } 1.1171 + 1.1172 + rv = mOpenTimer->InitWithCallback(this, mOpenTimeout, 1.1173 + nsITimer::TYPE_ONE_SHOT); 1.1174 + if (NS_FAILED(rv)) { 1.1175 + LOG(("WebSocketChannel::BeginOpen: cannot initialize open timer\n")); 1.1176 + AbortSession(NS_ERROR_UNEXPECTED); 1.1177 + return; 1.1178 + } 1.1179 +} 1.1180 + 1.1181 +bool 1.1182 +WebSocketChannel::IsPersistentFramePtr() 1.1183 +{ 1.1184 + return (mFramePtr >= mBuffer && mFramePtr < mBuffer + mBufferSize); 1.1185 +} 1.1186 + 1.1187 +// Extends the internal buffer by count and returns the total 1.1188 +// amount of data available for read 1.1189 +// 1.1190 +// Accumulated fragment size is passed in instead of using the member 1.1191 +// variable beacuse when transitioning from the stack to the persistent 1.1192 +// read buffer we want to explicitly include them in the buffer instead 1.1193 +// of as already existing data. 1.1194 +bool 1.1195 +WebSocketChannel::UpdateReadBuffer(uint8_t *buffer, uint32_t count, 1.1196 + uint32_t accumulatedFragments, 1.1197 + uint32_t *available) 1.1198 +{ 1.1199 + LOG(("WebSocketChannel::UpdateReadBuffer() %p [%p %u]\n", 1.1200 + this, buffer, count)); 1.1201 + 1.1202 + if (!mBuffered) 1.1203 + mFramePtr = mBuffer; 1.1204 + 1.1205 + NS_ABORT_IF_FALSE(IsPersistentFramePtr(), "update read buffer bad mFramePtr"); 1.1206 + NS_ABORT_IF_FALSE(mFramePtr - accumulatedFragments >= mBuffer, 1.1207 + "reserved FramePtr bad"); 1.1208 + 1.1209 + if (mBuffered + count <= mBufferSize) { 1.1210 + // append to existing buffer 1.1211 + LOG(("WebSocketChannel: update read buffer absorbed %u\n", count)); 1.1212 + } else if (mBuffered + count - 1.1213 + (mFramePtr - accumulatedFragments - mBuffer) <= mBufferSize) { 1.1214 + // make room in existing buffer by shifting unused data to start 1.1215 + mBuffered -= (mFramePtr - mBuffer - accumulatedFragments); 1.1216 + LOG(("WebSocketChannel: update read buffer shifted %u\n", mBuffered)); 1.1217 + ::memmove(mBuffer, mFramePtr - accumulatedFragments, mBuffered); 1.1218 + mFramePtr = mBuffer + accumulatedFragments; 1.1219 + } else { 1.1220 + // existing buffer is not sufficient, extend it 1.1221 + mBufferSize += count + 8192 + mBufferSize/3; 1.1222 + LOG(("WebSocketChannel: update read buffer extended to %u\n", mBufferSize)); 1.1223 + uint8_t *old = mBuffer; 1.1224 + mBuffer = (uint8_t *)moz_realloc(mBuffer, mBufferSize); 1.1225 + if (!mBuffer) { 1.1226 + mBuffer = old; 1.1227 + return false; 1.1228 + } 1.1229 + mFramePtr = mBuffer + (mFramePtr - old); 1.1230 + } 1.1231 + 1.1232 + ::memcpy(mBuffer + mBuffered, buffer, count); 1.1233 + mBuffered += count; 1.1234 + 1.1235 + if (available) 1.1236 + *available = mBuffered - (mFramePtr - mBuffer); 1.1237 + 1.1238 + return true; 1.1239 +} 1.1240 + 1.1241 +nsresult 1.1242 +WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count) 1.1243 +{ 1.1244 + LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered)); 1.1245 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread"); 1.1246 + 1.1247 + // The purpose of ping/pong is to actively probe the peer so that an 1.1248 + // unreachable peer is not mistaken for a period of idleness. This 1.1249 + // implementation accepts any application level read activity as a sign of 1.1250 + // life, it does not necessarily have to be a pong. 1.1251 + ResetPingTimer(); 1.1252 + 1.1253 + uint32_t avail; 1.1254 + 1.1255 + if (!mBuffered) { 1.1256 + // Most of the time we can process right off the stack buffer without 1.1257 + // having to accumulate anything 1.1258 + mFramePtr = buffer; 1.1259 + avail = count; 1.1260 + } else { 1.1261 + if (!UpdateReadBuffer(buffer, count, mFragmentAccumulator, &avail)) { 1.1262 + return NS_ERROR_FILE_TOO_BIG; 1.1263 + } 1.1264 + } 1.1265 + 1.1266 + uint8_t *payload; 1.1267 + uint32_t totalAvail = avail; 1.1268 + 1.1269 + while (avail >= 2) { 1.1270 + int64_t payloadLength64 = mFramePtr[1] & 0x7F; 1.1271 + uint8_t finBit = mFramePtr[0] & kFinalFragBit; 1.1272 + uint8_t rsvBits = mFramePtr[0] & 0x70; 1.1273 + uint8_t maskBit = mFramePtr[1] & kMaskBit; 1.1274 + uint8_t opcode = mFramePtr[0] & 0x0F; 1.1275 + 1.1276 + uint32_t framingLength = 2; 1.1277 + if (maskBit) 1.1278 + framingLength += 4; 1.1279 + 1.1280 + if (payloadLength64 < 126) { 1.1281 + if (avail < framingLength) 1.1282 + break; 1.1283 + } else if (payloadLength64 == 126) { 1.1284 + // 16 bit length field 1.1285 + framingLength += 2; 1.1286 + if (avail < framingLength) 1.1287 + break; 1.1288 + 1.1289 + payloadLength64 = mFramePtr[2] << 8 | mFramePtr[3]; 1.1290 + } else { 1.1291 + // 64 bit length 1.1292 + framingLength += 8; 1.1293 + if (avail < framingLength) 1.1294 + break; 1.1295 + 1.1296 + if (mFramePtr[2] & 0x80) { 1.1297 + // Section 4.2 says that the most significant bit MUST be 1.1298 + // 0. (i.e. this is really a 63 bit value) 1.1299 + LOG(("WebSocketChannel:: high bit of 64 bit length set")); 1.1300 + return NS_ERROR_ILLEGAL_VALUE; 1.1301 + } 1.1302 + 1.1303 + // copy this in case it is unaligned 1.1304 + payloadLength64 = NetworkEndian::readInt64(mFramePtr + 2); 1.1305 + } 1.1306 + 1.1307 + payload = mFramePtr + framingLength; 1.1308 + avail -= framingLength; 1.1309 + 1.1310 + LOG(("WebSocketChannel::ProcessInput: payload %lld avail %lu\n", 1.1311 + payloadLength64, avail)); 1.1312 + 1.1313 + if (payloadLength64 + mFragmentAccumulator > mMaxMessageSize) { 1.1314 + return NS_ERROR_FILE_TOO_BIG; 1.1315 + } 1.1316 + uint32_t payloadLength = static_cast<uint32_t>(payloadLength64); 1.1317 + 1.1318 + if (avail < payloadLength) 1.1319 + break; 1.1320 + 1.1321 + LOG(("WebSocketChannel::ProcessInput: Frame accumulated - opcode %d\n", 1.1322 + opcode)); 1.1323 + 1.1324 + if (maskBit) { 1.1325 + // This is unexpected - the server does not generally send masked 1.1326 + // frames to the client, but it is allowed 1.1327 + LOG(("WebSocketChannel:: Client RECEIVING masked frame.")); 1.1328 + 1.1329 + uint32_t mask = NetworkEndian::readUint32(payload - 4); 1.1330 + ApplyMask(mask, payload, payloadLength); 1.1331 + } 1.1332 + 1.1333 + // Control codes are required to have the fin bit set 1.1334 + if (!finBit && (opcode & kControlFrameMask)) { 1.1335 + LOG(("WebSocketChannel:: fragmented control frame code %d\n", opcode)); 1.1336 + return NS_ERROR_ILLEGAL_VALUE; 1.1337 + } 1.1338 + 1.1339 + if (rsvBits) { 1.1340 + LOG(("WebSocketChannel:: unexpected reserved bits %x\n", rsvBits)); 1.1341 + return NS_ERROR_ILLEGAL_VALUE; 1.1342 + } 1.1343 + 1.1344 + if (!finBit || opcode == kContinuation) { 1.1345 + // This is part of a fragment response 1.1346 + 1.1347 + // Only the first frame has a non zero op code: Make sure we don't see a 1.1348 + // first frame while some old fragments are open 1.1349 + if ((mFragmentAccumulator != 0) && (opcode != kContinuation)) { 1.1350 + LOG(("WebSocketChannel:: nested fragments\n")); 1.1351 + return NS_ERROR_ILLEGAL_VALUE; 1.1352 + } 1.1353 + 1.1354 + LOG(("WebSocketChannel:: Accumulating Fragment %ld\n", payloadLength)); 1.1355 + 1.1356 + if (opcode == kContinuation) { 1.1357 + 1.1358 + // Make sure this continuation fragment isn't the first fragment 1.1359 + if (mFragmentOpcode == kContinuation) { 1.1360 + LOG(("WebSocketHeandler:: continuation code in first fragment\n")); 1.1361 + return NS_ERROR_ILLEGAL_VALUE; 1.1362 + } 1.1363 + 1.1364 + // For frag > 1 move the data body back on top of the headers 1.1365 + // so we have contiguous stream of data 1.1366 + NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload, 1.1367 + "payload offset from frameptr wrong"); 1.1368 + ::memmove(mFramePtr, payload, avail); 1.1369 + payload = mFramePtr; 1.1370 + if (mBuffered) 1.1371 + mBuffered -= framingLength; 1.1372 + } else { 1.1373 + mFragmentOpcode = opcode; 1.1374 + } 1.1375 + 1.1376 + if (finBit) { 1.1377 + LOG(("WebSocketChannel:: Finalizing Fragment\n")); 1.1378 + payload -= mFragmentAccumulator; 1.1379 + payloadLength += mFragmentAccumulator; 1.1380 + avail += mFragmentAccumulator; 1.1381 + mFragmentAccumulator = 0; 1.1382 + opcode = mFragmentOpcode; 1.1383 + // reset to detect if next message illegally starts with continuation 1.1384 + mFragmentOpcode = kContinuation; 1.1385 + } else { 1.1386 + opcode = kContinuation; 1.1387 + mFragmentAccumulator += payloadLength; 1.1388 + } 1.1389 + } else if (mFragmentAccumulator != 0 && !(opcode & kControlFrameMask)) { 1.1390 + // This frame is not part of a fragment sequence but we 1.1391 + // have an open fragment.. it must be a control code or else 1.1392 + // we have a problem 1.1393 + LOG(("WebSocketChannel:: illegal fragment sequence\n")); 1.1394 + return NS_ERROR_ILLEGAL_VALUE; 1.1395 + } 1.1396 + 1.1397 + if (mServerClosed) { 1.1398 + LOG(("WebSocketChannel:: ignoring read frame code %d after close\n", 1.1399 + opcode)); 1.1400 + // nop 1.1401 + } else if (mStopped) { 1.1402 + LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n", 1.1403 + opcode)); 1.1404 + } else if (opcode == kText) { 1.1405 + LOG(("WebSocketChannel:: text frame received\n")); 1.1406 + if (mListener) { 1.1407 + nsCString utf8Data; 1.1408 + if (!utf8Data.Assign((const char *)payload, payloadLength, 1.1409 + mozilla::fallible_t())) 1.1410 + return NS_ERROR_OUT_OF_MEMORY; 1.1411 + 1.1412 + // Section 8.1 says to fail connection if invalid utf-8 in text message 1.1413 + if (!IsUTF8(utf8Data, false)) { 1.1414 + LOG(("WebSocketChannel:: text frame invalid utf-8\n")); 1.1415 + return NS_ERROR_CANNOT_CONVERT_DATA; 1.1416 + } 1.1417 + 1.1418 + mTargetThread->Dispatch(new CallOnMessageAvailable(this, utf8Data, -1), 1.1419 + NS_DISPATCH_NORMAL); 1.1420 + if (mConnectionLogService && !mPrivateBrowsing) { 1.1421 + mConnectionLogService->NewMsgReceived(mHost, mSerial, count); 1.1422 + LOG(("Added new msg received for %s", mHost.get())); 1.1423 + } 1.1424 + } 1.1425 + } else if (opcode & kControlFrameMask) { 1.1426 + // control frames 1.1427 + if (payloadLength > 125) { 1.1428 + LOG(("WebSocketChannel:: bad control frame code %d length %d\n", 1.1429 + opcode, payloadLength)); 1.1430 + return NS_ERROR_ILLEGAL_VALUE; 1.1431 + } 1.1432 + 1.1433 + if (opcode == kClose) { 1.1434 + LOG(("WebSocketChannel:: close received\n")); 1.1435 + mServerClosed = 1; 1.1436 + 1.1437 + mServerCloseCode = CLOSE_NO_STATUS; 1.1438 + if (payloadLength >= 2) { 1.1439 + mServerCloseCode = NetworkEndian::readUint16(payload); 1.1440 + LOG(("WebSocketChannel:: close recvd code %u\n", mServerCloseCode)); 1.1441 + uint16_t msglen = static_cast<uint16_t>(payloadLength - 2); 1.1442 + if (msglen > 0) { 1.1443 + mServerCloseReason.SetLength(msglen); 1.1444 + memcpy(mServerCloseReason.BeginWriting(), 1.1445 + (const char *)payload + 2, msglen); 1.1446 + 1.1447 + // section 8.1 says to replace received non utf-8 sequences 1.1448 + // (which are non-conformant to send) with u+fffd, 1.1449 + // but secteam feels that silently rewriting messages is 1.1450 + // inappropriate - so we will fail the connection instead. 1.1451 + if (!IsUTF8(mServerCloseReason, false)) { 1.1452 + LOG(("WebSocketChannel:: close frame invalid utf-8\n")); 1.1453 + return NS_ERROR_CANNOT_CONVERT_DATA; 1.1454 + } 1.1455 + 1.1456 + LOG(("WebSocketChannel:: close msg %s\n", 1.1457 + mServerCloseReason.get())); 1.1458 + } 1.1459 + } 1.1460 + 1.1461 + if (mCloseTimer) { 1.1462 + mCloseTimer->Cancel(); 1.1463 + mCloseTimer = nullptr; 1.1464 + } 1.1465 + if (mListener) { 1.1466 + mTargetThread->Dispatch(new CallOnServerClose(this, mServerCloseCode, 1.1467 + mServerCloseReason), 1.1468 + NS_DISPATCH_NORMAL); 1.1469 + } 1.1470 + 1.1471 + if (mClientClosed) 1.1472 + ReleaseSession(); 1.1473 + } else if (opcode == kPing) { 1.1474 + LOG(("WebSocketChannel:: ping received\n")); 1.1475 + GeneratePong(payload, payloadLength); 1.1476 + } else if (opcode == kPong) { 1.1477 + // opcode kPong: the mere act of receiving the packet is all we need 1.1478 + // to do for the pong to trigger the activity timers 1.1479 + LOG(("WebSocketChannel:: pong received\n")); 1.1480 + } else { 1.1481 + /* unknown control frame opcode */ 1.1482 + LOG(("WebSocketChannel:: unknown control op code %d\n", opcode)); 1.1483 + return NS_ERROR_ILLEGAL_VALUE; 1.1484 + } 1.1485 + 1.1486 + if (mFragmentAccumulator) { 1.1487 + // Remove the control frame from the stream so we have a contiguous 1.1488 + // data buffer of reassembled fragments 1.1489 + LOG(("WebSocketChannel:: Removing Control From Read buffer\n")); 1.1490 + NS_ABORT_IF_FALSE(mFramePtr + framingLength == payload, 1.1491 + "payload offset from frameptr wrong"); 1.1492 + ::memmove(mFramePtr, payload + payloadLength, avail - payloadLength); 1.1493 + payload = mFramePtr; 1.1494 + avail -= payloadLength; 1.1495 + if (mBuffered) 1.1496 + mBuffered -= framingLength + payloadLength; 1.1497 + payloadLength = 0; 1.1498 + } 1.1499 + } else if (opcode == kBinary) { 1.1500 + LOG(("WebSocketChannel:: binary frame received\n")); 1.1501 + if (mListener) { 1.1502 + nsCString binaryData((const char *)payload, payloadLength); 1.1503 + mTargetThread->Dispatch(new CallOnMessageAvailable(this, binaryData, 1.1504 + payloadLength), 1.1505 + NS_DISPATCH_NORMAL); 1.1506 + // To add the header to 'Networking Dashboard' log 1.1507 + if (mConnectionLogService && !mPrivateBrowsing) { 1.1508 + mConnectionLogService->NewMsgReceived(mHost, mSerial, count); 1.1509 + LOG(("Added new received msg for %s", mHost.get())); 1.1510 + } 1.1511 + } 1.1512 + } else if (opcode != kContinuation) { 1.1513 + /* unknown opcode */ 1.1514 + LOG(("WebSocketChannel:: unknown op code %d\n", opcode)); 1.1515 + return NS_ERROR_ILLEGAL_VALUE; 1.1516 + } 1.1517 + 1.1518 + mFramePtr = payload + payloadLength; 1.1519 + avail -= payloadLength; 1.1520 + totalAvail = avail; 1.1521 + } 1.1522 + 1.1523 + // Adjust the stateful buffer. If we were operating off the stack and 1.1524 + // now have a partial message then transition to the buffer, or if 1.1525 + // we were working off the buffer but no longer have any active state 1.1526 + // then transition to the stack 1.1527 + if (!IsPersistentFramePtr()) { 1.1528 + mBuffered = 0; 1.1529 + 1.1530 + if (mFragmentAccumulator) { 1.1531 + LOG(("WebSocketChannel:: Setup Buffer due to fragment")); 1.1532 + 1.1533 + if (!UpdateReadBuffer(mFramePtr - mFragmentAccumulator, 1.1534 + totalAvail + mFragmentAccumulator, 0, nullptr)) { 1.1535 + return NS_ERROR_FILE_TOO_BIG; 1.1536 + } 1.1537 + 1.1538 + // UpdateReadBuffer will reset the frameptr to the beginning 1.1539 + // of new saved state, so we need to skip past processed framgents 1.1540 + mFramePtr += mFragmentAccumulator; 1.1541 + } else if (totalAvail) { 1.1542 + LOG(("WebSocketChannel:: Setup Buffer due to partial frame")); 1.1543 + if (!UpdateReadBuffer(mFramePtr, totalAvail, 0, nullptr)) { 1.1544 + return NS_ERROR_FILE_TOO_BIG; 1.1545 + } 1.1546 + } 1.1547 + } else if (!mFragmentAccumulator && !totalAvail) { 1.1548 + // If we were working off a saved buffer state and there is no partial 1.1549 + // frame or fragment in process, then revert to stack behavior 1.1550 + LOG(("WebSocketChannel:: Internal buffering not needed anymore")); 1.1551 + mBuffered = 0; 1.1552 + 1.1553 + // release memory if we've been processing a large message 1.1554 + if (mBufferSize > kIncomingBufferStableSize) { 1.1555 + mBufferSize = kIncomingBufferStableSize; 1.1556 + moz_free(mBuffer); 1.1557 + mBuffer = (uint8_t *)moz_xmalloc(mBufferSize); 1.1558 + } 1.1559 + } 1.1560 + return NS_OK; 1.1561 +} 1.1562 + 1.1563 +void 1.1564 +WebSocketChannel::ApplyMask(uint32_t mask, uint8_t *data, uint64_t len) 1.1565 +{ 1.1566 + if (!data || len == 0) 1.1567 + return; 1.1568 + 1.1569 + // Optimally we want to apply the mask 32 bits at a time, 1.1570 + // but the buffer might not be alligned. So we first deal with 1.1571 + // 0 to 3 bytes of preamble individually 1.1572 + 1.1573 + while (len && (reinterpret_cast<uintptr_t>(data) & 3)) { 1.1574 + *data ^= mask >> 24; 1.1575 + mask = RotateLeft(mask, 8); 1.1576 + data++; 1.1577 + len--; 1.1578 + } 1.1579 + 1.1580 + // perform mask on full words of data 1.1581 + 1.1582 + uint32_t *iData = (uint32_t *) data; 1.1583 + uint32_t *end = iData + (len / 4); 1.1584 + NetworkEndian::writeUint32(&mask, mask); 1.1585 + for (; iData < end; iData++) 1.1586 + *iData ^= mask; 1.1587 + mask = NetworkEndian::readUint32(&mask); 1.1588 + data = (uint8_t *)iData; 1.1589 + len = len % 4; 1.1590 + 1.1591 + // There maybe up to 3 trailing bytes that need to be dealt with 1.1592 + // individually 1.1593 + 1.1594 + while (len) { 1.1595 + *data ^= mask >> 24; 1.1596 + mask = RotateLeft(mask, 8); 1.1597 + data++; 1.1598 + len--; 1.1599 + } 1.1600 +} 1.1601 + 1.1602 +void 1.1603 +WebSocketChannel::GeneratePing() 1.1604 +{ 1.1605 + nsCString *buf = new nsCString(); 1.1606 + buf->Assign("PING"); 1.1607 + EnqueueOutgoingMessage(mOutgoingPingMessages, 1.1608 + new OutboundMessage(kMsgTypePing, buf)); 1.1609 +} 1.1610 + 1.1611 +void 1.1612 +WebSocketChannel::GeneratePong(uint8_t *payload, uint32_t len) 1.1613 +{ 1.1614 + nsCString *buf = new nsCString(); 1.1615 + buf->SetLength(len); 1.1616 + if (buf->Length() < len) { 1.1617 + LOG(("WebSocketChannel::GeneratePong Allocation Failure\n")); 1.1618 + delete buf; 1.1619 + return; 1.1620 + } 1.1621 + 1.1622 + memcpy(buf->BeginWriting(), payload, len); 1.1623 + EnqueueOutgoingMessage(mOutgoingPongMessages, 1.1624 + new OutboundMessage(kMsgTypePong, buf)); 1.1625 +} 1.1626 + 1.1627 +void 1.1628 +WebSocketChannel::EnqueueOutgoingMessage(nsDeque &aQueue, 1.1629 + OutboundMessage *aMsg) 1.1630 +{ 1.1631 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread"); 1.1632 + 1.1633 + LOG(("WebSocketChannel::EnqueueOutgoingMessage %p " 1.1634 + "queueing msg %p [type=%s len=%d]\n", 1.1635 + this, aMsg, msgNames[aMsg->GetMsgType()], aMsg->Length())); 1.1636 + 1.1637 + aQueue.Push(aMsg); 1.1638 + OnOutputStreamReady(mSocketOut); 1.1639 +} 1.1640 + 1.1641 + 1.1642 +uint16_t 1.1643 +WebSocketChannel::ResultToCloseCode(nsresult resultCode) 1.1644 +{ 1.1645 + if (NS_SUCCEEDED(resultCode)) 1.1646 + return CLOSE_NORMAL; 1.1647 + 1.1648 + switch (resultCode) { 1.1649 + case NS_ERROR_FILE_TOO_BIG: 1.1650 + case NS_ERROR_OUT_OF_MEMORY: 1.1651 + return CLOSE_TOO_LARGE; 1.1652 + case NS_ERROR_CANNOT_CONVERT_DATA: 1.1653 + return CLOSE_INVALID_PAYLOAD; 1.1654 + case NS_ERROR_UNEXPECTED: 1.1655 + return CLOSE_INTERNAL_ERROR; 1.1656 + default: 1.1657 + return CLOSE_PROTOCOL_ERROR; 1.1658 + } 1.1659 +} 1.1660 + 1.1661 +void 1.1662 +WebSocketChannel::PrimeNewOutgoingMessage() 1.1663 +{ 1.1664 + LOG(("WebSocketChannel::PrimeNewOutgoingMessage() %p\n", this)); 1.1665 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread"); 1.1666 + NS_ABORT_IF_FALSE(!mCurrentOut, "Current message in progress"); 1.1667 + 1.1668 + nsresult rv = NS_OK; 1.1669 + 1.1670 + mCurrentOut = (OutboundMessage *)mOutgoingPongMessages.PopFront(); 1.1671 + if (mCurrentOut) { 1.1672 + NS_ABORT_IF_FALSE(mCurrentOut->GetMsgType() == kMsgTypePong, 1.1673 + "Not pong message!"); 1.1674 + } else { 1.1675 + mCurrentOut = (OutboundMessage *)mOutgoingPingMessages.PopFront(); 1.1676 + if (mCurrentOut) 1.1677 + NS_ABORT_IF_FALSE(mCurrentOut->GetMsgType() == kMsgTypePing, 1.1678 + "Not ping message!"); 1.1679 + else 1.1680 + mCurrentOut = (OutboundMessage *)mOutgoingMessages.PopFront(); 1.1681 + } 1.1682 + 1.1683 + if (!mCurrentOut) 1.1684 + return; 1.1685 + 1.1686 + WsMsgType msgType = mCurrentOut->GetMsgType(); 1.1687 + 1.1688 + LOG(("WebSocketChannel::PrimeNewOutgoingMessage " 1.1689 + "%p found queued msg %p [type=%s len=%d]\n", 1.1690 + this, mCurrentOut, msgNames[msgType], mCurrentOut->Length())); 1.1691 + 1.1692 + mCurrentOutSent = 0; 1.1693 + mHdrOut = mOutHeader; 1.1694 + 1.1695 + uint8_t *payload = nullptr; 1.1696 + 1.1697 + if (msgType == kMsgTypeFin) { 1.1698 + // This is a demand to create a close message 1.1699 + if (mClientClosed) { 1.1700 + DeleteCurrentOutGoingMessage(); 1.1701 + PrimeNewOutgoingMessage(); 1.1702 + return; 1.1703 + } 1.1704 + 1.1705 + mClientClosed = 1; 1.1706 + mOutHeader[0] = kFinalFragBit | kClose; 1.1707 + mOutHeader[1] = kMaskBit; 1.1708 + 1.1709 + // payload is offset 6 including 4 for the mask 1.1710 + payload = mOutHeader + 6; 1.1711 + 1.1712 + // The close reason code sits in the first 2 bytes of payload 1.1713 + // If the channel user provided a code and reason during Close() 1.1714 + // and there isn't an internal error, use that. 1.1715 + if (NS_SUCCEEDED(mStopOnClose)) { 1.1716 + if (mScriptCloseCode) { 1.1717 + NetworkEndian::writeUint16(payload, mScriptCloseCode); 1.1718 + mOutHeader[1] += 2; 1.1719 + mHdrOutToSend = 8; 1.1720 + if (!mScriptCloseReason.IsEmpty()) { 1.1721 + NS_ABORT_IF_FALSE(mScriptCloseReason.Length() <= 123, 1.1722 + "Close Reason Too Long"); 1.1723 + mOutHeader[1] += mScriptCloseReason.Length(); 1.1724 + mHdrOutToSend += mScriptCloseReason.Length(); 1.1725 + memcpy (payload + 2, 1.1726 + mScriptCloseReason.BeginReading(), 1.1727 + mScriptCloseReason.Length()); 1.1728 + } 1.1729 + } else { 1.1730 + // No close code/reason, so payload length = 0. We must still send mask 1.1731 + // even though it's not used. Keep payload offset so we write mask 1.1732 + // below. 1.1733 + mHdrOutToSend = 6; 1.1734 + } 1.1735 + } else { 1.1736 + NetworkEndian::writeUint16(payload, ResultToCloseCode(mStopOnClose)); 1.1737 + mOutHeader[1] += 2; 1.1738 + mHdrOutToSend = 8; 1.1739 + } 1.1740 + 1.1741 + if (mServerClosed) { 1.1742 + /* bidi close complete */ 1.1743 + mReleaseOnTransmit = 1; 1.1744 + } else if (NS_FAILED(mStopOnClose)) { 1.1745 + /* result of abort session - give up */ 1.1746 + StopSession(mStopOnClose); 1.1747 + } else { 1.1748 + /* wait for reciprocal close from server */ 1.1749 + mCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); 1.1750 + if (NS_SUCCEEDED(rv)) { 1.1751 + mCloseTimer->InitWithCallback(this, mCloseTimeout, 1.1752 + nsITimer::TYPE_ONE_SHOT); 1.1753 + } else { 1.1754 + StopSession(rv); 1.1755 + } 1.1756 + } 1.1757 + } else { 1.1758 + switch (msgType) { 1.1759 + case kMsgTypePong: 1.1760 + mOutHeader[0] = kFinalFragBit | kPong; 1.1761 + break; 1.1762 + case kMsgTypePing: 1.1763 + mOutHeader[0] = kFinalFragBit | kPing; 1.1764 + break; 1.1765 + case kMsgTypeString: 1.1766 + mOutHeader[0] = kFinalFragBit | kText; 1.1767 + break; 1.1768 + case kMsgTypeStream: 1.1769 + // HACK ALERT: read in entire stream into string. 1.1770 + // Will block socket transport thread if file is blocking. 1.1771 + // TODO: bug 704447: don't block socket thread! 1.1772 + rv = mCurrentOut->ConvertStreamToString(); 1.1773 + if (NS_FAILED(rv)) { 1.1774 + AbortSession(NS_ERROR_FILE_TOO_BIG); 1.1775 + return; 1.1776 + } 1.1777 + // Now we're a binary string 1.1778 + msgType = kMsgTypeBinaryString; 1.1779 + 1.1780 + // no break: fall down into binary string case 1.1781 + 1.1782 + case kMsgTypeBinaryString: 1.1783 + mOutHeader[0] = kFinalFragBit | kBinary; 1.1784 + break; 1.1785 + case kMsgTypeFin: 1.1786 + NS_ABORT_IF_FALSE(false, "unreachable"); // avoid compiler warning 1.1787 + break; 1.1788 + } 1.1789 + 1.1790 + if (mCurrentOut->Length() < 126) { 1.1791 + mOutHeader[1] = mCurrentOut->Length() | kMaskBit; 1.1792 + mHdrOutToSend = 6; 1.1793 + } else if (mCurrentOut->Length() <= 0xffff) { 1.1794 + mOutHeader[1] = 126 | kMaskBit; 1.1795 + NetworkEndian::writeUint16(mOutHeader + sizeof(uint16_t), 1.1796 + mCurrentOut->Length()); 1.1797 + mHdrOutToSend = 8; 1.1798 + } else { 1.1799 + mOutHeader[1] = 127 | kMaskBit; 1.1800 + NetworkEndian::writeUint64(mOutHeader + 2, mCurrentOut->Length()); 1.1801 + mHdrOutToSend = 14; 1.1802 + } 1.1803 + payload = mOutHeader + mHdrOutToSend; 1.1804 + } 1.1805 + 1.1806 + NS_ABORT_IF_FALSE(payload, "payload offset not found"); 1.1807 + 1.1808 + // Perform the sending mask. Never use a zero mask 1.1809 + uint32_t mask; 1.1810 + do { 1.1811 + uint8_t *buffer; 1.1812 + nsresult rv = mRandomGenerator->GenerateRandomBytes(4, &buffer); 1.1813 + if (NS_FAILED(rv)) { 1.1814 + LOG(("WebSocketChannel::PrimeNewOutgoingMessage(): " 1.1815 + "GenerateRandomBytes failure %x\n", rv)); 1.1816 + StopSession(rv); 1.1817 + return; 1.1818 + } 1.1819 + mask = * reinterpret_cast<uint32_t *>(buffer); 1.1820 + NS_Free(buffer); 1.1821 + } while (!mask); 1.1822 + NetworkEndian::writeUint32(payload - sizeof(uint32_t), mask); 1.1823 + 1.1824 + LOG(("WebSocketChannel::PrimeNewOutgoingMessage() using mask %08x\n", mask)); 1.1825 + 1.1826 + // We don't mask the framing, but occasionally we stick a little payload 1.1827 + // data in the buffer used for the framing. Close frames are the current 1.1828 + // example. This data needs to be masked, but it is never more than a 1.1829 + // handful of bytes and might rotate the mask, so we can just do it locally. 1.1830 + // For real data frames we ship the bulk of the payload off to ApplyMask() 1.1831 + 1.1832 + while (payload < (mOutHeader + mHdrOutToSend)) { 1.1833 + *payload ^= mask >> 24; 1.1834 + mask = RotateLeft(mask, 8); 1.1835 + payload++; 1.1836 + } 1.1837 + 1.1838 + // Mask the real message payloads 1.1839 + 1.1840 + ApplyMask(mask, mCurrentOut->BeginWriting(), mCurrentOut->Length()); 1.1841 + 1.1842 + int32_t len = mCurrentOut->Length(); 1.1843 + 1.1844 + // for small frames, copy it all together for a contiguous write 1.1845 + if (len && len <= kCopyBreak) { 1.1846 + memcpy(mOutHeader + mHdrOutToSend, mCurrentOut->BeginWriting(), len); 1.1847 + mHdrOutToSend += len; 1.1848 + mCurrentOutSent = len; 1.1849 + } 1.1850 + 1.1851 + if (len && mCompressor) { 1.1852 + // assume a 1/3 reduction in size for sizing the buffer 1.1853 + // the buffer is used multiple times if necessary 1.1854 + uint32_t currentHeaderSize = mHdrOutToSend; 1.1855 + mHdrOutToSend = 0; 1.1856 + 1.1857 + EnsureHdrOut(32 + (currentHeaderSize + len - mCurrentOutSent) / 2 * 3); 1.1858 + mCompressor->Deflate(mOutHeader, currentHeaderSize, 1.1859 + mCurrentOut->BeginReading() + mCurrentOutSent, 1.1860 + len - mCurrentOutSent); 1.1861 + 1.1862 + // All of the compressed data now resides in {mHdrOut, mHdrOutToSend} 1.1863 + // so do not send the body again 1.1864 + mCurrentOutSent = len; 1.1865 + } 1.1866 + 1.1867 + // Transmitting begins - mHdrOutToSend bytes from mOutHeader and 1.1868 + // mCurrentOut->Length() bytes from mCurrentOut. The latter may be 1.1869 + // coaleseced into the former for small messages or as the result of the 1.1870 + // compression process, 1.1871 +} 1.1872 + 1.1873 +void 1.1874 +WebSocketChannel::DeleteCurrentOutGoingMessage() 1.1875 +{ 1.1876 + delete mCurrentOut; 1.1877 + mCurrentOut = nullptr; 1.1878 + mCurrentOutSent = 0; 1.1879 +} 1.1880 + 1.1881 +void 1.1882 +WebSocketChannel::EnsureHdrOut(uint32_t size) 1.1883 +{ 1.1884 + LOG(("WebSocketChannel::EnsureHdrOut() %p [%d]\n", this, size)); 1.1885 + 1.1886 + if (mDynamicOutputSize < size) { 1.1887 + mDynamicOutputSize = size; 1.1888 + mDynamicOutput = 1.1889 + (uint8_t *) moz_xrealloc(mDynamicOutput, mDynamicOutputSize); 1.1890 + } 1.1891 + 1.1892 + mHdrOut = mDynamicOutput; 1.1893 +} 1.1894 + 1.1895 +void 1.1896 +WebSocketChannel::CleanupConnection() 1.1897 +{ 1.1898 + LOG(("WebSocketChannel::CleanupConnection() %p", this)); 1.1899 + 1.1900 + if (mLingeringCloseTimer) { 1.1901 + mLingeringCloseTimer->Cancel(); 1.1902 + mLingeringCloseTimer = nullptr; 1.1903 + } 1.1904 + 1.1905 + if (mSocketIn) { 1.1906 + mSocketIn->AsyncWait(nullptr, 0, 0, nullptr); 1.1907 + mSocketIn = nullptr; 1.1908 + } 1.1909 + 1.1910 + if (mSocketOut) { 1.1911 + mSocketOut->AsyncWait(nullptr, 0, 0, nullptr); 1.1912 + mSocketOut = nullptr; 1.1913 + } 1.1914 + 1.1915 + if (mTransport) { 1.1916 + mTransport->SetSecurityCallbacks(nullptr); 1.1917 + mTransport->SetEventSink(nullptr, nullptr); 1.1918 + mTransport->Close(NS_BASE_STREAM_CLOSED); 1.1919 + mTransport = nullptr; 1.1920 + } 1.1921 + 1.1922 + if (mConnectionLogService && !mPrivateBrowsing) { 1.1923 + mConnectionLogService->RemoveHost(mHost, mSerial); 1.1924 + } 1.1925 + 1.1926 + DecrementSessionCount(); 1.1927 +} 1.1928 + 1.1929 +void 1.1930 +WebSocketChannel::StopSession(nsresult reason) 1.1931 +{ 1.1932 + LOG(("WebSocketChannel::StopSession() %p [%x]\n", this, reason)); 1.1933 + 1.1934 + // normally this should be called on socket thread, but it is ok to call it 1.1935 + // from OnStartRequest before the socket thread machine has gotten underway 1.1936 + 1.1937 + mStopped = 1; 1.1938 + 1.1939 + if (!mOpenedHttpChannel) { 1.1940 + // The HTTP channel information will never be used in this case 1.1941 + mChannel = nullptr; 1.1942 + mHttpChannel = nullptr; 1.1943 + mLoadGroup = nullptr; 1.1944 + mCallbacks = nullptr; 1.1945 + } 1.1946 + 1.1947 + if (mCloseTimer) { 1.1948 + mCloseTimer->Cancel(); 1.1949 + mCloseTimer = nullptr; 1.1950 + } 1.1951 + 1.1952 + if (mOpenTimer) { 1.1953 + mOpenTimer->Cancel(); 1.1954 + mOpenTimer = nullptr; 1.1955 + } 1.1956 + 1.1957 + if (mReconnectDelayTimer) { 1.1958 + mReconnectDelayTimer->Cancel(); 1.1959 + mReconnectDelayTimer = nullptr; 1.1960 + } 1.1961 + 1.1962 + if (mPingTimer) { 1.1963 + mPingTimer->Cancel(); 1.1964 + mPingTimer = nullptr; 1.1965 + } 1.1966 + 1.1967 + if (mSocketIn && !mTCPClosed) { 1.1968 + // Drain, within reason, this socket. if we leave any data 1.1969 + // unconsumed (including the tcp fin) a RST will be generated 1.1970 + // The right thing to do here is shutdown(SHUT_WR) and then wait 1.1971 + // a little while to see if any data comes in.. but there is no 1.1972 + // reason to delay things for that when the websocket handshake 1.1973 + // is supposed to guarantee a quiet connection except for that fin. 1.1974 + 1.1975 + char buffer[512]; 1.1976 + uint32_t count = 0; 1.1977 + uint32_t total = 0; 1.1978 + nsresult rv; 1.1979 + do { 1.1980 + total += count; 1.1981 + rv = mSocketIn->Read(buffer, 512, &count); 1.1982 + if (rv != NS_BASE_STREAM_WOULD_BLOCK && 1.1983 + (NS_FAILED(rv) || count == 0)) 1.1984 + mTCPClosed = true; 1.1985 + } while (NS_SUCCEEDED(rv) && count > 0 && total < 32000); 1.1986 + } 1.1987 + 1.1988 + int32_t sessionCount = kLingeringCloseThreshold; 1.1989 + nsWSAdmissionManager::GetSessionCount(sessionCount); 1.1990 + 1.1991 + if (!mTCPClosed && mTransport && sessionCount < kLingeringCloseThreshold) { 1.1992 + 1.1993 + // 7.1.1 says that the client SHOULD wait for the server to close the TCP 1.1994 + // connection. This is so we can reuse port numbers before 2 MSL expires, 1.1995 + // which is not really as much of a concern for us as the amount of state 1.1996 + // that might be accrued by keeping this channel object around waiting for 1.1997 + // the server. We handle the SHOULD by waiting a short time in the common 1.1998 + // case, but not waiting in the case of high concurrency. 1.1999 + // 1.2000 + // Normally this will be taken care of in AbortSession() after mTCPClosed 1.2001 + // is set when the server close arrives without waiting for the timeout to 1.2002 + // expire. 1.2003 + 1.2004 + LOG(("WebSocketChannel::StopSession: Wait for Server TCP close")); 1.2005 + 1.2006 + nsresult rv; 1.2007 + mLingeringCloseTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); 1.2008 + if (NS_SUCCEEDED(rv)) 1.2009 + mLingeringCloseTimer->InitWithCallback(this, kLingeringCloseTimeout, 1.2010 + nsITimer::TYPE_ONE_SHOT); 1.2011 + else 1.2012 + CleanupConnection(); 1.2013 + } else { 1.2014 + CleanupConnection(); 1.2015 + } 1.2016 + 1.2017 + if (mCancelable) { 1.2018 + mCancelable->Cancel(NS_ERROR_UNEXPECTED); 1.2019 + mCancelable = nullptr; 1.2020 + } 1.2021 + 1.2022 + mInflateReader = nullptr; 1.2023 + mInflateStream = nullptr; 1.2024 + 1.2025 + delete mCompressor; 1.2026 + mCompressor = nullptr; 1.2027 + 1.2028 + if (!mCalledOnStop) { 1.2029 + mCalledOnStop = 1; 1.2030 + mTargetThread->Dispatch(new CallOnStop(this, reason), 1.2031 + NS_DISPATCH_NORMAL); 1.2032 + } 1.2033 + 1.2034 + return; 1.2035 +} 1.2036 + 1.2037 +void 1.2038 +WebSocketChannel::AbortSession(nsresult reason) 1.2039 +{ 1.2040 + LOG(("WebSocketChannel::AbortSession() %p [reason %x] stopped = %d\n", 1.2041 + this, reason, mStopped)); 1.2042 + 1.2043 + // normally this should be called on socket thread, but it is ok to call it 1.2044 + // from the main thread before StartWebsocketData() has completed 1.2045 + 1.2046 + // When we are failing we need to close the TCP connection immediately 1.2047 + // as per 7.1.1 1.2048 + mTCPClosed = true; 1.2049 + 1.2050 + if (mLingeringCloseTimer) { 1.2051 + NS_ABORT_IF_FALSE(mStopped, "Lingering without Stop"); 1.2052 + LOG(("WebSocketChannel:: Cleanup connection based on TCP Close")); 1.2053 + CleanupConnection(); 1.2054 + return; 1.2055 + } 1.2056 + 1.2057 + if (mStopped) 1.2058 + return; 1.2059 + mStopped = 1; 1.2060 + 1.2061 + if (mTransport && reason != NS_BASE_STREAM_CLOSED && 1.2062 + !mRequestedClose && !mClientClosed && !mServerClosed) { 1.2063 + mRequestedClose = 1; 1.2064 + mStopOnClose = reason; 1.2065 + mSocketThread->Dispatch( 1.2066 + new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)), 1.2067 + nsIEventTarget::DISPATCH_NORMAL); 1.2068 + } else { 1.2069 + StopSession(reason); 1.2070 + } 1.2071 +} 1.2072 + 1.2073 +// ReleaseSession is called on orderly shutdown 1.2074 +void 1.2075 +WebSocketChannel::ReleaseSession() 1.2076 +{ 1.2077 + LOG(("WebSocketChannel::ReleaseSession() %p stopped = %d\n", 1.2078 + this, mStopped)); 1.2079 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread"); 1.2080 + 1.2081 + if (mStopped) 1.2082 + return; 1.2083 + StopSession(NS_OK); 1.2084 +} 1.2085 + 1.2086 +void 1.2087 +WebSocketChannel::IncrementSessionCount() 1.2088 +{ 1.2089 + if (!mIncrementedSessionCount) { 1.2090 + nsWSAdmissionManager::IncrementSessionCount(); 1.2091 + mIncrementedSessionCount = 1; 1.2092 + } 1.2093 +} 1.2094 + 1.2095 +void 1.2096 +WebSocketChannel::DecrementSessionCount() 1.2097 +{ 1.2098 + // Make sure we decrement session count only once, and only if we incremented it. 1.2099 + // This code is thread-safe: sWebSocketAdmissions->DecrementSessionCount is 1.2100 + // atomic, and mIncrementedSessionCount/mDecrementedSessionCount are set at 1.2101 + // times when they'll never be a race condition for checking/setting them. 1.2102 + if (mIncrementedSessionCount && !mDecrementedSessionCount) { 1.2103 + nsWSAdmissionManager::DecrementSessionCount(); 1.2104 + mDecrementedSessionCount = 1; 1.2105 + } 1.2106 +} 1.2107 + 1.2108 +nsresult 1.2109 +WebSocketChannel::HandleExtensions() 1.2110 +{ 1.2111 + LOG(("WebSocketChannel::HandleExtensions() %p\n", this)); 1.2112 + 1.2113 + nsresult rv; 1.2114 + nsAutoCString extensions; 1.2115 + 1.2116 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.2117 + 1.2118 + rv = mHttpChannel->GetResponseHeader( 1.2119 + NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), extensions); 1.2120 + if (NS_SUCCEEDED(rv)) { 1.2121 + if (!extensions.IsEmpty()) { 1.2122 + if (!extensions.Equals(NS_LITERAL_CSTRING("deflate-stream"))) { 1.2123 + LOG(("WebSocketChannel::OnStartRequest: " 1.2124 + "HTTP Sec-WebSocket-Exensions negotiated unknown value %s\n", 1.2125 + extensions.get())); 1.2126 + AbortSession(NS_ERROR_ILLEGAL_VALUE); 1.2127 + return NS_ERROR_ILLEGAL_VALUE; 1.2128 + } 1.2129 + 1.2130 + if (!mAllowCompression) { 1.2131 + LOG(("WebSocketChannel::HandleExtensions: " 1.2132 + "Recvd Compression Extension that wasn't offered\n")); 1.2133 + AbortSession(NS_ERROR_ILLEGAL_VALUE); 1.2134 + return NS_ERROR_ILLEGAL_VALUE; 1.2135 + } 1.2136 + 1.2137 + nsCOMPtr<nsIStreamConverterService> serv = 1.2138 + do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); 1.2139 + if (NS_FAILED(rv)) { 1.2140 + LOG(("WebSocketChannel:: Cannot find compression service\n")); 1.2141 + AbortSession(NS_ERROR_UNEXPECTED); 1.2142 + return NS_ERROR_UNEXPECTED; 1.2143 + } 1.2144 + 1.2145 + rv = serv->AsyncConvertData("deflate", "uncompressed", this, nullptr, 1.2146 + getter_AddRefs(mInflateReader)); 1.2147 + 1.2148 + if (NS_FAILED(rv)) { 1.2149 + LOG(("WebSocketChannel:: Cannot find inflate listener\n")); 1.2150 + AbortSession(NS_ERROR_UNEXPECTED); 1.2151 + return NS_ERROR_UNEXPECTED; 1.2152 + } 1.2153 + 1.2154 + mInflateStream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); 1.2155 + 1.2156 + if (NS_FAILED(rv)) { 1.2157 + LOG(("WebSocketChannel:: Cannot find inflate stream\n")); 1.2158 + AbortSession(NS_ERROR_UNEXPECTED); 1.2159 + return NS_ERROR_UNEXPECTED; 1.2160 + } 1.2161 + 1.2162 + mCompressor = new nsWSCompression(this, mSocketOut); 1.2163 + if (!mCompressor->Active()) { 1.2164 + LOG(("WebSocketChannel:: Cannot init deflate object\n")); 1.2165 + delete mCompressor; 1.2166 + mCompressor = nullptr; 1.2167 + AbortSession(NS_ERROR_UNEXPECTED); 1.2168 + return NS_ERROR_UNEXPECTED; 1.2169 + } 1.2170 + mNegotiatedExtensions = extensions; 1.2171 + } 1.2172 + } 1.2173 + 1.2174 + return NS_OK; 1.2175 +} 1.2176 + 1.2177 +nsresult 1.2178 +WebSocketChannel::SetupRequest() 1.2179 +{ 1.2180 + LOG(("WebSocketChannel::SetupRequest() %p\n", this)); 1.2181 + 1.2182 + nsresult rv; 1.2183 + 1.2184 + if (mLoadGroup) { 1.2185 + rv = mHttpChannel->SetLoadGroup(mLoadGroup); 1.2186 + NS_ENSURE_SUCCESS(rv, rv); 1.2187 + } 1.2188 + 1.2189 + rv = mHttpChannel->SetLoadFlags(nsIRequest::LOAD_BACKGROUND | 1.2190 + nsIRequest::INHIBIT_CACHING | 1.2191 + nsIRequest::LOAD_BYPASS_CACHE); 1.2192 + NS_ENSURE_SUCCESS(rv, rv); 1.2193 + 1.2194 + // we never let websockets be blocked by head CSS/JS loads to avoid 1.2195 + // potential deadlock where server generation of CSS/JS requires 1.2196 + // an XHR signal. 1.2197 + rv = mChannel->SetLoadUnblocked(true); 1.2198 + NS_ENSURE_SUCCESS(rv, rv); 1.2199 + 1.2200 + // draft-ietf-hybi-thewebsocketprotocol-07 illustrates Upgrade: websocket 1.2201 + // in lower case, so go with that. It is technically case insensitive. 1.2202 + rv = mChannel->HTTPUpgrade(NS_LITERAL_CSTRING("websocket"), this); 1.2203 + NS_ENSURE_SUCCESS(rv, rv); 1.2204 + 1.2205 + mHttpChannel->SetRequestHeader( 1.2206 + NS_LITERAL_CSTRING("Sec-WebSocket-Version"), 1.2207 + NS_LITERAL_CSTRING(SEC_WEBSOCKET_VERSION), false); 1.2208 + 1.2209 + if (!mOrigin.IsEmpty()) 1.2210 + mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Origin"), mOrigin, 1.2211 + false); 1.2212 + 1.2213 + if (!mProtocol.IsEmpty()) 1.2214 + mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), 1.2215 + mProtocol, true); 1.2216 + 1.2217 + if (mAllowCompression) 1.2218 + mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Extensions"), 1.2219 + NS_LITERAL_CSTRING("deflate-stream"), 1.2220 + false); 1.2221 + 1.2222 + uint8_t *secKey; 1.2223 + nsAutoCString secKeyString; 1.2224 + 1.2225 + rv = mRandomGenerator->GenerateRandomBytes(16, &secKey); 1.2226 + NS_ENSURE_SUCCESS(rv, rv); 1.2227 + char* b64 = PL_Base64Encode((const char *)secKey, 16, nullptr); 1.2228 + NS_Free(secKey); 1.2229 + if (!b64) 1.2230 + return NS_ERROR_OUT_OF_MEMORY; 1.2231 + secKeyString.Assign(b64); 1.2232 + PR_Free(b64); 1.2233 + mHttpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Sec-WebSocket-Key"), 1.2234 + secKeyString, false); 1.2235 + LOG(("WebSocketChannel::SetupRequest: client key %s\n", secKeyString.get())); 1.2236 + 1.2237 + // prepare the value we expect to see in 1.2238 + // the sec-websocket-accept response header 1.2239 + secKeyString.AppendLiteral("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); 1.2240 + nsCOMPtr<nsICryptoHash> hasher = 1.2241 + do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); 1.2242 + NS_ENSURE_SUCCESS(rv, rv); 1.2243 + rv = hasher->Init(nsICryptoHash::SHA1); 1.2244 + NS_ENSURE_SUCCESS(rv, rv); 1.2245 + rv = hasher->Update((const uint8_t *) secKeyString.BeginWriting(), 1.2246 + secKeyString.Length()); 1.2247 + NS_ENSURE_SUCCESS(rv, rv); 1.2248 + rv = hasher->Finish(true, mHashedSecret); 1.2249 + NS_ENSURE_SUCCESS(rv, rv); 1.2250 + LOG(("WebSocketChannel::SetupRequest: expected server key %s\n", 1.2251 + mHashedSecret.get())); 1.2252 + 1.2253 + return NS_OK; 1.2254 +} 1.2255 + 1.2256 +nsresult 1.2257 +WebSocketChannel::DoAdmissionDNS() 1.2258 +{ 1.2259 + nsresult rv; 1.2260 + 1.2261 + nsCString hostName; 1.2262 + rv = mURI->GetHost(hostName); 1.2263 + NS_ENSURE_SUCCESS(rv, rv); 1.2264 + mAddress = hostName; 1.2265 + rv = mURI->GetPort(&mPort); 1.2266 + NS_ENSURE_SUCCESS(rv, rv); 1.2267 + if (mPort == -1) 1.2268 + mPort = (mEncrypted ? kDefaultWSSPort : kDefaultWSPort); 1.2269 + nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv); 1.2270 + NS_ENSURE_SUCCESS(rv, rv); 1.2271 + nsCOMPtr<nsIThread> mainThread; 1.2272 + NS_GetMainThread(getter_AddRefs(mainThread)); 1.2273 + MOZ_ASSERT(!mCancelable); 1.2274 + return dns->AsyncResolve(hostName, 0, this, mainThread, getter_AddRefs(mCancelable)); 1.2275 +} 1.2276 + 1.2277 +nsresult 1.2278 +WebSocketChannel::ApplyForAdmission() 1.2279 +{ 1.2280 + LOG(("WebSocketChannel::ApplyForAdmission() %p\n", this)); 1.2281 + 1.2282 + // Websockets has a policy of 1 session at a time being allowed in the 1.2283 + // CONNECTING state per server IP address (not hostname) 1.2284 + 1.2285 + // Check to see if a proxy is being used before making DNS call 1.2286 + nsCOMPtr<nsIProtocolProxyService> pps = 1.2287 + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); 1.2288 + 1.2289 + if (!pps) { 1.2290 + // go straight to DNS 1.2291 + // expect the callback in ::OnLookupComplete 1.2292 + LOG(("WebSocketChannel::ApplyForAdmission: checking for concurrent open\n")); 1.2293 + return DoAdmissionDNS(); 1.2294 + } 1.2295 + 1.2296 + MOZ_ASSERT(!mCancelable); 1.2297 + 1.2298 + return pps->AsyncResolve(mHttpChannel, 1.2299 + nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | 1.2300 + nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, 1.2301 + this, getter_AddRefs(mCancelable)); 1.2302 +} 1.2303 + 1.2304 +// Called after both OnStartRequest and OnTransportAvailable have 1.2305 +// executed. This essentially ends the handshake and starts the websockets 1.2306 +// protocol state machine. 1.2307 +nsresult 1.2308 +WebSocketChannel::StartWebsocketData() 1.2309 +{ 1.2310 + LOG(("WebSocketChannel::StartWebsocketData() %p", this)); 1.2311 + NS_ABORT_IF_FALSE(!mDataStarted, "StartWebsocketData twice"); 1.2312 + mDataStarted = 1; 1.2313 + 1.2314 + // We're now done CONNECTING, which means we can now open another, 1.2315 + // perhaps parallel, connection to the same host if one 1.2316 + // is pending 1.2317 + nsWSAdmissionManager::OnConnected(this); 1.2318 + 1.2319 + LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p\n", 1.2320 + mListener.get())); 1.2321 + 1.2322 + if (mListener) 1.2323 + mListener->OnStart(mContext); 1.2324 + 1.2325 + // Start keepalive ping timer, if we're using keepalive. 1.2326 + if (mPingInterval) { 1.2327 + nsresult rv; 1.2328 + mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); 1.2329 + if (NS_FAILED(rv)) { 1.2330 + NS_WARNING("unable to create ping timer. Carrying on."); 1.2331 + } else { 1.2332 + LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n", 1.2333 + mPingInterval)); 1.2334 + mPingTimer->SetTarget(mSocketThread); 1.2335 + mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT); 1.2336 + } 1.2337 + } 1.2338 + 1.2339 + return mSocketIn->AsyncWait(this, 0, 0, mSocketThread); 1.2340 +} 1.2341 + 1.2342 +void 1.2343 +WebSocketChannel::ReportConnectionTelemetry() 1.2344 +{ 1.2345 + // 3 bits are used. high bit is for wss, middle bit for failed, 1.2346 + // and low bit for proxy.. 1.2347 + // 0 - 7 : ws-ok-plain, ws-ok-proxy, ws-failed-plain, ws-failed-proxy, 1.2348 + // wss-ok-plain, wss-ok-proxy, wss-failed-plain, wss-failed-proxy 1.2349 + 1.2350 + bool didProxy = false; 1.2351 + 1.2352 + nsCOMPtr<nsIProxyInfo> pi; 1.2353 + nsCOMPtr<nsIProxiedChannel> pc = do_QueryInterface(mChannel); 1.2354 + if (pc) 1.2355 + pc->GetProxyInfo(getter_AddRefs(pi)); 1.2356 + if (pi) { 1.2357 + nsAutoCString proxyType; 1.2358 + pi->GetType(proxyType); 1.2359 + if (!proxyType.IsEmpty() && 1.2360 + !proxyType.Equals(NS_LITERAL_CSTRING("direct"))) 1.2361 + didProxy = true; 1.2362 + } 1.2363 + 1.2364 + uint8_t value = (mEncrypted ? (1 << 2) : 0) | 1.2365 + (!mGotUpgradeOK ? (1 << 1) : 0) | 1.2366 + (didProxy ? (1 << 0) : 0); 1.2367 + 1.2368 + LOG(("WebSocketChannel::ReportConnectionTelemetry() %p %d", this, value)); 1.2369 + Telemetry::Accumulate(Telemetry::WEBSOCKETS_HANDSHAKE_TYPE, value); 1.2370 +} 1.2371 + 1.2372 +// nsIDNSListener 1.2373 + 1.2374 +NS_IMETHODIMP 1.2375 +WebSocketChannel::OnLookupComplete(nsICancelable *aRequest, 1.2376 + nsIDNSRecord *aRecord, 1.2377 + nsresult aStatus) 1.2378 +{ 1.2379 + LOG(("WebSocketChannel::OnLookupComplete() %p [%p %p %x]\n", 1.2380 + this, aRequest, aRecord, aStatus)); 1.2381 + 1.2382 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.2383 + 1.2384 + if (mStopped) { 1.2385 + LOG(("WebSocketChannel::OnLookupComplete: Request Already Stopped\n")); 1.2386 + mCancelable = nullptr; 1.2387 + return NS_OK; 1.2388 + } 1.2389 + 1.2390 + mCancelable = nullptr; 1.2391 + 1.2392 + // These failures are not fatal - we just use the hostname as the key 1.2393 + if (NS_FAILED(aStatus)) { 1.2394 + LOG(("WebSocketChannel::OnLookupComplete: No DNS Response\n")); 1.2395 + 1.2396 + // set host in case we got here without calling DoAdmissionDNS() 1.2397 + mURI->GetHost(mAddress); 1.2398 + } else { 1.2399 + nsresult rv = aRecord->GetNextAddrAsString(mAddress); 1.2400 + if (NS_FAILED(rv)) 1.2401 + LOG(("WebSocketChannel::OnLookupComplete: Failed GetNextAddr\n")); 1.2402 + } 1.2403 + 1.2404 + LOG(("WebSocket OnLookupComplete: Proceeding to ConditionallyConnect\n")); 1.2405 + nsWSAdmissionManager::ConditionallyConnect(this); 1.2406 + 1.2407 + return NS_OK; 1.2408 +} 1.2409 + 1.2410 +// nsIProtocolProxyCallback 1.2411 +NS_IMETHODIMP 1.2412 +WebSocketChannel::OnProxyAvailable(nsICancelable *aRequest, nsIChannel *aChannel, 1.2413 + nsIProxyInfo *pi, nsresult status) 1.2414 +{ 1.2415 + if (mStopped) { 1.2416 + LOG(("WebSocketChannel::OnProxyAvailable: [%p] Request Already Stopped\n", this)); 1.2417 + mCancelable = nullptr; 1.2418 + return NS_OK; 1.2419 + } 1.2420 + 1.2421 + MOZ_ASSERT(aRequest == mCancelable); 1.2422 + mCancelable = nullptr; 1.2423 + 1.2424 + nsAutoCString type; 1.2425 + if (NS_SUCCEEDED(status) && pi && 1.2426 + NS_SUCCEEDED(pi->GetType(type)) && 1.2427 + !type.EqualsLiteral("direct")) { 1.2428 + LOG(("WebSocket OnProxyAvailable [%p] Proxy found skip DNS lookup\n", this)); 1.2429 + // call DNS callback directly without DNS resolver 1.2430 + OnLookupComplete(nullptr, nullptr, NS_ERROR_FAILURE); 1.2431 + return NS_OK; 1.2432 + } 1.2433 + 1.2434 + LOG(("WebSocketChannel::OnProxyAvailable[%] checking DNS resolution\n", this)); 1.2435 + DoAdmissionDNS(); 1.2436 + return NS_OK; 1.2437 +} 1.2438 + 1.2439 +// nsIInterfaceRequestor 1.2440 + 1.2441 +NS_IMETHODIMP 1.2442 +WebSocketChannel::GetInterface(const nsIID & iid, void **result) 1.2443 +{ 1.2444 + LOG(("WebSocketChannel::GetInterface() %p\n", this)); 1.2445 + 1.2446 + if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) 1.2447 + return QueryInterface(iid, result); 1.2448 + 1.2449 + if (mCallbacks) 1.2450 + return mCallbacks->GetInterface(iid, result); 1.2451 + 1.2452 + return NS_ERROR_FAILURE; 1.2453 +} 1.2454 + 1.2455 +// nsIChannelEventSink 1.2456 + 1.2457 +NS_IMETHODIMP 1.2458 +WebSocketChannel::AsyncOnChannelRedirect( 1.2459 + nsIChannel *oldChannel, 1.2460 + nsIChannel *newChannel, 1.2461 + uint32_t flags, 1.2462 + nsIAsyncVerifyRedirectCallback *callback) 1.2463 +{ 1.2464 + LOG(("WebSocketChannel::AsyncOnChannelRedirect() %p\n", this)); 1.2465 + nsresult rv; 1.2466 + 1.2467 + nsCOMPtr<nsIURI> newuri; 1.2468 + rv = newChannel->GetURI(getter_AddRefs(newuri)); 1.2469 + NS_ENSURE_SUCCESS(rv, rv); 1.2470 + 1.2471 + // newuri is expected to be http or https 1.2472 + bool newuriIsHttps = false; 1.2473 + rv = newuri->SchemeIs("https", &newuriIsHttps); 1.2474 + NS_ENSURE_SUCCESS(rv, rv); 1.2475 + 1.2476 + if (!mAutoFollowRedirects) { 1.2477 + // Even if redirects configured off, still allow them for HTTP Strict 1.2478 + // Transport Security (from ws://FOO to https://FOO (mapped to wss://FOO) 1.2479 + 1.2480 + nsCOMPtr<nsIURI> clonedNewURI; 1.2481 + rv = newuri->Clone(getter_AddRefs(clonedNewURI)); 1.2482 + NS_ENSURE_SUCCESS(rv, rv); 1.2483 + 1.2484 + rv = clonedNewURI->SetScheme(NS_LITERAL_CSTRING("ws")); 1.2485 + NS_ENSURE_SUCCESS(rv, rv); 1.2486 + 1.2487 + nsCOMPtr<nsIURI> currentURI; 1.2488 + rv = GetURI(getter_AddRefs(currentURI)); 1.2489 + NS_ENSURE_SUCCESS(rv, rv); 1.2490 + 1.2491 + // currentURI is expected to be ws or wss 1.2492 + bool currentIsHttps = false; 1.2493 + rv = currentURI->SchemeIs("wss", ¤tIsHttps); 1.2494 + NS_ENSURE_SUCCESS(rv, rv); 1.2495 + 1.2496 + bool uriEqual = false; 1.2497 + rv = clonedNewURI->Equals(currentURI, &uriEqual); 1.2498 + NS_ENSURE_SUCCESS(rv, rv); 1.2499 + 1.2500 + // It's only a HSTS redirect if we started with non-secure, are going to 1.2501 + // secure, and the new URI is otherwise the same as the old one. 1.2502 + if (!(!currentIsHttps && newuriIsHttps && uriEqual)) { 1.2503 + nsAutoCString newSpec; 1.2504 + rv = newuri->GetSpec(newSpec); 1.2505 + NS_ENSURE_SUCCESS(rv, rv); 1.2506 + 1.2507 + LOG(("WebSocketChannel: Redirect to %s denied by configuration\n", 1.2508 + newSpec.get())); 1.2509 + return NS_ERROR_FAILURE; 1.2510 + } 1.2511 + } 1.2512 + 1.2513 + if (mEncrypted && !newuriIsHttps) { 1.2514 + nsAutoCString spec; 1.2515 + if (NS_SUCCEEDED(newuri->GetSpec(spec))) 1.2516 + LOG(("WebSocketChannel: Redirect to %s violates encryption rule\n", 1.2517 + spec.get())); 1.2518 + return NS_ERROR_FAILURE; 1.2519 + } 1.2520 + 1.2521 + nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel, &rv); 1.2522 + if (NS_FAILED(rv)) { 1.2523 + LOG(("WebSocketChannel: Redirect could not QI to HTTP\n")); 1.2524 + return rv; 1.2525 + } 1.2526 + 1.2527 + nsCOMPtr<nsIHttpChannelInternal> newUpgradeChannel = 1.2528 + do_QueryInterface(newChannel, &rv); 1.2529 + 1.2530 + if (NS_FAILED(rv)) { 1.2531 + LOG(("WebSocketChannel: Redirect could not QI to HTTP Upgrade\n")); 1.2532 + return rv; 1.2533 + } 1.2534 + 1.2535 + // The redirect is likely OK 1.2536 + 1.2537 + newChannel->SetNotificationCallbacks(this); 1.2538 + 1.2539 + mEncrypted = newuriIsHttps; 1.2540 + newuri->Clone(getter_AddRefs(mURI)); 1.2541 + if (mEncrypted) 1.2542 + rv = mURI->SetScheme(NS_LITERAL_CSTRING("wss")); 1.2543 + else 1.2544 + rv = mURI->SetScheme(NS_LITERAL_CSTRING("ws")); 1.2545 + 1.2546 + mHttpChannel = newHttpChannel; 1.2547 + mChannel = newUpgradeChannel; 1.2548 + rv = SetupRequest(); 1.2549 + if (NS_FAILED(rv)) { 1.2550 + LOG(("WebSocketChannel: Redirect could not SetupRequest()\n")); 1.2551 + return rv; 1.2552 + } 1.2553 + 1.2554 + // Redirected-to URI may need to be delayed by 1-connecting-per-host and 1.2555 + // delay-after-fail algorithms. So hold off calling OnRedirectVerifyCallback 1.2556 + // until BeginOpen, when we know it's OK to proceed with new channel. 1.2557 + mRedirectCallback = callback; 1.2558 + 1.2559 + // Mark old channel as successfully connected so we'll clear any FailDelay 1.2560 + // associated with the old URI. Note: no need to also call OnStopSession: 1.2561 + // it's a no-op for successful, already-connected channels. 1.2562 + nsWSAdmissionManager::OnConnected(this); 1.2563 + 1.2564 + // ApplyForAdmission as if we were starting from fresh... 1.2565 + mAddress.Truncate(); 1.2566 + mOpenedHttpChannel = 0; 1.2567 + rv = ApplyForAdmission(); 1.2568 + if (NS_FAILED(rv)) { 1.2569 + LOG(("WebSocketChannel: Redirect failed due to DNS failure\n")); 1.2570 + mRedirectCallback = nullptr; 1.2571 + return rv; 1.2572 + } 1.2573 + 1.2574 + return NS_OK; 1.2575 +} 1.2576 + 1.2577 +// nsITimerCallback 1.2578 + 1.2579 +NS_IMETHODIMP 1.2580 +WebSocketChannel::Notify(nsITimer *timer) 1.2581 +{ 1.2582 + LOG(("WebSocketChannel::Notify() %p [%p]\n", this, timer)); 1.2583 + 1.2584 + if (timer == mCloseTimer) { 1.2585 + NS_ABORT_IF_FALSE(mClientClosed, "Close Timeout without local close"); 1.2586 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, 1.2587 + "not socket thread"); 1.2588 + 1.2589 + mCloseTimer = nullptr; 1.2590 + if (mStopped || mServerClosed) /* no longer relevant */ 1.2591 + return NS_OK; 1.2592 + 1.2593 + LOG(("WebSocketChannel:: Expecting Server Close - Timed Out\n")); 1.2594 + AbortSession(NS_ERROR_NET_TIMEOUT); 1.2595 + } else if (timer == mOpenTimer) { 1.2596 + NS_ABORT_IF_FALSE(!mGotUpgradeOK, 1.2597 + "Open Timer after open complete"); 1.2598 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.2599 + 1.2600 + mOpenTimer = nullptr; 1.2601 + LOG(("WebSocketChannel:: Connection Timed Out\n")); 1.2602 + if (mStopped || mServerClosed) /* no longer relevant */ 1.2603 + return NS_OK; 1.2604 + 1.2605 + AbortSession(NS_ERROR_NET_TIMEOUT); 1.2606 + } else if (timer == mReconnectDelayTimer) { 1.2607 + NS_ABORT_IF_FALSE(mConnecting == CONNECTING_DELAYED, 1.2608 + "woke up from delay w/o being delayed?"); 1.2609 + 1.2610 + mReconnectDelayTimer = nullptr; 1.2611 + LOG(("WebSocketChannel: connecting [this=%p] after reconnect delay", this)); 1.2612 + BeginOpen(); 1.2613 + } else if (timer == mPingTimer) { 1.2614 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, 1.2615 + "not socket thread"); 1.2616 + 1.2617 + if (mClientClosed || mServerClosed || mRequestedClose) { 1.2618 + // no point in worrying about ping now 1.2619 + mPingTimer = nullptr; 1.2620 + return NS_OK; 1.2621 + } 1.2622 + 1.2623 + if (!mPingOutstanding) { 1.2624 + LOG(("nsWebSocketChannel:: Generating Ping\n")); 1.2625 + mPingOutstanding = 1; 1.2626 + GeneratePing(); 1.2627 + mPingTimer->InitWithCallback(this, mPingResponseTimeout, 1.2628 + nsITimer::TYPE_ONE_SHOT); 1.2629 + } else { 1.2630 + LOG(("nsWebSocketChannel:: Timed out Ping\n")); 1.2631 + mPingTimer = nullptr; 1.2632 + AbortSession(NS_ERROR_NET_TIMEOUT); 1.2633 + } 1.2634 + } else if (timer == mLingeringCloseTimer) { 1.2635 + LOG(("WebSocketChannel:: Lingering Close Timer")); 1.2636 + CleanupConnection(); 1.2637 + } else { 1.2638 + NS_ABORT_IF_FALSE(0, "Unknown Timer"); 1.2639 + } 1.2640 + 1.2641 + return NS_OK; 1.2642 +} 1.2643 + 1.2644 +// nsIWebSocketChannel 1.2645 + 1.2646 +NS_IMETHODIMP 1.2647 +WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo) 1.2648 +{ 1.2649 + LOG(("WebSocketChannel::GetSecurityInfo() %p\n", this)); 1.2650 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.2651 + 1.2652 + if (mTransport) { 1.2653 + if (NS_FAILED(mTransport->GetSecurityInfo(aSecurityInfo))) 1.2654 + *aSecurityInfo = nullptr; 1.2655 + } 1.2656 + return NS_OK; 1.2657 +} 1.2658 + 1.2659 + 1.2660 +NS_IMETHODIMP 1.2661 +WebSocketChannel::AsyncOpen(nsIURI *aURI, 1.2662 + const nsACString &aOrigin, 1.2663 + nsIWebSocketListener *aListener, 1.2664 + nsISupports *aContext) 1.2665 +{ 1.2666 + LOG(("WebSocketChannel::AsyncOpen() %p\n", this)); 1.2667 + 1.2668 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.2669 + 1.2670 + if (!aURI || !aListener) { 1.2671 + LOG(("WebSocketChannel::AsyncOpen() Uri or Listener null")); 1.2672 + return NS_ERROR_UNEXPECTED; 1.2673 + } 1.2674 + 1.2675 + if (mListener || mWasOpened) 1.2676 + return NS_ERROR_ALREADY_OPENED; 1.2677 + 1.2678 + nsresult rv; 1.2679 + 1.2680 + // Ensure target thread is set. 1.2681 + if (!mTargetThread) { 1.2682 + mTargetThread = do_GetMainThread(); 1.2683 + } 1.2684 + 1.2685 + mSocketThread = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); 1.2686 + if (NS_FAILED(rv)) { 1.2687 + NS_WARNING("unable to continue without socket transport service"); 1.2688 + return rv; 1.2689 + } 1.2690 + 1.2691 + mRandomGenerator = 1.2692 + do_GetService("@mozilla.org/security/random-generator;1", &rv); 1.2693 + if (NS_FAILED(rv)) { 1.2694 + NS_WARNING("unable to continue without random number generator"); 1.2695 + return rv; 1.2696 + } 1.2697 + 1.2698 + nsCOMPtr<nsIPrefBranch> prefService; 1.2699 + prefService = do_GetService(NS_PREFSERVICE_CONTRACTID); 1.2700 + 1.2701 + if (prefService) { 1.2702 + int32_t intpref; 1.2703 + bool boolpref; 1.2704 + rv = prefService->GetIntPref("network.websocket.max-message-size", 1.2705 + &intpref); 1.2706 + if (NS_SUCCEEDED(rv)) { 1.2707 + mMaxMessageSize = clamped(intpref, 1024, INT32_MAX); 1.2708 + } 1.2709 + rv = prefService->GetIntPref("network.websocket.timeout.close", &intpref); 1.2710 + if (NS_SUCCEEDED(rv)) { 1.2711 + mCloseTimeout = clamped(intpref, 1, 1800) * 1000; 1.2712 + } 1.2713 + rv = prefService->GetIntPref("network.websocket.timeout.open", &intpref); 1.2714 + if (NS_SUCCEEDED(rv)) { 1.2715 + mOpenTimeout = clamped(intpref, 1, 1800) * 1000; 1.2716 + } 1.2717 + rv = prefService->GetIntPref("network.websocket.timeout.ping.request", 1.2718 + &intpref); 1.2719 + if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) { 1.2720 + mPingInterval = clamped(intpref, 0, 86400) * 1000; 1.2721 + } 1.2722 + rv = prefService->GetIntPref("network.websocket.timeout.ping.response", 1.2723 + &intpref); 1.2724 + if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) { 1.2725 + mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000; 1.2726 + } 1.2727 + rv = prefService->GetBoolPref("network.websocket.extensions.stream-deflate", 1.2728 + &boolpref); 1.2729 + if (NS_SUCCEEDED(rv)) { 1.2730 + mAllowCompression = boolpref ? 1 : 0; 1.2731 + } 1.2732 + rv = prefService->GetBoolPref("network.websocket.auto-follow-http-redirects", 1.2733 + &boolpref); 1.2734 + if (NS_SUCCEEDED(rv)) { 1.2735 + mAutoFollowRedirects = boolpref ? 1 : 0; 1.2736 + } 1.2737 + rv = prefService->GetIntPref 1.2738 + ("network.websocket.max-connections", &intpref); 1.2739 + if (NS_SUCCEEDED(rv)) { 1.2740 + mMaxConcurrentConnections = clamped(intpref, 1, 0xffff); 1.2741 + } 1.2742 + } 1.2743 + 1.2744 + int32_t sessionCount = -1; 1.2745 + nsWSAdmissionManager::GetSessionCount(sessionCount); 1.2746 + if (sessionCount >= 0) { 1.2747 + LOG(("WebSocketChannel::AsyncOpen %p sessionCount=%d max=%d\n", this, 1.2748 + sessionCount, mMaxConcurrentConnections)); 1.2749 + } 1.2750 + 1.2751 + if (sessionCount >= mMaxConcurrentConnections) { 1.2752 + LOG(("WebSocketChannel: max concurrency %d exceeded (%d)", 1.2753 + mMaxConcurrentConnections, 1.2754 + sessionCount)); 1.2755 + 1.2756 + // WebSocket connections are expected to be long lived, so return 1.2757 + // an error here instead of queueing 1.2758 + return NS_ERROR_SOCKET_CREATE_FAILED; 1.2759 + } 1.2760 + 1.2761 + mOriginalURI = aURI; 1.2762 + mURI = mOriginalURI; 1.2763 + mURI->GetHostPort(mHost); 1.2764 + mOrigin = aOrigin; 1.2765 + 1.2766 + nsCOMPtr<nsIURI> localURI; 1.2767 + nsCOMPtr<nsIChannel> localChannel; 1.2768 + 1.2769 + mURI->Clone(getter_AddRefs(localURI)); 1.2770 + if (mEncrypted) 1.2771 + rv = localURI->SetScheme(NS_LITERAL_CSTRING("https")); 1.2772 + else 1.2773 + rv = localURI->SetScheme(NS_LITERAL_CSTRING("http")); 1.2774 + NS_ENSURE_SUCCESS(rv, rv); 1.2775 + 1.2776 + nsCOMPtr<nsIIOService> ioService; 1.2777 + ioService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv); 1.2778 + if (NS_FAILED(rv)) { 1.2779 + NS_WARNING("unable to continue without io service"); 1.2780 + return rv; 1.2781 + } 1.2782 + 1.2783 + nsCOMPtr<nsIIOService2> io2 = do_QueryInterface(ioService, &rv); 1.2784 + if (NS_FAILED(rv)) { 1.2785 + NS_WARNING("WebSocketChannel: unable to continue without ioservice2"); 1.2786 + return rv; 1.2787 + } 1.2788 + 1.2789 + rv = io2->NewChannelFromURIWithProxyFlags( 1.2790 + localURI, 1.2791 + mURI, 1.2792 + nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | 1.2793 + nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, 1.2794 + getter_AddRefs(localChannel)); 1.2795 + NS_ENSURE_SUCCESS(rv, rv); 1.2796 + 1.2797 + // Pass most GetInterface() requests through to our instantiator, but handle 1.2798 + // nsIChannelEventSink in this object in order to deal with redirects 1.2799 + localChannel->SetNotificationCallbacks(this); 1.2800 + 1.2801 + mChannel = do_QueryInterface(localChannel, &rv); 1.2802 + NS_ENSURE_SUCCESS(rv, rv); 1.2803 + 1.2804 + mHttpChannel = do_QueryInterface(localChannel, &rv); 1.2805 + NS_ENSURE_SUCCESS(rv, rv); 1.2806 + 1.2807 + rv = SetupRequest(); 1.2808 + if (NS_FAILED(rv)) 1.2809 + return rv; 1.2810 + 1.2811 + mPrivateBrowsing = NS_UsePrivateBrowsing(localChannel); 1.2812 + 1.2813 + if (mConnectionLogService && !mPrivateBrowsing) { 1.2814 + mConnectionLogService->AddHost(mHost, mSerial, 1.2815 + BaseWebSocketChannel::mEncrypted); 1.2816 + } 1.2817 + 1.2818 + rv = ApplyForAdmission(); 1.2819 + if (NS_FAILED(rv)) 1.2820 + return rv; 1.2821 + 1.2822 + // Only set these if the open was successful: 1.2823 + // 1.2824 + mWasOpened = 1; 1.2825 + mListener = aListener; 1.2826 + mContext = aContext; 1.2827 + IncrementSessionCount(); 1.2828 + 1.2829 + return rv; 1.2830 +} 1.2831 + 1.2832 +NS_IMETHODIMP 1.2833 +WebSocketChannel::Close(uint16_t code, const nsACString & reason) 1.2834 +{ 1.2835 + LOG(("WebSocketChannel::Close() %p\n", this)); 1.2836 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.2837 + 1.2838 + // save the networkstats (bug 855949) 1.2839 + SaveNetworkStats(true); 1.2840 + 1.2841 + if (mRequestedClose) { 1.2842 + return NS_OK; 1.2843 + } 1.2844 + 1.2845 + // The API requires the UTF-8 string to be 123 or less bytes 1.2846 + if (reason.Length() > 123) 1.2847 + return NS_ERROR_ILLEGAL_VALUE; 1.2848 + 1.2849 + mRequestedClose = 1; 1.2850 + mScriptCloseReason = reason; 1.2851 + mScriptCloseCode = code; 1.2852 + 1.2853 + if (!mTransport) { 1.2854 + nsresult rv; 1.2855 + if (code == CLOSE_GOING_AWAY) { 1.2856 + // Not an error: for example, tab has closed or navigated away 1.2857 + LOG(("WebSocketChannel::Close() GOING_AWAY without transport.")); 1.2858 + rv = NS_OK; 1.2859 + } else { 1.2860 + LOG(("WebSocketChannel::Close() without transport - error.")); 1.2861 + rv = NS_ERROR_NOT_CONNECTED; 1.2862 + } 1.2863 + StopSession(rv); 1.2864 + return rv; 1.2865 + } 1.2866 + 1.2867 + return mSocketThread->Dispatch( 1.2868 + new OutboundEnqueuer(this, new OutboundMessage(kMsgTypeFin, nullptr)), 1.2869 + nsIEventTarget::DISPATCH_NORMAL); 1.2870 +} 1.2871 + 1.2872 +NS_IMETHODIMP 1.2873 +WebSocketChannel::SendMsg(const nsACString &aMsg) 1.2874 +{ 1.2875 + LOG(("WebSocketChannel::SendMsg() %p\n", this)); 1.2876 + 1.2877 + return SendMsgCommon(&aMsg, false, aMsg.Length()); 1.2878 +} 1.2879 + 1.2880 +NS_IMETHODIMP 1.2881 +WebSocketChannel::SendBinaryMsg(const nsACString &aMsg) 1.2882 +{ 1.2883 + LOG(("WebSocketChannel::SendBinaryMsg() %p len=%d\n", this, aMsg.Length())); 1.2884 + return SendMsgCommon(&aMsg, true, aMsg.Length()); 1.2885 +} 1.2886 + 1.2887 +NS_IMETHODIMP 1.2888 +WebSocketChannel::SendBinaryStream(nsIInputStream *aStream, uint32_t aLength) 1.2889 +{ 1.2890 + LOG(("WebSocketChannel::SendBinaryStream() %p\n", this)); 1.2891 + 1.2892 + return SendMsgCommon(nullptr, true, aLength, aStream); 1.2893 +} 1.2894 + 1.2895 +nsresult 1.2896 +WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary, 1.2897 + uint32_t aLength, nsIInputStream *aStream) 1.2898 +{ 1.2899 + NS_ABORT_IF_FALSE(NS_GetCurrentThread() == mTargetThread, "not target thread"); 1.2900 + 1.2901 + if (mRequestedClose) { 1.2902 + LOG(("WebSocketChannel:: Error: send when closed\n")); 1.2903 + return NS_ERROR_UNEXPECTED; 1.2904 + } 1.2905 + 1.2906 + if (mStopped) { 1.2907 + LOG(("WebSocketChannel:: Error: send when stopped\n")); 1.2908 + return NS_ERROR_NOT_CONNECTED; 1.2909 + } 1.2910 + 1.2911 + NS_ABORT_IF_FALSE(mMaxMessageSize >= 0, "max message size negative"); 1.2912 + if (aLength > static_cast<uint32_t>(mMaxMessageSize)) { 1.2913 + LOG(("WebSocketChannel:: Error: message too big\n")); 1.2914 + return NS_ERROR_FILE_TOO_BIG; 1.2915 + } 1.2916 + 1.2917 + if (mConnectionLogService && !mPrivateBrowsing) { 1.2918 + mConnectionLogService->NewMsgSent(mHost, mSerial, aLength); 1.2919 + LOG(("Added new msg sent for %s", mHost.get())); 1.2920 + } 1.2921 + 1.2922 + return mSocketThread->Dispatch( 1.2923 + aStream ? new OutboundEnqueuer(this, new OutboundMessage(aStream, aLength)) 1.2924 + : new OutboundEnqueuer(this, 1.2925 + new OutboundMessage(aIsBinary ? kMsgTypeBinaryString 1.2926 + : kMsgTypeString, 1.2927 + new nsCString(*aMsg))), 1.2928 + nsIEventTarget::DISPATCH_NORMAL); 1.2929 +} 1.2930 + 1.2931 +// nsIHttpUpgradeListener 1.2932 + 1.2933 +NS_IMETHODIMP 1.2934 +WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport, 1.2935 + nsIAsyncInputStream *aSocketIn, 1.2936 + nsIAsyncOutputStream *aSocketOut) 1.2937 +{ 1.2938 + if (!NS_IsMainThread()) { 1.2939 + return NS_DispatchToMainThread(new CallOnTransportAvailable(this, 1.2940 + aTransport, 1.2941 + aSocketIn, 1.2942 + aSocketOut)); 1.2943 + } 1.2944 + 1.2945 + LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n", 1.2946 + this, aTransport, aSocketIn, aSocketOut, mGotUpgradeOK)); 1.2947 + 1.2948 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.2949 + NS_ABORT_IF_FALSE(!mRecvdHttpUpgradeTransport, "OTA duplicated"); 1.2950 + NS_ABORT_IF_FALSE(aSocketIn, "OTA with invalid socketIn"); 1.2951 + 1.2952 + mTransport = aTransport; 1.2953 + mSocketIn = aSocketIn; 1.2954 + mSocketOut = aSocketOut; 1.2955 + 1.2956 + nsresult rv; 1.2957 + rv = mTransport->SetEventSink(nullptr, nullptr); 1.2958 + if (NS_FAILED(rv)) return rv; 1.2959 + rv = mTransport->SetSecurityCallbacks(this); 1.2960 + if (NS_FAILED(rv)) return rv; 1.2961 + 1.2962 + mRecvdHttpUpgradeTransport = 1; 1.2963 + if (mGotUpgradeOK) 1.2964 + return StartWebsocketData(); 1.2965 + return NS_OK; 1.2966 +} 1.2967 + 1.2968 +// nsIRequestObserver (from nsIStreamListener) 1.2969 + 1.2970 +NS_IMETHODIMP 1.2971 +WebSocketChannel::OnStartRequest(nsIRequest *aRequest, 1.2972 + nsISupports *aContext) 1.2973 +{ 1.2974 + LOG(("WebSocketChannel::OnStartRequest(): %p [%p %p] recvdhttpupgrade=%d\n", 1.2975 + this, aRequest, aContext, mRecvdHttpUpgradeTransport)); 1.2976 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.2977 + NS_ABORT_IF_FALSE(!mGotUpgradeOK, "OTA duplicated"); 1.2978 + 1.2979 + if (mOpenTimer) { 1.2980 + mOpenTimer->Cancel(); 1.2981 + mOpenTimer = nullptr; 1.2982 + } 1.2983 + 1.2984 + if (mStopped) { 1.2985 + LOG(("WebSocketChannel::OnStartRequest: Channel Already Done\n")); 1.2986 + AbortSession(NS_ERROR_CONNECTION_REFUSED); 1.2987 + return NS_ERROR_CONNECTION_REFUSED; 1.2988 + } 1.2989 + 1.2990 + nsresult rv; 1.2991 + uint32_t status; 1.2992 + char *val, *token; 1.2993 + 1.2994 + rv = mHttpChannel->GetResponseStatus(&status); 1.2995 + if (NS_FAILED(rv)) { 1.2996 + LOG(("WebSocketChannel::OnStartRequest: No HTTP Response\n")); 1.2997 + AbortSession(NS_ERROR_CONNECTION_REFUSED); 1.2998 + return NS_ERROR_CONNECTION_REFUSED; 1.2999 + } 1.3000 + 1.3001 + LOG(("WebSocketChannel::OnStartRequest: HTTP status %d\n", status)); 1.3002 + if (status != 101) { 1.3003 + AbortSession(NS_ERROR_CONNECTION_REFUSED); 1.3004 + return NS_ERROR_CONNECTION_REFUSED; 1.3005 + } 1.3006 + 1.3007 + nsAutoCString respUpgrade; 1.3008 + rv = mHttpChannel->GetResponseHeader( 1.3009 + NS_LITERAL_CSTRING("Upgrade"), respUpgrade); 1.3010 + 1.3011 + if (NS_SUCCEEDED(rv)) { 1.3012 + rv = NS_ERROR_ILLEGAL_VALUE; 1.3013 + if (!respUpgrade.IsEmpty()) { 1.3014 + val = respUpgrade.BeginWriting(); 1.3015 + while ((token = nsCRT::strtok(val, ", \t", &val))) { 1.3016 + if (PL_strcasecmp(token, "Websocket") == 0) { 1.3017 + rv = NS_OK; 1.3018 + break; 1.3019 + } 1.3020 + } 1.3021 + } 1.3022 + } 1.3023 + 1.3024 + if (NS_FAILED(rv)) { 1.3025 + LOG(("WebSocketChannel::OnStartRequest: " 1.3026 + "HTTP response header Upgrade: websocket not found\n")); 1.3027 + AbortSession(NS_ERROR_ILLEGAL_VALUE); 1.3028 + return rv; 1.3029 + } 1.3030 + 1.3031 + nsAutoCString respConnection; 1.3032 + rv = mHttpChannel->GetResponseHeader( 1.3033 + NS_LITERAL_CSTRING("Connection"), respConnection); 1.3034 + 1.3035 + if (NS_SUCCEEDED(rv)) { 1.3036 + rv = NS_ERROR_ILLEGAL_VALUE; 1.3037 + if (!respConnection.IsEmpty()) { 1.3038 + val = respConnection.BeginWriting(); 1.3039 + while ((token = nsCRT::strtok(val, ", \t", &val))) { 1.3040 + if (PL_strcasecmp(token, "Upgrade") == 0) { 1.3041 + rv = NS_OK; 1.3042 + break; 1.3043 + } 1.3044 + } 1.3045 + } 1.3046 + } 1.3047 + 1.3048 + if (NS_FAILED(rv)) { 1.3049 + LOG(("WebSocketChannel::OnStartRequest: " 1.3050 + "HTTP response header 'Connection: Upgrade' not found\n")); 1.3051 + AbortSession(NS_ERROR_ILLEGAL_VALUE); 1.3052 + return rv; 1.3053 + } 1.3054 + 1.3055 + nsAutoCString respAccept; 1.3056 + rv = mHttpChannel->GetResponseHeader( 1.3057 + NS_LITERAL_CSTRING("Sec-WebSocket-Accept"), 1.3058 + respAccept); 1.3059 + 1.3060 + if (NS_FAILED(rv) || 1.3061 + respAccept.IsEmpty() || !respAccept.Equals(mHashedSecret)) { 1.3062 + LOG(("WebSocketChannel::OnStartRequest: " 1.3063 + "HTTP response header Sec-WebSocket-Accept check failed\n")); 1.3064 + LOG(("WebSocketChannel::OnStartRequest: Expected %s received %s\n", 1.3065 + mHashedSecret.get(), respAccept.get())); 1.3066 + AbortSession(NS_ERROR_ILLEGAL_VALUE); 1.3067 + return NS_ERROR_ILLEGAL_VALUE; 1.3068 + } 1.3069 + 1.3070 + // If we sent a sub protocol header, verify the response matches 1.3071 + // If it does not, set mProtocol to "" so the protocol attribute 1.3072 + // of the WebSocket JS object reflects that 1.3073 + if (!mProtocol.IsEmpty()) { 1.3074 + nsAutoCString respProtocol; 1.3075 + rv = mHttpChannel->GetResponseHeader( 1.3076 + NS_LITERAL_CSTRING("Sec-WebSocket-Protocol"), 1.3077 + respProtocol); 1.3078 + if (NS_SUCCEEDED(rv)) { 1.3079 + rv = NS_ERROR_ILLEGAL_VALUE; 1.3080 + val = mProtocol.BeginWriting(); 1.3081 + while ((token = nsCRT::strtok(val, ", \t", &val))) { 1.3082 + if (PL_strcasecmp(token, respProtocol.get()) == 0) { 1.3083 + rv = NS_OK; 1.3084 + break; 1.3085 + } 1.3086 + } 1.3087 + 1.3088 + if (NS_SUCCEEDED(rv)) { 1.3089 + LOG(("WebsocketChannel::OnStartRequest: subprotocol %s confirmed", 1.3090 + respProtocol.get())); 1.3091 + mProtocol = respProtocol; 1.3092 + } else { 1.3093 + LOG(("WebsocketChannel::OnStartRequest: " 1.3094 + "subprotocol [%s] not found - %s returned", 1.3095 + mProtocol.get(), respProtocol.get())); 1.3096 + mProtocol.Truncate(); 1.3097 + } 1.3098 + } else { 1.3099 + LOG(("WebsocketChannel::OnStartRequest " 1.3100 + "subprotocol [%s] not found - none returned", 1.3101 + mProtocol.get())); 1.3102 + mProtocol.Truncate(); 1.3103 + } 1.3104 + } 1.3105 + 1.3106 + rv = HandleExtensions(); 1.3107 + if (NS_FAILED(rv)) 1.3108 + return rv; 1.3109 + 1.3110 + mGotUpgradeOK = 1; 1.3111 + if (mRecvdHttpUpgradeTransport) 1.3112 + return StartWebsocketData(); 1.3113 + 1.3114 + return NS_OK; 1.3115 +} 1.3116 + 1.3117 +NS_IMETHODIMP 1.3118 +WebSocketChannel::OnStopRequest(nsIRequest *aRequest, 1.3119 + nsISupports *aContext, 1.3120 + nsresult aStatusCode) 1.3121 +{ 1.3122 + LOG(("WebSocketChannel::OnStopRequest() %p [%p %p %x]\n", 1.3123 + this, aRequest, aContext, aStatusCode)); 1.3124 + NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread"); 1.3125 + 1.3126 + ReportConnectionTelemetry(); 1.3127 + 1.3128 + // This is the end of the HTTP upgrade transaction, the 1.3129 + // upgraded streams live on 1.3130 + 1.3131 + mChannel = nullptr; 1.3132 + mHttpChannel = nullptr; 1.3133 + mLoadGroup = nullptr; 1.3134 + mCallbacks = nullptr; 1.3135 + 1.3136 + return NS_OK; 1.3137 +} 1.3138 + 1.3139 +// nsIInputStreamCallback 1.3140 + 1.3141 +NS_IMETHODIMP 1.3142 +WebSocketChannel::OnInputStreamReady(nsIAsyncInputStream *aStream) 1.3143 +{ 1.3144 + LOG(("WebSocketChannel::OnInputStreamReady() %p\n", this)); 1.3145 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread"); 1.3146 + 1.3147 + if (!mSocketIn) // did we we clean up the socket after scheduling InputReady? 1.3148 + return NS_OK; 1.3149 + 1.3150 + nsRefPtr<nsIStreamListener> deleteProtector1(mInflateReader); 1.3151 + nsRefPtr<nsIStringInputStream> deleteProtector2(mInflateStream); 1.3152 + 1.3153 + // this is after the http upgrade - so we are speaking websockets 1.3154 + char buffer[2048]; 1.3155 + uint32_t count; 1.3156 + nsresult rv; 1.3157 + 1.3158 + do { 1.3159 + rv = mSocketIn->Read((char *)buffer, 2048, &count); 1.3160 + LOG(("WebSocketChannel::OnInputStreamReady: read %u rv %x\n", count, rv)); 1.3161 + 1.3162 + // accumulate received bytes 1.3163 + CountRecvBytes(count); 1.3164 + 1.3165 + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 1.3166 + mSocketIn->AsyncWait(this, 0, 0, mSocketThread); 1.3167 + return NS_OK; 1.3168 + } 1.3169 + 1.3170 + if (NS_FAILED(rv)) { 1.3171 + mTCPClosed = true; 1.3172 + AbortSession(rv); 1.3173 + return rv; 1.3174 + } 1.3175 + 1.3176 + if (count == 0) { 1.3177 + mTCPClosed = true; 1.3178 + AbortSession(NS_BASE_STREAM_CLOSED); 1.3179 + return NS_OK; 1.3180 + } 1.3181 + 1.3182 + if (mStopped) { 1.3183 + continue; 1.3184 + } 1.3185 + 1.3186 + if (mInflateReader) { 1.3187 + mInflateStream->ShareData(buffer, count); 1.3188 + rv = mInflateReader->OnDataAvailable(nullptr, mSocketIn, mInflateStream, 1.3189 + 0, count); 1.3190 + } else { 1.3191 + rv = ProcessInput((uint8_t *)buffer, count); 1.3192 + } 1.3193 + 1.3194 + if (NS_FAILED(rv)) { 1.3195 + AbortSession(rv); 1.3196 + return rv; 1.3197 + } 1.3198 + } while (NS_SUCCEEDED(rv) && mSocketIn); 1.3199 + 1.3200 + return NS_OK; 1.3201 +} 1.3202 + 1.3203 + 1.3204 +// nsIOutputStreamCallback 1.3205 + 1.3206 +NS_IMETHODIMP 1.3207 +WebSocketChannel::OnOutputStreamReady(nsIAsyncOutputStream *aStream) 1.3208 +{ 1.3209 + LOG(("WebSocketChannel::OnOutputStreamReady() %p\n", this)); 1.3210 + NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread"); 1.3211 + nsresult rv; 1.3212 + 1.3213 + if (!mCurrentOut) 1.3214 + PrimeNewOutgoingMessage(); 1.3215 + 1.3216 + while (mCurrentOut && mSocketOut) { 1.3217 + const char *sndBuf; 1.3218 + uint32_t toSend; 1.3219 + uint32_t amtSent; 1.3220 + 1.3221 + if (mHdrOut) { 1.3222 + sndBuf = (const char *)mHdrOut; 1.3223 + toSend = mHdrOutToSend; 1.3224 + LOG(("WebSocketChannel::OnOutputStreamReady: " 1.3225 + "Try to send %u of hdr/copybreak\n", toSend)); 1.3226 + } else { 1.3227 + sndBuf = (char *) mCurrentOut->BeginReading() + mCurrentOutSent; 1.3228 + toSend = mCurrentOut->Length() - mCurrentOutSent; 1.3229 + if (toSend > 0) { 1.3230 + LOG(("WebSocketChannel::OnOutputStreamReady: " 1.3231 + "Try to send %u of data\n", toSend)); 1.3232 + } 1.3233 + } 1.3234 + 1.3235 + if (toSend == 0) { 1.3236 + amtSent = 0; 1.3237 + } else { 1.3238 + rv = mSocketOut->Write(sndBuf, toSend, &amtSent); 1.3239 + LOG(("WebSocketChannel::OnOutputStreamReady: write %u rv %x\n", 1.3240 + amtSent, rv)); 1.3241 + 1.3242 + // accumulate sent bytes 1.3243 + CountSentBytes(amtSent); 1.3244 + 1.3245 + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 1.3246 + mSocketOut->AsyncWait(this, 0, 0, mSocketThread); 1.3247 + return NS_OK; 1.3248 + } 1.3249 + 1.3250 + if (NS_FAILED(rv)) { 1.3251 + AbortSession(rv); 1.3252 + return NS_OK; 1.3253 + } 1.3254 + } 1.3255 + 1.3256 + if (mHdrOut) { 1.3257 + if (amtSent == toSend) { 1.3258 + mHdrOut = nullptr; 1.3259 + mHdrOutToSend = 0; 1.3260 + } else { 1.3261 + mHdrOut += amtSent; 1.3262 + mHdrOutToSend -= amtSent; 1.3263 + } 1.3264 + } else { 1.3265 + if (amtSent == toSend) { 1.3266 + if (!mStopped) { 1.3267 + mTargetThread->Dispatch(new CallAcknowledge(this, 1.3268 + mCurrentOut->Length()), 1.3269 + NS_DISPATCH_NORMAL); 1.3270 + } 1.3271 + DeleteCurrentOutGoingMessage(); 1.3272 + PrimeNewOutgoingMessage(); 1.3273 + } else { 1.3274 + mCurrentOutSent += amtSent; 1.3275 + } 1.3276 + } 1.3277 + } 1.3278 + 1.3279 + if (mReleaseOnTransmit) 1.3280 + ReleaseSession(); 1.3281 + return NS_OK; 1.3282 +} 1.3283 + 1.3284 +// nsIStreamListener 1.3285 + 1.3286 +NS_IMETHODIMP 1.3287 +WebSocketChannel::OnDataAvailable(nsIRequest *aRequest, 1.3288 + nsISupports *aContext, 1.3289 + nsIInputStream *aInputStream, 1.3290 + uint64_t aOffset, 1.3291 + uint32_t aCount) 1.3292 +{ 1.3293 + LOG(("WebSocketChannel::OnDataAvailable() %p [%p %p %p %llu %u]\n", 1.3294 + this, aRequest, aContext, aInputStream, aOffset, aCount)); 1.3295 + 1.3296 + if (aContext == mSocketIn) { 1.3297 + // This is the deflate decoder 1.3298 + 1.3299 + LOG(("WebSocketChannel::OnDataAvailable: Deflate Data %u\n", 1.3300 + aCount)); 1.3301 + 1.3302 + uint8_t buffer[2048]; 1.3303 + uint32_t maxRead; 1.3304 + uint32_t count; 1.3305 + nsresult rv = NS_OK; // aCount always > 0, so this just avoids warning 1.3306 + 1.3307 + while (aCount > 0) { 1.3308 + if (mStopped) 1.3309 + return NS_BASE_STREAM_CLOSED; 1.3310 + 1.3311 + maxRead = std::min(2048U, aCount); 1.3312 + rv = aInputStream->Read((char *)buffer, maxRead, &count); 1.3313 + LOG(("WebSocketChannel::OnDataAvailable: InflateRead read %u rv %x\n", 1.3314 + count, rv)); 1.3315 + if (NS_FAILED(rv) || count == 0) { 1.3316 + AbortSession(NS_ERROR_UNEXPECTED); 1.3317 + break; 1.3318 + } 1.3319 + 1.3320 + aCount -= count; 1.3321 + rv = ProcessInput(buffer, count); 1.3322 + if (NS_FAILED(rv)) { 1.3323 + AbortSession(rv); 1.3324 + break; 1.3325 + } 1.3326 + } 1.3327 + return rv; 1.3328 + } 1.3329 + 1.3330 + if (aContext == mSocketOut) { 1.3331 + // This is the deflate encoder 1.3332 + 1.3333 + uint32_t maxRead; 1.3334 + uint32_t count; 1.3335 + nsresult rv; 1.3336 + 1.3337 + while (aCount > 0) { 1.3338 + if (mStopped) 1.3339 + return NS_BASE_STREAM_CLOSED; 1.3340 + 1.3341 + maxRead = std::min(2048U, aCount); 1.3342 + EnsureHdrOut(mHdrOutToSend + aCount); 1.3343 + rv = aInputStream->Read((char *)mHdrOut + mHdrOutToSend, maxRead, &count); 1.3344 + LOG(("WebSocketChannel::OnDataAvailable: DeflateWrite read %u rv %x\n", 1.3345 + count, rv)); 1.3346 + if (NS_FAILED(rv) || count == 0) { 1.3347 + AbortSession(rv); 1.3348 + break; 1.3349 + } 1.3350 + 1.3351 + mHdrOutToSend += count; 1.3352 + aCount -= count; 1.3353 + } 1.3354 + return NS_OK; 1.3355 + } 1.3356 + 1.3357 + 1.3358 + // Otherwise, this is the HTTP OnDataAvailable Method, which means 1.3359 + // this is http data in response to the upgrade request and 1.3360 + // there should be no http response body if the upgrade succeeded 1.3361 + 1.3362 + // This generally should be caught by a non 101 response code in 1.3363 + // OnStartRequest().. so we can ignore the data here 1.3364 + 1.3365 + LOG(("WebSocketChannel::OnDataAvailable: HTTP data unexpected len>=%u\n", 1.3366 + aCount)); 1.3367 + 1.3368 + return NS_OK; 1.3369 +} 1.3370 + 1.3371 +nsresult 1.3372 +WebSocketChannel::SaveNetworkStats(bool enforce) 1.3373 +{ 1.3374 +#ifdef MOZ_WIDGET_GONK 1.3375 + // Check if the active network and app id are valid. 1.3376 + if(!mActiveNetwork || mAppId == NECKO_NO_APP_ID) { 1.3377 + return NS_OK; 1.3378 + } 1.3379 + 1.3380 + if (mCountRecv <= 0 && mCountSent <= 0) { 1.3381 + // There is no traffic, no need to save. 1.3382 + return NS_OK; 1.3383 + } 1.3384 + 1.3385 + // If |enforce| is false, the traffic amount is saved 1.3386 + // only when the total amount exceeds the predefined 1.3387 + // threshold. 1.3388 + uint64_t totalBytes = mCountRecv + mCountSent; 1.3389 + if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) { 1.3390 + return NS_OK; 1.3391 + } 1.3392 + 1.3393 + // Create the event to save the network statistics. 1.3394 + // the event is then dispathed to the main thread. 1.3395 + nsRefPtr<nsRunnable> event = 1.3396 + new SaveNetworkStatsEvent(mAppId, mActiveNetwork, 1.3397 + mCountRecv, mCountSent, false); 1.3398 + NS_DispatchToMainThread(event); 1.3399 + 1.3400 + // Reset the counters after saving. 1.3401 + mCountSent = 0; 1.3402 + mCountRecv = 0; 1.3403 + 1.3404 + return NS_OK; 1.3405 +#else 1.3406 + return NS_ERROR_NOT_IMPLEMENTED; 1.3407 +#endif 1.3408 +} 1.3409 + 1.3410 +} // namespace mozilla::net 1.3411 +} // namespace mozilla