Mercurial > libervia-backend
view src/plugins/plugin_xep_0095.py @ 1198:16ce9a6580a3
misc (install): Lower default setuptools version
From 0d607b6ed49eab758fd9b272e148f032e65fb2e2 Mon Sep 17 00:00:00 2001
python-setuptools 5.7 is not yet in Debian, so we need to set the
default version to 5.5 (the current version in sid) to avoid the newer
version to be downloaded from pypi.
author | Matteo Cypriani <mcy@lm7.fr> |
---|---|
date | Tue, 09 Sep 2014 22:09:51 -0400 |
parents | 301b342c697a |
children | 069ad98b360d |
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 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(_("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 []