Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0363.py @ 2866:8ce5748bfe97
plugin XEP-0363: updated to namespace "urn:xmpp:http:upload:0", handle headers
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 22 Mar 2019 19:26:08 +0100 |
parents | 003b8b4b56a7 |
children | ab2696e34d29 |
comparison
equal
deleted
inserted
replaced
2865:9213c6dff48d | 2866:8ce5748bfe97 |
---|---|
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: _("""Implementation of HTTP File Upload"""), | 53 C.PI_DESCRIPTION: _(u"""Implementation of HTTP File Upload"""), |
54 } | 54 } |
55 | 55 |
56 NS_HTTP_UPLOAD = "urn:xmpp:http:upload" | 56 NS_HTTP_UPLOAD = "urn:xmpp:http:upload:0" |
57 | 57 ALLOWED_HEADERS = ('authorization', 'cookie', 'expires') |
58 | 58 |
59 Slot = namedtuple("Slot", ["put", "get"]) | 59 |
60 Slot = namedtuple("Slot", ["put", "get", "headers"]) | |
60 | 61 |
61 | 62 |
62 @implementer(IOpenSSLClientConnectionCreator) | 63 @implementer(IOpenSSLClientConnectionCreator) |
63 class NoCheckConnectionCreator(object): | 64 class NoCheckConnectionCreator(object): |
64 def __init__(self, hostname, ctx): | 65 def __init__(self, hostname, ctx): |
74 @implementer(iweb.IPolicyForHTTPS) | 75 @implementer(iweb.IPolicyForHTTPS) |
75 class NoCheckContextFactory(ssl.ClientContextFactory): | 76 class NoCheckContextFactory(ssl.ClientContextFactory): |
76 """Context factory which doesn't do TLS certificate check | 77 """Context factory which doesn't do TLS certificate check |
77 | 78 |
78 /!\\ it's obvisously a security flaw to use this class, | 79 /!\\ it's obvisously a security flaw to use this class, |
79 and it should be used only wiht explicite agreement from the end used | 80 and it should be used only with explicite agreement from the end used |
80 """ | 81 """ |
81 | 82 |
82 def creatorForNetloc(self, hostname, port): | 83 def creatorForNetloc(self, hostname, port): |
83 log.warning( | 84 log.warning( |
84 u"TLS check disabled for {host} on port {port}".format( | 85 u"TLS check disabled for {host} on port {port}".format( |
137 if entity is None: | 138 if entity is None: |
138 raise failure.Failure(exceptions.NotFound(u"No HTTP upload entity found")) | 139 raise failure.Failure(exceptions.NotFound(u"No HTTP upload entity found")) |
139 | 140 |
140 defer.returnValue(entity) | 141 defer.returnValue(entity) |
141 | 142 |
142 def _fileHTTPUpload( | 143 def _fileHTTPUpload(self, filepath, filename="", upload_jid="", |
143 self, | 144 ignore_tls_errors=False, profile=C.PROF_KEY_NONE): |
144 filepath, | |
145 filename="", | |
146 upload_jid="", | |
147 ignore_tls_errors=False, | |
148 profile=C.PROF_KEY_NONE, | |
149 ): | |
150 assert os.path.isabs(filepath) and os.path.isfile(filepath) | 145 assert os.path.isabs(filepath) and os.path.isfile(filepath) |
151 progress_id_d, __ = self.fileHTTPUpload( | 146 progress_id_d, __ = self.fileHTTPUpload( |
152 filepath, | 147 filepath, |
153 filename or None, | 148 filename or None, |
154 jid.JID(upload_jid) if upload_jid else None, | 149 jid.JID(upload_jid) if upload_jid else None, |
155 {"ignore_tls_errors": ignore_tls_errors}, | 150 {"ignore_tls_errors": ignore_tls_errors}, |
156 profile, | 151 profile, |
157 ) | 152 ) |
158 return progress_id_d | 153 return progress_id_d |
159 | 154 |
160 def fileHTTPUpload( | 155 def fileHTTPUpload(self, filepath, filename=None, upload_jid=None, options=None, |
161 self, | 156 profile=C.PROF_KEY_NONE): |
162 filepath, | 157 """Upload a file through HTTP |
163 filename=None, | |
164 upload_jid=None, | |
165 options=None, | |
166 profile=C.PROF_KEY_NONE, | |
167 ): | |
168 """upload a file through HTTP | |
169 | 158 |
170 @param filepath(str): absolute path of the file | 159 @param filepath(str): absolute path of the file |
171 @param filename(None, unicode): name to use for the upload | 160 @param filename(None, unicode): name to use for the upload |
172 None to use basename of the path | 161 None to use basename of the path |
173 @param upload_jid(jid.JID, None): upload capable entity jid, | 162 @param upload_jid(jid.JID, None): upload capable entity jid, |
174 or None to use autodetected, if possible | 163 or None to use autodetected, if possible |
175 @param options(dict): options where key can be: | 164 @param options(dict): options where key can be: |
176 - ignore_tls_errors(bool): if True, SSL certificate will not be checked | 165 - ignore_tls_errors(bool): if True, SSL certificate will not be checked |
177 @param profile: %(doc_profile)s | 166 @param profile: %(doc_profile)s |
178 @return (D(tuple[D(unicode), D(unicode)])): progress id and Deferred which fire download URL | 167 @return (D(tuple[D(unicode), D(unicode)])): progress id and Deferred which fire |
168 download URL | |
179 """ | 169 """ |
180 if options is None: | 170 if options is None: |
181 options = {} | 171 options = {} |
182 ignore_tls_errors = options.get("ignore_tls_errors", False) | 172 ignore_tls_errors = options.get("ignore_tls_errors", False) |
183 client = self.host.getClient(profile) | 173 client = self.host.getClient(profile) |
199 """an error happened while trying to get slot""" | 189 """an error happened while trying to get slot""" |
200 log.warning(u"Can't get upload slot: {reason}".format(reason=fail.value)) | 190 log.warning(u"Can't get upload slot: {reason}".format(reason=fail.value)) |
201 progress_id_d.errback(fail) | 191 progress_id_d.errback(fail) |
202 download_d.errback(fail) | 192 download_d.errback(fail) |
203 | 193 |
204 def _getSlotCb( | 194 def _getSlotCb(self, slot, client, progress_id_d, download_d, path, size, |
205 self, slot, client, progress_id_d, download_d, path, size, ignore_tls_errors=False | 195 ignore_tls_errors=False): |
206 ): | |
207 """Called when slot is received, try to do the upload | 196 """Called when slot is received, try to do the upload |
208 | 197 |
209 @param slot(Slot): slot instance with the get and put urls | 198 @param slot(Slot): slot instance with the get and put urls |
210 @param progress_id_d(defer.Deferred): Deferred to call when progress_id is known | 199 @param progress_id_d(defer.Deferred): Deferred to call when progress_id is known |
211 @param progress_id_d(defer.Deferred): Deferred to call with URL when upload is done | 200 @param progress_id_d(defer.Deferred): Deferred to call with URL when upload is |
201 done | |
212 @param path(str): path to the file to upload | 202 @param path(str): path to the file to upload |
213 @param size(int): size of the file to upload | 203 @param size(int): size of the file to upload |
214 @param ignore_tls_errors(bool): ignore TLS certificate is True | 204 @param ignore_tls_errors(bool): ignore TLS certificate is True |
215 @return (tuple | 205 @return (tuple |
216 """ | 206 """ |
222 file_producer = http_client.FileBodyProducer(sat_file) | 212 file_producer = http_client.FileBodyProducer(sat_file) |
223 if ignore_tls_errors: | 213 if ignore_tls_errors: |
224 agent = http_client.Agent(reactor, NoCheckContextFactory()) | 214 agent = http_client.Agent(reactor, NoCheckContextFactory()) |
225 else: | 215 else: |
226 agent = http_client.Agent(reactor) | 216 agent = http_client.Agent(reactor) |
217 | |
218 headers = {"User-Agent": [C.APP_NAME.encode("utf-8")]} | |
219 for name, value in slot.headers: | |
220 name = name.encode('utf-8') | |
221 value = value.encode('utf-8') | |
222 headers[name] = value | |
223 | |
227 d = agent.request( | 224 d = agent.request( |
228 "PUT", | 225 "PUT", |
229 slot.put.encode("utf-8"), | 226 slot.put.encode("utf-8"), |
230 http_headers.Headers({"User-Agent": [C.APP_NAME.encode("utf-8")]}), | 227 http_headers.Headers(headers), |
231 file_producer, | 228 file_producer, |
232 ) | 229 ) |
233 d.addCallbacks( | 230 d.addCallbacks( |
234 self._uploadCb, | 231 self._uploadCb, |
235 self._uploadEb, | 232 self._uploadEb, |
257 should be closed, be is needed to send the progressError signal | 254 should be closed, be is needed to send the progressError signal |
258 """ | 255 """ |
259 download_d.errback(fail) | 256 download_d.errback(fail) |
260 try: | 257 try: |
261 wrapped_fail = fail.value.reasons[0] | 258 wrapped_fail = fail.value.reasons[0] |
262 except (AttributeError, IndexError): | 259 except (AttributeError, IndexError) as e: |
260 log.warning(_(u"upload failed: {reason}").format(reason=e)) | |
263 sat_file.progressError(unicode(fail)) | 261 sat_file.progressError(unicode(fail)) |
264 raise fail | 262 raise fail |
265 else: | 263 else: |
266 if wrapped_fail.check(SSL.Error): | 264 if wrapped_fail.check(SSL.Error): |
267 msg = u"TLS validation error, can't connect to HTTPS server" | 265 msg = u"TLS validation error, can't connect to HTTPS server" |
268 log.warning(msg + ": " + unicode(wrapped_fail.value)) | 266 else: |
269 sat_file.progressError(msg) | 267 msg = u"can't upload file" |
268 log.warning(msg + ": " + unicode(wrapped_fail.value)) | |
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 = iq_elt.elements(NS_HTTP_UPLOAD, "slot").next() |
279 put_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "put").next()) | 279 put_elt = slot_elt.elements(NS_HTTP_UPLOAD, "put").next() |
280 get_url = unicode(slot_elt.elements(NS_HTTP_UPLOAD, "get").next()) | 280 put_url = put_elt['url'] |
281 except StopIteration: | 281 get_elt = slot_elt.elements(NS_HTTP_UPLOAD, "get").next() |
282 get_url = get_elt['url'] | |
283 except (StopIteration, KeyError): | |
282 raise exceptions.DataError(u"Incorrect stanza received from server") | 284 raise exceptions.DataError(u"Incorrect stanza received from server") |
283 slot = Slot(put=put_url, get=get_url) | 285 headers = [] |
286 for header_elt in put_elt.elements(NS_HTTP_UPLOAD, "header"): | |
287 try: | |
288 name = header_elt["name"] | |
289 value = unicode(header_elt) | |
290 except KeyError: | |
291 log.warning(_(u"Invalid header element: {xml}").format( | |
292 iq_elt.toXml())) | |
293 continue | |
294 name = name.replace('\n', '') | |
295 value = value.replace('\n', '') | |
296 if name.lower() not in ALLOWED_HEADERS: | |
297 log.warning(_(u'Ignoring unauthorised header "{name}": {xml}') | |
298 .format(name=name, xml = iq_elt.toXml())) | |
299 continue | |
300 headers.append((name, value)) | |
301 | |
302 slot = Slot(put=put_url, get=get_url, headers=tuple(headers)) | |
284 return slot | 303 return slot |
285 | 304 |
286 def _getSlot( | 305 def _getSlot(self, filename, size, content_type, upload_jid, |
287 self, filename, size, content_type, upload_jid, profile_key=C.PROF_KEY_NONE | 306 profile_key=C.PROF_KEY_NONE): |
288 ): | 307 """Get an upload slot |
289 """Get a upload slot | |
290 | 308 |
291 This method can be used when uploading is done by the frontend | 309 This method can be used when uploading is done by the frontend |
292 @param filename(unicode): name of the file to upload | 310 @param filename(unicode): name of the file to upload |
293 @param size(int): size of the file (must be non null) | 311 @param size(int): size of the file (must be non null) |
294 @param upload_jid(jid.JID(), None, ''): HTTP upload capable entity | 312 @param upload_jid(jid.JID(), None, ''): HTTP upload capable entity |
337 ) | 355 ) |
338 | 356 |
339 iq_elt = client.IQ("get") | 357 iq_elt = client.IQ("get") |
340 iq_elt["to"] = upload_jid.full() | 358 iq_elt["to"] = upload_jid.full() |
341 request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request")) | 359 request_elt = iq_elt.addElement((NS_HTTP_UPLOAD, "request")) |
342 request_elt.addElement("filename", content=filename) | 360 request_elt["filename"] = filename |
343 request_elt.addElement("size", content=unicode(size)) | 361 request_elt["size"] = unicode(size) |
344 if content_type is not None: | 362 if content_type is not None: |
345 request_elt.addElement("content-type", content=content_type) | 363 request_elt["content-type"] = content_type |
346 | 364 |
347 d = iq_elt.send() | 365 d = iq_elt.send() |
348 d.addCallback(self._gotSlot, client) | 366 d.addCallback(self._gotSlot, client) |
349 | 367 |
350 return d | 368 return d |