Bug 955606 - IRC ping does not properly send a timestamp, r=aleth,florian.
authorPatrick Cloke <clokep@gmail.com>
Fri, 18 Oct 2013 17:59:39 -0400
changeset 17311 fe1660b5139132dc358fc8184ab2522b2cd6070c
parent 17310 a2e3a113185da50bb18c44d533a46e179a31ca49
child 17312 03a1d87c844ec29708d1a2340283beba06a75997
push id1151
push userbugzilla@standard8.plus.com
push dateMon, 03 Feb 2014 22:50:32 +0000
treeherdercomm-aurora@267d8e9143d5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaleth, florian
bugs955606
Bug 955606 - IRC ping does not properly send a timestamp, r=aleth,florian.
chat/locales/en-US/irc.properties
chat/protocols/irc/irc.js
chat/protocols/irc/ircBase.jsm
chat/protocols/irc/ircCTCP.jsm
chat/protocols/irc/ircCommands.jsm
--- a/chat/locales/en-US/irc.properties
+++ b/chat/locales/en-US/irc.properties
@@ -28,21 +28,16 @@ options.server=Server
 options.port=Port
 options.ssl=Use SSL
 options.encoding=Character Set
 options.quitMessage=Quit message
 options.partMessage=Part message
 options.showServerTab=Show messages from the server
 options.alternateNicks=Alternate nicks
 
-# LOCALIZATION NOTE (ctcp.ping): Semi-colon list of plural forms.
-#  See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
-#   %1$S is the nickname of the user who was pinged.
-#   #2 is the delay (in seconds).
-ctcp.ping=Ping reply from %1$S in #2 second.;Ping reply from %1$S in #2 seconds.
 # LOCALIZATION NOTE (ctcp.version):
 #   %1$S is the nickname of the user whose version was requested.
 #   %2$S is the version response from the client.
 ctcp.version=%1$S is using "%2$S".
 # LOCALIZATION NOTE (ctcp.time):
 #   %1$S is the nickname of the user whose time was requested.
 #   %2$S is the time response.
 ctcp.time=The time for %1$S is %2$S.
@@ -139,16 +134,22 @@ message.unknownNick=%S is an unknown nic
 #    channel key (password).
 message.channelKeyAdded=%1$S changed the channel password to %2$S.
 message.channelKeyRemoved=%S removed the channel password.
 #    This will be followed by a list of ban masks.
 message.banMasks=Users connected from the following locations are banned from %S:
 message.noBanMasks=There are no banned locations for %S.
 message.banMaskAdded=Users connected from locations matching %1$S have been banned by %2$S.
 message.banMaskRemoved=Users connected from locations matching %1$S are no longer banned by %2$S.
+# LOCALIZATION NOTE (message.ping): Semi-colon list of plural forms.
+#  See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+#   %1$S is the nickname of the user or the server that was pinged.
+#   #2 is the delay (in milliseconds).
+message.ping=Ping reply from %1$S in #2 millisecond.;Ping reply from %1$S in #2 milliseconds.
+
 
 # LOCALIZATION NOTE (error.*):
 #    These are shown as error messages in the server tab.
 #    %S is the channel name.
 error.noChannel=There is no channel: %S.
 error.tooManyChannels=Cannot join %S; you've joined too many channels.
 #    %1$S is your new nick, %2$S is the kill message from the server.
 error.nickCollision=Nick already in use, changing nick to %1$S [%2$S].
--- a/chat/protocols/irc/irc.js
+++ b/chat/protocols/irc/irc.js
@@ -6,16 +6,19 @@ const {classes: Cc, interfaces: Ci, resu
 
 Cu.import("resource:///modules/imXPCOMUtils.jsm");
 Cu.import("resource:///modules/imServices.jsm");
 Cu.import("resource:///modules/ircUtils.jsm");
 Cu.import("resource:///modules/ircHandlers.jsm");
 Cu.import("resource:///modules/jsProtoHelper.jsm");
 Cu.import("resource:///modules/socket.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+  "resource://gre/modules/PluralForm.jsm");
+
 /*
  * Parses a raw IRC message into an object (see section 2.3 of RFC 2812). This
  * returns an object with the following fields:
  *   rawMessage The initial message string received without any processing.
  *   command    A string that is the command or response code.
  *   params     An array of strings for the parameters. The last parameter is
  *              stripped of its : prefix.
  * If the message is from a user:
@@ -631,18 +634,20 @@ ircSocket.prototype = {
   // Although RFCs 1459 and 2812 explicitly say that \r\n is the message
   // separator, some networks (euIRC) only send \n.
   delimiter: /\r?\n/,
   connectTimeout: 60, // Failure to connect after 1 minute
   readWriteTimeout: 300, // Failure when no data for 5 minutes
   _converter: null,
 
   sendPing: function() {
-    // Send a ping using the current timestamp as a payload.
-    this._account.sendMessage("PING", Date.now());
+    // Send a ping using the current timestamp as a payload prefixed with
+    // an underscore to signify this was an "automatic" PING (used to avoid
+    // socket timeouts).
+    this._account.sendMessage("PING", "_" + Date.now());
   },
 
   _initCharsetConverter: function() {
     this._converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                         .createInstance(Ci.nsIScriptableUnicodeConverter);
     try {
       this._converter.charset = this._account._encoding;
     } catch (e) {
@@ -1208,16 +1213,44 @@ ircAccount.prototype = {
       return true;
     }
   
     this.LOG(aOldNick + " is already in use, trying " + newNick);
     this.sendMessage("NICK", newNick); // Nick message.
     return true;
   },
 
+  handlePingReply: function(aSource, aPongTime) {
+    // Received PING response, display to the user.
+    let sentTime = new Date(parseInt(aPongTime, 10));
+
+    // The received timestamp is invalid.
+    if (isNaN(sentTime)) {
+      this.WARN(aMessage.servername +
+                " returned an invalid timestamp from a PING: " + aPongTime);
+      return false;
+    }
+
+    // Find the delay in milliseconds.
+    let delay = Date.now() - sentTime;
+
+    // If the delay is negative or greater than 1 minute, something is
+    // feeding us a crazy value. Don't display this to the user.
+    if (delay < 0 || 60 * 1000 < delay) {
+      this.WARN(aMessage.servername +
+                " returned an invalid delay from a PING: " + delay);
+      return false;
+    }
+
+    let msg = PluralForm.get(delay, _("message.ping", aSource))
+                        .replace("#2", delay);
+    this.getConversation(aSource).writeMessage(aSource, msg, {system: true});
+    return true;
+  },
+
   countBytes: function(aStr) {
     // Assume that if it's not UTF-8 then each character is 1 byte.
     if (this._encoding != "UTF-8")
       return aStr.length;
 
     // Count the number of bytes in a UTF-8 encoded string.
     function charCodeToByteCount(c) {
       // UTF-8 stores:
--- a/chat/protocols/irc/ircBase.jsm
+++ b/chat/protocols/irc/ircBase.jsm
@@ -271,19 +271,26 @@ var ircBase = {
     "PING": function(aMessage) {
       // PING <server1> [ <server2> ]
       // Keep the connection alive.
       this.sendMessage("PONG", aMessage.params[0]);
       return true;
     },
     "PONG": function(aMessage) {
       // PONG <server> [ <server2> ]
+      let pongTime = aMessage.params[1];
+
       // Ping to keep the connection alive.
-      this._socket.cancelDisconnectTimer();
-      return true;
+      if (pongTime.startsWith("_")) {
+        this._socket.cancelDisconnectTimer();
+        return true;
+      }
+      // Otherwise, the ping was from a user command.
+      else
+        return this.handlePingReply(aMessage.servername, pongTime);
     },
     "PRIVMSG": function(aMessage) {
       // PRIVMSG <msgtarget> <text to be sent>
       // Display message in conversation
       return privmsg(this, aMessage);
     },
     "QUIT": function(aMessage) {
       // QUIT [ < Quit Message> ]
--- a/chat/protocols/irc/ircCTCP.jsm
+++ b/chat/protocols/irc/ircCTCP.jsm
@@ -12,21 +12,16 @@ const EXPORTED_SYMBOLS = ["ircCTCP", "ct
 
 const Cu = Components.utils;
 
 Cu.import("resource:///modules/imServices.jsm");
 Cu.import("resource:///modules/imXPCOMUtils.jsm");
 Cu.import("resource:///modules/ircHandlers.jsm");
 Cu.import("resource:///modules/ircUtils.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "PluralForm", function() {
-  Cu.import("resource://gre/modules/PluralForm.jsm");
-  return PluralForm;
-});
-
 // Split into a CTCP message which is a single command and a single parameter:
 //   <command> " " <parameter>
 // The high level dequote is to unescape \001 in the message content.
 function CTCPMessage(aMessage, aRawCTCPMessage) {
   let message = aMessage;
   message.ctcp = {};
   message.ctcp.rawMessage = aRawCTCPMessage;
 
@@ -142,46 +137,27 @@ var ctcpBase = {
     // Returns the user's full name, and idle time.
     "FINGER": function(aMessage) false,
 
     // Dynamic master index of what a client knows.
     "CLIENTINFO": function(aMessage) false,
 
     // Used to measure the delay of the IRC network between clients.
     "PING": function(aMessage) {
+      // PING timestamp
       if (aMessage.command == "PRIVMSG") {
-        // PING timestamp
         // Received PING request, send PING response.
         this.LOG("Received PING request from " + aMessage.nickname +
                  ". Sending PING response: \"" + aMessage.ctcp.param + "\".");
         this.sendCTCPMessage("PING", aMessage.ctcp.param, aMessage.nickname,
-                              true);
+                             true);
+        return true;
       }
-      else {
-        // PING timestamp
-        // Received PING response, display to the user.
-        let sentTime = new Date(aMessage.ctcp.param);
-
-        // The received timestamp is invalid
-        if (isNaN(sentTime)) {
-          this.WARN(aMessage.nickname +
-                    " returned an invalid timestamp from a CTCP PING: " +
-                    aMessage.ctcp.param);
-          return false;
-        }
-
-        // Find the delay in seconds.
-        let delay = (Date.now() - sentTime) / 1000;
-
-        let message = PluralForm.get(delay, _("ctcp.ping", aMessage.nickname))
-                                .replace("#2", delay);
-        this.getConversation(aMessage.nickname)
-            .writeMessage(aMessage.nickname, message, {system: true});
-      }
-      return true;
+      else
+        return this.handlePingReply(aMessage.nickname, aMessage.ctcp.param);
     },
 
     // An encryption protocol between clients without any known reference.
     "SED": function(aMessage) false,
 
     // Where to obtain a copy of a client.
     "SOURCE": function(aMessage) false,
 
--- a/chat/protocols/irc/ircCommands.jsm
+++ b/chat/protocols/irc/ircCommands.jsm
@@ -332,19 +332,24 @@ var commands = [
       getConv(aConv).part(aMsg);
       return true;
     }
   },
   {
     name: "ping",
     get helpString() _("command.ping", "ping"),
     run: function(aMsg, aConv) {
-      if (!aMsg || !aMsg.trim().length)
-        return false;
-      ctcpCommand(aConv, aMsg, "PING");
+      // Send a ping to the entered nick using the current time (in
+      // milliseconds) as the param. If no nick is entered, ping the
+      // server.
+      if (aMsg && aMsg.trim().length)
+        ctcpCommand(aConv, aMsg, "PING", Date.now());
+      else
+        getAccount(aConv).sendMessage("PING", Date.now());
+
       return true;
     }
   },
   {
     name: "query",
     get helpString() _("command.msg", "query"),
     run: messageCommand
   },