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