Bug 1050779 - Allow and handle receiving invitations to XMPP room chat. r=aleth
authorAbdelrhman Ahmed <a.ahmed1026@gmail.com>
Thu, 12 Mar 2015 15:29:00 +0100
changeset 22167 357d9913125ce4e6b416d4c0257c06d5255ef5d4
parent 22166 05be474b042d42f358e5b4204ae05947639091e0
child 22168 08e2fd4c7a0e378ea7d441015eefd7222729803f
push idunknown
push userunknown
push dateunknown
reviewersaleth
bugs1050779
Bug 1050779 - Allow and handle receiving invitations to XMPP room chat. r=aleth
AUTHORS
chat/locales/en-US/xmpp.properties
chat/protocols/xmpp/xmpp-xml.jsm
chat/protocols/xmpp/xmpp.jsm
--- a/AUTHORS
+++ b/AUTHORS
@@ -3,16 +3,17 @@ codebase which lives in this repository.
 here, you may add your name and, optionally, email address in the
 appropriate place.
 
 For a full list of the people who are credited with making a
 contribution to Mozilla, see http://www.mozilla.org/credits/ .
 
 Aaron Kaluszka <ask@swva.net>
 Aaron Leventhal <aaronl@netscape.com>
+Abdelrhman Ahmed <a.ahmed1026@gmail.com>
 ActiveState Software Inc
 Adam Becevello <abecevello@sympatico.ca>
 Adam Christian <adam.christian@gmail.com>
 Adam D. Moss <adam@gimp.org>
 Adam Lock <adamlock@netscape.com>
 Adrian Havill <havill@redhat.com>
 Akihiro Misaki <spitfire.kuden@gmail.com>
 Akkana Peck <akkana@netscape.com>
--- a/chat/locales/en-US/xmpp.properties
+++ b/chat/locales/en-US/xmpp.properties
@@ -64,16 +64,26 @@ tooltip.subscription=Subscription
 #   The _ character won't be displayed; it indicates the next
 #   character of the string should be used as the access key for this
 #   field.
 chatRoomField.room=_Room
 chatRoomField.server=_Server
 chatRoomField.nick=_Nick
 chatRoomField.password=_Password
 
+# LOCALIZATION NOTE (conversation.muc.*):
+#   These are displayed as a system message when a chatroom invitation is
+#   received.
+#   %1$S is the inviter.
+#   %2$S is the room.
+#   %3$S is the reason which is a message provided by the person sending the
+#   invitation.
+conversation.muc.invitationWithReason=%1$S has invited you to join %2$S: %3$S
+conversation.muc.invitationWithoutReason=%1$S has invited you to join %2$S
+
 # LOCALIZATION NOTE (options.*):
 #   These are the protocol specific options shown in the account manager and
 #   account wizard windows.
 options.resource=Resource
 options.priority=Priority
 options.connectionSecurity=Connection security
 options.connectionSecurity.requireEncryption=Require encryption
 options.connectionSecurity.opportunisticTLS=Use encryption if available
--- a/chat/protocols/xmpp/xmpp-xml.jsm
+++ b/chat/protocols/xmpp/xmpp-xml.jsm
@@ -40,16 +40,17 @@ const NS = {
   disco_info                : "http://jabber.org/protocol/disco#info",
   disco_items               : "http://jabber.org/protocol/disco#items",
   caps                      : "http://jabber.org/protocol/caps",
 
   //addressing
   address                   : "http://jabber.org/protocol/address",
 
   muc_user                  : "http://jabber.org/protocol/muc#user",
+  conference                : "jabber:x:conference",
   muc                       : "http://jabber.org/protocol/muc",
   register                  : "jabber:iq:register",
   delay                     : "urn:xmpp:delay",
   delay_legacy              : "jabber:x:delay",
   bookmarks                 : "storage:bookmarks",
   chatstates                : "http://jabber.org/protocol/chatstates",
   event                     : "jabber:x:event",
   stanzas                   : "urn:ietf:params:xml:ns:xmpp-stanzas",
--- a/chat/protocols/xmpp/xmpp.jsm
+++ b/chat/protocols/xmpp/xmpp.jsm
@@ -1011,16 +1011,68 @@ const XMPPAccountPrototype = {
       // The join was successful.
       muc.left = false;
       muc.onPresenceStanza(aStanza);
     }
     else if (jid != this.normalize(this._connection._jid.jid))
       this.WARN("received presence stanza for unknown buddy " + from);
   },
 
