dom/network/src/TCPSocket.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial