Mercurial > libervia-backend
diff src/plugins/plugin_xep_0033.py @ 742:03744d9ebc13
plugin XEP-0033: implementation of the addressing feature:
- frontends pass the recipients in the extra parameter of sendMessage
- backend checks if the target server supports the feature (this is not done yet by prosody plugin)
- features and identities are cached per profile and server
- messages are duplicated in history for now (TODO: redesign the database)
- echos signals are also duplicated to the sender (FIXME)
author | souliane <souliane@mailoo.org> |
---|---|
date | Wed, 11 Dec 2013 17:16:53 +0100 |
parents | |
children | 192b804ee446 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/plugin_xep_0033.py Wed Dec 11 17:16:53 2013 +0100 @@ -0,0 +1,174 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# SAT plugin for Extended Stanza Addressing (xep-0033) +# Copyright (C) 2013 Adrien Cossa (souliane@mailoo.org) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import logging +from sat.core import exceptions +from wokkel import disco, iwokkel +from zope.interface import implements +from twisted.words.protocols.jabber.jid import JID +import copy +try: + from twisted.words.protocols.xmlstream import XMPPHandler +except ImportError: + from wokkel.subprotocols import XMPPHandler +from threading import Timer +from twisted.words.xish import domish +from twisted.internet import defer + +from sat.core.sat_main import MessageSentAndStored, AbortSendMessage +from sat.tools.misc import TriggerManager + +# TODO: fix Prosody "addressing" plugin to leave the concerned bcc according to the spec: +# +# http://xmpp.org/extensions/xep-0033.html#addr-type-bcc +# "This means that the server MUST remove these addresses before the stanza is delivered to anyone other than the given bcc addressee or the multicast service of the bcc addressee." +# +# http://xmpp.org/extensions/xep-0033.html#multicast +# "Each 'bcc' recipient MUST receive only the <address type='bcc'/> associated with that addressee." + +# TODO: fix Prosody "addressing" plugin to determine itself if remote servers supports this XEP + + +NS_XMPP_CLIENT = "jabber:client" +NS_ADDRESS = "http://jabber.org/protocol/address" +ATTRIBUTES = ["jid", "uri", "node", "desc", "delivered", "type"] +ADDRESS_TYPES = ["to", "cc", "bcc", "replyto", "replyroom", "noreply"] + +PLUGIN_INFO = { + "name": "Extended Stanza Addressing Protocol Plugin", + "import_name": "XEP-0033", + "type": "XEP", + "protocols": ["XEP-0033"], + "dependencies": [], + "main": "XEP_0033", + "handler": "yes", + "description": _("""Implementation of Extended Stanza Addressing""") +} + + +class XEP_0033(object): + """ + Implementation for XEP 0033 + """ + def __init__(self, host): + logging.info(_("Extended Stanza Addressing plugin initialization")) + self.host = host + host.trigger.add("sendMessage", self.sendMessageTrigger, TriggerManager.MIN_PRIORITY) + host.trigger.add("MessageReceived", self.messageReceivedTrigger) + + def sendMessageTrigger(self, mess_data, treatments, profile): + """Process the XEP-0033 related data to be sent""" + + def treatment(mess_data): + if not 'address' in mess_data['extra']: + return mess_data + + def discoCallback(entity): + if entity is None: + raise AbortSendMessage(_("XEP-0033 is being used but the server doesn't support it!")) + to = JID(mess_data["to"].host) + if to != entity: + logging.warning(_("Stanzas using XEP-0033 should be addressed to %s, not %s!") % (entity, to)) + logging.warning(_("TODO: addressing has be fixed by the backend... fix it in the frontend!")) + mess_data["to"] = entity + element = mess_data['xml'].addElement('addresses', NS_ADDRESS) + entries = [entry.split(':') for entry in mess_data['extra']['address'].split('\n') if entry != ''] + for type_, jid_ in entries: + element.addChild(domish.Element((None, 'address'), None, {'type': type_, 'jid': jid_})) + # when the prosody plugin is completed, we can immediately return mess_data from here + return self.sendAndStoreMessage(mess_data, entries, profile) + + d = self.host.requestServerDisco(NS_ADDRESS, profile_key=profile) + d.addCallbacks(discoCallback, lambda dummy: discoCallback(None)) + return d + + treatments.addCallback(treatment) + return True + + def sendAndStoreMessage(self, mess_data, entries, profile): + """Check if target servers support XEP-0033, send and store the messages + @raise: a friendly failure to let the core know that we sent the message already + + Later we should be able to remove this method because: + # XXX: sending the messages should be done by the local server + # FIXME: for now we duplicate the messages in the history for each recipient, this should change + # FIXME: for now we duplicate the echoes to the sender, this should also change + Ideas: + - fix Prosody plugin to check if target server support the feature + - redesign the database to save only one entry to the database + - change the newMessage signal to eventually pass more than one recipient + """ + def discoCallback(entity, to_jid): + new_data = copy.deepcopy(mess_data) + new_data['to'] = JID(to_jid) + new_data['xml']['to'] = to_jid + if entity: + if 'address' in mess_data['extra']: + self.host.sendAndStoreMessage(mess_data, False, profile) + # just to remember that the message has been sent + del mess_data['extra']['address'] + # we still need to fill the history and signal the echo... + self.host.sendAndStoreMessage(new_data, True, profile) + else: + # target server misses the addressing feature + self.host.sendAndStoreMessage(new_data, False, profile) + + def errback(failure, to_jid): + discoCallback(None, to_jid) + + for type_, jid_ in entries: + d = defer.Deferred() + d.addCallback(self.host.requestServerDisco, JID(JID(jid_).host), profile_key=profile) + d.addCallbacks(discoCallback, errback, callbackArgs=[jid_], errbackArgs=[jid_]) + d.callback(NS_ADDRESS) + + raise MessageSentAndStored("XEP-0033 took over") + + def messageReceivedTrigger(self, message, post_treat, profile): + """In order to save the addressing information in the history""" + def post_treat_addr(data, addresses): + data['extra']['addresses'] = "" + for address in addresses: + data['extra']['addresses'] += '%s:%s\n' % (address['type'], address['jid']) + return data + + try: + addresses = message.elements(NS_ADDRESS, 'addresses').next() + post_treat.addCallback(post_treat_addr, addresses.children) + except StopIteration: + pass # no addresses + return True + + def getHandler(self, profile): + return XEP_0033_handler(self, profile) + + +class XEP_0033_handler(XMPPHandler): + implements(iwokkel.IDisco) + + def __init__(self, plugin_parent, profile): + self.plugin_parent = plugin_parent + self.host = plugin_parent.host + self.profile = profile + + def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + return [disco.DiscoFeature(NS_ADDRESS)] + + def getDiscoItems(self, requestor, target, nodeIdentifier=''): + return []