diff -r 000000000000 -r 6474c204b198 dom/network/src/TCPSocket.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/network/src/TCPSocket.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,974 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; +const Cr = Components.results; +const CC = Components.Constructor; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +const InputStreamPump = CC( + "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"), + AsyncStreamCopier = CC( + "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"), + ScriptableInputStream = CC( + "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"), + BinaryInputStream = CC( + "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"), + StringInputStream = CC( + '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'), + ArrayBufferInputStream = CC( + '@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'), + MultiplexInputStream = CC( + '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream'); +const TCPServerSocket = CC( + "@mozilla.org/tcp-server-socket;1", "nsITCPServerSocketInternal", "init"); + +const kCONNECTING = 'connecting'; +const kOPEN = 'open'; +const kCLOSING = 'closing'; +const kCLOSED = 'closed'; +const kRESUME_ERROR = 'Calling resume() on a connection that was not suspended.'; + +const BUFFER_SIZE = 65536; +const NETWORK_STATS_THRESHOLD = 65536; + +// XXX we have no TCPError implementation right now because it's really hard to +// do on b2g18. On mozilla-central we want a proper TCPError that ideally +// sub-classes DOMError. Bug 867872 has been filed to implement this and +// contains a documented TCPError.webidl that maps all the error codes we use in +// this file to slightly more readable explanations. +function createTCPError(aWindow, aErrorName, aErrorType) { + return new (aWindow ? aWindow.DOMError : DOMError)(aErrorName); +} + + +/* + * Debug logging function + */ + +let debug = false; +function LOG(msg) { + if (debug) + dump("TCPSocket: " + msg + "\n"); +} + +/* + * nsITCPSocketEvent object + */ + +function TCPSocketEvent(type, sock, data) { + this._type = type; + this._target = sock; + this._data = data; +} + +TCPSocketEvent.prototype = { + __exposedProps__: { + type: 'r', + target: 'r', + data: 'r' + }, + get type() { + return this._type; + }, + get target() { + return this._target; + }, + get data() { + return this._data; + } +} + +/* + * nsIDOMTCPSocket object + */ + +function TCPSocket() { + this._readyState = kCLOSED; + + this._onopen = null; + this._ondrain = null; + this._ondata = null; + this._onerror = null; + this._onclose = null; + + this._binaryType = "string"; + + this._host = ""; + this._port = 0; + this._ssl = false; + + this.useWin = null; +} + +TCPSocket.prototype = { + __exposedProps__: { + open: 'r', + host: 'r', + port: 'r', + ssl: 'r', + bufferedAmount: 'r', + suspend: 'r', + resume: 'r', + close: 'r', + send: 'r', + readyState: 'r', + binaryType: 'r', + listen: 'r', + onopen: 'rw', + ondrain: 'rw', + ondata: 'rw', + onerror: 'rw', + onclose: 'rw' + }, + // The binary type, "string" or "arraybuffer" + _binaryType: null, + + // Internal + _hasPrivileges: null, + + // Raw socket streams + _transport: null, + _socketInputStream: null, + _socketOutputStream: null, + + // Input stream machinery + _inputStreamPump: null, + _inputStreamScriptable: null, + _inputStreamBinary: null, + + // Output stream machinery + _multiplexStream: null, + _multiplexStreamCopier: null, + + _asyncCopierActive: false, + _waitingForDrain: false, + _suspendCount: 0, + + // Reported parent process buffer + _bufferedAmount: 0, + + // IPC socket actor + _socketBridge: null, + + // StartTLS + _waitingForStartTLS: false, + _pendingDataAfterStartTLS: [], + + // Used to notify when update bufferedAmount is updated. + _onUpdateBufferedAmount: null, + _trackingNumber: 0, + +#ifdef MOZ_WIDGET_GONK + // Network statistics (Gonk-specific feature) + _txBytes: 0, + _rxBytes: 0, + _appId: Ci.nsIScriptSecurityManager.NO_APP_ID, + _activeNetwork: null, +#endif + + // Public accessors. + get readyState() { + return this._readyState; + }, + get binaryType() { + return this._binaryType; + }, + get host() { + return this._host; + }, + get port() { + return this._port; + }, + get ssl() { + return this._ssl; + }, + get bufferedAmount() { + if (this._inChild) { + return this._bufferedAmount; + } + return this._multiplexStream.available(); + }, + get onopen() { + return this._onopen; + }, + set onopen(f) { + this._onopen = f; + }, + get ondrain() { + return this._ondrain; + }, + set ondrain(f) { + this._ondrain = f; + }, + get ondata() { + return this._ondata; + }, + set ondata(f) { + this._ondata = f; + }, + get onerror() { + return this._onerror; + }, + set onerror(f) { + this._onerror = f; + }, + get onclose() { + return this._onclose; + }, + set onclose(f) { + this._onclose = f; + }, + + _activateTLS: function() { + let securityInfo = this._transport.securityInfo + .QueryInterface(Ci.nsISSLSocketControl); + securityInfo.StartTLS(); + }, + + // Helper methods. + _createTransport: function ts_createTransport(host, port, sslMode) { + let options; + if (sslMode === 'ssl') { + options = ['ssl']; + } else { + options = ['starttls']; + } + return Cc["@mozilla.org/network/socket-transport-service;1"] + .getService(Ci.nsISocketTransportService) + .createTransport(options, 1, host, port, null); + }, + + _sendBufferedAmount: function ts_sendBufferedAmount() { + if (this._onUpdateBufferedAmount) { + this._onUpdateBufferedAmount(this.bufferedAmount, this._trackingNumber); + } + }, + + _ensureCopying: function ts_ensureCopying() { + let self = this; + if (this._asyncCopierActive) { + return; + } + this._asyncCopierActive = true; + this._multiplexStreamCopier.asyncCopy({ + onStartRequest: function ts_output_onStartRequest() { + }, + onStopRequest: function ts_output_onStopRequest(request, context, status) { + self._asyncCopierActive = false; + self._multiplexStream.removeStream(0); + self._sendBufferedAmount(); + + if (!Components.isSuccessCode(status)) { + // Note that we can/will get an error here as well as in the + // onStopRequest for inbound data. + self._maybeReportErrorAndCloseIfOpen(status); + return; + } + + if (self._multiplexStream.count) { + self._ensureCopying(); + } else { + // If we are waiting for initiating starttls, we can begin to + // activate tls now. + if (self._waitingForStartTLS && self._readyState == kOPEN) { + self._activateTLS(); + self._waitingForStartTLS = false; + // If we have pending data, we should send them, or fire + // a drain event if we are waiting for it. + if (self._pendingDataAfterStartTLS.length > 0) { + while (self._pendingDataAfterStartTLS.length) + self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift()); + self._ensureCopying(); + return; + } + } + + // If we have a callback to update bufferedAmount, we let child to + // decide whether ondrain should be dispatched. + if (self._waitingForDrain && !self._onUpdateBufferedAmount) { + self._waitingForDrain = false; + self.callListener("drain"); + } + if (self._readyState === kCLOSING) { + self._socketOutputStream.close(); + self._readyState = kCLOSED; + self.callListener("close"); + } + } + } + }, null); + }, + + _initStream: function ts_initStream(binaryType) { + this._binaryType = binaryType; + this._socketInputStream = this._transport.openInputStream(0, 0, 0); + this._socketOutputStream = this._transport.openOutputStream( + Ci.nsITransport.OPEN_UNBUFFERED, 0, 0); + + // If the other side is not listening, we will + // get an onInputStreamReady callback where available + // raises to indicate the connection was refused. + this._socketInputStream.asyncWait( + this, this._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread); + + if (this._binaryType === "arraybuffer") { + this._inputStreamBinary = new BinaryInputStream(this._socketInputStream); + } else { + this._inputStreamScriptable = new ScriptableInputStream(this._socketInputStream); + } + + this._multiplexStream = new MultiplexInputStream(); + + this._multiplexStreamCopier = new AsyncStreamCopier( + this._multiplexStream, + this._socketOutputStream, + // (nsSocketTransport uses gSocketTransportService) + Cc["@mozilla.org/network/socket-transport-service;1"] + .getService(Ci.nsIEventTarget), + /* source buffered */ true, /* sink buffered */ false, + BUFFER_SIZE, /* close source*/ false, /* close sink */ false); + }, + +#ifdef MOZ_WIDGET_GONK + // Helper method for collecting network statistics. + // Note this method is Gonk-specific. + _saveNetworkStats: function ts_saveNetworkStats(enforce) { + if (this._txBytes <= 0 && this._rxBytes <= 0) { + // There is no traffic at all. No need to save statistics. + return; + } + + // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy + // only when the total amount exceeds the predefined threshold value. + // The purpose is to avoid too much overhead for collecting statistics. + let totalBytes = this._txBytes + this._rxBytes; + if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) { + return; + } + + let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"] + .getService(Ci.nsINetworkStatsServiceProxy); + if (!nssProxy) { + LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available."); + return; + } + nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(), + this._rxBytes, this._txBytes, false); + + // Reset the counters once the statistics is saved to NetworkStatsServiceProxy. + this._txBytes = this._rxBytes = 0; + }, + // End of helper method for network statistics. +#endif + + callListener: function ts_callListener(type, data) { + if (!this["on" + type]) + return; + + this["on" + type].call(null, new TCPSocketEvent(type, this, data || "")); + }, + + /* nsITCPSocketInternal methods */ + callListenerError: function ts_callListenerError(type, name) { + // XXX we're not really using TCPError at this time, so there's only a name + // attribute to pass. + this.callListener(type, createTCPError(this.useWin, name)); + }, + + callListenerData: function ts_callListenerString(type, data) { + this.callListener(type, data); + }, + + callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) { + this.callListener(type, data); + }, + + callListenerVoid: function ts_callListenerVoid(type) { + this.callListener(type); + }, + + /** + * This method is expected to be called by TCPSocketChild to update child's + * readyState. + */ + updateReadyState: function ts_updateReadyState(readyState) { + if (!this._inChild) { + LOG("Calling updateReadyState in parent, which should only be called " + + "in child"); + return; + } + this._readyState = readyState; + }, + + updateBufferedAmount: function ts_updateBufferedAmount(bufferedAmount, trackingNumber) { + if (trackingNumber != this._trackingNumber) { + LOG("updateBufferedAmount is called but trackingNumber is not matched " + + "parent's trackingNumber: " + trackingNumber + ", child's trackingNumber: " + + this._trackingNumber); + return; + } + this._bufferedAmount = bufferedAmount; + if (bufferedAmount == 0) { + if (this._waitingForDrain) { + this._waitingForDrain = false; + this.callListener("drain"); + } + } else { + LOG("bufferedAmount is updated but haven't reaches zero. bufferedAmount: " + + bufferedAmount); + } + }, + + createAcceptedParent: function ts_createAcceptedParent(transport, binaryType) { + let that = new TCPSocket(); + that._transport = transport; + that._initStream(binaryType); + + // ReadyState is kOpen since accepted transport stream has already been connected + that._readyState = kOPEN; + that._inputStreamPump = new InputStreamPump(that._socketInputStream, -1, -1, 0, 0, false); + that._inputStreamPump.asyncRead(that, null); + + return that; + }, + + createAcceptedChild: function ts_createAcceptedChild(socketChild, binaryType, windowObject) { + let that = new TCPSocket(); + + that._binaryType = binaryType; + that._inChild = true; + that._readyState = kOPEN; + socketChild.setSocketAndWindow(that, windowObject); + that._socketBridge = socketChild; + + return that; + }, + + setAppId: function ts_setAppId(appId) { +#ifdef MOZ_WIDGET_GONK + this._appId = appId; +#else + // Do nothing because _appId only exists on Gonk-specific platform. +#endif + }, + + setOnUpdateBufferedAmountHandler: function(aFunction) { + if (typeof(aFunction) == 'function') { + this._onUpdateBufferedAmount = aFunction; + } else { + throw new Error("only function can be passed to " + + "setOnUpdateBufferedAmountHandler"); + } + }, + + /** + * Handle the requst of sending data and update trackingNumber from + * child. + * This function is expected to be called by TCPSocketChild. + */ + onRecvSendFromChild: function(data, byteOffset, byteLength, trackingNumber) { + this._trackingNumber = trackingNumber; + this.send(data, byteOffset, byteLength); + }, + + /* end nsITCPSocketInternal methods */ + + initWindowless: function ts_initWindowless() { + try { + return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled"); + } catch (e) { + // no pref means return false + return false; + } + }, + + init: function ts_init(aWindow) { + if (!this.initWindowless()) + return null; + + let principal = aWindow.document.nodePrincipal; + let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] + .getService(Ci.nsIScriptSecurityManager); + + let perm = principal == secMan.getSystemPrincipal() + ? Ci.nsIPermissionManager.ALLOW_ACTION + : Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket"); + + this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION; + + let util = aWindow.QueryInterface( + Ci.nsIInterfaceRequestor + ).getInterface(Ci.nsIDOMWindowUtils); + + this.useWin = XPCNativeWrapper.unwrap(aWindow); + this.innerWindowID = util.currentInnerWindowID; + LOG("window init: " + this.innerWindowID); + }, + + observe: function(aSubject, aTopic, aData) { + if (aTopic == "inner-window-destroyed") { + let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; + if (wId == this.innerWindowID) { + LOG("inner-window-destroyed: " + this.innerWindowID); + + // This window is now dead, so we want to clear the callbacks + // so that we don't get a "can't access dead object" when the + // underlying stream goes to tell us that we are closed + this.onopen = null; + this.ondrain = null; + this.ondata = null; + this.onerror = null; + this.onclose = null; + + this.useWin = null; + + // Clean up our socket + this.close(); + } + } + }, + + // nsIDOMTCPSocket + open: function ts_open(host, port, options) { + this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) + .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; + LOG("content process: " + (this._inChild ? "true" : "false")); + + // in the testing case, init won't be called and + // hasPrivileges will be null. We want to proceed to test. + if (this._hasPrivileges !== true && this._hasPrivileges !== null) { + throw new Error("TCPSocket does not have permission in this context.\n"); + } + let that = new TCPSocket(); + + that.useWin = this.useWin; + that.innerWindowID = this.innerWindowID; + that._inChild = this._inChild; + + LOG("window init: " + that.innerWindowID); + Services.obs.addObserver(that, "inner-window-destroyed", true); + + LOG("startup called"); + LOG("Host info: " + host + ":" + port); + + that._readyState = kCONNECTING; + that._host = host; + that._port = port; + if (options !== undefined) { + if (options.useSecureTransport) { + that._ssl = 'ssl'; + } else { + that._ssl = false; + } + that._binaryType = options.binaryType || that._binaryType; + } + + LOG("SSL: " + that.ssl); + + if (this._inChild) { + that._socketBridge = Cc["@mozilla.org/tcp-socket-child;1"] + .createInstance(Ci.nsITCPSocketChild); + that._socketBridge.sendOpen(that, host, port, !!that._ssl, + that._binaryType, this.useWin, this.useWin || this); + return that; + } + + let transport = that._transport = this._createTransport(host, port, that._ssl); + transport.setEventSink(that, Services.tm.currentThread); + that._initStream(that._binaryType); + +#ifdef MOZ_WIDGET_GONK + // Set _activeNetwork, which is only required for network statistics. + // Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is + // Gonk-specific. + let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); + if (networkManager) { + that._activeNetwork = networkManager.active; + } +#endif + + return that; + }, + + upgradeToSecure: function ts_upgradeToSecure() { + if (this._readyState !== kOPEN) { + throw new Error("Socket not open."); + } + if (this._ssl == 'ssl') { + // Already SSL + return; + } + + this._ssl = 'ssl'; + + if (this._inChild) { + this._socketBridge.sendStartTLS(); + return; + } + + if (this._multiplexStream.count == 0) { + this._activateTLS(); + } else { + this._waitingForStartTLS = true; + } + }, + + listen: function ts_listen(localPort, options, backlog) { + // in the testing case, init won't be called and + // hasPrivileges will be null. We want to proceed to test. + if (this._hasPrivileges !== true && this._hasPrivileges !== null) { + throw new Error("TCPSocket does not have permission in this context.\n"); + } + + let that = new TCPServerSocket(this.useWin || this); + + options = options || { binaryType : this.binaryType }; + backlog = backlog || -1; + that.listen(localPort, options, backlog); + return that; + }, + + close: function ts_close() { + if (this._readyState === kCLOSED || this._readyState === kCLOSING) + return; + + LOG("close called"); + this._readyState = kCLOSING; + + if (this._inChild) { + this._socketBridge.sendClose(); + return; + } + + if (!this._multiplexStream.count) { + this._socketOutputStream.close(); + } + this._socketInputStream.close(); + }, + + send: function ts_send(data, byteOffset, byteLength) { + if (this._readyState !== kOPEN) { + throw new Error("Socket not open."); + } + + if (this._binaryType === "arraybuffer") { + byteLength = byteLength || data.byteLength; + } + + if (this._inChild) { + this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber); + } + + let length = this._binaryType === "arraybuffer" ? byteLength : data.length; + let newBufferedAmount = this.bufferedAmount + length; + let bufferFull = newBufferedAmount >= BUFFER_SIZE; + + if (bufferFull) { + // If we buffered more than some arbitrary amount of data, + // (65535 right now) we should tell the caller so they can + // wait until ondrain is called if they so desire. Once all the + // buffered data has been written to the socket, ondrain is + // called. + this._waitingForDrain = true; + } + + if (this._inChild) { + // In child, we just add buffer length to our bufferedAmount and let + // parent to update our bufferedAmount when data have been sent. + this._bufferedAmount = newBufferedAmount; + return !bufferFull; + } + + let new_stream; + if (this._binaryType === "arraybuffer") { + new_stream = new ArrayBufferInputStream(); + new_stream.setData(data, byteOffset, byteLength); + } else { + new_stream = new StringInputStream(); + new_stream.setData(data, length); + } + + if (this._waitingForStartTLS) { + // When we are waiting for starttls, new_stream is added to pendingData + // and will be appended to multiplexStream after tls had been set up. + this._pendingDataAfterStartTLS.push(new_stream); + } else { + this._multiplexStream.appendStream(new_stream); + } + + this._ensureCopying(); + +#ifdef MOZ_WIDGET_GONK + // Collect transmitted amount for network statistics. + this._txBytes += length; + this._saveNetworkStats(false); +#endif + + return !bufferFull; + }, + + suspend: function ts_suspend() { + if (this._inChild) { + this._socketBridge.sendSuspend(); + return; + } + + if (this._inputStreamPump) { + this._inputStreamPump.suspend(); + } else { + ++this._suspendCount; + } + }, + + resume: function ts_resume() { + if (this._inChild) { + this._socketBridge.sendResume(); + return; + } + + if (this._inputStreamPump) { + this._inputStreamPump.resume(); + } else if (this._suspendCount < 1) { + throw new Error(kRESUME_ERROR); + } else { + --this._suspendCount; + } + }, + + _maybeReportErrorAndCloseIfOpen: function(status) { +#ifdef MOZ_WIDGET_GONK + // Save network statistics once the connection is closed. + // For now this function is Gonk-specific. + this._saveNetworkStats(true); +#endif + + // If we're closed, we've already reported the error or just don't need to + // report the error. + if (this._readyState === kCLOSED) + return; + this._readyState = kCLOSED; + + if (!Components.isSuccessCode(status)) { + // Convert the status code to an appropriate error message. Raw constants + // are used inline in all cases for consistency. Some error codes are + // available in Components.results, some aren't. Network error codes are + // effectively stable, NSS error codes are officially not, but we have no + // symbolic way to dynamically resolve them anyways (other than an ability + // to determine the error class.) + let errName, errType; + // security module? (and this is an error) + if ((status & 0xff0000) === 0x5a0000) { + const nsINSSErrorsService = Ci.nsINSSErrorsService; + let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1'] + .getService(nsINSSErrorsService); + let errorClass; + // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is + // somehow not in the set of covered errors. + try { + errorClass = nssErrorsService.getErrorClass(status); + } + catch (ex) { + errorClass = 'SecurityProtocol'; + } + switch (errorClass) { + case nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL: + errType = 'SecurityProtocol'; + break; + case nsINSSErrorsService.ERROR_CLASS_BAD_CERT: + errType = 'SecurityCertificate'; + break; + // no default is required; the platform impl automatically defaults to + // ERROR_CLASS_SSL_PROTOCOL. + } + + // NSS_SEC errors (happen below the base value because of negative vals) + if ((status & 0xffff) < + Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) { + // The bases are actually negative, so in our positive numeric space, we + // need to subtract the base off our value. + let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) - + (status & 0xffff); + switch (nssErr) { + case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11) + errName = 'SecurityExpiredCertificateError'; + break; + case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12) + errName = 'SecurityRevokedCertificateError'; + break; + // per bsmith, we will be unable to tell these errors apart very soon, + // so it makes sense to just folder them all together already. + case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13) + case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20) + case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21) + case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36) + errName = 'SecurityUntrustedCertificateIssuerError'; + break; + case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90) + errName = 'SecurityInadequateKeyUsageError'; + break; + case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176) + errName = 'SecurityCertificateSignatureAlgorithmDisabledError'; + break; + default: + errName = 'SecurityError'; + break; + } + } + // NSS_SSL errors + else { + let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) - + (status & 0xffff); + switch (sslErr) { + case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3) + errName = 'SecurityNoCertificateError'; + break; + case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4) + errName = 'SecurityBadCertificateError'; + break; + case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8) + errName = 'SecurityUnsupportedCertificateTypeError'; + break; + case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9) + errName = 'SecurityUnsupportedTLSVersionError'; + break; + case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12) + errName = 'SecurityCertificateDomainMismatchError'; + break; + default: + errName = 'SecurityError'; + break; + } + } + } + // must be network + else { + errType = 'Network'; + switch (status) { + // connect to host:port failed + case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13) + errName = 'ConnectionRefusedError'; + break; + // network timeout error + case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14) + errName = 'NetworkTimeoutError'; + break; + // hostname lookup failed + case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30) + errName = 'DomainNotFoundError'; + break; + case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71) + errName = 'NetworkInterruptError'; + break; + default: + errName = 'NetworkError'; + break; + } + } + let err = createTCPError(this.useWin, errName, errType); + this.callListener("error", err); + } + this.callListener("close"); + }, + + // nsITransportEventSink (Triggered by transport.setEventSink) + onTransportStatus: function ts_onTransportStatus( + transport, status, progress, max) { + if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) { + this._readyState = kOPEN; + this.callListener("open"); + + this._inputStreamPump = new InputStreamPump( + this._socketInputStream, -1, -1, 0, 0, false + ); + + while (this._suspendCount--) { + this._inputStreamPump.suspend(); + } + + this._inputStreamPump.asyncRead(this, null); + } + }, + + // nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait) + // Only used for detecting connection refused + onInputStreamReady: function ts_onInputStreamReady(input) { + try { + input.available(); + } catch (e) { + // NS_ERROR_CONNECTION_REFUSED + this._maybeReportErrorAndCloseIfOpen(0x804B000C); + } + }, + + // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead) + onStartRequest: function ts_onStartRequest(request, context) { + }, + + // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead) + onStopRequest: function ts_onStopRequest(request, context, status) { + let buffered_output = this._multiplexStream.count !== 0; + + this._inputStreamPump = null; + + let statusIsError = !Components.isSuccessCode(status); + + if (buffered_output && !statusIsError) { + // If we have some buffered output still, and status is not an + // error, the other side has done a half-close, but we don't + // want to be in the close state until we are done sending + // everything that was buffered. We also don't want to call onclose + // yet. + return; + } + + // We call this even if there is no error. + this._maybeReportErrorAndCloseIfOpen(status); + }, + + // nsIStreamListener (Triggered by _inputStreamPump.asyncRead) + onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) { + if (this._binaryType === "arraybuffer") { + let buffer = new (this.useWin ? this.useWin.ArrayBuffer : ArrayBuffer)(count); + this._inputStreamBinary.readArrayBuffer(count, buffer); + this.callListener("data", buffer); + } else { + this.callListener("data", this._inputStreamScriptable.read(count)); + } + +#ifdef MOZ_WIDGET_GONK + // Collect received amount for network statistics. + this._rxBytes += count; + this._saveNetworkStats(false); +#endif + }, + + classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), + + classInfo: XPCOMUtils.generateCI({ + classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), + contractID: "@mozilla.org/tcp-socket;1", + classDescription: "Client TCP Socket", + interfaces: [ + Ci.nsIDOMTCPSocket, + ], + flags: Ci.nsIClassInfo.DOM_OBJECT, + }), + + QueryInterface: XPCOMUtils.generateQI([ + Ci.nsIDOMTCPSocket, + Ci.nsITCPSocketInternal, + Ci.nsIDOMGlobalPropertyInitializer, + Ci.nsIObserver, + Ci.nsISupportsWeakReference + ]) +} + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]);