comparison sat/plugins/plugin_xep_0363.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents 8ce5748bfe97
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
1 #!/usr/bin/env python2 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for HTTP File Upload (XEP-0363) 4 # SAT plugin for HTTP File Upload (XEP-0363)
5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
6 6
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23 23
24 log = getLogger(__name__) 24 log = getLogger(__name__)
25 from sat.core import exceptions 25 from sat.core import exceptions
26 from wokkel import disco, iwokkel 26 from wokkel import disco, iwokkel
27 from zope.interface import implements 27 from zope.interface import implementer
28 from twisted.words.protocols.jabber import jid 28 from twisted.words.protocols.jabber import jid
29 from twisted.words.protocols.jabber.xmlstream import XMPPHandler 29 from twisted.words.protocols.jabber.xmlstream import XMPPHandler
30 from twisted.internet import reactor 30 from twisted.internet import reactor
31 from twisted.internet import defer 31 from twisted.internet import defer
32 from twisted.internet import ssl 32 from twisted.internet import ssl
48 C.PI_TYPE: "XEP", 48 C.PI_TYPE: "XEP",
49 C.PI_PROTOCOLS: ["XEP-0363"], 49 C.PI_PROTOCOLS: ["XEP-0363"],
50 C.PI_DEPENDENCIES: ["FILE", "UPLOAD"], 50 C.PI_DEPENDENCIES: ["FILE", "UPLOAD"],
51 C.PI_MAIN: "XEP_0363", 51 C.PI_MAIN: "XEP_0363",
52 C.PI_HANDLER: "yes", 52 C.PI_HANDLER: "yes",
53 C.PI_DESCRIPTION: _(u"""Implementation of HTTP File Upload"""), 53 C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload"""),
54 } 54 }
55 55
56 NS_HTTP_UPLOAD = "urn:xmpp:http:upload:0" 56 NS_HTTP_UPLOAD = "urn:xmpp:http:upload:0"
57 ALLOWED_HEADERS = ('authorization', 'cookie', 'expires') 57 ALLOWED_HEADERS = ('authorization', 'cookie', 'expires')
58 58
80 and it should be used only with explicite agreement from the end used 80 and it should be used only with explicite agreement from the end used
81 """ 81 """
82 82
83 def creatorForNetloc(self, hostname, port): 83 def creatorForNetloc(self, hostname, port):
84 log.warning( 84 log.warning(
85 u"TLS check disabled for {host} on port {port}".format( 85 "TLS check disabled for {host} on port {port}".format(
86 host=hostname, port=port 86 host=hostname, port=port
87 ) 87 )
88 ) 88 )
89 certificateOptions = ssl.CertificateOptions(trustRoot=None) 89 certificateOptions = ssl.CertificateOptions(trustRoot=None)
90 return NoCheckConnectionCreator(hostname, certificateOptions.getContext()) 90 return NoCheckConnectionCreator(hostname, certificateOptions.getContext())
105 "fileHTTPUploadGetSlot", 105 "fileHTTPUploadGetSlot",
106 ".plugin", 106 ".plugin",
107 in_sign="sisss", 107 in_sign="sisss",
108 out_sign="(ss)", 108 out_sign="(ss)",
109 method=self._getSlot, 109 method=self._getSlot,
110 async=True, 110 async_=True,
111 ) 111 )
112 host.plugins["UPLOAD"].register( 112 host.plugins["UPLOAD"].register(
113 u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload 113 "HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload
114 ) 114 )
115 115
116 def getHandler(self, client): 116 def getHandler(self, client):
117 return XEP_0363_handler() 117 return XEP_0363_handler()
118 118
129 try: 129 try:
130 entity = client.http_upload_service 130 entity = client.http_upload_service
131 except AttributeError: 131 except AttributeError:
132 found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,)) 132 found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,))
133 try: 133 try:
134 entity = client.http_upload_service = iter(found_entities).next() 134 entity = client.http_upload_service = next(iter(found_entities))
135 except StopIteration: 135 except StopIteration:
136 entity = client.http_upload_service = None 136 entity = client.http_upload_service = None
137 137
138 if entity is None: 138 if entity is None:
139 raise failure.Failure(exceptions.NotFound(u"No HTTP upload entity found")) 139 raise failure.Failure(exceptions.NotFound("No HTTP upload entity found"))
140 140
141 defer.returnValue(entity) 141 defer.returnValue(entity)
142 142
143 def _fileHTTPUpload(self, filepath, filename="", upload_jid="", 143 def _fileHTTPUpload(self, filepath, filename="", upload_jid="",
144 ignore_tls_errors=False, profile=C.PROF_KEY_NONE): 144 ignore_tls_errors=False, profile=C.PROF_KEY_NONE):
185 ) 185 )
186 return progress_id_d, download_d 186 return progress_id_d, download_d
187 187
188 def _getSlotEb(self, fail, client, progress_id_d, download_d): 188 def _getSlotEb(self, fail, client, progress_id_d, download_d):
189 """an error happened while trying to get slot""" 189 """an error happened while trying to get slot"""
190 log.warning(u"Can't get upload slot: {reason}".format(reason=fail.value)) 190 log.warning("Can't get upload slot: {reason}".format(reason=fail.value))
191 progress_id_d.errback(fail) 191 progress_id_d.errback(fail)
192 download_d.errback(fail) 192 download_d.errback(fail)
193 193
194 def _getSlotCb(self, slot, client, progress_id_d, download_d, path, size, 194 def _getSlotCb(self, slot, client, progress_id_d, download_d, path, size,
195 ignore_tls_errors=False): 195 ignore_tls_errors=False):
202 @param path(str): path to the file to upload 202 @param path(str): path to the file to upload
203 @param size(int): size of the file to upload 203 @param size(int): size of the file to upload
204 @param ignore_tls_errors(bool): ignore TLS certificate is True 204 @param ignore_tls_errors(bool): ignore TLS certificate is True
205 @return (tuple 205 @return (tuple
206 """ 206 """
207 log.debug(u"Got upload slot: {}".format(slot)) 207 log.debug("Got upload slot: {}".format(slot))
208 sat_file = self.host.plugins["FILE"].File( 208 sat_file = self.host.plugins["FILE"].File(
209 self.host, client, path, size=size, auto_end_signals=False 209 self.host, client, path, size=size, auto_end_signals=False
210 ) 210 )
211 progress_id_d.callback(sat_file.uid) 211 progress_id_d.callback(sat_file.uid)
212 file_producer = http_client.FileBodyProducer(sat_file) 212 file_producer = http_client.FileBodyProducer(sat_file)
241 241
242 @param sat_file(SatFile): file used for the upload 242 @param sat_file(SatFile): file used for the upload
243 should be closed, be is needed to send the progressFinished signal 243 should be closed, be is needed to send the progressFinished signal
244 @param slot(Slot): put/get urls 244 @param slot(Slot): put/get urls
245 """ 245 """
246 log.info(u"HTTP upload finished") 246 log.info("HTTP upload finished")
247 sat_file.progressFinished({"url": slot.get}) 247 sat_file.progressFinished({"url": slot.get})
248 download_d.callback(slot.get) 248 download_d.callback(slot.get)
249 249
250 def _uploadEb(self, fail, sat_file, download_d): 250 def _uploadEb(self, fail, sat_file, download_d):
251 """Called on unsuccessful upload 251 """Called on unsuccessful upload
255 """ 255 """
256 download_d.errback(fail) 256 download_d.errback(fail)
257 try: 257 try:
258 wrapped_fail = fail.value.reasons[0] 258 wrapped_fail = fail.value.reasons[0]
259 except (AttributeError, IndexError) as e: 259 except (AttributeError, IndexError) as e:
260 log.warning(_(u"upload failed: {reason}").format(reason=e)) 260 log.warning(_("upload failed: {reason}").format(reason=e))
261 sat_file.progressError(unicode(fail)) 261 sat_file.progressError(str(fail))
262 raise fail 262 raise fail
263 else: 263 else:
264 if wrapped_fail.check(SSL.Error): 264 if wrapped_fail.check(SSL.Error):
265 msg = u"TLS validation error, can't connect to HTTPS server" 265 msg = "TLS validation error, can't connect to HTTPS server"
266 else: 266 else:
267 msg = u"can't upload file" 267 msg = "can't upload file"
268 log.warning(msg + ": " + unicode(wrapped_fail.value)) 268 log.warning(msg + ": " + str(wrapped_fail.value))
269 sat_file.progressError(msg) 269 sat_file.progressError(msg)
270 270
271 def _gotSlot(self, iq_elt, client): 271 def _gotSlot(self, iq_elt, client):
272 """Slot have been received 272 """Slot have been received
273 273
274 This method convert the iq_elt result to a Slot instance 274 This method convert the iq_elt result to a Slot instance
275 @param iq_elt(domish.Element): <IQ/> result as specified in XEP-0363 275 @param iq_elt(domish.Element): <IQ/> result as specified in XEP-0363
276 """ 276 """
277 try: 277 try:
278 slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, "slot").next() 278 slot_elt = next(iq_elt.elements(NS_HTTP_UPLOAD, "slot"))
279 put_elt = slot_elt.elements(NS_HTTP_UPLOAD, "put").next() 279 put_elt = next(slot_elt.elements(NS_HTTP_UPLOAD, "put"))
280 put_url = put_elt['url'] 280 put_url = put_elt['url']
281 get_elt = slot_elt.elements(NS_HTTP_UPLOAD, "get").next() 281 get_elt = next(slot_elt.elements(NS_HTTP_UPLOAD, "get"))
282 get_url = get_elt['url'] 282 get_url = get_elt['url']
283 except (StopIteration, KeyError): 283 except (StopIteration, KeyError):
284 raise exceptions.DataError(u"Incorrect stanza received from server") 284 raise exceptions.DataError("Incorrect stanza received from server")
285 headers = [] 285 headers = []
286 for header_elt in put_elt.elements(NS_HTTP_UPLOAD, "header"): 286 for header_elt in put_elt.elements(NS_HTTP_UPLOAD, "header"):
287 try: 287 try:
288 name = header_elt["name"] 288 name = header_elt["name"]
289 value = unicode(header_elt) 289 value = str(header_elt)
290 except KeyError: 290 except KeyError:
291 log.warning(_(u"Invalid header element: {xml}").format( 291 log.warning(_("Invalid header element: {xml}").format(
292 iq_elt.toXml())) 292 iq_elt.toXml()))
293 continue 293 continue
294 name = name.replace('\n', '') 294 name = name.replace('\n', '')
295 value = value.replace('\n', '') 295 value = value.replace('\n', '')
296 if name.lower() not in ALLOWED_HEADERS: 296 if name.lower() not in ALLOWED_HEADERS:
297 log.warning(_(u'Ignoring unauthorised header "{name}": {xml}') 297 log.warning(_('Ignoring unauthorised header "{name}": {xml}')
298 .format(name=name, xml = iq_elt.toXml())) 298 .format(name=name, xml = iq_elt.toXml()))
299 continue 299 continue
300 headers.append((name, value)) 300 headers.append((name, value))
301 301
302 slot = Slot(put=put_url, get=get_url, headers=tuple(headers)) 302 slot = Slot(put=put_url, get=get_url, headers=tuple(headers))
349 ) 349 )
350 return d 350 return d
351 else: 351 else:
352 if upload_jid is None: 352 if upload_jid is None:
353 raise failure.Failure( 353 raise failure.Failure(
354 exceptions.NotFound(u"No HTTP upload entity found") 354 exceptions.NotFound("No HTTP upload entity found")
355 ) 355 )
356 356
357 iq_elt = client.IQ("get") 357 iq_elt = client.IQ("get")
358 iq_elt["to"] = upload_jid.full() 358 iq_elt["to"] = upload_jid.full()
359 request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request")) 359 request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request"))
360 request_elt["filename"] = filename 360 request_elt["filename"] = filename
361 request_elt["size"] = unicode(size) 361 request_elt["size"] = str(size)
362 if content_type is not None: 362 if content_type is not None:
363 request_elt["content-type"] = content_type 363 request_elt["content-type"] = content_type
364 364
365 d = iq_elt.send() 365 d = iq_elt.send()
366 d.addCallback(self._gotSlot, client) 366 d.addCallback(self._gotSlot, client)
367 367
368 return d 368 return d
369 369
370 370
371 @implementer(iwokkel.IDisco)
371 class XEP_0363_handler(XMPPHandler): 372 class XEP_0363_handler(XMPPHandler):
372 implements(iwokkel.IDisco)
373 373
374 def getDiscoInfo(self, requestor, target, nodeIdentifier=""): 374 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
375 return [disco.DiscoFeature(NS_HTTP_UPLOAD)] 375 return [disco.DiscoFeature(NS_HTTP_UPLOAD)]
376 376
377 def getDiscoItems(self, requestor, target, nodeIdentifier=""): 377 def getDiscoItems(self, requestor, target, nodeIdentifier=""):