+  // Returns null if not an invitation stanza, and an object
+  // describing the invitation otherwise.
+  parseInvitation: function(aStanza) {
+      let x = aStanza.getElement(["x"]);
+      if (!x)
+        return null;
+      let retVal = {};
+
+      // XEP-0045. Direct Invitation (7.8.1)
+      // Described in XEP-0249.
+      // jid (chatroom) is required.
+      // Password, reason, continue and thread are optional.
+      if (x.uri == Stanza.NS.conference) {
+        if (!x.attributes["jid"]) {
+          this.WARN("Received an invitation with missing MUC jid.");
+          return null;
+        }
+        retVal.mucJid = this.normalize(x.attributes["jid"]);
+        retVal.from = this.normalize(aStanza.attributes["from"]);
+        retVal.password = x.attributes["password"];
+        retVal.reason = x.attributes["reason"];
+        retVal.continue = x.attributes["continue"];
+        retVal.thread = x.attributes["thread"];
+        return retVal;
+      }
+
+      // XEP-0045. Mediated Invitation (7.8.2)
+      // Sent by the chatroom on behalf of someone in the chatroom.
+      // jid (chatroom) and from (inviter) are required.
+      // password and reason are optional.
+      if (x.uri == Stanza.NS.muc_user) {
+        let invite = x.getElement(["invite"]);
+        if (!invite || !invite.attributes["from"]) {
+          this.WARN("Received an invitation with missing MUC invite or from.");
+          return null;
+        }
+        retVal.mucJid = this.normalize(aStanza.attributes["from"]);
+        retVal.from = this.normalize(invite.attributes["from"]);
+        let continueElement = invite.getElement(["continue"]);
+        retVal.continue = !!continueElement;
+        if (continueElement)
+          retVal.thread = continueElement.attributes["thread"];
+        if (x.getElement(["password"]))
+          retVal.password = x.getElement(["password"]).innerText;
+        if (invite.getElement(["reason"]))
+          retVal.reason = invite.getElement(["reason"]).innerText;
+        return retVal;
+      }
+
+      return null;
+  },
+
   /* Called when a message stanza is received */
   onMessageStanza: function(aStanza) {
     let norm = this.normalize(aStanza.attributes["from"]);
 
     let type = aStanza.attributes["type"];
     let body;
     let b = aStanza.getElement(["body"]);
     if (b) {
@@ -1060,16 +1112,42 @@ const XMPPAccountPrototype = {
         // attributes.
         if (s)
           muc.setTopic(s.innerText);
 
         muc.incomingMessage(body, aStanza, date);
         return;
       }
 
+      let invitation = this.parseInvitation(aStanza);
+      if (invitation) {
+        if (invitation.reason) {
+          body = _("conversation.muc.invitationWithReason",
+                   invitation.from, invitation.mucJid, invitation.reason);
+        }
+        else {
+          body = _("conversation.muc.invitationWithoutReason",
+                   invitation.from, invitation.mucJid);
+        }
+        if (Services.prefs.getIntPref("messenger.conversations.autoAcceptChatInvitations") == 1) {
+          // Auto-accept the invitation.
+          let chatRoomFields = this.getChatRoomDefaultFieldValues(invitation.mucJid);
+          if (invitation.password)
+            chatRoomFields.setValue("password", invitation.password);
+          let muc = this.joinChat(chatRoomFields);
+          muc.writeMessage(muc.name, body, {system: true});
+          return;
+        }
+        // Otherwise, just notify the user.
+        let conv = this.createConversation(invitation.from);
+        if (conv)
+          conv.writeMessage(invitation.from, body, {system: true});
+        return;
+      }
+
       let conv = this.createConversation(norm);
       if (!conv)
         return;
       conv.incomingMessage(body, aStanza, date);
     }
     else if (type == "error") {
       let conv = this.createConversation(norm);
       if (conv)