Commit 0f2fa095 authored by Philipp Hörist's avatar Philipp Hörist

Add HTTP Upload (XEP-0363) support

parent 3d6df88c
Pipeline #4782 passed with stages
in 26 seconds
......@@ -79,6 +79,7 @@ from nbxmpp.modules.attention import Attention
from nbxmpp.modules.security_labels import SecurityLabels
from nbxmpp.modules.chatstates import Chatstates
from nbxmpp.modules.register import Register
from nbxmpp.modules.http_upload import HTTPUpload
from nbxmpp.modules.misc import unwrap_carbon
from nbxmpp.modules.misc import unwrap_mam
from nbxmpp.util import get_properties_struct
......@@ -225,6 +226,7 @@ class XMPPDispatcher(PlugIn):
self._modules['SecurityLabels'] = SecurityLabels(self._owner)
self._modules['Chatstates'] = Chatstates(self._owner)
self._modules['Register'] = Register(self._owner)
self._modules['HTTPUpload'] = HTTPUpload(self._owner)
for instance in self._modules.values():
for handler in instance.handlers:
# Copyright (C) 2019 Philipp Hörist <philipp AT>
# This file is part of nbxmpp.
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU 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
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; If not, see <>.
import logging
from nbxmpp.protocol import NS_HTTPUPLOAD_0
from nbxmpp.protocol import Iq
from nbxmpp.protocol import isResultNode
from nbxmpp.structs import HTTPUploadData
from nbxmpp.util import call_on_response
from nbxmpp.util import callback
from nbxmpp.util import raise_error
ALLOWED_HEADERS = ['Authorization', 'Cookie', 'Expires']
log = logging.getLogger('nbxmpp.m.http_upload')
class HTTPUpload:
def __init__(self, client):
self._client = client
self.handlers = []
def request_slot(self, jid, filename, size, content_type):
iq = Iq(typ='get', to=jid)
attr = {'filename': filename,
'size': size,
'content-type': content_type}
return iq
def _received_slot(self, stanza):
if not isResultNode(stanza):
return raise_error(, stanza)
slot = stanza.getTag('slot', namespace=NS_HTTPUPLOAD_0)
if slot is None:
return raise_error(log.warning, stanza, 'stanza-malformed',
'No slot node found')
put_uri = slot.getTagAttr('put', 'url')
if put_uri is None:
return raise_error(log.warning, stanza, 'stanza-malformed',
'No put uri found')
get_uri = slot.getTagAttr('get', 'url')
if get_uri is None:
return raise_error(log.warning, stanza, 'stanza-malformed',
'No get uri found')
headers = {}
for header in slot.getTag('put').getTags('header'):
name = header.getAttr('name')
if name not in ALLOWED_HEADERS:
return raise_error(log.warning, stanza, 'stanza-malformed',
'Not allowed header found: %s' % name)
data = header.getData()
if '\n' in data:
return raise_error(log.warning, stanza, 'stanza-malformed',
'NNewline in header data found')
headers[name] = data
return HTTPUploadData(put_uri=put_uri,
......@@ -136,6 +136,10 @@ RegisterData = namedtuple('RegisterData', 'instructions form fields_form oob_url
ChangePasswordResult = namedtuple('ChangePasswordResult', 'successful form')
ChangePasswordResult.__new__.__defaults__ = (None,)
HTTPUploadData = namedtuple('HTTPUploadData', 'put_uri get_uri headers')
HTTPUploadData.__new__.__defaults__ = (None,)
class DiscoInfo(namedtuple('DiscoInfo', 'stanza identities features dataforms timestamp')):
__slots__ = []
......@@ -449,6 +453,25 @@ class CommonError:
class HTTPUploadError(CommonError):
def __init__(self, stanza):
CommonError.__init__(self, stanza)
def get_max_file_size(self):
if not self.app_condition == 'file-too-large':
return None
node = self._error_node.getTag(self.app_condition)
return float(node.getTagData('max-file-size'))
except Exception:
return None
def get_retry_date(self):
if not self.app_condition == 'retry':
return None
return self._error_node.getTagAttr('stamp')
class StanzaMalformedError(CommonError):
def __init__(self, stanza, text):
self._error_node = None
......@@ -28,11 +28,13 @@ import precis_i18n.codec
from nbxmpp.protocol import DiscoInfoMalformed
from nbxmpp.protocol import isErrorNode
from nbxmpp.protocol import NS_DATA
from nbxmpp.protocol import NS_HTTPUPLOAD_0
from nbxmpp.structs import Properties
from nbxmpp.structs import IqProperties
from nbxmpp.structs import MessageProperties
from nbxmpp.structs import PresenceProperties
from nbxmpp.structs import CommonError
from nbxmpp.structs import HTTPUploadError
from nbxmpp.structs import StanzaMalformedError
from nbxmpp.modules.dataforms import extend_form
from nbxmpp.third_party.hsluv import hsluv_to_rgb
......@@ -139,7 +141,9 @@ def to_xs_boolean(value):
'Cant convert %s to xs:boolean' % value)
error_classes = {}
error_classes = {
def error_factory(stanza, condition=None, text=None):
if condition == 'stanza-malformed':
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment