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