# HG changeset patch # User Goffi # Date 1553279168 -3600 # Node ID 8ce5748bfe975cd98158dc54d827f0527c40c4a6 # Parent 9213c6dff48dc631e80a6e7197ee8df75b7dedb1 plugin XEP-0363: updated to namespace "urn:xmpp:http:upload:0", handle headers diff -r 9213c6dff48d -r 8ce5748bfe97 sat/plugins/plugin_xep_0363.py --- a/sat/plugins/plugin_xep_0363.py Thu Mar 21 08:54:59 2019 +0100 +++ b/sat/plugins/plugin_xep_0363.py Fri Mar 22 19:26:08 2019 +0100 @@ -50,13 +50,14 @@ 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 @@ """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 @@ 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 @@ ) 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 @@ @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 @@ 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 @@ 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 @@ 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 @@ """ 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 @@ 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) diff -r 9213c6dff48d -r 8ce5748bfe97 sat/tools/stream.py --- a/sat/tools/stream.py Thu Mar 21 08:54:59 2019 +0100 +++ b/sat/tools/stream.py Fri Mar 22 19:26:08 2019 +0100 @@ -37,6 +37,7 @@ @return (D): deferred fired when stream is finished """ + pass class SatFile(object): @@ -44,17 +45,8 @@ # 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 @@ @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