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