plugin XEP-0363: updated to namespace "urn:xmpp:http:upload:0", handle headers

This commit is contained in:
Goffi 2019-03-22 19:26:08 +01:00
commit 9335194f7b
2 changed files with 66 additions and 54 deletions

View file

@ -50,13 +50,14 @@ PLUGIN_INFO = {
C.PI_DEPENDENCIES: ["FILE", "UPLOAD"],
C.PI_MAIN: "XEP_0363",
C.PI_HANDLER: "yes",
C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload"""),
C.PI_DESCRIPTION: _(u"""Implementation of HTTP File Upload"""),
}
NS_HTTP_UPLOAD = "urn:xmpp:http:upload"
NS_HTTP_UPLOAD = "urn:xmpp:http:upload:0"
ALLOWED_HEADERS = ('authorization', 'cookie', 'expires')
Slot = namedtuple("Slot", ["put", "get"])
Slot = namedtuple("Slot", ["put", "get", "headers"])
@implementer(IOpenSSLClientConnectionCreator)
@ -76,7 +77,7 @@ class NoCheckContextFactory(ssl.ClientContextFactory):
"""Context factory which doesn't do TLS certificate check
/!\\ it's obvisously a security flaw to use this class,
and it should be used only wiht explicite agreement from the end used
and it should be used only with explicite agreement from the end used
"""
def creatorForNetloc(self, hostname, port):
@ -139,14 +140,8 @@ class XEP_0363(object):
defer.returnValue(entity)
def _fileHTTPUpload(
self,
filepath,
filename="",
upload_jid="",
ignore_tls_errors=False,
profile=C.PROF_KEY_NONE,
):
def _fileHTTPUpload(self, filepath, filename="", upload_jid="",
ignore_tls_errors=False, profile=C.PROF_KEY_NONE):
assert os.path.isabs(filepath) and os.path.isfile(filepath)
progress_id_d, __ = self.fileHTTPUpload(
filepath,
@ -157,15 +152,9 @@ class XEP_0363(object):
)
return progress_id_d
def fileHTTPUpload(
self,
filepath,
filename=None,
upload_jid=None,
options=None,
profile=C.PROF_KEY_NONE,
):
"""upload a file through HTTP
def fileHTTPUpload(self, filepath, filename=None, upload_jid=None, options=None,
profile=C.PROF_KEY_NONE):
"""Upload a file through HTTP
@param filepath(str): absolute path of the file
@param filename(None, unicode): name to use for the upload
@ -175,7 +164,8 @@ class XEP_0363(object):
@param options(dict): options where key can be:
- ignore_tls_errors(bool): if True, SSL certificate will not be checked
@param profile: %(doc_profile)s
@return (D(tuple[D(unicode), D(unicode)])): progress id and Deferred which fire download URL
@return (D(tuple[D(unicode), D(unicode)])): progress id and Deferred which fire
download URL
"""
if options is None:
options = {}
@ -201,14 +191,14 @@ class XEP_0363(object):
progress_id_d.errback(fail)
download_d.errback(fail)
def _getSlotCb(
self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False
):
def _getSlotCb(self, slot, client, progress_id_d, download_d, path, size,
ignore_tls_errors=False):
"""Called when slot is received, try to do the upload
@param slot(Slot): slot instance with the get and put urls
@param progress_id_d(defer.Deferred): Deferred to call when progress_id is known
@param progress_id_d(defer.Deferred): Deferred to call with URL when upload is done
@param progress_id_d(defer.Deferred): Deferred to call with URL when upload is
done
@param path(str): path to the file to upload
@param size(int): size of the file to upload
@param ignore_tls_errors(bool): ignore TLS certificate is True
@ -224,10 +214,17 @@ class XEP_0363(object):
agent = http_client.Agent(reactor, NoCheckContextFactory())
else:
agent = http_client.Agent(reactor)
headers = {"User-Agent": [C.APP_NAME.encode("utf-8")]}
for name, value in slot.headers:
name = name.encode('utf-8')
value = value.encode('utf-8')
headers[name] = value
d = agent.request(
"PUT",
slot.put.encode("utf-8"),
http_headers.Headers({"User-Agent": [C.APP_NAME.encode("utf-8")]}),
http_headers.Headers(headers),
file_producer,
)
d.addCallbacks(
@ -259,14 +256,17 @@ class XEP_0363(object):
download_d.errback(fail)
try:
wrapped_fail = fail.value.reasons[0]
except (AttributeError, IndexError):
except (AttributeError, IndexError) as e:
log.warning(_(u"upload failed: {reason}").format(reason=e))
sat_file.progressError(unicode(fail))
raise fail
else:
if wrapped_fail.check(SSL.Error):
msg = u"TLS validation error, can't connect to HTTPS server"
log.warning(msg + ": " + unicode(wrapped_fail.value))
sat_file.progressError(msg)
else:
msg = u"can't upload file"
log.warning(msg + ": " + unicode(wrapped_fail.value))
sat_file.progressError(msg)
def _gotSlot(self, iq_elt, client):
"""Slot have been received
@ -276,17 +276,35 @@ class XEP_0363(object):
"""
try:
slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, "slot").next()
put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "put").next())
get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "get").next())
except StopIteration:
put_elt = slot_elt.elements(NS_HTTP_UPLOAD, "put").next()
put_url = put_elt['url']
get_elt = slot_elt.elements(NS_HTTP_UPLOAD, "get").next()
get_url = get_elt['url']
except (StopIteration, KeyError):
raise exceptions.DataError(u"Incorrect stanza received from server")
slot = Slot(put=put_url, get=get_url)
headers = []
for header_elt in put_elt.elements(NS_HTTP_UPLOAD, "header"):
try:
name = header_elt["name"]
value = unicode(header_elt)
except KeyError:
log.warning(_(u"Invalid header element: {xml}").format(
iq_elt.toXml()))
continue
name = name.replace('\n', '')
value = value.replace('\n', '')
if name.lower() not in ALLOWED_HEADERS:
log.warning(_(u'Ignoring unauthorised header "{name}": {xml}')
.format(name=name, xml = iq_elt.toXml()))
continue
headers.append((name, value))
slot = Slot(put=put_url, get=get_url, headers=tuple(headers))
return slot
def _getSlot(
self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE
):
"""Get a upload slot
def _getSlot(self, filename, size, content_type, upload_jid,
profile_key=C.PROF_KEY_NONE):
"""Get an upload slot
This method can be used when uploading is done by the frontend
@param filename(unicode): name of the file to upload
@ -339,10 +357,10 @@ class XEP_0363(object):
iq_elt = client.IQ("get")
iq_elt["to"] = upload_jid.full()
request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request"))
request_elt.addElement("filename", content=filename)
request_elt.addElement("size", content=unicode(size))
request_elt["filename"] = filename
request_elt["size"] = unicode(size)
if content_type is not None:
request_elt.addElement("content-type", content=content_type)
request_elt["content-type"] = content_type
d = iq_elt.send()
d.addCallback(self._gotSlot, client)

View file

@ -37,6 +37,7 @@ class IStreamProducer(interface.Interface):
@return (D): deferred fired when stream is finished
"""
pass
class SatFile(object):
@ -44,17 +45,8 @@ class SatFile(object):
# TODO: manage "with" statement
def __init__(
self,
host,
client,
path,
mode="rb",
uid=None,
size=None,
data_cb=None,
auto_end_signals=True,
):
def __init__(self, host, client, path, mode="rb", uid=None, size=None, data_cb=None,
auto_end_signals=True):
"""
@param host: %(doc_host)s
@param path(str): path of the file to get
@ -65,8 +57,10 @@ class SatFile(object):
@param size(None, int): size of the file (when known in advance)
@param data_cb(None, callable): method to call on each data read/write
mainly useful to do things like calculating hash
@param auto_end_signals(bool): if True, progressFinished and progressError signals are automatically sent
if False, you'll have to call self.progressFinished and self.progressError yourself
@param auto_end_signals(bool): if True, progressFinished and progressError signals
are automatically sent.
if False, you'll have to call self.progressFinished and self.progressError
yourself.
progressStarted signal is always sent automatically
"""
self.host = host