comparison sat/plugins/plugin_xep_0363.py @ 2624:56f94936df1e

code style reformatting using black
author Goffi <goffi@goffi.org>
date Wed, 27 Jun 2018 20:14:46 +0200
parents 26edcf3a30eb
children 378188abe941
comparison
equal deleted inserted replaced
2623:49533de4540b 2624:56f94936df1e
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _
21 from sat.core.constants import Const as C 21 from sat.core.constants import Const as C
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23
23 log = getLogger(__name__) 24 log = getLogger(__name__)
24 from sat.core import exceptions 25 from sat.core import exceptions
25 from wokkel import disco, iwokkel 26 from wokkel import disco, iwokkel
26 from zope.interface import implements 27 from zope.interface import implements
27 from twisted.words.protocols.jabber import jid 28 from twisted.words.protocols.jabber import jid
47 C.PI_TYPE: "XEP", 48 C.PI_TYPE: "XEP",
48 C.PI_PROTOCOLS: ["XEP-0363"], 49 C.PI_PROTOCOLS: ["XEP-0363"],
49 C.PI_DEPENDENCIES: ["FILE", "UPLOAD"], 50 C.PI_DEPENDENCIES: ["FILE", "UPLOAD"],
50 C.PI_MAIN: "XEP_0363", 51 C.PI_MAIN: "XEP_0363",
51 C.PI_HANDLER: "yes", 52 C.PI_HANDLER: "yes",
52 C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload""") 53 C.PI_DESCRIPTION: _("""Implementation of HTTP File Upload"""),
53 } 54 }
54 55
55 NS_HTTP_UPLOAD = 'urn:xmpp:http:upload' 56 NS_HTTP_UPLOAD = "urn:xmpp:http:upload"
56 57
57 58
58 Slot = namedtuple('Slot', ['put', 'get']) 59 Slot = namedtuple("Slot", ["put", "get"])
59 60
60 61
61 @implementer(IOpenSSLClientConnectionCreator) 62 @implementer(IOpenSSLClientConnectionCreator)
62 class NoCheckConnectionCreator(object): 63 class NoCheckConnectionCreator(object):
63
64 def __init__(self, hostname, ctx): 64 def __init__(self, hostname, ctx):
65 self._ctx = ctx 65 self._ctx = ctx
66 66
67 def clientConnectionForTLS(self, tlsProtocol): 67 def clientConnectionForTLS(self, tlsProtocol):
68 context = self._ctx 68 context = self._ctx
78 /!\\ it's obvisously a security flaw to use this class, 78 /!\\ it's obvisously a security flaw to use this class,
79 and it should be used only wiht explicite agreement from the end used 79 and it should be used only wiht explicite agreement from the end used
80 """ 80 """
81 81
82 def creatorForNetloc(self, hostname, port): 82 def creatorForNetloc(self, hostname, port):
83 log.warning(u"TLS check disabled for {host} on port {port}".format(host=hostname, port=port)) 83 log.warning(
84 u"TLS check disabled for {host} on port {port}".format(
85 host=hostname, port=port
86 )
87 )
84 certificateOptions = ssl.CertificateOptions(trustRoot=None) 88 certificateOptions = ssl.CertificateOptions(trustRoot=None)
85 return NoCheckConnectionCreator(hostname, certificateOptions.getContext()) 89 return NoCheckConnectionCreator(hostname, certificateOptions.getContext())
86 90
87 91
88 class XEP_0363(object): 92 class XEP_0363(object):
89
90 def __init__(self, host): 93 def __init__(self, host):
91 log.info(_("plugin HTTP File Upload initialization")) 94 log.info(_("plugin HTTP File Upload initialization"))
92 self.host = host 95 self.host = host
93 host.bridge.addMethod("fileHTTPUpload", ".plugin", in_sign='sssbs', out_sign='', method=self._fileHTTPUpload) 96 host.bridge.addMethod(
94 host.bridge.addMethod("fileHTTPUploadGetSlot", ".plugin", in_sign='sisss', out_sign='(ss)', method=self._getSlot, async=True) 97 "fileHTTPUpload",
95 host.plugins['UPLOAD'].register(u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload) 98 ".plugin",
99 in_sign="sssbs",
100 out_sign="",
101 method=self._fileHTTPUpload,
102 )
103 host.bridge.addMethod(
104 "fileHTTPUploadGetSlot",
105 ".plugin",
106 in_sign="sisss",
107 out_sign="(ss)",
108 method=self._getSlot,
109 async=True,
110 )
111 host.plugins["UPLOAD"].register(
112 u"HTTP Upload", self.getHTTPUploadEntity, self.fileHTTPUpload
113 )
96 114
97 def getHandler(self, client): 115 def getHandler(self, client):
98 return XEP_0363_handler() 116 return XEP_0363_handler()
99 117
100 @defer.inlineCallbacks 118 @defer.inlineCallbacks
101 def getHTTPUploadEntity(self, upload_jid=None, profile=C.PROF_KEY_NONE): 119 def getHTTPUploadEntity(self, upload_jid=None, profile=C.PROF_KEY_NONE):
102 """Get HTTP upload capable entity 120 """Get HTTP upload capable entity
103 121
104 upload_jid is checked, then its components 122 upload_jid is checked, then its components
105 @param upload_jid(None, jid.JID): entity to check 123 @param upload_jid(None, jid.JID): entity to check
106 @return(D(jid.JID)): first HTTP upload capable entity 124 @return(D(jid.JID)): first HTTP upload capable entity
107 @raise exceptions.NotFound: no entity found 125 @raise exceptions.NotFound: no entity found
108 """ 126 """
109 client = self.host.getClient(profile) 127 client = self.host.getClient(profile)
110 try: 128 try:
111 entity = client.http_upload_service 129 entity = client.http_upload_service
112 except AttributeError: 130 except AttributeError:
113 found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,)) 131 found_entities = yield self.host.findFeaturesSet(client, (NS_HTTP_UPLOAD,))
114 try: 132 try:
115 entity = client.http_upload_service = iter(found_entities).next() 133 entity = client.http_upload_service = iter(found_entities).next()
116 except StopIteration: 134 except StopIteration:
117 entity = client.http_upload_service = None 135 entity = client.http_upload_service = None
118 136
119 if entity is None: 137 if entity is None:
120 raise failure.Failure(exceptions.NotFound(u'No HTTP upload entity found')) 138 raise failure.Failure(exceptions.NotFound(u"No HTTP upload entity found"))
121 139
122 defer.returnValue(entity) 140 defer.returnValue(entity)
123 141
124 def _fileHTTPUpload(self, filepath, filename='', upload_jid='', ignore_tls_errors=False, profile=C.PROF_KEY_NONE): 142 def _fileHTTPUpload(
143 self,
144 filepath,
145 filename="",
146 upload_jid="",
147 ignore_tls_errors=False,
148 profile=C.PROF_KEY_NONE,
149 ):
125 assert os.path.isabs(filepath) and os.path.isfile(filepath) 150 assert os.path.isabs(filepath) and os.path.isfile(filepath)
126 progress_id_d, dummy = self.fileHTTPUpload(filepath, filename or None, jid.JID(upload_jid) if upload_jid else None, {'ignore_tls_errors': ignore_tls_errors}, profile) 151 progress_id_d, dummy = self.fileHTTPUpload(
152 filepath,
153 filename or None,
154 jid.JID(upload_jid) if upload_jid else None,
155 {"ignore_tls_errors": ignore_tls_errors},
156 profile,
157 )
127 return progress_id_d 158 return progress_id_d
128 159
129 def fileHTTPUpload(self, filepath, filename=None, upload_jid=None, options=None, profile=C.PROF_KEY_NONE): 160 def fileHTTPUpload(
161 self,
162 filepath,
163 filename=None,
164 upload_jid=None,
165 options=None,
166 profile=C.PROF_KEY_NONE,
167 ):
130 """upload a file through HTTP 168 """upload a file through HTTP
131 169
132 @param filepath(str): absolute path of the file 170 @param filepath(str): absolute path of the file
133 @param filename(None, unicode): name to use for the upload 171 @param filename(None, unicode): name to use for the upload
134 None to use basename of the path 172 None to use basename of the path
139 @param profile: %(doc_profile)s 177 @param profile: %(doc_profile)s
140 @return (D(tuple[D(unicode), D(unicode)])): progress id and Deferred which fire download URL 178 @return (D(tuple[D(unicode), D(unicode)])): progress id and Deferred which fire download URL
141 """ 179 """
142 if options is None: 180 if options is None:
143 options = {} 181 options = {}
144 ignore_tls_errors = options.get('ignore_tls_errors', False) 182 ignore_tls_errors = options.get("ignore_tls_errors", False)
145 client = self.host.getClient(profile) 183 client = self.host.getClient(profile)
146 filename = filename or os.path.basename(filepath) 184 filename = filename or os.path.basename(filepath)
147 size = os.path.getsize(filepath) 185 size = os.path.getsize(filepath)
148 progress_id_d = defer.Deferred() 186 progress_id_d = defer.Deferred()
149 download_d = defer.Deferred() 187 download_d = defer.Deferred()
150 d = self.getSlot(client, filename, size, upload_jid=upload_jid) 188 d = self.getSlot(client, filename, size, upload_jid=upload_jid)
151 d.addCallbacks(self._getSlotCb, self._getSlotEb, (client, progress_id_d, download_d, filepath, size, ignore_tls_errors), None, (client, progress_id_d, download_d)) 189 d.addCallbacks(
190 self._getSlotCb,
191 self._getSlotEb,
192 (client, progress_id_d, download_d, filepath, size, ignore_tls_errors),
193 None,
194 (client, progress_id_d, download_d),
195 )
152 return progress_id_d, download_d 196 return progress_id_d, download_d
153 197
154 def _getSlotEb(self, fail, client, progress_id_d, download_d): 198 def _getSlotEb(self, fail, client, progress_id_d, download_d):
155 """an error happened while trying to get slot""" 199 """an error happened while trying to get slot"""
156 log.warning(u"Can't get upload slot: {reason}".format(reason=fail.value)) 200 log.warning(u"Can't get upload slot: {reason}".format(reason=fail.value))
157 progress_id_d.errback(fail) 201 progress_id_d.errback(fail)
158 download_d.errback(fail) 202 download_d.errback(fail)
159 203
160 def _getSlotCb(self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False): 204 def _getSlotCb(
205 self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False
206 ):
161 """Called when slot is received, try to do the upload 207 """Called when slot is received, try to do the upload
162 208
163 @param slot(Slot): slot instance with the get and put urls 209 @param slot(Slot): slot instance with the get and put urls
164 @param progress_id_d(defer.Deferred): Deferred to call when progress_id is known 210 @param progress_id_d(defer.Deferred): Deferred to call when progress_id is known
165 @param progress_id_d(defer.Deferred): Deferred to call with URL when upload is done 211 @param progress_id_d(defer.Deferred): Deferred to call with URL when upload is done
167 @param size(int): size of the file to upload 213 @param size(int): size of the file to upload
168 @param ignore_tls_errors(bool): ignore TLS certificate is True 214 @param ignore_tls_errors(bool): ignore TLS certificate is True
169 @return (tuple 215 @return (tuple
170 """ 216 """
171 log.debug(u"Got upload slot: {}".format(slot)) 217 log.debug(u"Got upload slot: {}".format(slot))
172 sat_file = self.host.plugins['FILE'].File(self.host, client, path, size=size, auto_end_signals=False) 218 sat_file = self.host.plugins["FILE"].File(
219 self.host, client, path, size=size, auto_end_signals=False
220 )
173 progress_id_d.callback(sat_file.uid) 221 progress_id_d.callback(sat_file.uid)
174 file_producer = http_client.FileBodyProducer(sat_file) 222 file_producer = http_client.FileBodyProducer(sat_file)
175 if ignore_tls_errors: 223 if ignore_tls_errors:
176 agent = http_client.Agent(reactor, NoCheckContextFactory()) 224 agent = http_client.Agent(reactor, NoCheckContextFactory())
177 else: 225 else:
178 agent = http_client.Agent(reactor) 226 agent = http_client.Agent(reactor)
179 d = agent.request('PUT', slot.put.encode('utf-8'), http_headers.Headers({'User-Agent': [C.APP_NAME.encode('utf-8')]}), file_producer) 227 d = agent.request(
180 d.addCallbacks(self._uploadCb, self._uploadEb, (sat_file, slot, download_d), None, (sat_file, download_d)) 228 "PUT",
229 slot.put.encode("utf-8"),
230 http_headers.Headers({"User-Agent": [C.APP_NAME.encode("utf-8")]}),
231 file_producer,
232 )
233 d.addCallbacks(
234 self._uploadCb,
235 self._uploadEb,
236 (sat_file, slot, download_d),
237 None,
238 (sat_file, download_d),
239 )
181 return d 240 return d
182 241
183 def _uploadCb(self, dummy, sat_file, slot, download_d): 242 def _uploadCb(self, dummy, sat_file, slot, download_d):
184 """Called once file is successfully uploaded 243 """Called once file is successfully uploaded
185 244
186 @param sat_file(SatFile): file used for the upload 245 @param sat_file(SatFile): file used for the upload
187 should be closed, be is needed to send the progressFinished signal 246 should be closed, be is needed to send the progressFinished signal
188 @param slot(Slot): put/get urls 247 @param slot(Slot): put/get urls
189 """ 248 """
190 log.info(u"HTTP upload finished") 249 log.info(u"HTTP upload finished")
191 sat_file.progressFinished({'url': slot.get}) 250 sat_file.progressFinished({"url": slot.get})
192 download_d.callback(slot.get) 251 download_d.callback(slot.get)
193 252
194 def _uploadEb(self, fail, sat_file, download_d): 253 def _uploadEb(self, fail, sat_file, download_d):
195 """Called on unsuccessful upload 254 """Called on unsuccessful upload
196 255
214 273
215 This method convert the iq_elt result to a Slot instance 274 This method convert the iq_elt result to a Slot instance
216 @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
217 """ 276 """
218 try: 277 try:
219 slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, 'slot').next() 278 slot_elt = iq_elt.elements(NS_HTTP_UPLOAD, "slot").next()
220 put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, 'put').next()) 279 put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "put").next())
221 get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, 'get').next()) 280 get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "get").next())
222 except StopIteration: 281 except StopIteration:
223 raise exceptions.DataError(u"Incorrect stanza received from server") 282 raise exceptions.DataError(u"Incorrect stanza received from server")
224 slot = Slot(put=put_url, get=get_url) 283 slot = Slot(put=put_url, get=get_url)
225 return slot 284 return slot
226 285
227 def _getSlot(self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE): 286 def _getSlot(
287 self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE
288 ):
228 """Get a upload slot 289 """Get a upload slot
229 290
230 This method can be used when uploading is done by the frontend 291 This method can be used when uploading is done by the frontend
231 @param filename(unicode): name of the file to upload 292 @param filename(unicode): name of the file to upload
232 @param size(int): size of the file (must be non null) 293 @param size(int): size of the file (must be non null)
233 @param upload_jid(jid.JID(), None, ''): HTTP upload capable entity 294 @param upload_jid(jid.JID(), None, ''): HTTP upload capable entity
234 @param content_type(unicode, None): MIME type of the content 295 @param content_type(unicode, None): MIME type of the content
235 empty string or None to guess automatically 296 empty string or None to guess automatically
236 """ 297 """
237 filename = filename.replace('/', '_') 298 filename = filename.replace("/", "_")
238 client = self.host.getClient(profile_key) 299 client = self.host.getClient(profile_key)
239 return self.getSlot(client, filename, size, content_type or None, upload_jid or None) 300 return self.getSlot(
301 client, filename, size, content_type or None, upload_jid or None
302 )
240 303
241 def getSlot(self, client, filename, size, content_type=None, upload_jid=None): 304 def getSlot(self, client, filename, size, content_type=None, upload_jid=None):
242 """Get a slot (i.e. download/upload links) 305 """Get a slot (i.e. download/upload links)
243 306
244 @param filename(unicode): name to use for the upload 307 @param filename(unicode): name to use for the upload
259 if upload_jid is None: 322 if upload_jid is None:
260 try: 323 try:
261 upload_jid = client.http_upload_service 324 upload_jid = client.http_upload_service
262 except AttributeError: 325 except AttributeError:
263 d = self.getHTTPUploadEntity(profile=client.profile) 326 d = self.getHTTPUploadEntity(profile=client.profile)
264 d.addCallback(lambda found_entity: self.getSlot(client, filename, size, content_type, found_entity)) 327 d.addCallback(
328 lambda found_entity: self.getSlot(
329 client, filename, size, content_type, found_entity
330 )
331 )
265 return d 332 return d
266 else: 333 else:
267 if upload_jid is None: 334 if upload_jid is None:
268 raise failure.Failure(exceptions.NotFound(u'No HTTP upload entity found')) 335 raise failure.Failure(
269 336 exceptions.NotFound(u"No HTTP upload entity found")
270 iq_elt = client.IQ('get') 337 )
271 iq_elt['to'] = upload_jid.full() 338
272 request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, 'request')) 339 iq_elt = client.IQ("get")
273 request_elt.addElement('filename', content=filename) 340 iq_elt["to"] = upload_jid.full()
274 request_elt.addElement('size', content=unicode(size)) 341 request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request"))
342 request_elt.addElement("filename", content=filename)
343 request_elt.addElement("size", content=unicode(size))
275 if content_type is not None: 344 if content_type is not None:
276 request_elt.addElement('content-type', content=content_type) 345 request_elt.addElement("content-type", content=content_type)
277 346
278 d = iq_elt.send() 347 d = iq_elt.send()
279 d.addCallback(self._gotSlot, client) 348 d.addCallback(self._gotSlot, client)
280 349
281 return d 350 return d
282 351
283 352
284 class XEP_0363_handler(XMPPHandler): 353 class XEP_0363_handler(XMPPHandler):
285 implements(iwokkel.IDisco) 354 implements(iwokkel.IDisco)
286 355
287 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 356 def getDiscoInfo(self, requestor, target, nodeIdentifier=""):
288 return [disco.DiscoFeature(NS_HTTP_UPLOAD)] 357 return [disco.DiscoFeature(NS_HTTP_UPLOAD)]
289 358
290 def getDiscoItems(self, requestor, target, nodeIdentifier=''): 359 def getDiscoItems(self, requestor, target, nodeIdentifier=""):
291 return [] 360 return []