1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/network/src/TCPSocket.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,974 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cu = Components.utils; 1.13 +const Cr = Components.results; 1.14 +const CC = Components.Constructor; 1.15 + 1.16 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.17 +Cu.import("resource://gre/modules/Services.jsm"); 1.18 + 1.19 +const InputStreamPump = CC( 1.20 + "@mozilla.org/network/input-stream-pump;1", "nsIInputStreamPump", "init"), 1.21 + AsyncStreamCopier = CC( 1.22 + "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"), 1.23 + ScriptableInputStream = CC( 1.24 + "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"), 1.25 + BinaryInputStream = CC( 1.26 + "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"), 1.27 + StringInputStream = CC( 1.28 + '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'), 1.29 + ArrayBufferInputStream = CC( 1.30 + '@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'), 1.31 + MultiplexInputStream = CC( 1.32 + '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream'); 1.33 +const TCPServerSocket = CC( 1.34 + "@mozilla.org/tcp-server-socket;1", "nsITCPServerSocketInternal", "init"); 1.35 + 1.36 +const kCONNECTING = 'connecting'; 1.37 +const kOPEN = 'open'; 1.38 +const kCLOSING = 'closing'; 1.39 +const kCLOSED = 'closed'; 1.40 +const kRESUME_ERROR = 'Calling resume() on a connection that was not suspended.'; 1.41 + 1.42 +const BUFFER_SIZE = 65536; 1.43 +const NETWORK_STATS_THRESHOLD = 65536; 1.44 + 1.45 +// XXX we have no TCPError implementation right now because it's really hard to 1.46 +// do on b2g18. On mozilla-central we want a proper TCPError that ideally 1.47 +// sub-classes DOMError. Bug 867872 has been filed to implement this and 1.48 +// contains a documented TCPError.webidl that maps all the error codes we use in 1.49 +// this file to slightly more readable explanations. 1.50 +function createTCPError(aWindow, aErrorName, aErrorType) { 1.51 + return new (aWindow ? aWindow.DOMError : DOMError)(aErrorName); 1.52 +} 1.53 + 1.54 + 1.55 +/* 1.56 + * Debug logging function 1.57 + */ 1.58 + 1.59 +let debug = false; 1.60 +function LOG(msg) { 1.61 + if (debug) 1.62 + dump("TCPSocket: " + msg + "\n"); 1.63 +} 1.64 + 1.65 +/* 1.66 + * nsITCPSocketEvent object 1.67 + */ 1.68 + 1.69 +function TCPSocketEvent(type, sock, data) { 1.70 + this._type = type; 1.71 + this._target = sock; 1.72 + this._data = data; 1.73 +} 1.74 + 1.75 +TCPSocketEvent.prototype = { 1.76 + __exposedProps__: { 1.77 + type: 'r', 1.78 + target: 'r', 1.79 + data: 'r' 1.80 + }, 1.81 + get type() { 1.82 + return this._type; 1.83 + }, 1.84 + get target() { 1.85 + return this._target; 1.86 + }, 1.87 + get data() { 1.88 + return this._data; 1.89 + } 1.90 +} 1.91 + 1.92 +/* 1.93 + * nsIDOMTCPSocket object 1.94 + */ 1.95 + 1.96 +function TCPSocket() { 1.97 + this._readyState = kCLOSED; 1.98 + 1.99 + this._onopen = null; 1.100 + this._ondrain = null; 1.101 + this._ondata = null; 1.102 + this._onerror = null; 1.103 + this._onclose = null; 1.104 + 1.105 + this._binaryType = "string"; 1.106 + 1.107 + this._host = ""; 1.108 + this._port = 0; 1.109 + this._ssl = false; 1.110 + 1.111 + this.useWin = null; 1.112 +} 1.113 + 1.114 +TCPSocket.prototype = { 1.115 + __exposedProps__: { 1.116 + open: 'r', 1.117 + host: 'r', 1.118 + port: 'r', 1.119 + ssl: 'r', 1.120 + bufferedAmount: 'r', 1.121 + suspend: 'r', 1.122 + resume: 'r', 1.123 + close: 'r', 1.124 + send: 'r', 1.125 + readyState: 'r', 1.126 + binaryType: 'r', 1.127 + listen: 'r', 1.128 + onopen: 'rw', 1.129 + ondrain: 'rw', 1.130 + ondata: 'rw', 1.131 + onerror: 'rw', 1.132 + onclose: 'rw' 1.133 + }, 1.134 + // The binary type, "string" or "arraybuffer" 1.135 + _binaryType: null, 1.136 + 1.137 + // Internal 1.138 + _hasPrivileges: null, 1.139 + 1.140 + // Raw socket streams 1.141 + _transport: null, 1.142 + _socketInputStream: null, 1.143 + _socketOutputStream: null, 1.144 + 1.145 + // Input stream machinery 1.146 + _inputStreamPump: null, 1.147 + _inputStreamScriptable: null, 1.148 + _inputStreamBinary: null, 1.149 + 1.150 + // Output stream machinery 1.151 + _multiplexStream: null, 1.152 + _multiplexStreamCopier: null, 1.153 + 1.154 + _asyncCopierActive: false, 1.155 + _waitingForDrain: false, 1.156 + _suspendCount: 0, 1.157 + 1.158 + // Reported parent process buffer 1.159 + _bufferedAmount: 0, 1.160 + 1.161 + // IPC socket actor 1.162 + _socketBridge: null, 1.163 + 1.164 + // StartTLS 1.165 + _waitingForStartTLS: false, 1.166 + _pendingDataAfterStartTLS: [], 1.167 + 1.168 + // Used to notify when update bufferedAmount is updated. 1.169 + _onUpdateBufferedAmount: null, 1.170 + _trackingNumber: 0, 1.171 + 1.172 +#ifdef MOZ_WIDGET_GONK 1.173 + // Network statistics (Gonk-specific feature) 1.174 + _txBytes: 0, 1.175 + _rxBytes: 0, 1.176 + _appId: Ci.nsIScriptSecurityManager.NO_APP_ID, 1.177 + _activeNetwork: null, 1.178 +#endif 1.179 + 1.180 + // Public accessors. 1.181 + get readyState() { 1.182 + return this._readyState; 1.183 + }, 1.184 + get binaryType() { 1.185 + return this._binaryType; 1.186 + }, 1.187 + get host() { 1.188 + return this._host; 1.189 + }, 1.190 + get port() { 1.191 + return this._port; 1.192 + }, 1.193 + get ssl() { 1.194 + return this._ssl; 1.195 + }, 1.196 + get bufferedAmount() { 1.197 + if (this._inChild) { 1.198 + return this._bufferedAmount; 1.199 + } 1.200 + return this._multiplexStream.available(); 1.201 + }, 1.202 + get onopen() { 1.203 + return this._onopen; 1.204 + }, 1.205 + set onopen(f) { 1.206 + this._onopen = f; 1.207 + }, 1.208 + get ondrain() { 1.209 + return this._ondrain; 1.210 + }, 1.211 + set ondrain(f) { 1.212 + this._ondrain = f; 1.213 + }, 1.214 + get ondata() { 1.215 + return this._ondata; 1.216 + }, 1.217 + set ondata(f) { 1.218 + this._ondata = f; 1.219 + }, 1.220 + get onerror() { 1.221 + return this._onerror; 1.222 + }, 1.223 + set onerror(f) { 1.224 + this._onerror = f; 1.225 + }, 1.226 + get onclose() { 1.227 + return this._onclose; 1.228 + }, 1.229 + set onclose(f) { 1.230 + this._onclose = f; 1.231 + }, 1.232 + 1.233 + _activateTLS: function() { 1.234 + let securityInfo = this._transport.securityInfo 1.235 + .QueryInterface(Ci.nsISSLSocketControl); 1.236 + securityInfo.StartTLS(); 1.237 + }, 1.238 + 1.239 + // Helper methods. 1.240 + _createTransport: function ts_createTransport(host, port, sslMode) { 1.241 + let options; 1.242 + if (sslMode === 'ssl') { 1.243 + options = ['ssl']; 1.244 + } else { 1.245 + options = ['starttls']; 1.246 + } 1.247 + return Cc["@mozilla.org/network/socket-transport-service;1"] 1.248 + .getService(Ci.nsISocketTransportService) 1.249 + .createTransport(options, 1, host, port, null); 1.250 + }, 1.251 + 1.252 + _sendBufferedAmount: function ts_sendBufferedAmount() { 1.253 + if (this._onUpdateBufferedAmount) { 1.254 + this._onUpdateBufferedAmount(this.bufferedAmount, this._trackingNumber); 1.255 + } 1.256 + }, 1.257 + 1.258 + _ensureCopying: function ts_ensureCopying() { 1.259 + let self = this; 1.260 + if (this._asyncCopierActive) { 1.261 + return; 1.262 + } 1.263 + this._asyncCopierActive = true; 1.264 + this._multiplexStreamCopier.asyncCopy({ 1.265 + onStartRequest: function ts_output_onStartRequest() { 1.266 + }, 1.267 + onStopRequest: function ts_output_onStopRequest(request, context, status) { 1.268 + self._asyncCopierActive = false; 1.269 + self._multiplexStream.removeStream(0); 1.270 + self._sendBufferedAmount(); 1.271 + 1.272 + if (!Components.isSuccessCode(status)) { 1.273 + // Note that we can/will get an error here as well as in the 1.274 + // onStopRequest for inbound data. 1.275 + self._maybeReportErrorAndCloseIfOpen(status); 1.276 + return; 1.277 + } 1.278 + 1.279 + if (self._multiplexStream.count) { 1.280 + self._ensureCopying(); 1.281 + } else { 1.282 + // If we are waiting for initiating starttls, we can begin to 1.283 + // activate tls now. 1.284 + if (self._waitingForStartTLS && self._readyState == kOPEN) { 1.285 + self._activateTLS(); 1.286 + self._waitingForStartTLS = false; 1.287 + // If we have pending data, we should send them, or fire 1.288 + // a drain event if we are waiting for it. 1.289 + if (self._pendingDataAfterStartTLS.length > 0) { 1.290 + while (self._pendingDataAfterStartTLS.length) 1.291 + self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift()); 1.292 + self._ensureCopying(); 1.293 + return; 1.294 + } 1.295 + } 1.296 + 1.297 + // If we have a callback to update bufferedAmount, we let child to 1.298 + // decide whether ondrain should be dispatched. 1.299 + if (self._waitingForDrain && !self._onUpdateBufferedAmount) { 1.300 + self._waitingForDrain = false; 1.301 + self.callListener("drain"); 1.302 + } 1.303 + if (self._readyState === kCLOSING) { 1.304 + self._socketOutputStream.close(); 1.305 + self._readyState = kCLOSED; 1.306 + self.callListener("close"); 1.307 + } 1.308 + } 1.309 + } 1.310 + }, null); 1.311 + }, 1.312 + 1.313 + _initStream: function ts_initStream(binaryType) { 1.314 + this._binaryType = binaryType; 1.315 + this._socketInputStream = this._transport.openInputStream(0, 0, 0); 1.316 + this._socketOutputStream = this._transport.openOutputStream( 1.317 + Ci.nsITransport.OPEN_UNBUFFERED, 0, 0); 1.318 + 1.319 + // If the other side is not listening, we will 1.320 + // get an onInputStreamReady callback where available 1.321 + // raises to indicate the connection was refused. 1.322 + this._socketInputStream.asyncWait( 1.323 + this, this._socketInputStream.WAIT_CLOSURE_ONLY, 0, Services.tm.currentThread); 1.324 + 1.325 + if (this._binaryType === "arraybuffer") { 1.326 + this._inputStreamBinary = new BinaryInputStream(this._socketInputStream); 1.327 + } else { 1.328 + this._inputStreamScriptable = new ScriptableInputStream(this._socketInputStream); 1.329 + } 1.330 + 1.331 + this._multiplexStream = new MultiplexInputStream(); 1.332 + 1.333 + this._multiplexStreamCopier = new AsyncStreamCopier( 1.334 + this._multiplexStream, 1.335 + this._socketOutputStream, 1.336 + // (nsSocketTransport uses gSocketTransportService) 1.337 + Cc["@mozilla.org/network/socket-transport-service;1"] 1.338 + .getService(Ci.nsIEventTarget), 1.339 + /* source buffered */ true, /* sink buffered */ false, 1.340 + BUFFER_SIZE, /* close source*/ false, /* close sink */ false); 1.341 + }, 1.342 + 1.343 +#ifdef MOZ_WIDGET_GONK 1.344 + // Helper method for collecting network statistics. 1.345 + // Note this method is Gonk-specific. 1.346 + _saveNetworkStats: function ts_saveNetworkStats(enforce) { 1.347 + if (this._txBytes <= 0 && this._rxBytes <= 0) { 1.348 + // There is no traffic at all. No need to save statistics. 1.349 + return; 1.350 + } 1.351 + 1.352 + // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy 1.353 + // only when the total amount exceeds the predefined threshold value. 1.354 + // The purpose is to avoid too much overhead for collecting statistics. 1.355 + let totalBytes = this._txBytes + this._rxBytes; 1.356 + if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) { 1.357 + return; 1.358 + } 1.359 + 1.360 + let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"] 1.361 + .getService(Ci.nsINetworkStatsServiceProxy); 1.362 + if (!nssProxy) { 1.363 + LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available."); 1.364 + return; 1.365 + } 1.366 + nssProxy.saveAppStats(this._appId, this._activeNetwork, Date.now(), 1.367 + this._rxBytes, this._txBytes, false); 1.368 + 1.369 + // Reset the counters once the statistics is saved to NetworkStatsServiceProxy. 1.370 + this._txBytes = this._rxBytes = 0; 1.371 + }, 1.372 + // End of helper method for network statistics. 1.373 +#endif 1.374 + 1.375 + callListener: function ts_callListener(type, data) { 1.376 + if (!this["on" + type]) 1.377 + return; 1.378 + 1.379 + this["on" + type].call(null, new TCPSocketEvent(type, this, data || "")); 1.380 + }, 1.381 + 1.382 + /* nsITCPSocketInternal methods */ 1.383 + callListenerError: function ts_callListenerError(type, name) { 1.384 + // XXX we're not really using TCPError at this time, so there's only a name 1.385 + // attribute to pass. 1.386 + this.callListener(type, createTCPError(this.useWin, name)); 1.387 + }, 1.388 + 1.389 + callListenerData: function ts_callListenerString(type, data) { 1.390 + this.callListener(type, data); 1.391 + }, 1.392 + 1.393 + callListenerArrayBuffer: function ts_callListenerArrayBuffer(type, data) { 1.394 + this.callListener(type, data); 1.395 + }, 1.396 + 1.397 + callListenerVoid: function ts_callListenerVoid(type) { 1.398 + this.callListener(type); 1.399 + }, 1.400 + 1.401 + /** 1.402 + * This method is expected to be called by TCPSocketChild to update child's 1.403 + * readyState. 1.404 + */ 1.405 + updateReadyState: function ts_updateReadyState(readyState) { 1.406 + if (!this._inChild) { 1.407 + LOG("Calling updateReadyState in parent, which should only be called " + 1.408 + "in child"); 1.409 + return; 1.410 + } 1.411 + this._readyState = readyState; 1.412 + }, 1.413 + 1.414 + updateBufferedAmount: function ts_updateBufferedAmount(bufferedAmount, trackingNumber) { 1.415 + if (trackingNumber != this._trackingNumber) { 1.416 + LOG("updateBufferedAmount is called but trackingNumber is not matched " + 1.417 + "parent's trackingNumber: " + trackingNumber + ", child's trackingNumber: " + 1.418 + this._trackingNumber); 1.419 + return; 1.420 + } 1.421 + this._bufferedAmount = bufferedAmount; 1.422 + if (bufferedAmount == 0) { 1.423 + if (this._waitingForDrain) { 1.424 + this._waitingForDrain = false; 1.425 + this.callListener("drain"); 1.426 + } 1.427 + } else { 1.428 + LOG("bufferedAmount is updated but haven't reaches zero. bufferedAmount: " + 1.429 + bufferedAmount); 1.430 + } 1.431 + }, 1.432 + 1.433 + createAcceptedParent: function ts_createAcceptedParent(transport, binaryType) { 1.434 + let that = new TCPSocket(); 1.435 + that._transport = transport; 1.436 + that._initStream(binaryType); 1.437 + 1.438 + // ReadyState is kOpen since accepted transport stream has already been connected 1.439 + that._readyState = kOPEN; 1.440 + that._inputStreamPump = new InputStreamPump(that._socketInputStream, -1, -1, 0, 0, false); 1.441 + that._inputStreamPump.asyncRead(that, null); 1.442 + 1.443 + return that; 1.444 + }, 1.445 + 1.446 + createAcceptedChild: function ts_createAcceptedChild(socketChild, binaryType, windowObject) { 1.447 + let that = new TCPSocket(); 1.448 + 1.449 + that._binaryType = binaryType; 1.450 + that._inChild = true; 1.451 + that._readyState = kOPEN; 1.452 + socketChild.setSocketAndWindow(that, windowObject); 1.453 + that._socketBridge = socketChild; 1.454 + 1.455 + return that; 1.456 + }, 1.457 + 1.458 + setAppId: function ts_setAppId(appId) { 1.459 +#ifdef MOZ_WIDGET_GONK 1.460 + this._appId = appId; 1.461 +#else 1.462 + // Do nothing because _appId only exists on Gonk-specific platform. 1.463 +#endif 1.464 + }, 1.465 + 1.466 + setOnUpdateBufferedAmountHandler: function(aFunction) { 1.467 + if (typeof(aFunction) == 'function') { 1.468 + this._onUpdateBufferedAmount = aFunction; 1.469 + } else { 1.470 + throw new Error("only function can be passed to " + 1.471 + "setOnUpdateBufferedAmountHandler"); 1.472 + } 1.473 + }, 1.474 + 1.475 + /** 1.476 + * Handle the requst of sending data and update trackingNumber from 1.477 + * child. 1.478 + * This function is expected to be called by TCPSocketChild. 1.479 + */ 1.480 + onRecvSendFromChild: function(data, byteOffset, byteLength, trackingNumber) { 1.481 + this._trackingNumber = trackingNumber; 1.482 + this.send(data, byteOffset, byteLength); 1.483 + }, 1.484 + 1.485 + /* end nsITCPSocketInternal methods */ 1.486 + 1.487 + initWindowless: function ts_initWindowless() { 1.488 + try { 1.489 + return Services.prefs.getBoolPref("dom.mozTCPSocket.enabled"); 1.490 + } catch (e) { 1.491 + // no pref means return false 1.492 + return false; 1.493 + } 1.494 + }, 1.495 + 1.496 + init: function ts_init(aWindow) { 1.497 + if (!this.initWindowless()) 1.498 + return null; 1.499 + 1.500 + let principal = aWindow.document.nodePrincipal; 1.501 + let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] 1.502 + .getService(Ci.nsIScriptSecurityManager); 1.503 + 1.504 + let perm = principal == secMan.getSystemPrincipal() 1.505 + ? Ci.nsIPermissionManager.ALLOW_ACTION 1.506 + : Services.perms.testExactPermissionFromPrincipal(principal, "tcp-socket"); 1.507 + 1.508 + this._hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION; 1.509 + 1.510 + let util = aWindow.QueryInterface( 1.511 + Ci.nsIInterfaceRequestor 1.512 + ).getInterface(Ci.nsIDOMWindowUtils); 1.513 + 1.514 + this.useWin = XPCNativeWrapper.unwrap(aWindow); 1.515 + this.innerWindowID = util.currentInnerWindowID; 1.516 + LOG("window init: " + this.innerWindowID); 1.517 + }, 1.518 + 1.519 + observe: function(aSubject, aTopic, aData) { 1.520 + if (aTopic == "inner-window-destroyed") { 1.521 + let wId = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data; 1.522 + if (wId == this.innerWindowID) { 1.523 + LOG("inner-window-destroyed: " + this.innerWindowID); 1.524 + 1.525 + // This window is now dead, so we want to clear the callbacks 1.526 + // so that we don't get a "can't access dead object" when the 1.527 + // underlying stream goes to tell us that we are closed 1.528 + this.onopen = null; 1.529 + this.ondrain = null; 1.530 + this.ondata = null; 1.531 + this.onerror = null; 1.532 + this.onclose = null; 1.533 + 1.534 + this.useWin = null; 1.535 + 1.536 + // Clean up our socket 1.537 + this.close(); 1.538 + } 1.539 + } 1.540 + }, 1.541 + 1.542 + // nsIDOMTCPSocket 1.543 + open: function ts_open(host, port, options) { 1.544 + this._inChild = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime) 1.545 + .processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; 1.546 + LOG("content process: " + (this._inChild ? "true" : "false")); 1.547 + 1.548 + // in the testing case, init won't be called and 1.549 + // hasPrivileges will be null. We want to proceed to test. 1.550 + if (this._hasPrivileges !== true && this._hasPrivileges !== null) { 1.551 + throw new Error("TCPSocket does not have permission in this context.\n"); 1.552 + } 1.553 + let that = new TCPSocket(); 1.554 + 1.555 + that.useWin = this.useWin; 1.556 + that.innerWindowID = this.innerWindowID; 1.557 + that._inChild = this._inChild; 1.558 + 1.559 + LOG("window init: " + that.innerWindowID); 1.560 + Services.obs.addObserver(that, "inner-window-destroyed", true); 1.561 + 1.562 + LOG("startup called"); 1.563 + LOG("Host info: " + host + ":" + port); 1.564 + 1.565 + that._readyState = kCONNECTING; 1.566 + that._host = host; 1.567 + that._port = port; 1.568 + if (options !== undefined) { 1.569 + if (options.useSecureTransport) { 1.570 + that._ssl = 'ssl'; 1.571 + } else { 1.572 + that._ssl = false; 1.573 + } 1.574 + that._binaryType = options.binaryType || that._binaryType; 1.575 + } 1.576 + 1.577 + LOG("SSL: " + that.ssl); 1.578 + 1.579 + if (this._inChild) { 1.580 + that._socketBridge = Cc["@mozilla.org/tcp-socket-child;1"] 1.581 + .createInstance(Ci.nsITCPSocketChild); 1.582 + that._socketBridge.sendOpen(that, host, port, !!that._ssl, 1.583 + that._binaryType, this.useWin, this.useWin || this); 1.584 + return that; 1.585 + } 1.586 + 1.587 + let transport = that._transport = this._createTransport(host, port, that._ssl); 1.588 + transport.setEventSink(that, Services.tm.currentThread); 1.589 + that._initStream(that._binaryType); 1.590 + 1.591 +#ifdef MOZ_WIDGET_GONK 1.592 + // Set _activeNetwork, which is only required for network statistics. 1.593 + // Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is 1.594 + // Gonk-specific. 1.595 + let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); 1.596 + if (networkManager) { 1.597 + that._activeNetwork = networkManager.active; 1.598 + } 1.599 +#endif 1.600 + 1.601 + return that; 1.602 + }, 1.603 + 1.604 + upgradeToSecure: function ts_upgradeToSecure() { 1.605 + if (this._readyState !== kOPEN) { 1.606 + throw new Error("Socket not open."); 1.607 + } 1.608 + if (this._ssl == 'ssl') { 1.609 + // Already SSL 1.610 + return; 1.611 + } 1.612 + 1.613 + this._ssl = 'ssl'; 1.614 + 1.615 + if (this._inChild) { 1.616 + this._socketBridge.sendStartTLS(); 1.617 + return; 1.618 + } 1.619 + 1.620 + if (this._multiplexStream.count == 0) { 1.621 + this._activateTLS(); 1.622 + } else { 1.623 + this._waitingForStartTLS = true; 1.624 + } 1.625 + }, 1.626 + 1.627 + listen: function ts_listen(localPort, options, backlog) { 1.628 + // in the testing case, init won't be called and 1.629 + // hasPrivileges will be null. We want to proceed to test. 1.630 + if (this._hasPrivileges !== true && this._hasPrivileges !== null) { 1.631 + throw new Error("TCPSocket does not have permission in this context.\n"); 1.632 + } 1.633 + 1.634 + let that = new TCPServerSocket(this.useWin || this); 1.635 + 1.636 + options = options || { binaryType : this.binaryType }; 1.637 + backlog = backlog || -1; 1.638 + that.listen(localPort, options, backlog); 1.639 + return that; 1.640 + }, 1.641 + 1.642 + close: function ts_close() { 1.643 + if (this._readyState === kCLOSED || this._readyState === kCLOSING) 1.644 + return; 1.645 + 1.646 + LOG("close called"); 1.647 + this._readyState = kCLOSING; 1.648 + 1.649 + if (this._inChild) { 1.650 + this._socketBridge.sendClose(); 1.651 + return; 1.652 + } 1.653 + 1.654 + if (!this._multiplexStream.count) { 1.655 + this._socketOutputStream.close(); 1.656 + } 1.657 + this._socketInputStream.close(); 1.658 + }, 1.659 + 1.660 + send: function ts_send(data, byteOffset, byteLength) { 1.661 + if (this._readyState !== kOPEN) { 1.662 + throw new Error("Socket not open."); 1.663 + } 1.664 + 1.665 + if (this._binaryType === "arraybuffer") { 1.666 + byteLength = byteLength || data.byteLength; 1.667 + } 1.668 + 1.669 + if (this._inChild) { 1.670 + this._socketBridge.sendSend(data, byteOffset, byteLength, ++this._trackingNumber); 1.671 + } 1.672 + 1.673 + let length = this._binaryType === "arraybuffer" ? byteLength : data.length; 1.674 + let newBufferedAmount = this.bufferedAmount + length; 1.675 + let bufferFull = newBufferedAmount >= BUFFER_SIZE; 1.676 + 1.677 + if (bufferFull) { 1.678 + // If we buffered more than some arbitrary amount of data, 1.679 + // (65535 right now) we should tell the caller so they can 1.680 + // wait until ondrain is called if they so desire. Once all the 1.681 + // buffered data has been written to the socket, ondrain is 1.682 + // called. 1.683 + this._waitingForDrain = true; 1.684 + } 1.685 + 1.686 + if (this._inChild) { 1.687 + // In child, we just add buffer length to our bufferedAmount and let 1.688 + // parent to update our bufferedAmount when data have been sent. 1.689 + this._bufferedAmount = newBufferedAmount; 1.690 + return !bufferFull; 1.691 + } 1.692 + 1.693 + let new_stream; 1.694 + if (this._binaryType === "arraybuffer") { 1.695 + new_stream = new ArrayBufferInputStream(); 1.696 + new_stream.setData(data, byteOffset, byteLength); 1.697 + } else { 1.698 + new_stream = new StringInputStream(); 1.699 + new_stream.setData(data, length); 1.700 + } 1.701 + 1.702 + if (this._waitingForStartTLS) { 1.703 + // When we are waiting for starttls, new_stream is added to pendingData 1.704 + // and will be appended to multiplexStream after tls had been set up. 1.705 + this._pendingDataAfterStartTLS.push(new_stream); 1.706 + } else { 1.707 + this._multiplexStream.appendStream(new_stream); 1.708 + } 1.709 + 1.710 + this._ensureCopying(); 1.711 + 1.712 +#ifdef MOZ_WIDGET_GONK 1.713 + // Collect transmitted amount for network statistics. 1.714 + this._txBytes += length; 1.715 + this._saveNetworkStats(false); 1.716 +#endif 1.717 + 1.718 + return !bufferFull; 1.719 + }, 1.720 + 1.721 + suspend: function ts_suspend() { 1.722 + if (this._inChild) { 1.723 + this._socketBridge.sendSuspend(); 1.724 + return; 1.725 + } 1.726 + 1.727 + if (this._inputStreamPump) { 1.728 + this._inputStreamPump.suspend(); 1.729 + } else { 1.730 + ++this._suspendCount; 1.731 + } 1.732 + }, 1.733 + 1.734 + resume: function ts_resume() { 1.735 + if (this._inChild) { 1.736 + this._socketBridge.sendResume(); 1.737 + return; 1.738 + } 1.739 + 1.740 + if (this._inputStreamPump) { 1.741 + this._inputStreamPump.resume(); 1.742 + } else if (this._suspendCount < 1) { 1.743 + throw new Error(kRESUME_ERROR); 1.744 + } else { 1.745 + --this._suspendCount; 1.746 + } 1.747 + }, 1.748 + 1.749 + _maybeReportErrorAndCloseIfOpen: function(status) { 1.750 +#ifdef MOZ_WIDGET_GONK 1.751 + // Save network statistics once the connection is closed. 1.752 + // For now this function is Gonk-specific. 1.753 + this._saveNetworkStats(true); 1.754 +#endif 1.755 + 1.756 + // If we're closed, we've already reported the error or just don't need to 1.757 + // report the error. 1.758 + if (this._readyState === kCLOSED) 1.759 + return; 1.760 + this._readyState = kCLOSED; 1.761 + 1.762 + if (!Components.isSuccessCode(status)) { 1.763 + // Convert the status code to an appropriate error message. Raw constants 1.764 + // are used inline in all cases for consistency. Some error codes are 1.765 + // available in Components.results, some aren't. Network error codes are 1.766 + // effectively stable, NSS error codes are officially not, but we have no 1.767 + // symbolic way to dynamically resolve them anyways (other than an ability 1.768 + // to determine the error class.) 1.769 + let errName, errType; 1.770 + // security module? (and this is an error) 1.771 + if ((status & 0xff0000) === 0x5a0000) { 1.772 + const nsINSSErrorsService = Ci.nsINSSErrorsService; 1.773 + let nssErrorsService = Cc['@mozilla.org/nss_errors_service;1'] 1.774 + .getService(nsINSSErrorsService); 1.775 + let errorClass; 1.776 + // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is 1.777 + // somehow not in the set of covered errors. 1.778 + try { 1.779 + errorClass = nssErrorsService.getErrorClass(status); 1.780 + } 1.781 + catch (ex) { 1.782 + errorClass = 'SecurityProtocol'; 1.783 + } 1.784 + switch (errorClass) { 1.785 + case nsINSSErrorsService.ERROR_CLASS_SSL_PROTOCOL: 1.786 + errType = 'SecurityProtocol'; 1.787 + break; 1.788 + case nsINSSErrorsService.ERROR_CLASS_BAD_CERT: 1.789 + errType = 'SecurityCertificate'; 1.790 + break; 1.791 + // no default is required; the platform impl automatically defaults to 1.792 + // ERROR_CLASS_SSL_PROTOCOL. 1.793 + } 1.794 + 1.795 + // NSS_SEC errors (happen below the base value because of negative vals) 1.796 + if ((status & 0xffff) < 1.797 + Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE)) { 1.798 + // The bases are actually negative, so in our positive numeric space, we 1.799 + // need to subtract the base off our value. 1.800 + let nssErr = Math.abs(nsINSSErrorsService.NSS_SEC_ERROR_BASE) - 1.801 + (status & 0xffff); 1.802 + switch (nssErr) { 1.803 + case 11: // SEC_ERROR_EXPIRED_CERTIFICATE, sec(11) 1.804 + errName = 'SecurityExpiredCertificateError'; 1.805 + break; 1.806 + case 12: // SEC_ERROR_REVOKED_CERTIFICATE, sec(12) 1.807 + errName = 'SecurityRevokedCertificateError'; 1.808 + break; 1.809 + // per bsmith, we will be unable to tell these errors apart very soon, 1.810 + // so it makes sense to just folder them all together already. 1.811 + case 13: // SEC_ERROR_UNKNOWN_ISSUER, sec(13) 1.812 + case 20: // SEC_ERROR_UNTRUSTED_ISSUER, sec(20) 1.813 + case 21: // SEC_ERROR_UNTRUSTED_CERT, sec(21) 1.814 + case 36: // SEC_ERROR_CA_CERT_INVALID, sec(36) 1.815 + errName = 'SecurityUntrustedCertificateIssuerError'; 1.816 + break; 1.817 + case 90: // SEC_ERROR_INADEQUATE_KEY_USAGE, sec(90) 1.818 + errName = 'SecurityInadequateKeyUsageError'; 1.819 + break; 1.820 + case 176: // SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED, sec(176) 1.821 + errName = 'SecurityCertificateSignatureAlgorithmDisabledError'; 1.822 + break; 1.823 + default: 1.824 + errName = 'SecurityError'; 1.825 + break; 1.826 + } 1.827 + } 1.828 + // NSS_SSL errors 1.829 + else { 1.830 + let sslErr = Math.abs(nsINSSErrorsService.NSS_SSL_ERROR_BASE) - 1.831 + (status & 0xffff); 1.832 + switch (sslErr) { 1.833 + case 3: // SSL_ERROR_NO_CERTIFICATE, ssl(3) 1.834 + errName = 'SecurityNoCertificateError'; 1.835 + break; 1.836 + case 4: // SSL_ERROR_BAD_CERTIFICATE, ssl(4) 1.837 + errName = 'SecurityBadCertificateError'; 1.838 + break; 1.839 + case 8: // SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE, ssl(8) 1.840 + errName = 'SecurityUnsupportedCertificateTypeError'; 1.841 + break; 1.842 + case 9: // SSL_ERROR_UNSUPPORTED_VERSION, ssl(9) 1.843 + errName = 'SecurityUnsupportedTLSVersionError'; 1.844 + break; 1.845 + case 12: // SSL_ERROR_BAD_CERT_DOMAIN, ssl(12) 1.846 + errName = 'SecurityCertificateDomainMismatchError'; 1.847 + break; 1.848 + default: 1.849 + errName = 'SecurityError'; 1.850 + break; 1.851 + } 1.852 + } 1.853 + } 1.854 + // must be network 1.855 + else { 1.856 + errType = 'Network'; 1.857 + switch (status) { 1.858 + // connect to host:port failed 1.859 + case 0x804B000C: // NS_ERROR_CONNECTION_REFUSED, network(13) 1.860 + errName = 'ConnectionRefusedError'; 1.861 + break; 1.862 + // network timeout error 1.863 + case 0x804B000E: // NS_ERROR_NET_TIMEOUT, network(14) 1.864 + errName = 'NetworkTimeoutError'; 1.865 + break; 1.866 + // hostname lookup failed 1.867 + case 0x804B001E: // NS_ERROR_UNKNOWN_HOST, network(30) 1.868 + errName = 'DomainNotFoundError'; 1.869 + break; 1.870 + case 0x804B0047: // NS_ERROR_NET_INTERRUPT, network(71) 1.871 + errName = 'NetworkInterruptError'; 1.872 + break; 1.873 + default: 1.874 + errName = 'NetworkError'; 1.875 + break; 1.876 + } 1.877 + } 1.878 + let err = createTCPError(this.useWin, errName, errType); 1.879 + this.callListener("error", err); 1.880 + } 1.881 + this.callListener("close"); 1.882 + }, 1.883 + 1.884 + // nsITransportEventSink (Triggered by transport.setEventSink) 1.885 + onTransportStatus: function ts_onTransportStatus( 1.886 + transport, status, progress, max) { 1.887 + if (status === Ci.nsISocketTransport.STATUS_CONNECTED_TO) { 1.888 + this._readyState = kOPEN; 1.889 + this.callListener("open"); 1.890 + 1.891 + this._inputStreamPump = new InputStreamPump( 1.892 + this._socketInputStream, -1, -1, 0, 0, false 1.893 + ); 1.894 + 1.895 + while (this._suspendCount--) { 1.896 + this._inputStreamPump.suspend(); 1.897 + } 1.898 + 1.899 + this._inputStreamPump.asyncRead(this, null); 1.900 + } 1.901 + }, 1.902 + 1.903 + // nsIAsyncInputStream (Triggered by _socketInputStream.asyncWait) 1.904 + // Only used for detecting connection refused 1.905 + onInputStreamReady: function ts_onInputStreamReady(input) { 1.906 + try { 1.907 + input.available(); 1.908 + } catch (e) { 1.909 + // NS_ERROR_CONNECTION_REFUSED 1.910 + this._maybeReportErrorAndCloseIfOpen(0x804B000C); 1.911 + } 1.912 + }, 1.913 + 1.914 + // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead) 1.915 + onStartRequest: function ts_onStartRequest(request, context) { 1.916 + }, 1.917 + 1.918 + // nsIRequestObserver (Triggered by _inputStreamPump.asyncRead) 1.919 + onStopRequest: function ts_onStopRequest(request, context, status) { 1.920 + let buffered_output = this._multiplexStream.count !== 0; 1.921 + 1.922 + this._inputStreamPump = null; 1.923 + 1.924 + let statusIsError = !Components.isSuccessCode(status); 1.925 + 1.926 + if (buffered_output && !statusIsError) { 1.927 + // If we have some buffered output still, and status is not an 1.928 + // error, the other side has done a half-close, but we don't 1.929 + // want to be in the close state until we are done sending 1.930 + // everything that was buffered. We also don't want to call onclose 1.931 + // yet. 1.932 + return; 1.933 + } 1.934 + 1.935 + // We call this even if there is no error. 1.936 + this._maybeReportErrorAndCloseIfOpen(status); 1.937 + }, 1.938 + 1.939 + // nsIStreamListener (Triggered by _inputStreamPump.asyncRead) 1.940 + onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) { 1.941 + if (this._binaryType === "arraybuffer") { 1.942 + let buffer = new (this.useWin ? this.useWin.ArrayBuffer : ArrayBuffer)(count); 1.943 + this._inputStreamBinary.readArrayBuffer(count, buffer); 1.944 + this.callListener("data", buffer); 1.945 + } else { 1.946 + this.callListener("data", this._inputStreamScriptable.read(count)); 1.947 + } 1.948 + 1.949 +#ifdef MOZ_WIDGET_GONK 1.950 + // Collect received amount for network statistics. 1.951 + this._rxBytes += count; 1.952 + this._saveNetworkStats(false); 1.953 +#endif 1.954 + }, 1.955 + 1.956 + classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), 1.957 + 1.958 + classInfo: XPCOMUtils.generateCI({ 1.959 + classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), 1.960 + contractID: "@mozilla.org/tcp-socket;1", 1.961 + classDescription: "Client TCP Socket", 1.962 + interfaces: [ 1.963 + Ci.nsIDOMTCPSocket, 1.964 + ], 1.965 + flags: Ci.nsIClassInfo.DOM_OBJECT, 1.966 + }), 1.967 + 1.968 + QueryInterface: XPCOMUtils.generateQI([ 1.969 + Ci.nsIDOMTCPSocket, 1.970 + Ci.nsITCPSocketInternal, 1.971 + Ci.nsIDOMGlobalPropertyInitializer, 1.972 + Ci.nsIObserver, 1.973 + Ci.nsISupportsWeakReference 1.974 + ]) 1.975 +} 1.976 + 1.977 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TCPSocket]);