dom/network/src/TCPSocket.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial