Mercurial > libervia-backend
view src/plugins/plugin_xep_0095.py @ 1422:be1fccf4854d
tmp (wokkel): licenses fixes:
the licenses headers were wrong, it was fixed: original work from Adrien Cossa is directly under AGPL v3 (with his agreement), work derivated from Wokkel is sublicensed to AGPL v3 as allowed by the original license, to stay consistent with the rest of the code base.
Theses files (and only these ones) can be relicensed again to fill Wokkel license if Ralph plan to merge them upstream...
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 23 Apr 2015 10:57:40 +0200 |
parents | 3265a2639182 |
children | d04d7402b8e9 |
line wrap: on
line source
#!/usr/bin/python # -*- coding: utf-8 -*- # SAT plugin for managing xep-0095 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 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 <http://www.gnu.org/licenses/>. from sat.core.i18n import _ from sat.core.constants import Const as C from sat.core.log import getLogger log = getLogger(__name__) from twisted.words.xish import domish from twisted.words.protocols.jabber import client import uuid from zope.interface import implements try: from twisted.words.protocols.xmlstream import XMPPHandler except ImportError: from wokkel.subprotocols import XMPPHandler from wokkel import disco, iwokkel IQ_SET = '/iq[@type="set"]' NS_SI = 'http://jabber.org/protocol/si' SI_REQUEST = IQ_SET + '/si[@xmlns="' + NS_SI + '"]' SI_PROFILE_HEADER = "http://jabber.org/protocol/si/profile/" PLUGIN_INFO = { "name": "XEP 0095 Plugin", "import_name": "XEP-0095", "type": "XEP", "protocols": ["XEP-0095"], "main": "XEP_0095", "handler": "yes", "description": _("""Implementation of Stream Initiation""") } class XEP_0095(object): def __init__(self, host): log.info(_("Plugin XEP_0095 initialization")) self.host = host self.si_profiles = {} # key: SI profile, value: callback def getHandler(self, profile): return XEP_0095_handler(self) def registerSIProfile(self, si_profile, callback): """Add a callback for a SI Profile param si_profile: SI profile name (e.g. file-transfer) param callback: method to call when the profile name is asked""" self.si_profiles[si_profile] = callback def streamInit(self, iq_el, profile): """This method is called on stream initiation (XEP-0095 #3.2) @param iq_el: IQ element @param profile: %(doc_profile)s""" log.info(_("XEP-0095 Stream initiation")) iq_el.handled = True si_el = iq_el.firstChildElement() si_id = si_el.getAttribute('id') si_mime_type = iq_el.getAttribute('mime-type', 'application/octet-stream') si_profile = si_el.getAttribute('profile') si_profile_key = si_profile[len(SI_PROFILE_HEADER):] if si_profile.startswith(SI_PROFILE_HEADER) else si_profile if si_profile_key in self.si_profiles: #We know this SI profile, we call the callback self.si_profiles[si_profile_key](iq_el['id'], iq_el['from'], si_id, si_mime_type, si_el, profile) else: #We don't know this profile, we send an error self.sendBadProfileError(iq_el['id'], iq_el['from'], profile) def sendRejectedError(self, iq_id, to_jid, reason='Offer Declined', profile=C.PROF_KEY_NONE): """Helper method to send when the stream is rejected @param iq_id: IQ id @param to_jid: recipient @param reason: human readable reason (string) @param profile: %(doc_profile)s""" self.sendError(iq_id, to_jid, 403, 'cancel', {'text': reason}, profile=profile) def sendBadProfileError(self, iq_id, to_jid, profile): """Helper method to send when we don't know the SI profile @param iq_id: IQ id @param to_jid: recipient @param profile: %(doc_profile)s""" self.sendError(iq_id, to_jid, 400, 'modify', profile=profile) def sendBadRequestError(self, iq_id, to_jid, profile): """Helper method to send when we don't know the SI profile @param iq_id: IQ id @param to_jid: recipient @param profile: %(doc_profile)s""" self.sendError(iq_id, to_jid, 400, 'cancel', profile=profile) def sendFailedError(self, iq_id, to_jid, profile): """Helper method to send when we transfer failed @param iq_id: IQ id @param to_jid: recipient @param profile: %(doc_profile)s""" self.sendError(iq_id, to_jid, 500, 'cancel', {'custom': 'failed'}, profile=profile) # as there is no lerror code for failed transfer, we use 500 (undefined-condition) def sendError(self, iq_id, to_jid, err_code, err_type='cancel', data={}, profile=C.PROF_KEY_NONE): """Send IQ error as a result @param iq_id: IQ id @param to_jid: recipient @param err_code: error err_code (see XEP-0095 #4.2) @param err_type: one of cancel, modify @param data: error specific data (dictionary) @param profile: %(doc_profile)s """ client_ = self.host.getClient(profile) result = domish.Element((None, 'iq')) result['type'] = 'result' result['id'] = iq_id result['to'] = to_jid error_el = result.addElement('error') error_el['err_code'] = str(err_code) error_el['type'] = err_type if err_code == 400 and err_type == 'cancel': error_el.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas', 'bad-request')) error_el.addElement((NS_SI, 'no-valid-streams')) elif err_code == 400 and err_type == 'modify': error_el.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas', 'bad-request')) error_el.addElement((NS_SI, 'bad-profile')) elif err_code == 403 and err_type == 'cancel': error_el.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas', 'forbidden')) if 'text' in data: error_el.addElement(('urn:ietf:params:xml:ns:xmpp-stanzas', 'text'), content=data['text']) elif err_code == 500 and err_type == 'cancel': condition_el = error_el.addElement((NS_SI, 'undefined-condition')) if 'custom' in data and data['custom'] == 'failed': condition_el.addContent('Stream failed') client_.xmlstream.send(result) def acceptStream(self, iq_id, to_jid, feature_elt, misc_elts=[], profile=C.PROF_KEY_NONE): """Send the accept stream initiation answer @param iq_id: IQ id @param feature_elt: domish element 'feature' containing stream method to use @param misc_elts: list of domish element to add @param profile: %(doc_profile)s""" _client = self.host.getClient(profile) assert(_client) log.info(_("sending stream initiation accept answer")) result = domish.Element((None, 'iq')) result['type'] = 'result' result['id'] = iq_id result['to'] = to_jid si = result.addElement('si', NS_SI) si.addChild(feature_elt) for elt in misc_elts: si.addChild(elt) _client.xmlstream.send(result) def proposeStream(self, to_jid, si_profile, feature_elt, misc_elts, mime_type='application/octet-stream', profile_key=C.PROF_KEY_NONE): """Propose a stream initiation @param to_jid: recipient (JID) @param si_profile: Stream initiation profile (XEP-0095) @param feature_elt: feature domish element, according to XEP-0020 @param misc_elts: list of domish element to add for this profile @param mime_type: stream mime type @param profile: %(doc_profile)s @return: session id, offer""" current_jid, xmlstream = self.host.getJidNStream(profile_key) if not xmlstream: log.error(_('Asking for an non-existant or not connected profile')) return "" offer = client.IQ(xmlstream, 'set') sid = str(uuid.uuid4()) log.debug(_(u"Stream Session ID: %s") % offer["id"]) offer["from"] = current_jid.full() offer["to"] = to_jid.full() si = offer.addElement('si', NS_SI) si['id'] = sid si["mime-type"] = mime_type si["profile"] = si_profile for elt in misc_elts: si.addChild(elt) si.addChild(feature_elt) offer.send() return sid, offer class XEP_0095_handler(XMPPHandler): implements(iwokkel.IDisco) def __init__(self, plugin_parent): self.plugin_parent = plugin_parent self.host = plugin_parent.host def connectionInitialized(self): self.xmlstream.addObserver(SI_REQUEST, self.plugin_parent.streamInit, profile=self.parent.profile) def getDiscoInfo(self, requestor, target, nodeIdentifier=''): return [disco.DiscoFeature(NS_SI)] + [disco.DiscoFeature("http://jabber.org/protocol/si/profile/%s" % profile_name) for profile_name in self.plugin_parent.si_profiles] def getDiscoItems(self, requestor, target, nodeIdentifier=''): return []