dom/network/src/TCPSocket.js

changeset 0
6474c204b198
     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]);

mercurial