Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cu = Components.utils;
10 const Cr = Components.results;
11 const CC = Components.Constructor;
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
14 Cu.import("resource://gre/modules/Services.jsm");
16 const InputStreamPump = CC(
17 "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"),
18 AsyncStreamCopier = CC(
19 "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"),
20 ScriptableInputStream = CC(
21 "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"),
22 BinaryInputStream = CC(
23 "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"),
24 StringInputStream = CC(
25 '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'),
26 ArrayBufferInputStream = CC(
27 '@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'),
28 MultiplexInputStream = CC(
29 '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream');
30 const TCPServerSocket = CC(
31 "@mozilla.org/tcp-server-socket;1", "nsITCPServerSocketInternal", "init");
33 const kCONNECTING = 'connecting';
34 const kOPEN = 'open';
35 const kCLOSING = 'closing';
36 const kCLOSED = 'closed';
37 const kRESUME_ERROR = 'Calling resume() on a connection that was not suspended.';
39 const BUFFER_SIZE = 65536;
40 const NETWORK_STATS_THRESHOLD = 65536;
42 // XXX we have no TCPError implementation right now because it's really hard to
43 // do on b2g18. On mozilla-central we want a proper TCPError that ideally
44 // sub-classes DOMError. Bug 867872 has been filed to implement this and
45 // contains a documented TCPError.webidl that maps all the error codes we use in
46 // this file to slightly more readable explanations.
47 function createTCPError(aWindow, aErrorName, aErrorType) {
48 return new (aWindow ? aWindow.DOMError : DOMError)(aErrorName);
49 }
52 /*
53 * Debug logging function
54 */
56 let debug = false;
57 function LOG(msg) {
58 if (debug)
59 dump("TCPSocket: " + msg + "\n");
60 }
62 /*
63 * nsITCPSocketEvent object
64 */
66 function TCPSocketEvent(type, sock, data) {
67 this._type = type;
68 this._target = sock;
69 this._data = data;
70 }
72 TCPSocketEvent.prototype = {
73 __exposedProps__: {
74 type: 'r',
75 target: 'r',
76 data: 'r'
77 },
78 get type() {
79 return this._type;
80 },
81 get target() {
82 return this._target;
83 },
84 get data() {
85 return this._data;
86 }
87 }
89 /*
90 * nsIDOMTCPSocket object
91 */
93 function TCPSocket() {
94 this._readyState = kCLOSED;
96 this._onopen = null;
97 this._ondrain = null;
98 this._ondata = null;
99 this._onerror = null;
100 this._onclose = null;
102 this._binaryType = "string";
104 this._host = "";
105 this._port = 0;
106 this._ssl = false;
108 this.useWin = null;
109 }
111 TCPSocket.prototype = {
112 __exposedProps__: {
113 open: 'r',
114 host: 'r',
115 port: 'r',
116 ssl: 'r',
117 bufferedAmount: 'r',
118 suspend: 'r',
119 resume: 'r',
120 close: 'r',
121 send: 'r',
122 readyState: 'r',
123 binaryType: 'r',
124 listen: 'r',
125 onopen: 'rw',
126 ondrain: 'rw',
127 ondata: 'rw',
128 onerror: 'rw',
129 onclose: 'rw'
130 },
131 // The binary type, "string" or "arraybuffer"
132 _binaryType: null,
134 // Internal
135 _hasPrivileges: null,
137 // Raw socket streams
138 _transport: null,
139 _socketInputStream: null,
140 _socketOutputStream: null,
142 // Input stream machinery
143 _inputStreamPump: null,
144 _inputStreamScriptable: null,
145 _inputStreamBinary: null,
147 // Output stream machinery
148 _multiplexStream: null,
149 _multiplexStreamCopier: null,
151 _asyncCopierActive: false,
152 _waitingForDrain: false,
153 _suspendCount: 0,
155 // Reported parent process buffer
156 _bufferedAmount: 0,
158 // IPC socket actor
159 _socketBridge: null,
161 // StartTLS
162 _waitingForStartTLS: false,
163 _pendingDataAfterStartTLS: [],
165 // Used to notify when update bufferedAmount is updated.
166 _onUpdateBufferedAmount: null,
167 _trackingNumber: 0,
169 #ifdef MOZ_WIDGET_GONK
170 // Network statistics (Gonk-specific feature)
171 _txBytes: 0,
172 _rxBytes: 0,
173 _appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
174 _activeNetwork: null,
175 #endif
177 // Public accessors.
178 get readyState() {
179 return this._readyState;
180 },
181 get binaryType() {
182 return this._binaryType;
183 },
184 get host() {
185 return this._host;
186 },
187 get port() {
188 return this._port;
189 },
190 get ssl() {
191 return this._ssl;
192 },
193 get bufferedAmount() {
194 if (this._inChild) {
195 return this._bufferedAmount;
196 }
197 return this._multiplexStream.available();
198 },
199 get onopen() {
200 return this._onopen;
201 },
202 set onopen(f) {
203 this._onopen = f;
204 },
205 get ondrain() {
206 return this._ondrain;
207 },
208 set ondrain(f) {
209 this._ondrain = f;
210 },
211 get ondata() {
212 return this._ondata;
213 },
214 set ondata(f) {
215 this._ondata = f;
216 },
217 get onerror() {
218 return this._onerror;
219 },
220 set onerror(f) {
221 this._onerror = f;
222 },
223 get onclose() {
224 return this._onclose;
225 },
226 set onclose(f) {
227 this._onclose = f;
228 },
230 _activateTLS: function() {
231 let securityInfo = this._transport.securityInfo
232 .QueryInterface(Ci.nsISSLSocketControl);
233 securityInfo.StartTLS();
234 },
236 // Helper methods.
237 _createTransport: function ts_createTransport(host, port, sslMode) {
238 let options;
239 if (sslMode === 'ssl') {
240 options = ['ssl'];
241 } else {
242 options = ['starttls'];
243 }
244 return Cc["@mozilla.org/network/socket-transport-service;1"]
245 .getService(Ci.nsISocketTransportService)
246 .createTransport(options, 1, host, port, null);
247 },
249 _sendBufferedAmount: function ts_sendBufferedAmount() {
250 if (this._onUpdateBufferedAmount) {
251 this._onUpdateBufferedAmount(this.bufferedAmount, this._trackingNumber);
252 }
253 },
255 _ensureCopying: function ts_ensureCopying() {
256 let self = this;
257 if (this._asyncCopierActive) {
258 return;
259 }
260 this._asyncCopierActive = true;
261 this._multiplexStreamCopier.asyncCopy({
262 onStartRequest: function ts_output_onStartRequest() {
263 },
264 onStopRequest: function ts_output_onStopRequest(request, context, status) {
265 self._asyncCopierActive = false;
266 self._multiplexStream.removeStream(0);
267 self._sendBufferedAmount();
269 if (!Components.isSuccessCode(status)) {
270 // Note that we can/will get an error here as well as in the
271 // onStopRequest for inbound data.
272 self._maybeReportErrorAndCloseIfOpen(status);
273 return;
274 }
276 if (self._multiplexStream.count) {
277 self._ensureCopying();
278 } else {
279 // If we are waiting for initiating starttls, we can begin to
280 // activate tls now.
281 if (self._waitingForStartTLS && self._readyState == kOPEN) {
282 self._activateTLS();
283 self._waitingForStartTLS = false;
284 // If we have pending data, we should send them, or fire
285 // a drain event if we are waiting for it.
286 if (self._pendingDataAfterStartTLS.length > 0) {
287 while (self._pendingDataAfterStartTLS.length)
288 self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift());
289 self._ensureCopying();
290 return;
291 }
292 }
294 // If we have a callback to update bufferedAmount, we let child to
295 // decide whether ondrain should be dispatched.
296 if (self._waitingForDrain && !self._onUpdateBufferedAmount) {
297 self._waitingForDrain = false;
298 self.callListener("drain");
299 }
300 if (self._readyState === kCLOSING) {
301 self._socketOutputStream.close();
302 self._readyState = kCLOSED;
303 self.callListener("close");
304 }
305 }
306 }
307 }, null);
308 },
310 _initStream: function ts_initStream(binaryType) {
311 this._binaryType = binaryType;
312 this._socketInputStream = this._transport.openInputStream(0, 0, 0);
313 this._socketOutputStream = this._transport.openOutputStream(
314 Ci.nsITransport.OPEN_UNBUFFERED, 0, 0);
316 // If the other side is not listening, we will
317 // get an onInputStreamReady callback where available
318 // raises to indicate the connection was refused.
319 this._socketInputStream.asyncWait(
320 this, this._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread);
322 if (this._binaryType === "arraybuffer") {
323 this._inputStreamBinary = new BinaryInputStream(this._socketInputStream);
324 } else {
325 this._inputStreamScriptable = new ScriptableInputStream(this._socketInputStream);
326 }
328 this._multiplexStream = new MultiplexInputStream();
330 this._multiplexStreamCopier = new AsyncStreamCopier(
331 this._multiplexStream,
332 this._socketOutputStream,
333 // (nsSocketTransport uses gSocketTransportService)
334 Cc["@mozilla.org/network/socket-transport-service;1"]
335 .getService(Ci.nsIEventTarget),
336 /* source buffered */ true, /* sink buffered */ false,
337 BUFFER_SIZE, /* close source*/ false, /* close sink */ false);
338 },
340 #ifdef MOZ_WIDGET_GONK
341 // Helper method for collecting network statistics.
342 // Note this method is Gonk-specific.
343 _saveNetworkStats: function ts_saveNetworkStats(enforce) {
344 if (this._txBytes <= 0 && this._rxBytes <= 0) {
345 // There is no traffic at all. No need to save statistics.
346 return;
347 }
349 // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy
350 // only when the total amount exceeds the predefined threshold value.
351 // The purpose is to avoid too much overhead for collecting statistics.
352 let totalBytes = this._txBytes + this._rxBytes;
353 if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
354 return;
355 }
357 let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"]
358 .getService(Ci.nsINetworkStatsServiceProxy);
359 if (!nssProxy) {
360 LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available.");
361 return;
362 }
363 nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(),
364 this._rxBytes, this._txBytes, false);
366 // Reset the counters once the statistics is saved to NetworkStatsServiceProxy.
367 this._txBytes = this._rxBytes = 0;
368 },
369 // End of helper method for network statistics.
370 #endif
372 callListener: function ts_callListener(type, data) {
373 if (!this["on" + type])
374 return;
376 this["on" + type].call(null, new TCPSocketEvent(type, this, data || ""));
377 },
379 /* nsITCPSocketInternal methods */
380 callListenerError: function ts_callListenerError(type, name) {
381 // XXX we're not really using TCPError at this time, so there's only a name
382 // attribute to pass.
383 this.callListener(type, createTCPError(this.useWin, name));
384 },
386 callListenerData: function ts_callListenerString(type, data) {
387 this.callListener(type, data);
388 },
390 callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) {
391 this.callListener(type, data);
392 },
394 callListenerVoid: function ts_callListenerVoid(type) {
395 this.callListener(type);
396 },
398 /**
399 * This method is expected to be called by TCPSocketChild to update child's
400 * readyState.
401 */
402 updateReadyState: function ts_updateReadyState(readyState) {
403 if (!this._inChild) {
404 LOG("Calling updateReadyState in parent, which should only be called " +
405 "in child");
406 return;
407 }
408 this._readyState = readyState;
409 },
411 updateBufferedAmount: function ts_updateBufferedAmount(bufferedAmount, trackingNumber) {
412 if (trackingNumber != this._trackingNumber) {
413 LOG("updateBufferedAmount is called but trackingNumber is not matched " +
414 "parent's trackingNumber: " + trackingNumber + ", child's trackingNumber: " +
415 this._trackingNumber);
416 return;
417 }
418 this._bufferedAmount = bufferedAmount;
419 if (bufferedAmount == 0) {
420 if (this._waitingForDrain) {
421 this._waitingForDrain = false;
422 this.callListener("drain");
423 }
424 } else {
425 LOG("bufferedAmount is updated but haven't reaches zero. bufferedAmount: " +
426 bufferedAmount);
427 }
428 },
430 createAcceptedParent: function ts_createAcceptedParent(transport, binaryType) {
431 let that = new TCPSocket();
432 that._transport = transport;
433 that._initStream(binaryType);
435 // ReadyState is kOpen since accepted transport stream has already been connected
436 that._readyState = kOPEN;
437 that._inputStreamPump = new InputStreamPump(that._socketInputStream, -1, -1, 0, 0, false);
438 that._inputStreamPump.asyncRead(that, null);
440 return that;
441 },
443 createAcceptedChild: function ts_createAcceptedChild(socketChild, binaryType, windowObject) {
444 let that = new TCPSocket();
446 that._binaryType = binaryType;
447 that._inChild = true;
448 that._readyState = kOPEN;
449 socketChild.setSocketAndWindow(that, windowObject);
450 that._socketBridge = socketChild;
452 return that;
453 },
455 setAppId: function ts_setAppId(appId) {
456 #ifdef MOZ_WIDGET_GONK
457 this._appId = appId;
458 #else
459 // Do nothing because _appId only exists on Gonk-specific platform.
460 #endif
461 },
463 setOnUpdateBufferedAmountHandler: function(aFunction) {
464 if (typeof(aFunction) == 'function') {
465 this._onUpdateBufferedAmount = aFunction;
466 } else {
467 throw new Error("only function can be passed to " +
468 "setOnUpdateBufferedAmountHandler");
469 }
470 },
472 /**
473 * Handle the requst of sending data and update trackingNumber from
474 * child.
475 * This function is expected to be called by TCPSocketChild.
476 */
477 onRecvSendFromChild: function(data, byteOffset, byteLength, trackingNumber) {
478 this._trackingNumber = trackingNumber;
479 this.send(data, byteOffset, byteLength);
480 },
482 /* end nsITCPSocketInternal methods */
484 initWindowless: function ts_initWindowless() {
485 try {
486 return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled");
487 } catch (e) {
488 // no pref means return false
489 return false;
490 }
491 },
493 init: function ts_init(aWindow) {
494 if (!this.initWindowless())
495 return null;
497 let principal = aWindow.document.nodePrincipal;
498 let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
499 .getService(Ci.nsIScriptSecurityManager);
501 let perm = principal == secMan.getSystemPrincipal()
502 ? Ci.nsIPermissionManager.ALLOW_ACTION
503 : Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket");
505 this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
507 let util = aWindow.QueryInterface(
508 Ci.nsIInterfaceRequestor
509 ).getInterface(Ci.nsIDOMWindowUtils);
511 this.useWin = XPCNativeWrapper.unwrap(aWindow);
512 this.innerWindowID = util.currentInnerWindowID;
513 LOG("window init: " + this.innerWindowID);
514 },
516 observe: function(aSubject, aTopic, aData) {
517 if (aTopic == "inner-window-destroyed") {
518 let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
519 if (wId == this.innerWindowID) {
520 LOG("inner-window-destroyed: " + this.innerWindowID);
522 // This window is now dead, so we want to clear the callbacks
523 // so that we don't get a "can't access dead object" when the
524 // underlying stream goes to tell us that we are closed
525 this.onopen = null;
526 this.ondrain = null;
527 this.ondata = null;
528 this.onerror = null;
529 this.onclose = null;
531 this.useWin = null;
533 // Clean up our socket
534 this.close();
535 }
536 }
537 },
539 // nsIDOMTCPSocket
540 open: function ts_open(host, port, options) {
541 this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
542 .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
543 LOG("content process: " + (this._inChild ? "true" : "false"));
545 // in the testing case, init won't be called and
546 // hasPrivileges will be null. We want to proceed to test.
547 if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
548 throw new Error("TCPSocket does not have permission in this context.\n");
549 }
550 let that = new TCPSocket();
552 that.useWin = this.useWin;
553 that.innerWindowID = this.innerWindowID;
554 that._inChild = this._inChild;
556 LOG("window init: " + that.innerWindowID);
557 Services.obs.addObserver(that, "inner-window-destroyed", true);
559 LOG("startup called");
560 LOG("Host info: " + host + ":" + port);
562 that._readyState = kCONNECTING;
563 that._host = host;
564 that._port = port;
565 if (options !== undefined) {
566 if (options.useSecureTransport) {
567 that._ssl = 'ssl';
568 } else {
569 that._ssl = false;
570 }
571 that._binaryType = options.binaryType || that._binaryType;
572 }
574 LOG("SSL: " + that.ssl);
576 if (this._inChild) {
577 that._socketBridge = Cc["@mozilla.org/tcp-socket-child;1"]
578 .createInstance(Ci.nsITCPSocketChild);
579 that._socketBridge.sendOpen(that, host, port, !!that._ssl,
580 that._binaryType, this.useWin, this.useWin || this);
581 return that;
582 }
584 let transport = that._transport = this._createTransport(host, port, that._ssl);
585 transport.setEventSink(that, Services.tm.currentThread);
586 that._initStream(that._binaryType);
588 #ifdef MOZ_WIDGET_GONK
589 // Set _activeNetwork, which is only required for network statistics.
590 // Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is
591 // Gonk-specific.
592 let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
593 if (networkManager) {
594 that._activeNetwork = networkManager.active;
595 }
596 #endif
598 return that;
599 },
601 upgradeToSecure: function ts_upgradeToSecure() {
602 if (this._readyState !== kOPEN) {
603 throw new Error("Socket not open.");
604 }
605 if (this._ssl == 'ssl') {
606 // Already SSL
607 return;
608 }
610 this._ssl = 'ssl';
612 if (this._inChild) {
613 this._socketBridge.sendStartTLS();
614 return;
615 }
617 if (this._multiplexStream.count == 0) {
618 this._activateTLS();
619 } else {
620 this._waitingForStartTLS = true;
621 }
622 },
624 listen: function ts_listen(localPort, options, backlog) {
625 // in the testing case, init won't be called and
626 // hasPrivileges will be null. We want to proceed to test.
627 if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
628 throw new Error("TCPSocket does not have permission in this context.\n");
629 }
631 let that = new TCPServerSocket(this.useWin || this);
633 options = options || { binaryType : this.binaryType };
634 backlog = backlog || -1;
635 that.listen(localPort, options, backlog);
636 return that;
637 },
639 close: function ts_close() {
640 if (this._readyState === kCLOSED || this._readyState === kCLOSING)
641 return;
643 LOG("close called");
644 this._readyState = kCLOSING;
646 if (this._inChild) {
647 this._socketBridge.sendClose();
648 return;
649 }
651 if (!this._multiplexStream.count) {
652 this._socketOutputStream.close();
653 }
654 this._socketInputStream.close();
655 },
657 send: function ts_send(data, byteOffset, byteLength) {
658 if (this._readyState !== kOPEN) {
659 throw new Error("Socket not open.");
660 }
662 if (this._binaryType === "arraybuffer") {
663 byteLength = byteLength || data.byteLength;
664 }
666 if (this._inChild) {
667 this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber);
668 }
670 let length = this._binaryType === "arraybuffer" ? byteLength : data.length;
671 let newBufferedAmount = this.bufferedAmount + length;
672 let bufferFull = newBufferedAmount >= BUFFER_SIZE;
674 if (bufferFull) {
675 // If we buffered more than some arbitrary amount of data,
676 // (65535 right now) we should tell the caller so they can
677 // wait until ondrain is called if they so desire. Once all the
678 // buffered data has been written to the socket, ondrain is
679 // called.
680 this._waitingForDrain = true;
681 }
683 if (this._inChild) {
684 // In child, we just add buffer length to our bufferedAmount and let
685 // parent to update our bufferedAmount when data have been sent.
686 this._bufferedAmount = newBufferedAmount;
687 return !bufferFull;
688 }
690 let new_stream;
691 if (this._binaryType === "arraybuffer") {
692 new_stream = new ArrayBufferInputStream();
693 new_stream.setData(data, byteOffset, byteLength);
694 } else {
695 new_stream = new StringInputStream();
696 new_stream.setData(data, length);
697 }
699 if (this._waitingForStartTLS) {
700 // When we are waiting for starttls, new_stream is added to pendingData
701 // and will be appended to multiplexStream after tls had been set up.
702 this._pendingDataAfterStartTLS.push(new_stream);
703 } else {
704 this._multiplexStream.appendStream(new_stream);
705 }
707 this._ensureCopying();
709 #ifdef MOZ_WIDGET_GONK
710 // Collect transmitted amount for network statistics.
711 this._txBytes += length;
712 this._saveNetworkStats(false);
713 #endif
715 return !bufferFull;
716 },
718 suspend: function ts_suspend() {
719 if (this._inChild) {
720 this._socketBridge.sendSuspend();
721 return;
722 }
724 if (this._inputStreamPump) {
725 this._inputStreamPump.suspend();
726 } else {
727 ++this._suspendCount;
728 }
729 },
731 resume: function ts_resume() {
732 if (this._inChild) {
733 this._socketBridge.sendResume();
734 return;
735 }
737 if (this._inputStreamPump) {
738 this._inputStreamPump.resume();
739 } else if (this._suspendCount < 1) {
740 throw new Error(kRESUME_ERROR);
741 } else {
742 --this._suspendCount;
743 }
744 },
746 _maybeReportErrorAndCloseIfOpen: function(status) {
747 #ifdef MOZ_WIDGET_GONK
748 // Save network statistics once the connection is closed.
749 // For now this function is Gonk-specific.
750 this._saveNetworkStats(true);
751 #endif
753 // If we're closed, we've already reported the error or just don't need to
754 // report the error.
755 if (this._readyState === kCLOSED)
756 return;
757 this._readyState = kCLOSED;
759 if (!Components.isSuccessCode(status)) {
760 // Convert the status code to an appropriate error message. Raw constants
761 // are used inline in all cases for consistency. Some error codes are
762 // available in Components.results, some aren't. Network error codes are
763 // effectively stable, NSS error codes are officially not, but we have no
764 // symbolic way to dynamically resolve them anyways (other than an ability
765 // to determine the error class.)
766 let errName, errType;
767 // security module? (and this is an error)
768 if ((status & 0xff0000) === 0x5a0000) {
769 const nsINSSErrorsService = Ci.nsINSSErrorsService;
770 let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1']
771 .getService(nsINSSErrorsService);
772 let errorClass;
773 // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
774 // somehow not in the set of covered errors.
775 try {
776 errorClass = nssErrorsService.getErrorClass(status);
777 }
778 catch (ex) {
779 errorClass = 'SecurityProtocol';
780 }
781 switch (errorClass) {
782 case nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL:
783 errType = 'SecurityProtocol';
784 break;
785 case nsINSSErrorsService.ERROR_CLASS_BAD_CERT:
786 errType = 'SecurityCertificate';
787 break;
788 // no default is required; the platform impl automatically defaults to
789 // ERROR_CLASS_SSL_PROTOCOL.
790 }
792 // NSS_SEC errors (happen below the base value because of negative vals)
793 if ((status & 0xffff) <
794 Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) {
795 // The bases are actually negative, so in our positive numeric space, we
796 // need to subtract the base off our value.
797 let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) -
798 (status & 0xffff);
799 switch (nssErr) {
800 case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11)
801 errName = 'SecurityExpiredCertificateError';
802 break;
803 case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12)
804 errName = 'SecurityRevokedCertificateError';
805 break;
806 // per bsmith, we will be unable to tell these errors apart very soon,
807 // so it makes sense to just folder them all together already.
808 case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13)
809 case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20)
810 case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21)
811 case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36)
812 errName = 'SecurityUntrustedCertificateIssuerError';
813 break;
814 case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90)
815 errName = 'SecurityInadequateKeyUsageError';
816 break;
817 case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176)
818 errName = 'SecurityCertificateSignatureAlgorithmDisabledError';
819 break;
820 default:
821 errName = 'SecurityError';
822 break;
823 }
824 }
825 // NSS_SSL errors
826 else {
827 let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) -
828 (status & 0xffff);
829 switch (sslErr) {
830 case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3)
831 errName = 'SecurityNoCertificateError';
832 break;
833 case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4)
834 errName = 'SecurityBadCertificateError';
835 break;
836 case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8)
837 errName = 'SecurityUnsupportedCertificateTypeError';
838 break;
839 case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9)
840 errName = 'SecurityUnsupportedTLSVersionError';
841 break;
842 case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12)
843 errName = 'SecurityCertificateDomainMismatchError';
844 break;
845 default:
846 errName = 'SecurityError';
847 break;
848 }
849 }
850 }
851 // must be network
852 else {
853 errType = 'Network';
854 switch (status) {
855 // connect to host:port failed
856 case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13)
857 errName = 'ConnectionRefusedError';
858 break;
859 // network timeout error
860 case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14)
861 errName = 'NetworkTimeoutError';
862 break;
863 // hostname lookup failed
864 case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30)
865 errName = 'DomainNotFoundError';
866 break;
867 case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71)
868 errName = 'NetworkInterruptError';
869 break;
870 default:
871 errName = 'NetworkError';
872 break;
873 }
874 }
875 let err = createTCPError(this.useWin, errName, errType);
876 this.callListener("error", err);
877 }
878 this.callListener("close");
879 },
881 // nsITransportEventSink (Triggered by transport.setEventSink)
882 onTransportStatus: function ts_onTransportStatus(
883 transport, status, progress, max) {
884 if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) {
885 this._readyState = kOPEN;
886 this.callListener("open");
888 this._inputStreamPump = new InputStreamPump(
889 this._socketInputStream, -1, -1, 0, 0, false
890 );
892 while (this._suspendCount--) {
893 this._inputStreamPump.suspend();
894 }
896 this._inputStreamPump.asyncRead(this, null);
897 }
898 },
900 // nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait)
901 // Only used for detecting connection refused
902 onInputStreamReady: function ts_onInputStreamReady(input) {
903 try {
904 input.available();
905 } catch (e) {
906 // NS_ERROR_CONNECTION_REFUSED
907 this._maybeReportErrorAndCloseIfOpen(0x804B000C);
908 }
909 },
911 // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
912 onStartRequest: function ts_onStartRequest(request, context) {
913 },
915 // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead)
916 onStopRequest: function ts_onStopRequest(request, context, status) {
917 let buffered_output = this._multiplexStream.count !== 0;
919 this._inputStreamPump = null;
921 let statusIsError = !Components.isSuccessCode(status);
923 if (buffered_output && !statusIsError) {
924 // If we have some buffered output still, and status is not an
925 // error, the other side has done a half-close, but we don't
926 // want to be in the close state until we are done sending
927 // everything that was buffered. We also don't want to call onclose
928 // yet.
929 return;
930 }
932 // We call this even if there is no error.
933 this._maybeReportErrorAndCloseIfOpen(status);
934 },
936 // nsIStreamListener (Triggered by _inputStreamPump.asyncRead)
937 onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) {
938 if (this._binaryType === "arraybuffer") {
939 let buffer = new (this.useWin ? this.useWin.ArrayBuffer : ArrayBuffer)(count);
940 this._inputStreamBinary.readArrayBuffer(count, buffer);
941 this.callListener("data", buffer);
942 } else {
943 this.callListener("data", this._inputStreamScriptable.read(count));
944 }
946 #ifdef MOZ_WIDGET_GONK
947 // Collect received amount for network statistics.
948 this._rxBytes += count;
949 this._saveNetworkStats(false);
950 #endif
951 },
953 classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
955 classInfo: XPCOMUtils.generateCI({
956 classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
957 contractID: "@mozilla.org/tcp-socket;1",
958 classDescription: "Client TCP Socket",
959 interfaces: [
960 Ci.nsIDOMTCPSocket,
961 ],
962 flags: Ci.nsIClassInfo.DOM_OBJECT,
963 }),
965 QueryInterface: XPCOMUtils.generateQI([
966 Ci.nsIDOMTCPSocket,
967 Ci.nsITCPSocketInternal,
968 Ci.nsIDOMGlobalPropertyInitializer,
969 Ci.nsIObserver,
970 Ci.nsISupportsWeakReference
971 ])
972 }
974 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]);