Bug 784816 - Adding upgradeToSecure() to support startTLS in MozTCPSocket. r=mayhemer sr=sicking
authorPatrick Wang <kk1fff@patrickz.net>
Sun, 01 Sep 2013 03:09:17 +0800
changeset 146163 9edc229b7d09c689d870ca6be7a8a3dd655d6088
parent 146162 687e3a6868bf803ab7efefb0fa807a7821a446ad
child 146201 f320b8c034bd81f0a1ff29d0dd1fe6247ce42a32
push id25243
push userryanvm@gmail.com
push dateMon, 09 Sep 2013 19:52:36 +0000
treeherdermozilla-central@9edc229b7d09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmayhemer, sicking
bugs784816
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 784816 - Adding upgradeToSecure() to support startTLS in MozTCPSocket. r=mayhemer sr=sicking
dom/network/interfaces/nsIDOMTCPSocket.idl
dom/network/interfaces/nsITCPSocketChild.idl
dom/network/src/PTCPSocket.ipdl
dom/network/src/TCPSocket.js
dom/network/src/TCPSocketChild.cpp
dom/network/src/TCPSocketParent.cpp
dom/network/src/TCPSocketParent.h
dom/network/src/TCPSocketParentIntermediary.js
--- a/dom/network/interfaces/nsIDOMTCPSocket.idl
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -27,29 +27,29 @@ interface nsISocketTransport;
 //  Once bug 723206 will be fixed, this method could be replaced by
 //  arguments when instantiating a TCPSocket object. For example it will
 //  be possible to do (similarly to the WebSocket API):
 //    var s = new MozTCPSocket(host, port); 
 
 // Bug 797561 - Expose a server tcp socket API to web applications
 
 
