Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_xep_0447.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 4b842c1fb686 |
children | 111dce64dcb5 |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
64 self._u = host.plugins["XEP-0103"] | 64 self._u = host.plugins["XEP-0103"] |
65 self._hints = host.plugins["XEP-0334"] | 65 self._hints = host.plugins["XEP-0334"] |
66 self._m = host.plugins["XEP-0446"] | 66 self._m = host.plugins["XEP-0446"] |
67 self._http_upload = host.plugins.get("XEP-0363") | 67 self._http_upload = host.plugins.get("XEP-0363") |
68 self._attach = host.plugins["ATTACH"] | 68 self._attach = host.plugins["ATTACH"] |
69 self._attach.register( | 69 self._attach.register(self.can_handle_attachment, self.attach, priority=1000) |
70 self.can_handle_attachment, self.attach, priority=1000 | |
71 ) | |
72 self.register_source_handler( | 70 self.register_source_handler( |
73 self._u.namespace, "url-data", self._u.parse_url_data_elt | 71 self._u.namespace, "url-data", self._u.parse_url_data_elt |
74 ) | 72 ) |
75 host.plugins["DOWNLOAD"].register_download_handler(self._u.namespace, self.download) | 73 host.plugins["DOWNLOAD"].register_download_handler( |
74 self._u.namespace, self.download | |
75 ) | |
76 host.trigger.add("message_received", self._message_received_trigger) | 76 host.trigger.add("message_received", self._message_received_trigger) |
77 | 77 |
78 def register_source_handler( | 78 def register_source_handler( |
79 self, namespace: str, element_name: str, | 79 self, |
80 namespace: str, | |
81 element_name: str, | |
80 callback: Callable[[domish.Element], Dict[str, Any]], | 82 callback: Callable[[domish.Element], Dict[str, Any]], |
81 encrypted: bool = False | 83 encrypted: bool = False, |
82 ) -> None: | 84 ) -> None: |
83 """Register a handler for file source | 85 """Register a handler for file source |
84 | 86 |
85 @param namespace: namespace of the element supported | 87 @param namespace: namespace of the element supported |
86 @param element_name: name of the element supported | 88 @param element_name: name of the element supported |
101 self, | 103 self, |
102 client: SatXMPPEntity, | 104 client: SatXMPPEntity, |
103 attachment: Dict[str, Any], | 105 attachment: Dict[str, Any], |
104 source: Dict[str, Any], | 106 source: Dict[str, Any], |
105 dest_path: Union[Path, str], | 107 dest_path: Union[Path, str], |
106 extra: Optional[Dict[str, Any]] = None | 108 extra: Optional[Dict[str, Any]] = None, |
107 ) -> Tuple[str, defer.Deferred]: | 109 ) -> Tuple[str, defer.Deferred]: |
108 # TODO: handle url-data headers | 110 # TODO: handle url-data headers |
109 if extra is None: | 111 if extra is None: |
110 extra = {} | 112 extra = {} |
111 try: | 113 try: |
112 download_url = source["url"] | 114 download_url = source["url"] |
113 except KeyError: | 115 except KeyError: |
114 raise ValueError(f"{source} has missing URL") | 116 raise ValueError(f"{source} has missing URL") |
115 | 117 |
116 if extra.get('ignore_tls_errors', False): | 118 if extra.get("ignore_tls_errors", False): |
117 log.warning( | 119 log.warning("TLS certificate check disabled, this is highly insecure") |
118 "TLS certificate check disabled, this is highly insecure" | |
119 ) | |
120 treq_client = treq_client_no_ssl | 120 treq_client = treq_client_no_ssl |
121 else: | 121 else: |
122 treq_client = treq | 122 treq_client = treq |
123 | 123 |
124 try: | 124 try: |
125 file_size = int(attachment["size"]) | 125 file_size = int(attachment["size"]) |
126 except (KeyError, ValueError): | 126 except (KeyError, ValueError): |
127 head_data = await treq_client.head(download_url) | 127 head_data = await treq_client.head(download_url) |
128 file_size = int(head_data.headers.getRawHeaders('content-length')[0]) | 128 file_size = int(head_data.headers.getRawHeaders("content-length")[0]) |
129 | 129 |
130 file_obj = stream.SatFile( | 130 file_obj = stream.SatFile( |
131 self.host, | 131 self.host, |
132 client, | 132 client, |
133 dest_path, | 133 dest_path, |
134 mode="wb", | 134 mode="wb", |
135 size = file_size, | 135 size=file_size, |
136 ) | 136 ) |
137 | 137 |
138 progress_id = file_obj.uid | 138 progress_id = file_obj.uid |
139 | 139 |
140 resp = await treq_client.get(download_url, unbuffered=True) | 140 resp = await treq_client.get(download_url, unbuffered=True) |
155 return False | 155 return False |
156 else: | 156 else: |
157 return True | 157 return True |
158 | 158 |
159 def get_sources_elt( | 159 def get_sources_elt( |
160 self, | 160 self, children: Optional[List[domish.Element]] = None |
161 children: Optional[List[domish.Element]] = None | |
162 ) -> domish.Element: | 161 ) -> domish.Element: |
163 """Generate <sources> element""" | 162 """Generate <sources> element""" |
164 sources_elt = domish.Element((NS_SFS, "sources")) | 163 sources_elt = domish.Element((NS_SFS, "sources")) |
165 if children: | 164 if children: |
166 for child in children: | 165 for child in children: |
209 ) | 208 ) |
210 sources_elt = self.get_sources_elt() | 209 sources_elt = self.get_sources_elt() |
211 file_sharing_elt.addChild(sources_elt) | 210 file_sharing_elt.addChild(sources_elt) |
212 for source_data in sources: | 211 for source_data in sources: |
213 if "url" in source_data: | 212 if "url" in source_data: |
214 sources_elt.addChild( | 213 sources_elt.addChild(self._u.get_url_data_elt(**source_data)) |
215 self._u.get_url_data_elt(**source_data) | |
216 ) | |
217 else: | 214 else: |
218 raise NotImplementedError( | 215 raise NotImplementedError(f"source data not implemented: {source_data}") |
219 f"source data not implemented: {source_data}" | |
220 ) | |
221 | 216 |
222 return file_sharing_elt | 217 return file_sharing_elt |
223 | 218 |
224 def parse_sources_elt( | 219 def parse_sources_elt(self, sources_elt: domish.Element) -> List[Dict[str, Any]]: |
225 self, | |
226 sources_elt: domish.Element | |
227 ) -> List[Dict[str, Any]]: | |
228 """Parse <sources/> element | 220 """Parse <sources/> element |
229 | 221 |
230 @param sources_elt: <sources/> element, or a direct parent element | 222 @param sources_elt: <sources/> element, or a direct parent element |
231 @return: list of found sources data | 223 @return: list of found sources data |
232 @raise: exceptions.NotFound: Can't find <sources/> element | 224 @raise: exceptions.NotFound: Can't find <sources/> element |
234 if sources_elt.name != "sources" or sources_elt.uri != NS_SFS: | 226 if sources_elt.name != "sources" or sources_elt.uri != NS_SFS: |
235 try: | 227 try: |
236 sources_elt = next(sources_elt.elements(NS_SFS, "sources")) | 228 sources_elt = next(sources_elt.elements(NS_SFS, "sources")) |
237 except StopIteration: | 229 except StopIteration: |
238 raise exceptions.NotFound( | 230 raise exceptions.NotFound( |
239 f"<sources/> element is missing: {sources_elt.toXml()}") | 231 f"<sources/> element is missing: {sources_elt.toXml()}" |
232 ) | |
240 sources = [] | 233 sources = [] |
241 for elt in sources_elt.elements(): | 234 for elt in sources_elt.elements(): |
242 if not elt.uri: | 235 if not elt.uri: |
243 log.warning("ignoring source element {elt.toXml()}") | 236 log.warning("ignoring source element {elt.toXml()}") |
244 continue | 237 continue |
255 if "type" not in source_data: | 248 if "type" not in source_data: |
256 source_data["type"] = elt.uri | 249 source_data["type"] = elt.uri |
257 sources.append(source_data) | 250 sources.append(source_data) |
258 return sources | 251 return sources |
259 | 252 |
260 def parse_file_sharing_elt( | 253 def parse_file_sharing_elt(self, file_sharing_elt: domish.Element) -> Dict[str, Any]: |
261 self, | |
262 file_sharing_elt: domish.Element | |
263 ) -> Dict[str, Any]: | |
264 """Parse <file-sharing/> element and return file-sharing data | 254 """Parse <file-sharing/> element and return file-sharing data |
265 | 255 |
266 @param file_sharing_elt: <file-sharing/> element | 256 @param file_sharing_elt: <file-sharing/> element |
267 @return: file-sharing data. It a dict whose keys correspond to | 257 @return: file-sharing data. It a dict whose keys correspond to |
268 [get_file_sharing_elt] parameters | 258 [get_file_sharing_elt] parameters |
269 """ | 259 """ |
270 if file_sharing_elt.name != "file-sharing" or file_sharing_elt.uri != NS_SFS: | 260 if file_sharing_elt.name != "file-sharing" or file_sharing_elt.uri != NS_SFS: |
271 try: | 261 try: |
272 file_sharing_elt = next( | 262 file_sharing_elt = next(file_sharing_elt.elements(NS_SFS, "file-sharing")) |
273 file_sharing_elt.elements(NS_SFS, "file-sharing") | |
274 ) | |
275 except StopIteration: | 263 except StopIteration: |
276 raise exceptions.NotFound | 264 raise exceptions.NotFound |
277 try: | 265 try: |
278 data = self._m.parse_file_metadata_elt(file_sharing_elt) | 266 data = self._m.parse_file_metadata_elt(file_sharing_elt) |
279 except exceptions.NotFound: | 267 except exceptions.NotFound: |
287 raise ValueError(str(e)) | 275 raise ValueError(str(e)) |
288 | 276 |
289 return data | 277 return data |
290 | 278 |
291 def _add_file_sharing_attachments( | 279 def _add_file_sharing_attachments( |
292 self, | 280 self, client: SatXMPPEntity, message_elt: domish.Element, data: Dict[str, Any] |
293 client: SatXMPPEntity, | |
294 message_elt: domish.Element, | |
295 data: Dict[str, Any] | |
296 ) -> Dict[str, Any]: | 281 ) -> Dict[str, Any]: |
297 """Check <message> for a shared file, and add it as an attachment""" | 282 """Check <message> for a shared file, and add it as an attachment""" |
298 # XXX: XEP-0447 doesn't support several attachments in a single message, for now | 283 # XXX: XEP-0447 doesn't support several attachments in a single message, for now |
299 # however that should be fixed in future version, and so we accept several | 284 # however that should be fixed in future version, and so we accept several |
300 # <file-sharing> element in a message. | 285 # <file-sharing> element in a message. |
301 for file_sharing_elt in message_elt.elements(NS_SFS, "file-sharing"): | 286 for file_sharing_elt in message_elt.elements(NS_SFS, "file-sharing"): |
302 attachment = self.parse_file_sharing_elt(message_elt) | 287 attachment = self.parse_file_sharing_elt(message_elt) |
303 | 288 |
304 if any( | 289 if any( |
305 s.get(C.MESS_KEY_ENCRYPTED, False) | 290 s.get(C.MESS_KEY_ENCRYPTED, False) for s in attachment["sources"] |
306 for s in attachment["sources"] | |
307 ) and client.encryption.isEncrypted(data): | 291 ) and client.encryption.isEncrypted(data): |
308 # we don't add the encrypted flag if the message itself is not encrypted, | 292 # we don't add the encrypted flag if the message itself is not encrypted, |
309 # because the decryption key is part of the link, so sending it over | 293 # because the decryption key is part of the link, so sending it over |
310 # unencrypted channel is like having no encryption at all. | 294 # unencrypted channel is like having no encryption at all. |
311 attachment[C.MESS_KEY_ENCRYPTED] = True | 295 attachment[C.MESS_KEY_ENCRYPTED] = True |
312 | 296 |
313 attachments = data['extra'].setdefault(C.KEY_ATTACHMENTS, []) | 297 attachments = data["extra"].setdefault(C.KEY_ATTACHMENTS, []) |
314 attachments.append(attachment) | 298 attachments.append(attachment) |
315 | 299 |
316 return data | 300 return data |
317 | 301 |
318 async def attach(self, client, data): | 302 async def attach(self, client, data): |
319 # XXX: for now, XEP-0447 only allow to send one file per <message/>, thus we need | 303 # XXX: for now, XEP-0447 only allow to send one file per <message/>, thus we need |
320 # to send each file in a separate message | 304 # to send each file in a separate message |
321 attachments = data["extra"][C.KEY_ATTACHMENTS] | 305 attachments = data["extra"][C.KEY_ATTACHMENTS] |
322 if not data['message'] or data['message'] == {'': ''}: | 306 if not data["message"] or data["message"] == {"": ""}: |
323 extra_attachments = attachments[1:] | 307 extra_attachments = attachments[1:] |
324 del attachments[1:] | 308 del attachments[1:] |
325 else: | 309 else: |
326 # we have a message, we must send first attachment separately | 310 # we have a message, we must send first attachment separately |
327 extra_attachments = attachments[:] | 311 extra_attachments = attachments[:] |
344 [{"url": attachment["url"]}], | 328 [{"url": attachment["url"]}], |
345 name=attachment.get("name"), | 329 name=attachment.get("name"), |
346 size=attachment.get("size"), | 330 size=attachment.get("size"), |
347 desc=attachment.get("desc"), | 331 desc=attachment.get("desc"), |
348 media_type=attachment.get("media_type"), | 332 media_type=attachment.get("media_type"), |
349 file_hash=file_hash | 333 file_hash=file_hash, |
350 ) | 334 ) |
351 data["xml"].addChild(file_sharing_elt) | 335 data["xml"].addChild(file_sharing_elt) |
352 | 336 |
353 for attachment in extra_attachments: | 337 for attachment in extra_attachments: |
354 # we send all remaining attachment in a separate message | 338 # we send all remaining attachment in a separate message |
355 await client.sendMessage( | 339 await client.sendMessage( |
356 to_jid=data['to'], | 340 to_jid=data["to"], |
357 message={'': ''}, | 341 message={"": ""}, |
358 subject=data['subject'], | 342 subject=data["subject"], |
359 mess_type=data['type'], | 343 mess_type=data["type"], |
360 extra={C.KEY_ATTACHMENTS: [attachment]}, | 344 extra={C.KEY_ATTACHMENTS: [attachment]}, |
361 ) | 345 ) |
362 | 346 |
363 if ((not data['extra'] | 347 if ( |
364 and (not data['message'] or data['message'] == {'': ''}) | 348 not data["extra"] |
365 and not data['subject'])): | 349 and (not data["message"] or data["message"] == {"": ""}) |
350 and not data["subject"] | |
351 ): | |
366 # nothing left to send, we can cancel the message | 352 # nothing left to send, we can cancel the message |
367 raise exceptions.CancelError("Cancelled by XEP_0447 attachment handling") | 353 raise exceptions.CancelError("Cancelled by XEP_0447 attachment handling") |
368 | 354 |
369 def _message_received_trigger(self, client, message_elt, post_treat): | 355 def _message_received_trigger(self, client, message_elt, post_treat): |
370 # we use a post_treat callback instead of "message_parse" trigger because we need | 356 # we use a post_treat callback instead of "message_parse" trigger because we need |