# HG changeset patch # User Goffi # Date 1483644545 -3600 # Node ID 2d633b3c923dc076eb77189a8b7eb62b12e86c19 # Parent 85f3e12e984d44c667f4c36e076a76f12c0d2bdc plugin XEP-0231: Bits of Binary first draft: The current implementation only retrieve data from Bits of Binary, it's not used yet for sending. When an XHTML-IM message is received (XEP-0071), all are checked and if a cid scheme is found in src attribute the file is retrieved and the src is changed with cached file. diff -r 85f3e12e984d -r 2d633b3c923d src/core/xmpp.py --- a/src/core/xmpp.py Thu Jan 05 20:23:38 2017 +0100 +++ b/src/core/xmpp.py Thu Jan 05 20:29:05 2017 +0100 @@ -76,6 +76,8 @@ @param type_(unicode): IQ type ('set' or 'get') @param timeout(None, int): timeout in seconds + @return((D)domish.Element: result stanza + errback is called if and error stanza is returned """ iq_elt = xmlstream.IQ(self.xmlstream, type_) iq_elt.timeout = timeout diff -r 85f3e12e984d -r 2d633b3c923d src/plugins/plugin_xep_0071.py --- a/src/plugins/plugin_xep_0071.py Thu Jan 05 20:23:38 2017 +0100 +++ b/src/plugins/plugin_xep_0071.py Thu Jan 05 20:29:05 2017 +0100 @@ -86,9 +86,11 @@ def getHandler(self, profile): return XEP_0071_handler(self) - def _messagePostTreat(self, data, body_elts): + def _messagePostTreat(self, data, message_elt, body_elts, client): """Callback which manage the post treatment of the message in case of XHTML-IM found + @param data: data send by MessageReceived trigger through post_treat deferred + @param message_elt: whole stanza @param body_elts: XHTML-IM body elements found @return: the data with the extra parameter updated """ @@ -102,9 +104,12 @@ defers = [] for body_elt in body_elts: lang = body_elt.getAttribute((C.NS_XML, 'lang'), '') - d = self._s.convert(body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True) - d.addCallback(converted, lang) - defers.append(d) + treat_d = defer.succeed(None) # deferred used for treatments + if self.host.trigger.point("xhtml_post_treat", client, message_elt, body_elt, lang, treat_d): + continue + treat_d.addCallback(lambda dummy: self._s.convert(body_elt.toXml(), self.SYNTAX_XHTML_IM, safe=True)) + treat_d.addCallback(converted, lang) + defers.append(treat_d) d_list = defer.DeferredList(defers) d_list.addCallback(lambda dummy: data) @@ -175,7 +180,7 @@ pass else: body_elts = html_elt.elements(NS_XHTML, 'body') - post_treat.addCallback(self._messagePostTreat, body_elts) + post_treat.addCallback(self._messagePostTreat, message, body_elts, client) return True def messageSendTrigger(self, client, data, pre_xml_treatments, post_xml_treatments): diff -r 85f3e12e984d -r 2d633b3c923d src/plugins/plugin_xep_0231.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/plugins/plugin_xep_0231.py Thu Jan 05 20:29:05 2017 +0100 @@ -0,0 +1,135 @@ +#!/usr/bin/env python2 +# -*- coding: utf-8 -*- + +# SAT plugin for Jingle File Transfer (XEP-0231) +# Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.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 . + +from sat.core.i18n import _ +from sat.core.constants import Const as C +from sat.core.log import getLogger +log = getLogger(__name__) +from sat.tools import xml_tools +from wokkel import disco, iwokkel +from zope.interface import implements +from twisted.words.protocols.jabber.xmlstream import XMPPHandler +import base64 + + +PLUGIN_INFO = { + "name": "Bits of Binary", + "import_name": "XEP-0231", + "type": "XEP", + "protocols": ["XEP-0231"], + "dependencies": ["XEP-0071"], + "main": "XEP_0231", + "handler": "yes", + "description": _("""Implementation of bits of binary (used for small images/files)""") +} + +NS_BOB = u'urn:xmpp:bob' + + +class XEP_0231(object): + + def __init__(self, host): + log.info(_(u"plugin Bits of Binary initialization")) + self.host = host + host.trigger.add("xhtml_post_treat", self.XHTMLTrigger) + + def dumpData(self, client, data_elt, cid): + """save file encoded in data_elt to cache + + @param data_elt(domish.Element): as in XEP-0231 + @param cid(unicode): content-id + @return(unicode): full path to dumped file + """ + # FIXME: is it needed to use a separate thread? + # probably not with the little data expected with BoB + mime_type = data_elt.getAttribute('type','') + + try: + max_age = int(data_elt['max-age']) + except (KeyError, ValueError): + log.warning(u'invalid max-age found') + max_age = None + + with client.cache.cacheData( + PLUGIN_INFO['import_name'], + cid, + mime_type, + max_age) as f: + + file_path = f.name + f.write(base64.b64decode(str(data_elt))) + + return file_path + + def getHandler(self, profile): + return XEP_0231_handler() + + def _dataCb(self, iq_elt, client, img_elt, cid): + for data_elt in iq_elt.elements(NS_BOB, u'data'): + if data_elt.getAttribute('cid') == cid: + file_path = self.dumpData(client, data_elt, cid) + img_elt[u'src'] = u'file://{}'.format(file_path) + break + else: + log.warning(u"invalid data stanza received, requested cid was not found:\n{iq_elt}\nrequested cid: {cid}".format( + iq_elt = iq_elt, + cid = cid + )) + + def _dataEb(self, iq_elt): + log.warning(u"Can't get requested data:\n{iq_elt}".format(iq_elt=iq_elt)) + + def XHTMLTrigger(self, client, message_elt, body_elt, lang, treat_d): + for img_elt in xml_tools.findAll(body_elt, C.NS_XHTML, u'img'): + source = img_elt.getAttribute(u'src','') + if source.startswith(u'cid:'): + cid = source[4:] + file_path = client.cache.getFilePath(cid) + if file_path is not None: + # image is in cache, we change change the url + img_elt[u'src'] = u'file://{}'.format(file_path) + continue + else: + # image is not in cache, is it given locally? + for data_elt in message_elt.elements(NS_BOB, u'data'): + if data_elt.getAttribute('cid') == cid: + file_path = self.dumpData(data_elt, cid) + img_elt[u'src'] = u'file://{}'.format(file_path) + break + else: + # cid not found locally, we need to request it + # so we use the deferred + iq_elt = client.IQ('get') + iq_elt['to'] = message_elt['from'] + data_elt = iq_elt.addElement((NS_BOB, 'data')) + data_elt['cid'] = cid + d = iq_elt.send() + d.addCallback(self._dataCb, client, img_elt, cid) + d.addErrback(self._dataEb) + treat_d.addCallback(lambda dummy: d) + + +class XEP_0231_handler(XMPPHandler): + implements(iwokkel.IDisco) + + def getDiscoInfo(self, requestor, target, nodeIdentifier=''): + return [disco.DiscoFeature(NS_BOB)] + + def getDiscoItems(self, requestor, target, nodeIdentifier=''): + return []