-[scriptable, uuid(b7803a0b-4492-45ec-ac7a-3e29f6445fa4)]
+[scriptable, uuid(65f6d2c8-4be6-4695-958d-0735e8935289)]
 interface nsIDOMTCPSocket : nsISupports
 {
   /**
    * Create and return a socket object which will attempt to connect to
    * the given host and port.
    *
    * @param host The hostname of the server to connect to.
    * @param port The port to connect to.
    * @param options An object specifying one or more parameters which
    *                determine the details of the socket.
    *
-   *        useSSL: true to create an SSL socket. Defaults to false.
+   *        useSecureTransport: true to create an SSL socket. Defaults to false.
    *
    *        binaryType: "arraybuffer" to use ArrayBuffer
    *          instances in the ondata callback and as the argument
    *          to send. Defaults to "string", to use JavaScript strings.
    *
    * @return The new TCPSocket instance.
    */
   nsIDOMTCPSocket open(in DOMString host, in unsigned short port, [optional] in jsval options);
@@ -70,16 +70,21 @@ interface nsIDOMTCPSocket : nsISupports
    *                Pass -1 to use the default value.
    *
    * @return The new TCPServerSocket instance.
    */
   nsIDOMTCPServerSocket listen(in unsigned short localPort, [optional] in jsval options,
                                [optional] in unsigned short backlog);
 
   /**
+   * Enable secure on channel.
+   */
+  void upgradeToSecure();
+
+  /**
    * The host of this socket object.
    */
   readonly attribute DOMString host;
 
   /**
    * The port of this socket object.
    */
   readonly attribute unsigned short port;
--- a/dom/network/interfaces/nsITCPSocketChild.idl
+++ b/dom/network/interfaces/nsITCPSocketChild.idl
@@ -3,31 +3,32 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "domstubs.idl"
 
 interface nsITCPSocketInternal;
 interface nsIDOMWindow;
 
 // Interface to allow the content process socket to reach the IPC bridge.
-[scriptable, uuid(edf07a93-36a6-4574-8e23-40f64ab5f596)]
+[scriptable, uuid(ada5342d-6d45-4ff1-a7d3-6a4b150d0385)]
 interface nsITCPSocketChild : nsISupports
 {
   // Tell the chrome process to open a corresponding connection with the given parameters
   [implicit_jscontext]
   void open(in nsITCPSocketInternal socket, in DOMString host,
             in unsigned short port, in boolean ssl, in DOMString binaryType,
             in nsIDOMWindow window, in jsval windowVal);
 
   // Tell the chrome process to perform equivalent operations to all following methods
   [implicit_jscontext] 
   void send(in jsval data, in unsigned long byteOffset, in unsigned long byteLength);
   void resume();
   void suspend();
   void close();
+  void startTLS();
 
   /**
    * Initialize the TCP socket on the child side for IPC. It is called from the child side,
    * which is generated in receiving a notification of accepting any open request
    * on the parent side. We use single implementation that works on a child process 
    * as well as in the single process model.
    *
    * @param socket
--- a/dom/network/src/PTCPSocket.ipdl
+++ b/dom/network/src/PTCPSocket.ipdl
@@ -32,16 +32,17 @@ namespace net {
 //-------------------------------------------------------------------
 protocol PTCPSocket
 {
   manager PNecko;
 
 parent:
   Open(nsString host, uint16_t port, bool useSSL, nsString binaryType);
   Data(SendableData data);
+  StartTLS();
   Suspend();
   Resume();
   Close();
 
 child:
   Callback(nsString type, CallbackData data,
            nsString readyState, uint32_t bufferedAmount);
 
--- a/dom/network/src/TCPSocket.js
+++ b/dom/network/src/TCPSocket.js
@@ -152,16 +152,20 @@ TCPSocket.prototype = {
   _suspendCount: 0,
 
   // Reported parent process buffer
   _bufferedAmount: 0,
 
   // IPC socket actor
   _socketBridge: null,
 
+  // StartTLS
+  _waitingForStartTLS: false,
+  _pendingDataAfterStartTLS: [],
+
   // Public accessors.
   get readyState() {
     return this._readyState;
   },
   get binaryType() {
     return this._binaryType;
   },
   get host() {
@@ -205,29 +209,33 @@ TCPSocket.prototype = {
   },
   get onclose() {
     return this._onclose;
   },
   set onclose(f) {
     this._onclose = f;
   },
 
+  _activateTLS: function() {
+    let securityInfo = this._transport.securityInfo
+          .QueryInterface(Ci.nsISSLSocketControl);
+    securityInfo.StartTLS();
+  },
+
   // Helper methods.
   _createTransport: function ts_createTransport(host, port, sslMode) {
-    let options, optlen;
-    if (sslMode) {
-      options = [sslMode];
-      optlen = 1;
+    let options;
+    if (sslMode === 'ssl') {
+      options = ['ssl'];
     } else {
-      options = null;
-      optlen = 0;
+      options = ['starttls'];
     }
     return Cc["@mozilla.org/network/socket-transport-service;1"]
              .getService(Ci.nsISocketTransportService)
-             .createTransport(options, optlen, host, port, null);
+             .createTransport(options, 1, host, port, null);
   },
 
   _ensureCopying: function ts_ensureCopying() {
     let self = this;
     if (this._asyncCopierActive) {
       return;
     }
     this._asyncCopierActive = true;
@@ -243,16 +251,31 @@ TCPSocket.prototype = {
           // onStopRequest for inbound data.
           self._maybeReportErrorAndCloseIfOpen(status);
           return;
         }
 
         if (self._multiplexStream.count) {
           self._ensureCopying();
         } else {
+          // If we are waiting for initiating starttls, we can begin to
+          // activate tls now.
+          if (self._waitingForStartTLS && self._readyState == kOPEN) {
+            self._activateTLS();
+            self._waitingForStartTLS = false;
+            // If we have pending data, we should send them, or fire
+            // a drain event if we are waiting for it.
+            if (self._pendingDataAfterStartTLS.length > 0) {
+              while (self._pendingDataAfterStartTLS.length)
+                self._multiplexStream.appendStream(self._pendingDataAfterStartTLS.shift());
+              self._ensureCopying();
+              return;
+            }
+          }
+
           if (self._waitingForDrain) {
             self._waitingForDrain = false;
             self.callListener("drain");
           }
           if (self._readyState === kCLOSING) {
             self._socketOutputStream.close();
             self._readyState = kCLOSED;
             self.callListener("close");
@@ -430,17 +453,17 @@ TCPSocket.prototype = {
 
     LOG("startup called");
     LOG("Host info: " + host + ":" + port);
 
     that._readyState = kCONNECTING;
     that._host = host;
     that._port = port;
     if (options !== undefined) {
-      if (options.useSSL) {
+      if (options.useSecureTransport) {
           that._ssl = 'ssl';
       } else {
           that._ssl = false;
       }
       that._binaryType = options.binaryType || that._binaryType;
     }
 
     LOG("SSL: " + that.ssl);
@@ -453,17 +476,40 @@ TCPSocket.prototype = {
       return that;
     }
 
     let transport = that._transport = this._createTransport(host, port, that._ssl);
     transport.setEventSink(that, Services.tm.currentThread);
     that._initStream(that._binaryType);
     return that;
   },
-  
+
+  upgradeToSecure: function ts_upgradeToSecure() {
+    if (this._readyState !== kOPEN) {
+      throw new Error("Socket not open.");
+    }
+    if (this._ssl == 'ssl') {
+      // Already SSL
+      return;
+    }
+
+    this._ssl = 'ssl';
+
+    if (this._inChild) {
+      this._socketBridge.startTLS();
+      return;
+    }
+
+    if (this._multiplexStream.count == 0) {
+      this._activateTLS();
+    } else {
+      this._waitingForStartTLS = true;
+    }
+  },
+
   listen: function ts_listen(localPort, options, backlog) {
     if (!this.initWindowless())
       return null;
 
     // in the testing case, init won't be called and
     // hasPrivileges will be null. We want to proceed to test.
     if (this._hasPrivileges !== true && this._hasPrivileges !== null) {
       throw new Error("TCPSocket does not have permission in this context.\n");
@@ -519,17 +565,24 @@ TCPSocket.prototype = {
     let new_stream;
     if (this._binaryType === "arraybuffer") {
       new_stream = new ArrayBufferInputStream();
       new_stream.setData(data, byteOffset, byteLength);
     } else {
       new_stream = new StringInputStream();
       new_stream.setData(data, length);
     }
-    this._multiplexStream.appendStream(new_stream);
+
+    if (this._waitingForStartTLS) {
+      // When we are waiting for starttls, new_stream is added to pendingData
+      // and will be appended to multiplexStream after tls had been set up.
+      this._pendingDataAfterStartTLS.push(new_stream);
+    } else {
+      this._multiplexStream.appendStream(new_stream);
+    }
 
     if (newBufferedAmount >= BUFFER_SIZE) {
       // If we buffered more than some arbitrary amount of data,
       // (65535 right now) we should tell the caller so they can
       // wait until ondrain is called if they so desire. Once all the
       //buffered data has been written to the socket, ondrain is
       // called.
       this._waitingForDrain = true;
--- a/dom/network/src/TCPSocketChild.cpp
+++ b/dom/network/src/TCPSocketChild.cpp
@@ -154,16 +154,23 @@ TCPSocketChild::RecvCallback(const nsStr
   } else {
     MOZ_CRASH("Invalid callback type!");
   }
   NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
 NS_IMETHODIMP
+TCPSocketChild::StartTLS()
+{
+  SendStartTLS();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 TCPSocketChild::Suspend()
 {
   SendSuspend();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TCPSocketChild::Resume()
--- a/dom/network/src/TCPSocketParent.cpp
+++ b/dom/network/src/TCPSocketParent.cpp
@@ -111,16 +111,25 @@ NS_IMETHODIMP
 TCPSocketParent::InitJS(const JS::Value& aIntermediary, JSContext* aCx)
 {
   MOZ_ASSERT(aIntermediary.isObject());
   mIntermediaryObj = &aIntermediary.toObject();
   return NS_OK;
 }
 
 bool
+TCPSocketParent::RecvStartTLS()
+{
+  NS_ENSURE_TRUE(mSocket, true);
+  nsresult rv = mSocket->UpgradeToSecure();
+  NS_ENSURE_SUCCESS(rv, true);
+  return true;
+}
+
+bool
 TCPSocketParent::RecvSuspend()
 {
   NS_ENSURE_TRUE(mSocket, true);
   nsresult rv = mSocket->Suspend();
   NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
--- a/dom/network/src/TCPSocketParent.h
+++ b/dom/network/src/TCPSocketParent.h
@@ -42,16 +42,17 @@ public:
   NS_DECL_NSITCPSOCKETPARENT
   NS_IMETHOD_(nsrefcnt) Release() MOZ_OVERRIDE;
 
   TCPSocketParent() : mIntermediaryObj(nullptr) {}
 
   virtual bool RecvOpen(const nsString& aHost, const uint16_t& aPort,
                         const bool& useSSL, const nsString& aBinaryType);
 
+  virtual bool RecvStartTLS() MOZ_OVERRIDE;
   virtual bool RecvSuspend() MOZ_OVERRIDE;
   virtual bool RecvResume() MOZ_OVERRIDE;
   virtual bool RecvClose() MOZ_OVERRIDE;
   virtual bool RecvData(const SendableData& aData) MOZ_OVERRIDE;
   virtual bool RecvRequestDelete() MOZ_OVERRIDE;
 
 private:
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
--- a/dom/network/src/TCPSocketParentIntermediary.js
+++ b/dom/network/src/TCPSocketParentIntermediary.js
@@ -27,17 +27,17 @@ TCPSocketParentIntermediary.prototype = 
                                    socket.bufferedAmount);
         };
       }
     );
  },
 
   open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType) {
     let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
-    let socket = baseSocket.open(aHost, aPort, {useSSL: aUseSSL, binaryType: aBinaryType});
+    let socket = baseSocket.open(aHost, aPort, {useSecureTransport: aUseSSL, binaryType: aBinaryType});
     if (!socket)
       return null;
 
     // Handlers are set to the JS-implemented socket object on the parent side.
     this._setCallbacks(aParentSide, socket);
     return socket;
   },