comparison sat/plugins/plugin_xep_0384.py @ 3914:4cb38c8312a1

plugin XEP-0384, xml_tools: avoid `getItems` timeout + fix empty node crash + parsing: - use `max_items` in `getItems` calls for bundles, as otherwise some pubsub service may return full nodes, which may be huge is `max_items=1` is not set on the node, possibly resulting in timeouts. - the plugin was crashing when TWOMEMO devices list node has no items at all. This is not the case anymore. - a naive parsing method has been implemented in `xml_tools` to replace the serialisation/deserialisation method. This should be more efficient and will avoid annoying `ns0:` prefixes in XML logs.
author Goffi <goffi@goffi.org>
date Sat, 24 Sep 2022 16:37:46 +0200
parents 8289ac1b34f4
children e63f96e60f7b
comparison
equal deleted inserted replaced
3913:944f51f9c2b4 3914:4cb38c8312a1
417 417
418 namespace = oldmemo.oldmemo.NAMESPACE 418 namespace = oldmemo.oldmemo.NAMESPACE
419 node = f"eu.siacs.conversations.axolotl.bundles:{device_id}" 419 node = f"eu.siacs.conversations.axolotl.bundles:{device_id}"
420 420
421 try: 421 try:
422 items, __ = await xep_0060.getItems(client, jid.JID(bare_jid), node) 422 items, __ = await xep_0060.getItems(client, jid.JID(bare_jid), node, max_items=1)
423 except Exception as e: 423 except Exception as e:
424 raise omemo.BundleDownloadFailed( 424 raise omemo.BundleDownloadFailed(
425 f"Bundle download failed for {bare_jid}: {device_id} under namespace" 425 f"Bundle download failed for {bare_jid}: {device_id} under namespace"
426 f" {namespace}" 426 f" {namespace}"
427 ) from e 427 ) from e
430 raise omemo.BundleDownloadFailed( 430 raise omemo.BundleDownloadFailed(
431 f"Bundle download failed for {bare_jid}: {device_id} under namespace" 431 f"Bundle download failed for {bare_jid}: {device_id} under namespace"
432 f" {namespace}: Unexpected number of items retrieved: {len(items)}." 432 f" {namespace}: Unexpected number of items retrieved: {len(items)}."
433 ) 433 )
434 434
435 element = next(iter(domish_to_etree(cast(domish.Element, items[0]))), None) 435 element = next(iter(xml_tools.domish_elt_2_et_elt(cast(domish.Element, items[0]))), None)
436 if element is None: 436 if element is None:
437 raise omemo.BundleDownloadFailed( 437 raise omemo.BundleDownloadFailed(
438 f"Bundle download failed for {bare_jid}: {device_id} under namespace" 438 f"Bundle download failed for {bare_jid}: {device_id} under namespace"
439 f" {namespace}: Item download succeeded but parsing failed: {element}." 439 f" {namespace}: Item download succeeded but parsing failed: {element}."
440 ) 440 )
474 try: 474 try:
475 await xep_0060.sendItem( 475 await xep_0060.sendItem(
476 client, 476 client,
477 client.jid.userhostJID(), 477 client.jid.userhostJID(),
478 node, 478 node,
479 etree_to_domish(element), 479 xml_tools.et_elt_2_domish_elt(element),
480 item_id=str(bundle.device_id), 480 item_id=str(bundle.device_id),
481 extra={ 481 extra={
482 xep_0060.EXTRA_PUBLISH_OPTIONS: { 482 xep_0060.EXTRA_PUBLISH_OPTIONS: {
483 xep_0060.OPT_MAX_ITEMS: "max" 483 xep_0060.OPT_MAX_ITEMS: "max"
484 }, 484 },
514 try: 514 try:
515 await xep_0060.sendItem( 515 await xep_0060.sendItem(
516 client, 516 client,
517 client.jid.userhostJID(), 517 client.jid.userhostJID(),
518 node, 518 node,
519 etree_to_domish(element), 519 xml_tools.et_elt_2_domish_elt(element),
520 item_id=xep_0060.ID_SINGLETON, 520 item_id=xep_0060.ID_SINGLETON,
521 extra={ 521 extra={
522 xep_0060.EXTRA_PUBLISH_OPTIONS: { xep_0060.OPT_MAX_ITEMS: 1 }, 522 xep_0060.EXTRA_PUBLISH_OPTIONS: { xep_0060.OPT_MAX_ITEMS: 1 },
523 xep_0060.EXTRA_ON_PRECOND_NOT_MET: "publish_without_options" 523 xep_0060.EXTRA_ON_PRECOND_NOT_MET: "publish_without_options"
524 } 524 }
561 f" namespace {namespace}: Unexpected number of items retrieved:" 561 f" namespace {namespace}: Unexpected number of items retrieved:"
562 f" {len(items)}." 562 f" {len(items)}."
563 ) 563 )
564 564
565 element = next( 565 element = next(
566 iter(domish_to_etree(cast(domish.Element, items[0]))), 566 iter(xml_tools.domish_elt_2_et_elt(cast(domish.Element, items[0]))),
567 None 567 None
568 ) 568 )
569 if element is None: 569 if element is None:
570 raise omemo.BundleDownloadFailed( 570 raise omemo.BundleDownloadFailed(
571 f"Bundle download failed for {bare_jid}: {device_id} under" 571 f"Bundle download failed for {bare_jid}: {device_id} under"
648 try: 648 try:
649 await xep_0060.sendItem( 649 await xep_0060.sendItem(
650 client, 650 client,
651 client.jid.userhostJID(), 651 client.jid.userhostJID(),
652 node, 652 node,
653 etree_to_domish(element), 653 xml_tools.et_elt_2_domish_elt(element),
654 item_id=xep_0060.ID_SINGLETON, 654 item_id=xep_0060.ID_SINGLETON,
655 extra={ 655 extra={
656 xep_0060.EXTRA_PUBLISH_OPTIONS: { 656 xep_0060.EXTRA_PUBLISH_OPTIONS: {
657 xep_0060.OPT_MAX_ITEMS: 1, 657 xep_0060.OPT_MAX_ITEMS: 1,
658 xep_0060.OPT_ACCESS_MODEL: "open" 658 xep_0060.OPT_ACCESS_MODEL: "open"
703 raise omemo.DeviceListDownloadFailed( 703 raise omemo.DeviceListDownloadFailed(
704 f"Device list download failed for {bare_jid} under namespace" 704 f"Device list download failed for {bare_jid} under namespace"
705 f" {namespace}" 705 f" {namespace}"
706 ) from e 706 ) from e
707 707
708 if len(items) != 1: 708 if len(items) == 0:
709 return {}
710 elif len(items) != 1:
709 raise omemo.DeviceListDownloadFailed( 711 raise omemo.DeviceListDownloadFailed(
710 f"Device list download failed for {bare_jid} under namespace" 712 f"Device list download failed for {bare_jid} under namespace"
711 f" {namespace}: Unexpected number of items retrieved: {len(items)}." 713 f" {namespace}: Unexpected number of items retrieved: {len(items)}."
712 ) 714 )
713 715
714 element = next(iter(domish_to_etree(cast(domish.Element, items[0]))), None) 716 element = next(iter(xml_tools.domish_elt_2_et_elt(cast(domish.Element, items[0]))), None)
715 if element is None: 717 if element is None:
716 raise omemo.DeviceListDownloadFailed( 718 raise omemo.DeviceListDownloadFailed(
717 f"Device list download failed for {bare_jid} under namespace" 719 f"Device list download failed for {bare_jid} under namespace"
718 f" {namespace}: Item download succeeded but parsing failed:" 720 f" {namespace}: Item download succeeded but parsing failed:"
719 f" {element}." 721 f" {element}."
864 "type": C.MESS_TYPE_CHAT, 866 "type": C.MESS_TYPE_CHAT,
865 "extra": {}, 867 "extra": {},
866 "timestamp": time.time() 868 "timestamp": time.time()
867 })) 869 }))
868 870
869 message_data["xml"].addChild(etree_to_domish(element)) 871 message_data["xml"].addChild(xml_tools.et_elt_2_domish_elt(element))
870 872
871 try: 873 try:
872 await client.send(message_data["xml"]) 874 await client.a_send(message_data["xml"])
873 except Exception as e: 875 except Exception as e:
874 raise omemo.MessageSendingFailed() from e 876 raise omemo.MessageSendingFailed() from e
875 877
876 async def __prompt_manual_trust( 878 async def __prompt_manual_trust(
877 self, 879 self,
1491 message has fully progressed through the message receiving flow. Can be used 1493 message has fully progressed through the message receiving flow. Can be used
1492 to apply treatments to the fully processed message, like marking it as 1494 to apply treatments to the fully processed message, like marking it as
1493 encrypted. 1495 encrypted.
1494 @return: Whether to continue the message received flow. 1496 @return: Whether to continue the message received flow.
1495 """ 1497 """
1496
1497 muc_plaintext_cache_key: Optional[MUCPlaintextCacheKey] = None 1498 muc_plaintext_cache_key: Optional[MUCPlaintextCacheKey] = None
1498 1499
1499 sender_jid = jid.JID(message_elt["from"]) 1500 sender_jid = jid.JID(message_elt["from"])
1500 feedback_jid: jid.JID 1501 feedback_jid: jid.JID
1501 1502
1581 session_manager = await self.__prepare_for_profile(cast(str, client.profile)) 1582 session_manager = await self.__prepare_for_profile(cast(str, client.profile))
1582 1583
1583 if twomemo_encrypted_elt is not None: 1584 if twomemo_encrypted_elt is not None:
1584 try: 1585 try:
1585 message = twomemo.etree.parse_message( 1586 message = twomemo.etree.parse_message(
1586 domish_to_etree(twomemo_encrypted_elt), 1587 xml_tools.domish_elt_2_et_elt(twomemo_encrypted_elt),
1587 sender_bare_jid 1588 sender_bare_jid
1588 ) 1589 )
1589 except (ValueError, XMLSchemaValidationError): 1590 except (ValueError, XMLSchemaValidationError):
1590 log.warning( 1591 log.warning(
1591 f"Ingoring malformed encrypted message for namespace" 1592 f"Ingoring malformed encrypted message for namespace"
1595 encrypted_elt = twomemo_encrypted_elt 1596 encrypted_elt = twomemo_encrypted_elt
1596 1597
1597 if oldmemo_encrypted_elt is not None: 1598 if oldmemo_encrypted_elt is not None:
1598 try: 1599 try:
1599 message = await oldmemo.etree.parse_message( 1600 message = await oldmemo.etree.parse_message(
1600 domish_to_etree(oldmemo_encrypted_elt), 1601 xml_tools.domish_elt_2_et_elt(oldmemo_encrypted_elt),
1601 sender_bare_jid, 1602 sender_bare_jid,
1602 client.jid.userhost(), 1603 client.jid.userhost(),
1603 session_manager 1604 session_manager
1604 ) 1605 )
1605 except (ValueError, XMLSchemaValidationError): 1606 except (ValueError, XMLSchemaValidationError):
1997 1998
1998 message = next(message for message in messages if message.namespace == namespace) 1999 message = next(message for message in messages if message.namespace == namespace)
1999 2000
2000 if namespace == twomemo.twomemo.NAMESPACE: 2001 if namespace == twomemo.twomemo.NAMESPACE:
2001 # Add the encrypted element 2002 # Add the encrypted element
2002 stanza.addChild(etree_to_domish(twomemo.etree.serialize_message(message))) 2003 stanza.addChild(xml_tools.et_elt_2_domish_elt(twomemo.etree.serialize_message(message)))
2003 2004
2004 if namespace == oldmemo.oldmemo.NAMESPACE: 2005 if namespace == oldmemo.oldmemo.NAMESPACE:
2005 # Add the encrypted element 2006 # Add the encrypted element
2006 stanza.addChild(etree_to_domish(oldmemo.etree.serialize_message(message))) 2007 stanza.addChild(xml_tools.et_elt_2_domish_elt(oldmemo.etree.serialize_message(message)))
2007 2008
2008 if muc_plaintext_cache_key is not None: 2009 if muc_plaintext_cache_key is not None:
2009 self.__muc_plaintext_cache[muc_plaintext_cache_key] = plaintext 2010 self.__muc_plaintext_cache[muc_plaintext_cache_key] = plaintext
2010 2011
2011 async def __on_device_list_update( 2012 async def __on_device_list_update(
2029 item = next(iter(items), None) 2030 item = next(iter(items), None)
2030 if item is None: 2031 if item is None:
2031 log.debug("Ignoring empty device list update.") 2032 log.debug("Ignoring empty device list update.")
2032 return 2033 return
2033 2034
2034 item_elt = domish_to_etree(item) 2035 item_elt = xml_tools.domish_elt_2_et_elt(item)
2035 2036
2036 device_list: Dict[int, Optional[str]] = {} 2037 device_list: Dict[int, Optional[str]] = {}
2037 namespace: Optional[str] = None 2038 namespace: Optional[str] = None
2038 2039
2039 list_elt = item_elt.find(f"{{{twomemo.twomemo.NAMESPACE}}}devices") 2040 list_elt = item_elt.find(f"{{{twomemo.twomemo.NAMESPACE}}}devices")