plugin XEP-0363: updated to namespace "urn:xmpp:http:upload:0", handle headers
This commit is contained in:
parent
430649f58f
commit
9335194f7b
2 changed files with 66 additions and 54 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue