From dba577f39c489b9db29960f2bf5cc78ac4f29d7b Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 4 Oct 2026 14:42:16 +0200 Subject: Move OMEMO, OpenPGP and RTP plugins into main/libdino --- .../plugins/omemo/ui/bad_messages_populator.vala | 198 +++++++++++++ .../plugins/omemo/ui/call_encryption_entry.vala | 58 ++++ .../plugins/omemo/ui/contact_details_provider.vala | 28 ++ .../omemo/ui/device_notification_populator.vala | 90 ++++++ .../plugins/omemo/ui/encryption_list_entry.vala | 78 +++++ .../omemo/ui/encryption_preferences_entry.vala | 320 +++++++++++++++++++++ main/src/plugins/omemo/ui/manage_key_dialog.vala | 178 ++++++++++++ main/src/plugins/omemo/ui/own_notifications.vala | 35 +++ main/src/plugins/omemo/ui/util.vala | 40 +++ 9 files changed, 1025 insertions(+) create mode 100644 main/src/plugins/omemo/ui/bad_messages_populator.vala create mode 100644 main/src/plugins/omemo/ui/call_encryption_entry.vala create mode 100644 main/src/plugins/omemo/ui/contact_details_provider.vala create mode 100644 main/src/plugins/omemo/ui/device_notification_populator.vala create mode 100644 main/src/plugins/omemo/ui/encryption_list_entry.vala create mode 100644 main/src/plugins/omemo/ui/encryption_preferences_entry.vala create mode 100644 main/src/plugins/omemo/ui/manage_key_dialog.vala create mode 100644 main/src/plugins/omemo/ui/own_notifications.vala create mode 100644 main/src/plugins/omemo/ui/util.vala (limited to 'main/src/plugins/omemo/ui') diff --git a/main/src/plugins/omemo/ui/bad_messages_populator.vala b/main/src/plugins/omemo/ui/bad_messages_populator.vala new file mode 100644 index 00000000..c291a422 --- /dev/null +++ b/main/src/plugins/omemo/ui/bad_messages_populator.vala @@ -0,0 +1,198 @@ +using Gee; +using Gtk; + +using Dino.Entities; +using Dino.Qlite; +using Xmpp; + +namespace Dino.Plugins.Omemo { + +public enum BadnessType { + UNTRUSTED, + UNDECRYPTABLE +} + +public class BadMessagesPopulator : Plugins.ConversationItemPopulator, Plugins.ConversationAdditionPopulator, Object { + + public string id { get { return "bad_omemo_messages"; } } + + private StreamInteractor stream_interactor; + private Plugin plugin; + private Database db; + + private Conversation? current_conversation; + private Plugins.ConversationItemCollection? item_collection; + + private Gee.List bad_items = new ArrayList(); + + public BadMessagesPopulator(StreamInteractor stream_interactor, Plugin plugin) { + this.stream_interactor = stream_interactor; + this.plugin = plugin; + this.db = plugin.db; + + plugin.trust_manager.bad_message_state_updated.connect((account, jid, device_id) => { + clear_state(); + init_state(); + }); + } + + private void init_state() { + if (current_conversation == null) return; + if (current_conversation.type_ == Conversation.Type.GROUPCHAT_PM) return; + + var qry = db.identity_meta.select() + .join_with(db.identity, db.identity.id, db.identity_meta.identity_id) + .with(db.identity.account_id, "=", current_conversation.account.id) + .where("last_message_untrusted is not NULL OR last_message_undecryptable is not NULL"); + + switch (current_conversation.type_) { + case Conversation.Type.CHAT: + qry.with(db.identity_meta.address_name, "=", current_conversation.counterpart.to_string()); + break; + case Conversation.Type.GROUPCHAT: + bool is_private = stream_interactor.get_module(MucManager.IDENTITY).is_private_room(current_conversation.account, current_conversation.counterpart); + if (!is_private) return; + + var list = stream_interactor.get_module(MucManager.IDENTITY).get_offline_members(current_conversation.counterpart, current_conversation.account); + if (list == null || list.is_empty) return; + + var selection = new StringBuilder(); + string[] selection_args = {}; + foreach (Jid jid in list) { + if (selection.len == 0) { + selection.append(@" ($(db.identity_meta.address_name) = ?"); + } else { + selection.append(@" OR $(db.identity_meta.address_name) = ?"); + } + selection_args += jid.to_string(); + } + selection.append(")"); + qry.where(selection.str, selection_args); + break; + case Conversation.Type.GROUPCHAT_PM: + break; + } + + foreach (Row row in qry) { + Jid jid = Jid.from_string(row[db.identity_meta.address_name]); + if (!db.identity_meta.last_message_untrusted.is_null(row)) { + DateTime time = new DateTime.from_unix_utc(row[db.identity_meta.last_message_untrusted]); + var item = new BadMessageItem(plugin, current_conversation, jid, time, BadnessType.UNTRUSTED); + bad_items.add(item); + item_collection.insert_item(item); + } + if (!db.identity_meta.last_message_undecryptable.is_null(row)) { + DateTime time = new DateTime.from_unix_utc(row[db.identity_meta.last_message_undecryptable]); + var item = new BadMessageItem(plugin, current_conversation, jid, time, BadnessType.UNDECRYPTABLE); + bad_items.add(item); + item_collection.insert_item(item); + } + } + } + + private void clear_state() { + foreach (BadMessageItem bad_item in bad_items) { + item_collection.remove_item(bad_item); + } + bad_items.clear(); + } + + public void init(Conversation conversation, Plugins.ConversationItemCollection item_collection, Plugins.WidgetType type) { + current_conversation = conversation; + this.item_collection = item_collection; + + init_state(); + } + + public void close(Conversation conversation) { + clear_state(); + } + + public void populate_timespan(Conversation conversation, DateTime after, DateTime before) { } +} + +public class BadMessageItem : Plugins.MetaConversationItem { + + private Plugin plugin; + private Conversation conversation; + private Jid problem_jid; + private BadnessType badness_type; + + public BadMessageItem(Plugin plugin, Conversation conversation, Jid jid, DateTime date, BadnessType badness_type) { + this.plugin = plugin; + this.conversation = conversation; + this.problem_jid = jid; + this.time = date; + this.badness_type = badness_type; + } + + public override Object? get_widget(Plugins.ConversationItemWidgetInterface outer, Plugins.WidgetType widget_type) { + return new BadMessagesWidget(plugin, conversation, problem_jid, badness_type); + } + + public override Gee.List? get_item_actions(Plugins.WidgetType type) { return null; } +} + +public class BadMessagesWidget : Box { + private Plugin plugin; + private Conversation conversation; + private Jid jid; + private Label label; + + public BadMessagesWidget(Plugin plugin, Conversation conversation, Jid jid, BadnessType badness_type) { + Object(orientation:Orientation.HORIZONTAL, spacing:5); + + this.plugin = plugin; + this.conversation = conversation; + this.jid = jid; + this.halign = Align.CENTER; + this.visible = true; + + string who = ""; + if (conversation.type_ == Conversation.Type.CHAT) { + who = Dino.get_participant_display_name(plugin.app.stream_interactor, conversation, jid); + } else if (conversation.type_ == Conversation.Type.GROUPCHAT) { + who = jid.to_string(); + // `jid` is a real JID. In MUCs, try to show nicks instead (given that the JID is currently online) + var occupants = plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_occupants(conversation.counterpart, conversation.account); + if (occupants == null) return; + foreach (Jid occupant in occupants) { + if (jid.equals_bare(plugin.app.stream_interactor.get_module(MucManager.IDENTITY).get_real_jid(occupant, conversation.account))) { + who = occupant.resourcepart; + } + } + } + + string warning_text = ""; + if (badness_type == BadnessType.UNTRUSTED) { + warning_text = _("%s has been using an untrusted device. You won't see messages from devices that you do not trust.").printf(who) + + " %s".printf(_("Manage devices")); + } else { + warning_text += _("%s does not trust this device. That means, you might be missing messages.").printf(who); + } + label = new Label(warning_text) { margin_start=70, margin_end=70, justify=Justification.CENTER, use_markup=true, selectable=true, wrap=true, wrap_mode=Pango.WrapMode.WORD_CHAR, hexpand=true }; + label.add_css_class("dim-label"); + this.append(label); + + if (badness_type == BadnessType.UNTRUSTED) { + label.activate_link.connect(on_label_activate_link); + } + } + + private bool on_label_activate_link() { + var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.string("encryption")}); + GLib.Application.get_default().activate_action("open-conversation-details", variant); + return false; + } + + public override void dispose() { + if (label != null) { + label.unparent(); + label.dispose(); + label = null; + } + base.dispose(); + } +} + +} diff --git a/main/src/plugins/omemo/ui/call_encryption_entry.vala b/main/src/plugins/omemo/ui/call_encryption_entry.vala new file mode 100644 index 00000000..04f0443c --- /dev/null +++ b/main/src/plugins/omemo/ui/call_encryption_entry.vala @@ -0,0 +1,58 @@ +using Gtk; + +using Dino.Entities; +using Dino.Qlite; +using Xmpp; + +namespace Dino.Plugins.Omemo { + + public class CallEncryptionEntry : Plugins.CallEncryptionEntry, Object { + private Database db; + + public CallEncryptionEntry(Database db) { + this.db = db; + } + + public Plugins.CallEncryptionWidget? get_widget(Account account, Xmpp.Xep.Jingle.ContentEncryption encryption) { + DtlsSrtpVerificationDraft.OmemoContentEncryption? omemo_encryption = encryption as DtlsSrtpVerificationDraft.OmemoContentEncryption; + if (omemo_encryption == null) return null; + + int identity_id = db.identity.get_id(account.id); + Row? device = db.identity_meta.get_device(identity_id, omemo_encryption.jid.to_string(), omemo_encryption.sid); + if (device == null) return null; + TrustLevel trust = (TrustLevel) device[db.identity_meta.trust_level]; + + return new CallEncryptionWidget(trust); + } + } + + public class CallEncryptionWidget : Plugins.CallEncryptionWidget, Object { + + string? title = null; + string? icon = null; + bool should_show_keys = false; + + public CallEncryptionWidget(TrustLevel trust) { + if (trust == TrustLevel.VERIFIED) { + title = "This call is encrypted and verified with OMEMO."; + icon = "dino-security-high-symbolic"; + should_show_keys = false; + } else { + title = "This call is encrypted with OMEMO."; + should_show_keys = true; + } + } + + public string? get_title() { + return title; + } + + public string? get_icon_name() { + return icon; + } + + public bool show_keys() { + return should_show_keys; + } + } +} diff --git a/main/src/plugins/omemo/ui/contact_details_provider.vala b/main/src/plugins/omemo/ui/contact_details_provider.vala new file mode 100644 index 00000000..1b61f31d --- /dev/null +++ b/main/src/plugins/omemo/ui/contact_details_provider.vala @@ -0,0 +1,28 @@ +using Gtk; +using Gee; +using Dino.Entities; + +namespace Dino.Plugins.Omemo { + +public class ContactDetailsProvider : Plugins.ContactDetailsProvider, Object { + public string id { get { return "omemo_info"; } } + public string tab { get { return "encryption"; } } + + private Plugin plugin; + + public ContactDetailsProvider(Plugin plugin) { + this.plugin = plugin; + } + + public void populate(Conversation conversation, Plugins.ContactDetails contact_details, WidgetType type) { } + + public Object? get_widget(Conversation conversation) { + if (conversation.type_ != Conversation.Type.CHAT) return null; + + var widget = new OmemoPreferencesWidget(plugin); + widget.set_jid(conversation.account, conversation.counterpart); + return widget; + } +} + +} diff --git a/main/src/plugins/omemo/ui/device_notification_populator.vala b/main/src/plugins/omemo/ui/device_notification_populator.vala new file mode 100644 index 00000000..7829d89c --- /dev/null +++ b/main/src/plugins/omemo/ui/device_notification_populator.vala @@ -0,0 +1,90 @@ +using Dino.Entities; +using Xmpp; +using Gtk; + +namespace Dino.Plugins.Omemo { + +public class DeviceNotificationPopulator : NotificationPopulator, Object { + + public string id { get { return "device_notification"; } } + + private StreamInteractor? stream_interactor; + private Plugin plugin; + private Conversation? current_conversation; + private NotificationCollection? notification_collection; + private ConversationNotification notification; + + public DeviceNotificationPopulator(Plugin plugin, StreamInteractor stream_interactor) { + this.stream_interactor = stream_interactor; + this.plugin = plugin; + + stream_interactor.account_added.connect(on_account_added); + } + + public void init(Conversation conversation, NotificationCollection notification_collection, Plugins.WidgetType type) { + current_conversation = conversation; + this.notification_collection = notification_collection; + if (plugin.has_new_devices(conversation.account, conversation.counterpart) && conversation.type_ == Conversation.Type.CHAT) { + display_notification(); + } + } + + public void close(Conversation conversation) { + notification = null; + } + + private void display_notification() { + if (notification == null) { + notification = new ConversationNotification(plugin, current_conversation); + notification.should_hide.connect(should_hide); + notification_collection.add_meta_notification(notification); + } + } + + public void should_hide() { + if (!plugin.has_new_devices(current_conversation.account, current_conversation.counterpart) && notification != null){ + notification_collection.remove_meta_notification(notification); + notification = null; + } + } + + private void on_account_added(Account account) { + stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => { + if (current_conversation != null && jid.equals(current_conversation.counterpart) && plugin.has_new_devices(current_conversation.account, current_conversation.counterpart)) { + display_notification(); + } + }); + } +} + +private class ConversationNotification : MetaConversationNotification { + private Widget widget; + private Plugin plugin; + private Jid jid; + private Account account; + public signal void should_hide(); + + public ConversationNotification(Plugin plugin, Conversation conversation) { + this.plugin = plugin; + this.jid = jid; + this.account = account; + + Box box = new Box(Orientation.HORIZONTAL, 5); + Button manage_button = new Button.with_label(_("Manage")); + manage_button.clicked.connect(() => { + manage_button.activate(); + + var variant = new Variant.tuple(new Variant[] {new Variant.int32(conversation.id), new Variant.string("encryption")}); + GLib.Application.get_default().activate_action("open-conversation-details", variant); + }); + box.append(new Label(_("This contact has new devices")) { margin_end=10 }); + box.append(manage_button); + widget = box; + } + + public override Object? get_widget(WidgetType type) { + return widget; + } +} + +} diff --git a/main/src/plugins/omemo/ui/encryption_list_entry.vala b/main/src/plugins/omemo/ui/encryption_list_entry.vala new file mode 100644 index 00000000..dfec813a --- /dev/null +++ b/main/src/plugins/omemo/ui/encryption_list_entry.vala @@ -0,0 +1,78 @@ +using Gtk; + +using Dino.Entities; +using Dino.Qlite; +using Xmpp; + +namespace Dino.Plugins.Omemo { + +public class EncryptionListEntry : Plugins.EncryptionListEntry, Object { + private Plugin plugin; + private Database db; + + public EncryptionListEntry(Plugin plugin) { + this.plugin = plugin; + this.db = plugin.db; + } + + public Entities.Encryption encryption { get { + return Entities.Encryption.OMEMO; + }} + + public string name { get { + return "OMEMO"; + }} + + public Object? get_encryption_icon(Entities.Conversation conversation, ContentItem content_item) { + return null; + } + + public string? get_encryption_icon_name(Entities.Conversation conversation, ContentItem content_item) { + if (content_item.encryption != encryption) return null; + + RowOption row = db.content_item_meta.select( { db.identity_meta.trust_level } ).with(db.content_item_meta.content_item_id, "=", content_item.id) + .join_on(db.identity_meta, @"$(db.identity_meta.address_name)=$(db.content_item_meta.address_name) AND $(db.identity_meta.device_id)=$(db.content_item_meta.device_id)") + .single().row(); + + + if (row.is_present() && (TrustLevel) row[db.identity_meta.trust_level] == TrustLevel.VERIFIED) { + return "dino-security-high-symbolic"; + } + return null; + } + + public void encryption_activated(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { + encryption_activated_async.begin(conversation, input_status_callback); + } + + public async void encryption_activated_async(Entities.Conversation conversation, Plugins.SetInputFieldStatus input_status_callback) { + if (conversation.type_ == Conversation.Type.GROUPCHAT_PM) { + input_status_callback(new Plugins.InputFieldStatus("Can't use encryption in a groupchat private message.", Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + return; + } + MucManager muc_manager = plugin.app.stream_interactor.get_module(MucManager.IDENTITY); + Manager omemo_manager = plugin.app.stream_interactor.get_module(Manager.IDENTITY); + + if (muc_manager.is_private_room(conversation.account, conversation.counterpart)) { + var offline_members = muc_manager.get_offline_members(conversation.counterpart, conversation.account); + if (offline_members == null) { + // We don't store offline members yet, and it'll be null if we're offline + return; + } + foreach (Jid offline_member in offline_members) { + bool ok = yield omemo_manager.ensure_get_keys_for_jid(conversation.account, offline_member); + if (!ok) { + input_status_callback(new Plugins.InputFieldStatus("A member does not support OMEMO: %s".printf(offline_member.to_string()), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + return; + } + } + return; + } + + if (!(yield omemo_manager.ensure_get_keys_for_jid(conversation.account, conversation.counterpart.bare_jid))) { + input_status_callback(new Plugins.InputFieldStatus("This contact does not support %s encryption".printf("OMEMO"), Plugins.InputFieldStatus.MessageType.ERROR, Plugins.InputFieldStatus.InputState.NO_SEND)); + } + } + +} +} diff --git a/main/src/plugins/omemo/ui/encryption_preferences_entry.vala b/main/src/plugins/omemo/ui/encryption_preferences_entry.vala new file mode 100644 index 00000000..55faecac --- /dev/null +++ b/main/src/plugins/omemo/ui/encryption_preferences_entry.vala @@ -0,0 +1,320 @@ +using Gee; +using Gtk; +using Qrencode; + +using Dino.Crypto.Omemo; +using Dino.Entities; +using Dino.Qlite; +using Omemo; +using Xmpp; + +namespace Dino.Plugins.Omemo { + +public class OmemoPreferencesEntry : Plugins.EncryptionPreferencesEntry { + + OmemoPreferencesWidget widget; + Plugin plugin; + + public OmemoPreferencesEntry(Plugin plugin) { + this.plugin = plugin; + } + + public override Object? get_widget(Account account, WidgetType type) { + if (type != WidgetType.GTK4) return null; + var widget = new OmemoPreferencesWidget(plugin); + widget.set_jid(account, account.bare_jid); + return widget; + } + + public override string id { get { return "omemo_preferences_entryption"; }} +} + +[GtkTemplate (ui = "/im/dino/Dino/encryption_preferences_entry.ui")] +public class OmemoPreferencesWidget : Adw.PreferencesGroup { + private Plugin plugin; + private Account account; + private Jid jid; + private int identity_id = 0; + private Store store; + private Set displayed_ids = new HashSet(); + + [GtkChild] private unowned Adw.SwitchRow automatically_accept_new_row; + [GtkChild] private unowned Adw.SwitchRow encrypt_by_default_row; + [GtkChild] private unowned Label new_keys_label; + + [GtkChild] private unowned Adw.PreferencesGroup keys_preferences_group; + [GtkChild] private unowned ListBox new_keys_listbox; + [GtkChild] private unowned Picture qrcode_picture; + [GtkChild] private unowned Popover qrcode_popover; + + private ArrayList keys_preferences_group_children = new ArrayList(); + + construct { + // If we set the strings in the .ui file, they don't get translated + encrypt_by_default_row.title = _("OMEMO by default"); + encrypt_by_default_row.subtitle = _("Enable OMEMO encryption for new conversations"); + automatically_accept_new_row.title = _("Encrypt to new devices"); + automatically_accept_new_row.subtitle = _("Automatically encrypt to new devices from this contact."); + new_keys_label.label = _("New keys"); + } + + public OmemoPreferencesWidget(Plugin plugin) { + this.plugin = plugin; + } + + public void set_jid(Account account, Jid jid) { + this.account = account; + this.jid = jid; + this.identity_id = plugin.db.identity.get_id(account.id); + if (identity_id <= 0) { + warning("OmemoPreferencesWidget missing identity_id"); + return; + } + + automatically_accept_new_row.active = plugin.db.trust.get_blind_trust(identity_id, jid.bare_jid.to_string(), true); + automatically_accept_new_row.notify["active"].connect((obj, _) => { on_auto_accept_toggled(((Adw.SwitchRow) obj).active); }); + + encrypt_by_default_row.visible = account.bare_jid.equals_bare(jid); + encrypt_by_default_row.active = plugin.app.settings.get_default_encryption(account) != Encryption.NONE; + encrypt_by_default_row.notify["active"].connect((obj, _) => { on_omemo_by_default_toggled(((Adw.SwitchRow) obj).active); }); + + Dino.Application? app = Application.get_default() as Dino.Application; + if (app != null) { + store = app.stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).store; + } + + redraw_key_list(); + + // Check for unknown devices + fetch_unknown_bundles(); + } + + private void redraw_key_list() { + // Remove current widgets + foreach (var widget in keys_preferences_group_children) { + keys_preferences_group.remove(widget); + } + keys_preferences_group_children.clear(); + + // Dialog opened from the account settings menu + // Show the fingerprint for this device separately with buttons for a qrcode and to copy + if(jid.equals(account.bare_jid)) { + automatically_accept_new_row.subtitle = _("New encryption keys from your other devices will be accepted automatically."); + add_own_fingerprint(); + } + + //Show the normal devicelist + var own_id = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; + foreach (Row device in plugin.db.identity_meta.get_known_devices(identity_id, jid.to_string())) { + if(jid.equals(account.bare_jid) && device[plugin.db.identity_meta.device_id] == own_id) { + // If this is our own account, don't show this device twice (did it separately already) + continue; + } + add_fingerprint(device, (TrustLevel) device[plugin.db.identity_meta.trust_level]); + } + + //Show any new devices for which the user must decide whether to accept or reject + foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) { + add_new_fingerprint(device); + } + } + + private static string escape_for_iri_path_segment(string s) { + // from RFC 3986, 2.2. Reserved Characters: + string SUB_DELIMS = "!$&'()*+,;="; + // from RFC 3986, 3.3. Path (pchar without unreserved and pct-encoded): + string ALLOWED_RESERVED_CHARS = SUB_DELIMS + ":@"; + return GLib.Uri.escape_string(s, ALLOWED_RESERVED_CHARS, true); + } + + private void fetch_unknown_bundles() { + Dino.Application app = Application.get_default() as Dino.Application; + XmppStream? stream = app.stream_interactor.get_stream(account); + if (stream == null) return; + StreamModule? module = stream.get_module(StreamModule.IDENTITY); + if (module == null) return; + module.bundle_fetched.connect_after((bundle_jid, device_id, bundle) => { + if (bundle_jid.equals(jid) && !displayed_ids.contains(device_id)) { + redraw_key_list(); + } + }); + foreach (Row device in plugin.db.identity_meta.get_unknown_devices(identity_id, jid.to_string())) { + try { + module.fetch_bundle(stream, Jid.from_string(device[plugin.db.identity_meta.address_name]), device[plugin.db.identity_meta.device_id], false); + } catch (InvalidJidError e) { + warning("Ignoring device with invalid Jid: %s", e.message); + } + } + } + + private void add_own_fingerprint() { + string own_b64 = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.identity_key_public_base64]; + string fingerprint = fingerprint_from_base64(own_b64); + + var own_action_box = new Box(Orientation.HORIZONTAL, 6); + var show_qrcode_button = new MenuButton() { icon_name="dino-qr-code-symbolic", valign=Align.CENTER }; + own_action_box.append(show_qrcode_button); + var copy_button = new Button() { icon_name="dino-edit-copy-symbolic", valign=Align.CENTER }; + copy_button.clicked.connect(() => { copy_button.get_clipboard().set_text(fingerprint); }); + own_action_box.append(copy_button); + + Adw.ActionRow action_row = new Adw.ActionRow() { use_markup = true }; + action_row.title = _("This device"); + action_row.subtitle = fingerprint_markup(fingerprint_from_base64(own_b64)); + action_row.add_suffix(own_action_box); + add_key_row(action_row); + + // Create and set QR code popover + int sid = plugin.db.identity.row_with(plugin.db.identity.account_id, account.id)[plugin.db.identity.device_id]; + var iri_query = @"omemo-sid-$(sid)=$(fingerprint)"; + string iri = GLib.Uri.join(UriFlags.NONE, "xmpp", null, null, 0, jid.to_string(), iri_query, null); + + const int QUIET_ZONE_MODULES = 4; // MUST be at least 4 + const int MODULE_SIZE_PX = 4; // arbitrary + var qr_paintable = new QRcode(iri, 2) + .to_paintable(MODULE_SIZE_PX * qrcode_picture.scale_factor); + qrcode_picture.paintable = qr_paintable; + qrcode_picture.margin_top = qrcode_picture.margin_end = + qrcode_picture.margin_bottom = qrcode_picture.margin_start = QUIET_ZONE_MODULES * MODULE_SIZE_PX; + qrcode_popover.add_css_class("qrcode-container"); + + show_qrcode_button.popover = qrcode_popover; + } + + private void add_fingerprint(Row device, TrustLevel trust) { + string key_base64 = device[plugin.db.identity_meta.identity_key_public_base64]; + bool key_active = device[plugin.db.identity_meta.now_active]; + if (store != null) { + try { + Address address = new Address(jid.to_string(), device[plugin.db.identity_meta.device_id]); + SessionRecord? session = null; + if (store.contains_session(address)) { + session = store.load_session(address); + string session_key_base64 = Base64.encode(session.state.remote_identity_key.serialize()); + if (key_base64 != session_key_base64) { + critical("Session and database identity key mismatch!"); + key_base64 = session_key_base64; + } + } + } catch (Error e) { + print("Error while reading session store: %s", e.message); + } + } + + if (device[plugin.db.identity_meta.now_active]) { + Adw.ActionRow action_row = new Adw.ActionRow() { use_markup = true }; + action_row.activated.connect(() => { + Row updated_device = plugin.db.identity_meta.get_device(device[plugin.db.identity_meta.identity_id], device[plugin.db.identity_meta.address_name], device[plugin.db.identity_meta.device_id]); + ManageKeyDialog manage_dialog = new ManageKeyDialog(updated_device, plugin.db); + manage_dialog.set_transient_for((Gtk.Window) get_root()); + manage_dialog.present(); + manage_dialog.response.connect((response) => { + update_stored_trust(response, updated_device); + redraw_key_list(); + }); + }); + action_row.activatable = true; + action_row.title = account.bare_jid.equals_bare(jid) ? _("Other device") : _("Device"); + action_row.subtitle = fingerprint_markup(fingerprint_from_base64(key_base64)); + string trust_str = _("Accepted"); + switch(trust) { + case TrustLevel.UNTRUSTED: + trust_str = _("Rejected"); + break; + case TrustLevel.VERIFIED: + trust_str = _("Verified"); + break; + } + + action_row.add_suffix(new Label(trust_str)); + add_key_row(action_row); + } + displayed_ids.add(device[plugin.db.identity_meta.device_id]); + } + + private bool on_auto_accept_toggled(bool active) { + plugin.trust_manager.set_blind_trust(account, jid, active); + + if (active) { + int identity_id = plugin.db.identity.get_id(account.id); + if (identity_id < 0) return false; + + foreach (Row device in plugin.db.identity_meta.get_new_devices(identity_id, jid.to_string())) { + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); + add_fingerprint(device, TrustLevel.TRUSTED); + } + } + return false; + } + + private bool on_omemo_by_default_toggled(bool active) { + var encryption_value = active ? Encryption.OMEMO : Encryption.NONE; + plugin.app.settings.set_default_encryption(account, encryption_value); + return false; + } + + private void update_stored_trust(int response, Row device) { + switch (response) { + case TrustLevel.TRUSTED: + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); + break; + case TrustLevel.UNTRUSTED: + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); + break; + case TrustLevel.VERIFIED: + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.VERIFIED); + plugin.trust_manager.set_blind_trust(account, jid, false); + automatically_accept_new_row.active = false; + break; + } + } + + private void add_new_fingerprint(Row device) { + Adw.ActionRow action_row = new Adw.ActionRow() { use_markup = true }; + action_row.title = _("New device"); + action_row.subtitle = fingerprint_markup(fingerprint_from_base64(device[plugin.db.identity_meta.identity_key_public_base64])); + + Button accept_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; + accept_button.set_icon_name("dino-check-plain-symbolic"); // using .image = sets .image-button. Together with .suggested/destructive action that breaks the button Adwaita + accept_button.add_css_class("suggested-action"); + accept_button.tooltip_text = _("Accept key"); + + Button reject_button = new Button() { visible = true, valign = Align.CENTER, hexpand = true }; + reject_button.set_icon_name("action-unavailable-symbolic"); + reject_button.add_css_class("destructive-action"); + reject_button.tooltip_text = _("Reject key"); + + accept_button.clicked.connect(() => { + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.TRUSTED); + add_fingerprint(device, TrustLevel.TRUSTED); + remove_key_row(action_row); + }); + + reject_button.clicked.connect(() => { + plugin.trust_manager.set_device_trust(account, jid, device[plugin.db.identity_meta.device_id], TrustLevel.UNTRUSTED); + add_fingerprint(device, TrustLevel.UNTRUSTED); + remove_key_row(action_row); + }); + + Box control_box = new Box(Gtk.Orientation.HORIZONTAL, 0) { visible = true, hexpand = true }; + control_box.append(accept_button); + control_box.append(reject_button); + control_box.add_css_class("linked"); // .linked: Visually link the accept / reject buttons + + action_row.add_suffix(control_box); + + add_key_row(action_row); + displayed_ids.add(device[plugin.db.identity_meta.device_id]); + } + + private void add_key_row(Adw.PreferencesRow widget) { + keys_preferences_group.add(widget); + keys_preferences_group_children.add(widget); + } + + private void remove_key_row(Adw.PreferencesRow widget) { + keys_preferences_group.remove(widget); + keys_preferences_group_children.remove(widget); + } +} +} \ No newline at end of file diff --git a/main/src/plugins/omemo/ui/manage_key_dialog.vala b/main/src/plugins/omemo/ui/manage_key_dialog.vala new file mode 100644 index 00000000..65033999 --- /dev/null +++ b/main/src/plugins/omemo/ui/manage_key_dialog.vala @@ -0,0 +1,178 @@ +using Gtk; + +using Dino.Qlite; + +namespace Dino.Plugins.Omemo { + +[GtkTemplate (ui = "/im/dino/Dino/manage_key_dialog.ui")] +public class ManageKeyDialog : Gtk.Dialog { + + [GtkChild] private unowned Stack manage_stack; + + [GtkChild] private unowned Button cancel_button; + [GtkChild] private unowned Button ok_button; + + [GtkChild] private unowned Label main_desc_label; + [GtkChild] private unowned ListBox main_action_list; + + [GtkChild] private unowned Image confirm_image; + [GtkChild] private unowned Label confirm_title_label; + [GtkChild] private unowned Label confirm_desc_label; + + [GtkChild] private unowned Label verify_label; + [GtkChild] private unowned Label compare_fingerprint_label; + [GtkChild] private unowned Button verify_yes_button; + [GtkChild] private unowned Button verify_no_button; + + private Row device; + private Database db; + + private bool return_to_main; + private int current_response; + + construct { + // If we set the strings in the .ui file, they don't get translated + this.title = _("Manage Key"); + compare_fingerprint_label.label = _("Compare the fingerprint, character by character, with the one shown on your contact's device."); + verify_no_button.label = _("Fingerprints differ"); + verify_yes_button.label = _("Fingerprints match"); + cancel_button.label = _("Cancel"); + ok_button.label = _("Confirm"); + } + + public ManageKeyDialog(Row device, Database db) { + Object(use_header_bar : 1); + + this.device = device; + this.db = db; + + setup_main_screen(); + setup_verify_screen(); + + cancel_button.clicked.connect(handle_cancel); + ok_button.clicked.connect(() => { + response(current_response); + close(); + }); + + verify_yes_button.clicked.connect(() => { + confirm_image.set_from_icon_name("security-high-symbolic"); + confirm_title_label.label = _("Verify key"); + confirm_desc_label.set_markup(_("Future messages sent by %s from the device that uses this key will be highlighted accordingly in the chat window.").printf(@"$(device[db.identity_meta.address_name])")); + manage_stack.set_visible_child_name("confirm"); + ok_button.sensitive = true; + return_to_main = false; + current_response = TrustLevel.VERIFIED; + }); + + verify_no_button.clicked.connect(() => { + return_to_main = false; + confirm_image.set_from_icon_name("dino-dialog-warning-symbolic"); + confirm_title_label.label = _("Fingerprints do not match"); + confirm_desc_label.set_markup(_("Please verify that you are comparing the correct fingerprint. If fingerprints do not match, %s's account may be compromised and you should consider rejecting this key.").printf(@"$(device[db.identity_meta.address_name])")); + manage_stack.set_visible_child_name("confirm"); + }); + } + + private void handle_cancel() { + if (manage_stack.get_visible_child_name() == "main") close(); + + if (manage_stack.get_visible_child_name() == "verify") { + manage_stack.set_visible_child_name("main"); + cancel_button.label = _("Cancel"); + } + + if (manage_stack.get_visible_child_name() == "confirm") { + if (return_to_main) { + manage_stack.set_visible_child_name("main"); + cancel_button.label = _("Cancel"); + } else { + manage_stack.set_visible_child_name("verify"); + } + } + + ok_button.sensitive = false; + } + + private Box make_action_box(string title, string desc){ + Box box = new Box(Orientation.VERTICAL, 0) { visible = true, margin_start = 20, margin_end = 20, margin_top = 14, margin_bottom = 14 }; + Label lbl_title = new Label(title) { visible = true, halign = Align.START }; + Label lbl_desc = new Label(desc) { visible = true, xalign = 0, wrap = true, max_width_chars = 40 }; + + Pango.AttrList title_attrs = new Pango.AttrList(); + title_attrs.insert(Pango.attr_scale_new(1.1)); + lbl_title.attributes = title_attrs; + Pango.AttrList desc_attrs = new Pango.AttrList(); + desc_attrs.insert(Pango.attr_scale_new(0.8)); + lbl_desc.attributes = desc_attrs; + lbl_desc.add_css_class("dim-label"); + + box.append(lbl_title); + box.append(lbl_desc); + + return box; + } + + private void setup_main_screen() { + main_action_list.set_header_func((row, before_row) => { + if (row.get_header() == null && before_row != null) { + row.set_header(new Separator(Orientation.HORIZONTAL)); + } + }); + + ListBoxRow verify_row = new ListBoxRow() { visible = true }; + verify_row.set_child(make_action_box(_("Verify key fingerprint"), _("Compare this key's fingerprint with the fingerprint displayed on the contact's device."))); + ListBoxRow reject_row = new ListBoxRow() { visible = true }; + reject_row.set_child(make_action_box(_("Reject key"), _("Block encrypted communication with the contact's device that uses this key."))); + ListBoxRow accept_row = new ListBoxRow() {visible = true }; + accept_row.set_child(make_action_box(_("Accept key"), _("Allow encrypted communication with the contact's device that uses this key."))); + + switch((TrustLevel) device[db.identity_meta.trust_level]) { + case TrustLevel.TRUSTED: + main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("accepted")+"")+" "+_("This means it can be used by %s to receive and send encrypted messages.").printf(@"$(device[db.identity_meta.address_name])")); + main_action_list.append(verify_row); + main_action_list.append(reject_row); + break; + case TrustLevel.VERIFIED: + main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("verified")+"")+" "+_("This means it can be used by %s to receive and send encrypted messages.").printf(@"$(device[db.identity_meta.address_name])") + " " + _("Additionally it has been verified to match the key on the contact's device.")); + main_action_list.append(reject_row); + break; + case TrustLevel.UNTRUSTED: + main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("rejected")+"")+" "+_("This means it cannot be used by %s to decipher your messages, and you won't see messages encrypted with it.").printf(@"$(device[db.identity_meta.address_name])")); + main_action_list.append(accept_row); + break; + } + + //Row clicked - go to appropriate screen + main_action_list.row_activated.connect((row) => { + if(row == verify_row) { + manage_stack.set_visible_child_name("verify"); + } else if (row == reject_row) { + confirm_image.set_from_icon_name("action-unavailable-symbolic"); + confirm_title_label.label = _("Reject key"); + confirm_desc_label.set_markup(_("You won't see encrypted messages from the device of %s that uses this key. Conversely, that device won't be able to decipher your messages anymore.").printf(@"$(device[db.identity_meta.address_name])")); + manage_stack.set_visible_child_name("confirm"); + ok_button.sensitive = true; + return_to_main = true; + current_response = TrustLevel.UNTRUSTED; + } else if (row == accept_row) { + confirm_image.set_from_icon_name("dino-check-plain-symbolic"); + confirm_title_label.label = _("Accept key"); + confirm_desc_label.set_markup(_("You will be able to exchange encrypted messages with the device of %s that uses this key.").printf(@"$(device[db.identity_meta.address_name])")); + manage_stack.set_visible_child_name("confirm"); + ok_button.sensitive = true; + return_to_main = true; + current_response = TrustLevel.TRUSTED; + } + cancel_button.label = _("Back"); + }); + + manage_stack.set_visible_child_name("main"); + } + + private void setup_verify_screen() { + verify_label.set_markup(fingerprint_markup(fingerprint_from_base64(device[db.identity_meta.identity_key_public_base64]))); + } +} + +} diff --git a/main/src/plugins/omemo/ui/own_notifications.vala b/main/src/plugins/omemo/ui/own_notifications.vala new file mode 100644 index 00000000..7ff10e30 --- /dev/null +++ b/main/src/plugins/omemo/ui/own_notifications.vala @@ -0,0 +1,35 @@ +using Dino.Entities; +using Xmpp; +using Gtk; + +namespace Dino.Plugins.Omemo { + +public class OwnNotifications { + + private StreamInteractor stream_interactor; + private Plugin plugin; + private Account account; + + public OwnNotifications (Plugin plugin, StreamInteractor stream_interactor, Account account) { + this.stream_interactor = (!)stream_interactor; + this.plugin = plugin; + this.account = account; + stream_interactor.module_manager.get_module(account, StreamModule.IDENTITY).bundle_fetched.connect_after((jid, device_id, bundle) => { + if (jid.equals(account.bare_jid) && plugin.has_new_devices(account, account.bare_jid)) { + display_notification(); + } + }); + + if (plugin.has_new_devices(account, account.bare_jid)) { + display_notification(); + } + } + + private void display_notification() { + Notification notification = new Notification(_("OMEMO trust decision required")); + notification.set_default_action_and_target_value("app.own-keys", new Variant.int32(account.id)); + notification.set_body(_("Did you add a new device for account %s?").printf(@"$(account.bare_jid.to_string())")); + plugin.app.send_notification(account.id.to_string()+"-new-device", notification); + } +} +} diff --git a/main/src/plugins/omemo/ui/util.vala b/main/src/plugins/omemo/ui/util.vala new file mode 100644 index 00000000..e250ff4d --- /dev/null +++ b/main/src/plugins/omemo/ui/util.vala @@ -0,0 +1,40 @@ +using Xmpp.Util; + +namespace Dino.Plugins.Omemo { + +public static string fingerprint_from_base64(string b64) { + uint8[] arr = Base64.decode(b64); + + arr = arr[1:arr.length]; + string s = ""; + foreach (uint8 i in arr) { + string tmp = i.to_string("%x"); + if (tmp.length == 1) tmp = "0" + tmp; + s = s + tmp; + } + + return s; +} + +public static string fingerprint_markup(string s) { + return "" + format_fingerprint(s) + ""; +} + +public static string format_fingerprint(string s) { + string markup = ""; + for (int i = 0; i < s.length; i += 4) { + string four_chars = s.substring(i, 4).down(); + + if (i % 32 == 0 && i != 0) markup += "\n"; + markup += four_chars; + if (i % 16 == 12 && i % 32 != 28) { + markup += " "; + } + if (i % 8 == 4 && i % 16 != 12) { + markup += "\u00a0"; // Non-breaking space + } + } + return markup; +} + +} -- cgit v1.3 From 630b1368331ef0034b343bd2f00b116c8cd90391 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 4 Oct 2026 17:49:16 +0200 Subject: Fix some warnings --- .../omemo/ui/encryption_preferences_entry.vala | 4 + main/src/plugins/omemo/ui/manage_key_dialog.vala | 3 + main/src/ui/main_window.vala | 24 +++--- main/src/ui/util/data_forms.vala | 2 +- xmpp-vala/src/core/stanza_attribute.vala | 16 +++- xmpp-vala/src/core/stanza_node.vala | 92 ++++++++++++---------- xmpp-vala/src/core/stream_connect.vala | 10 ++- xmpp-vala/src/module/bookmarks_provider.vala | 8 +- xmpp-vala/src/module/iq/module.vala | 2 +- xmpp-vala/src/module/sasl.vala | 14 ++-- xmpp-vala/src/module/session.vala | 10 ++- xmpp-vala/src/module/xep/0004_data_forms.vala | 3 +- .../module/xep/0030_service_discovery/module.vala | 4 +- xmpp-vala/src/module/xep/0045_muc/module.vala | 24 +++--- xmpp-vala/src/module/xep/0048_bookmarks.vala | 10 +-- .../src/module/xep/0049_private_xml_storage.vala | 4 +- xmpp-vala/src/module/xep/0054_vcard/module.vala | 2 +- xmpp-vala/src/module/xep/0060_pubsub.vala | 10 +-- .../src/module/xep/0065_socks5_bytestreams.vala | 2 +- .../src/module/xep/0077_in_band_registration.vala | 6 +- .../src/module/xep/0082_date_time_profiles.vala | 4 +- xmpp-vala/src/module/xep/0084_user_avatars.vala | 2 +- .../src/module/xep/0166_jingle/component.vala | 12 ++- xmpp-vala/src/module/xep/0166_jingle/content.vala | 12 ++- .../src/module/xep/0166_jingle/jingle_flag.vala | 7 +- xmpp-vala/src/module/xep/0166_jingle/session.vala | 7 +- .../0176_jingle_ice_udp/transport_parameters.vala | 6 +- .../src/module/xep/0191_blocking_command.vala | 8 +- xmpp-vala/src/module/xep/0199_ping.vala | 2 +- .../xep/0215_external_service_discovery.vala | 2 +- .../src/module/xep/0234_jingle_file_transfer.vala | 2 +- .../module/xep/0260_jingle_socks5_bytestreams.vala | 10 ++- xmpp-vala/src/module/xep/0272_muji.vala | 7 +- xmpp-vala/src/module/xep/0280_message_carbons.vala | 12 ++- .../xep/0313_2_message_archive_management.vala | 4 +- .../xep/0313_message_archive_management.vala | 2 +- .../module/xep/0392_consistent_color/hsluv.vala | 6 +- xmpp-vala/src/module/xep/0402_bookmarks2.vala | 6 +- xmpp-vala/src/module/xep/0461_replies.vala | 1 - xmpp-vala/tests/color.vala | 6 +- xmpp-vala/tests/stanza.vala | 48 ++++++----- xmpp-vala/tests/testcase.vala | 13 ++- 42 files changed, 271 insertions(+), 158 deletions(-) (limited to 'main/src/plugins/omemo/ui') diff --git a/main/src/plugins/omemo/ui/encryption_preferences_entry.vala b/main/src/plugins/omemo/ui/encryption_preferences_entry.vala index 55faecac..7f53a1b2 100644 --- a/main/src/plugins/omemo/ui/encryption_preferences_entry.vala +++ b/main/src/plugins/omemo/ui/encryption_preferences_entry.vala @@ -224,6 +224,10 @@ public class OmemoPreferencesWidget : Adw.PreferencesGroup { case TrustLevel.VERIFIED: trust_str = _("Verified"); break; + case TrustLevel.TRUSTED: + case TrustLevel.UNKNOWN: + default: + break; } action_row.add_suffix(new Label(trust_str)); diff --git a/main/src/plugins/omemo/ui/manage_key_dialog.vala b/main/src/plugins/omemo/ui/manage_key_dialog.vala index 65033999..59e6db24 100644 --- a/main/src/plugins/omemo/ui/manage_key_dialog.vala +++ b/main/src/plugins/omemo/ui/manage_key_dialog.vala @@ -141,6 +141,9 @@ public class ManageKeyDialog : Gtk.Dialog { main_desc_label.set_markup(_("This key is currently %s.").printf(""+_("rejected")+"")+" "+_("This means it cannot be used by %s to decipher your messages, and you won't see messages encrypted with it.").printf(@"$(device[db.identity_meta.address_name])")); main_action_list.append(accept_row); break; + case TrustLevel.UNKNOWN: + default: + break; } //Row clicked - go to appropriate screen diff --git a/main/src/ui/main_window.vala b/main/src/ui/main_window.vala index d2a23954..da6960ea 100644 --- a/main/src/ui/main_window.vala +++ b/main/src/ui/main_window.vala @@ -11,21 +11,21 @@ public class MainWindow : Adw.ApplicationWindow { public signal void conversation_selected(Conversation conversation); - [GtkChild] public Stack stack; - [GtkChild] public Adw.NavigationSplitView navigation_split_view; + [GtkChild] public unowned Stack stack; + [GtkChild] public unowned Adw.NavigationSplitView navigation_split_view; - [GtkChild] public MenuButton add_button; - [GtkChild] public MenuButton menu_button; + [GtkChild] public unowned MenuButton add_button; + [GtkChild] public unowned MenuButton menu_button; - [GtkChild] public Adw.HeaderBar conversation_headerbar; - [GtkChild] public Adw.WindowTitle conversation_window_title; + [GtkChild] public unowned Adw.HeaderBar conversation_headerbar; + [GtkChild] public unowned Adw.WindowTitle conversation_window_title; - [GtkChild] public ConversationView conversation_view; - [GtkChild] public ConversationSelector conversation_selector; - [GtkChild] public Adw.Flap search_flap; - [GtkChild] private Stack left_stack; - [GtkChild] private Stack right_stack; - [GtkChild] public Adw.Bin search_frame; + [GtkChild] public unowned ConversationView conversation_view; + [GtkChild] public unowned ConversationSelector conversation_selector; + [GtkChild] public unowned Adw.Flap search_flap; + [GtkChild] private unowned Stack left_stack; + [GtkChild] private unowned Stack right_stack; + [GtkChild] public unowned Adw.Bin search_frame; public GlobalSearch global_search; diff --git a/main/src/ui/util/data_forms.vala b/main/src/ui/util/data_forms.vala index 7fb9b87d..0b91da15 100644 --- a/main/src/ui/util/data_forms.vala +++ b/main/src/ui/util/data_forms.vala @@ -18,7 +18,7 @@ namespace Dino.Ui.Util { } public static ViewModel.PreferencesRow.Any? get_data_form_field_view_model(DataForms.DataForm.Field field) { - if (field.type_ == null) return null; + if (field.type_ == DataForms.DataForm.Type.UNKNOWN) return null; ViewModel.PreferencesRow.Any? view_model = null; diff --git a/xmpp-vala/src/core/stanza_attribute.vala b/xmpp-vala/src/core/stanza_attribute.vala index 6c6ac579..ba1616a5 100644 --- a/xmpp-vala/src/core/stanza_attribute.vala +++ b/xmpp-vala/src/core/stanza_attribute.vala @@ -56,7 +56,13 @@ public class StanzaAttribute : StanzaEntry { if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { return printf(ATTRIBUTE_XML_NO_NS_FORMAT, true); } else { - return printf(ATTRIBUTE_XML_FORMAT, false, state.find_name((!)ns_uri)); + string ns_name = "_"; + try { + ns_name = state.find_name((!)ns_uri); + } catch (IOError e) { + warning("Error finding name: %s", e.message); + } + return printf(ATTRIBUTE_XML_FORMAT, false, ns_name); } } @@ -65,7 +71,13 @@ public class StanzaAttribute : StanzaEntry { if (ns_uri == state.current_ns_uri || (ns_uri == XMLNS_URI && name == "xmlns")) { return printf(ATTRIBUTE_XML_ANSI_NO_NS_FORMAT, true); } else { - return printf(ATTRIBUTE_XML_ANSI_FORMAT, false, state.find_name((!)ns_uri)); + string ns_name = "_"; + try { + ns_name = state.find_name((!)ns_uri); + } catch (IOError e) { + warning("Error finding name: %s", e.message); + } + return printf(ATTRIBUTE_XML_ANSI_FORMAT, false, ns_name); } } } diff --git a/xmpp-vala/src/core/stanza_node.vala b/xmpp-vala/src/core/stanza_node.vala index f44657f1..7a68da62 100644 --- a/xmpp-vala/src/core/stanza_node.vala +++ b/xmpp-vala/src/core/stanza_node.vala @@ -18,54 +18,62 @@ public abstract class StanzaEntry { public string? encoded_val { owned get { if (val == null) return null; - return ENCODABLE_VALUE_CHARS.replace_eval(val, -1, 0, 0, (match_info, result_builder) => { - int start; - match_info.fetch_pos(0, out start, null); - unowned string src = match_info.get_string(); - switch (src[start]) { - case '&': result_builder.append("&"); break; - case '"': result_builder.append("""); break; - case '\'': result_builder.append("'"); break; - case '<': result_builder.append("<"); break; - case '>': result_builder.append(">"); break; - default: error("Unreachable"); break; - } - return false; - }); + try { + return ENCODABLE_VALUE_CHARS.replace_eval(val, -1, 0, 0, (match_info, result_builder) => { + int start; + match_info.fetch_pos(0, out start, null); + unowned string src = match_info.get_string(); + switch (src[start]) { + case '&': result_builder.append("&"); break; + case '"': result_builder.append("""); break; + case '\'': result_builder.append("'"); break; + case '<': result_builder.append("<"); break; + case '>': result_builder.append(">"); break; + default: error("Unreachable"); break; + } + return false; + }); + } catch (RegexError e) { + error("Error encoding entities in string \"%s\": %s", val, e.message); + } } set { if (value == null) { val = null; return; } - val = DECODABLE_VALUE_SEQUENCES.replace_eval(value, -1, 0, 0, (match_info, result_builder) => { - int start, end; - match_info.fetch_pos(0, out start, out end); - unowned string src = match_info.get_string(); - switch (src[start+1]) { - case 'g': result_builder.append_c('>'); break; - case 'l': result_builder.append_c('<'); break; - case 'q': result_builder.append_c('"'); break; - case 'a': - switch (src[start+2]) { - case 'p': result_builder.append_c('\''); break; - case 'm': result_builder.append_c('&'); break; - default: critical("Invalid encoding: %s in %s", match_info.fetch(0), src); break; - } - break; - case '#': - unichar num = -1; - if (src[start+2] == 'x') { - src.substring(start+3, start-end-3).scanf("%x", &num); - } else { - num = int.parse(src.substring(start+2, start-end-2)); - } - result_builder.append_unichar(num); - break; - default: critical("Invalid encoding: %s in %s", match_info.fetch(0), src); break; - } - return false; - }); + try { + val = DECODABLE_VALUE_SEQUENCES.replace_eval(value, -1, 0, 0, (match_info, result_builder) => { + int start, end; + match_info.fetch_pos(0, out start, out end); + unowned string src = match_info.get_string(); + switch (src[start + 1]) { + case 'g': result_builder.append_c('>'); break; + case 'l': result_builder.append_c('<'); break; + case 'q': result_builder.append_c('"'); break; + case 'a': + switch (src[start + 2]) { + case 'p': result_builder.append_c('\''); break; + case 'm': result_builder.append_c('&'); break; + default: critical("Invalid encoding: %s in %s", match_info.fetch(0), src); break; + } + break; + case '#': + unichar num = -1; + if (src[start + 2] == 'x') { + src.substring(start + 3, start - end - 3).scanf("%x", &num); + } else { + num = int.parse(src.substring(start + 2, start - end - 2)); + } + result_builder.append_unichar(num); + break; + default: critical("Invalid encoding: %s in %s", match_info.fetch(0), src); break; + } + return false; + }); + } catch (RegexError e) { + error("Error decoding entities in string \"%s\": %s", value, e.message); + } } } diff --git a/xmpp-vala/src/core/stream_connect.vala b/xmpp-vala/src/core/stream_connect.vala index 7e3843fa..a496fc3c 100644 --- a/xmpp-vala/src/core/stream_connect.vala +++ b/xmpp-vala/src/core/stream_connect.vala @@ -15,7 +15,7 @@ namespace Xmpp { public async XmppStreamResult establish_stream(Jid bare_jid, Gee.List modules, string? log_options, owned TlsXmppStream.OnInvalidCert on_invalid_cert) { Jid remote = bare_jid.domain_jid; - TlsXmppStream.OnInvalidCertWrapper on_invalid_cert_wrapper = new TlsXmppStream.OnInvalidCertWrapper(on_invalid_cert); + TlsXmppStream.OnInvalidCertWrapper on_invalid_cert_wrapper = new TlsXmppStream.OnInvalidCertWrapper((owned) on_invalid_cert); //Lookup xmpp-client and xmpps-client SRV records GLib.List? targets = new GLib.List(); @@ -72,7 +72,13 @@ namespace Xmpp { connection_timeout_id = Timeout.add_seconds(30, () => { warning("Connection attempt timed out"); - stream.disconnect(); + stream.disconnect.begin((_, res) => { + try { + stream.disconnect.end(res); + } catch (IOError e) { + debug("Error while disconnecting: %s", e.message); + } + }); connection_timeout_id = 0; return Source.REMOVE; }); diff --git a/xmpp-vala/src/module/bookmarks_provider.vala b/xmpp-vala/src/module/bookmarks_provider.vala index 6ddcc5af..458ff183 100644 --- a/xmpp-vala/src/module/bookmarks_provider.vala +++ b/xmpp-vala/src/module/bookmarks_provider.vala @@ -8,10 +8,10 @@ public interface BookmarksProvider : Object { public signal void conference_changed(XmppStream stream, Conference conferences); public signal void received_conferences(XmppStream stream, Set conferences); - public async abstract async Set? get_conferences(XmppStream stream); - public async abstract void add_conference(XmppStream stream, Conference conference); - public async abstract void remove_conference(XmppStream stream, Conference conference); - public async abstract void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference); + public async abstract async Set? get_conferences(XmppStream stream) throws IOError; + public async abstract void add_conference(XmppStream stream, Conference conference) throws IOError; + public async abstract void remove_conference(XmppStream stream, Conference conference) throws IOError; + public async abstract void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) throws IOError; } } diff --git a/xmpp-vala/src/module/iq/module.vala b/xmpp-vala/src/module/iq/module.vala index bb9260c9..917f6660 100644 --- a/xmpp-vala/src/module/iq/module.vala +++ b/xmpp-vala/src/module/iq/module.vala @@ -23,7 +23,7 @@ namespace Xmpp.Iq { Idle.add(send_iq_async.callback); } }); - stream.write_async(iq.stanza, io_priority, cancellable); + yield stream.write_async(iq.stanza, io_priority, cancellable); ulong cancelled_handler_id = 0; if (cancellable != null) { cancelled_handler_id = cancellable.cancelled.connect(() => Idle.add(send_iq_async.callback)); diff --git a/xmpp-vala/src/module/sasl.vala b/xmpp-vala/src/module/sasl.vala index 035cc723..f0c2d6ed 100644 --- a/xmpp-vala/src/module/sasl.vala +++ b/xmpp-vala/src/module/sasl.vala @@ -73,10 +73,14 @@ namespace Xmpp.Sasl { } else if (node.name == "challenge" && stream.has_flag(Flag.IDENTITY)) { Flag flag = stream.get_flag(Flag.IDENTITY); if (flag.mechanism == Mechanism.SCRAM_SHA_1) { - var challenge_response = compute_challenge_response(node.get_string_content(), flag.password, flag.client_nonce, flag.name); - flag.server_signature = challenge_response.expected_server_signature; - stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() - .put_node(new StanzaNode.text(Base64.encode((uchar[]) (challenge_response.response).to_utf8())))); + try { + var challenge_response = compute_challenge_response(node.get_string_content(), flag.password, flag.client_nonce, flag.name); + flag.server_signature = challenge_response.expected_server_signature; + stream.write(new StanzaNode.build("response", NS_URI).add_self_xmlns() + .put_node(new StanzaNode.text(Base64.encode((uchar[]) (challenge_response.response).to_utf8())))); + } catch (Error e) { + warning("Failed processing challenge: %s", e.message); + } } } } @@ -165,7 +169,7 @@ namespace Xmpp.Sasl { public string response { get; set; } } - public static ChallengeResponse compute_challenge_response(string challenge, string password, string client_nonce, string name) { + public static ChallengeResponse compute_challenge_response(string challenge, string password, string client_nonce, string name) throws Error { string challenge_base64 = (string) Base64.decode(challenge); string? server_nonce = null; uint8[] salt = null; diff --git a/xmpp-vala/src/module/session.vala b/xmpp-vala/src/module/session.vala index b1436d1e..fa947624 100644 --- a/xmpp-vala/src/module/session.vala +++ b/xmpp-vala/src/module/session.vala @@ -28,9 +28,13 @@ public class Module : XmppStreamNegotiationModule { stream.add_flag(new Flag()); Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("session", NS_URI).add_self_xmlns()) { to=stream.remote_name }; - Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); - if (!result_iq.is_error()) { - stream.get_flag(Flag.IDENTITY).finished = true; + try { + Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + if (!result_iq.is_error()) { + stream.get_flag(Flag.IDENTITY).finished = true; + } + } catch (IOError e) { + warning("Error establishing legacy session: %s", e.message); } } } diff --git a/xmpp-vala/src/module/xep/0004_data_forms.vala b/xmpp-vala/src/module/xep/0004_data_forms.vala index 6b5624da..f481e174 100644 --- a/xmpp-vala/src/module/xep/0004_data_forms.vala +++ b/xmpp-vala/src/module/xep/0004_data_forms.vala @@ -18,6 +18,7 @@ public class DataForm { } public enum Type { + UNKNOWN, BOOLEAN, FIXED, HIDDEN, @@ -44,7 +45,7 @@ public class DataForm { get { return node.get_attribute("label", NS_URI); } set { node.set_attribute("label", value); } } - public virtual Type? type_ { get; internal set; default=null; } + public virtual Type type_ { get; internal set; default=Type.UNKNOWN; } public string? var { get { return node.get_attribute("var", NS_URI); } set { node.set_attribute("var", value); } diff --git a/xmpp-vala/src/module/xep/0030_service_discovery/module.vala b/xmpp-vala/src/module/xep/0030_service_discovery/module.vala index 537e460b..1ba8ac3a 100644 --- a/xmpp-vala/src/module/xep/0030_service_discovery/module.vala +++ b/xmpp-vala/src/module/xep/0030_service_discovery/module.vala @@ -45,7 +45,7 @@ public class Module : XmppStreamModule, Iq.Handler { return yield this.cache.get_entity_identities(jid); } - public async InfoResult? request_info(XmppStream stream, Jid jid) { + public async InfoResult? request_info(XmppStream stream, Jid jid) throws IOError { var future = active_info_requests[jid]; if (future == null) { var promise = new Promise(); @@ -69,7 +69,7 @@ public class Module : XmppStreamModule, Iq.Handler { } } - public async ItemsResult? request_items(XmppStream stream, Jid jid) { + public async ItemsResult? request_items(XmppStream stream, Jid jid) throws IOError { StanzaNode query_node = new StanzaNode.build("query", NS_URI_ITEMS).add_self_xmlns(); Iq.Stanza iq = new Iq.Stanza.get(query_node) { to=jid }; diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index 76045172..e4c066f0 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -215,7 +215,7 @@ public class Module : XmppStreamModule { stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq); } - public async void change_affiliation(XmppStream stream, Jid muc_jid, Jid? user_jid, string? nick, string new_affiliation) { + public async void change_affiliation(XmppStream stream, Jid muc_jid, Jid? user_jid, string? nick, string new_affiliation) throws IOError { StanzaNode item_node = new StanzaNode.build("item", NS_URI_ADMIN) .put_attribute("affiliation", new_affiliation, NS_URI_ADMIN); if (user_jid != null) { @@ -229,7 +229,7 @@ public class Module : XmppStreamModule { yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); } - public async DataForms.DataForm? get_config_form(XmppStream stream, Jid jid) { + public async DataForms.DataForm? get_config_form(XmppStream stream, Jid jid) throws IOError { Iq.Stanza get_iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_OWNER).add_self_xmlns()) { to=jid }; Iq.Stanza result_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, get_iq); @@ -241,7 +241,7 @@ public class Module : XmppStreamModule { return null; } - public async void set_config_form(XmppStream stream, Jid jid, DataForms.DataForm data_form) { + public async void set_config_form(XmppStream stream, Jid jid, DataForms.DataForm data_form) throws IOError { StanzaNode stanza_node = new StanzaNode.build("query", NS_URI_OWNER); stanza_node.add_self_xmlns().put_node(data_form.get_submit_node()); Iq.Stanza set_iq = new Iq.Stanza.set(stanza_node) { to=jid }; @@ -420,7 +420,7 @@ public class Module : XmppStreamModule { yield query_affiliation(stream, muc_jid, "owner"); } - private async Gee.List? query_affiliation(XmppStream stream, Jid jid, string affiliation) { + private async Gee.List? query_affiliation(XmppStream stream, Jid jid, string affiliation) throws IOError { Iq.Stanza iq = new Iq.Stanza.get( new StanzaNode.build("query", NS_URI_ADMIN) .add_self_xmlns() @@ -538,14 +538,18 @@ public class ReceivedPipelineListener : StanzaListener { if (item_nodes.size > 0) { var affiliations = new HashMap(); foreach (StanzaNode item_node in item_nodes) { - string jid_str = item_node.get_attribute("jid", Muc.NS_URI_USER); - Jid real_jid = Jid.from_string(jid_str); - string affiliation_str = item_node.get_attribute("affiliation", Muc.NS_URI_USER); - Muc.Affiliation affiliation = Muc.Module.parse_affiliation(affiliation_str); + try { + string jid_str = item_node.get_attribute("jid", Muc.NS_URI_USER); + Jid real_jid = Jid.from_string(jid_str); + string affiliation_str = item_node.get_attribute("affiliation", Muc.NS_URI_USER); + Muc.Affiliation affiliation = Muc.Module.parse_affiliation(affiliation_str); - affiliations[real_jid] = affiliation; + affiliations[real_jid] = affiliation; - stream.get_flag(Flag.IDENTITY).set_offline_member(message.from, real_jid, affiliation); + stream.get_flag(Flag.IDENTITY).set_offline_member(message.from, real_jid, affiliation); + } catch (InvalidJidError e) { + warning("Received invalid affiliation update: %s", e.message); + } } string? mav_since = MucAffiliationsVersioning.get_mav_since(x_node); diff --git a/xmpp-vala/src/module/xep/0048_bookmarks.vala b/xmpp-vala/src/module/xep/0048_bookmarks.vala index 46814e4a..dc009a1e 100644 --- a/xmpp-vala/src/module/xep/0048_bookmarks.vala +++ b/xmpp-vala/src/module/xep/0048_bookmarks.vala @@ -6,7 +6,7 @@ private const string NS_URI = "storage:bookmarks"; public class Module : BookmarksProvider, XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0048_bookmarks_module"); - public async Set? get_conferences(XmppStream stream) { + public async Set? get_conferences(XmppStream stream) throws IOError { StanzaNode get_node = new StanzaNode.build("storage", NS_URI).add_self_xmlns(); StanzaNode? result_node = yield stream.get_module(PrivateXmlStorage.Module.IDENTITY).retrieve(stream, get_node); if (result_node == null) return null; @@ -20,7 +20,7 @@ public class Module : BookmarksProvider, XmppStreamModule { return ret; } - private async void set_conferences(XmppStream stream, Set conferences) { + private async void set_conferences(XmppStream stream, Set conferences) throws IOError { StanzaNode storage_node = (new StanzaNode.build("storage", NS_URI)).add_self_xmlns(); foreach (Conference conference in conferences) { Bookmarks1Conference? bookmarks1conference = conference as Bookmarks1Conference; @@ -45,13 +45,13 @@ public class Module : BookmarksProvider, XmppStreamModule { stream.get_module(Module.IDENTITY).received_conferences(stream, conferences); } - public async void add_conference(XmppStream stream, Conference conference) { + public async void add_conference(XmppStream stream, Conference conference) throws IOError { Set? conferences = yield get_conferences(stream); conferences.add(conference); yield set_conferences(stream, conferences); } - public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) { + public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) throws IOError { Set? conferences = yield get_conferences(stream); foreach (Conference conference in conferences) { if (conference.jid.equals(muc_jid)) { @@ -64,7 +64,7 @@ public class Module : BookmarksProvider, XmppStreamModule { yield set_conferences(stream, conferences); } - public async void remove_conference(XmppStream stream, Conference conference_remove) { + public async void remove_conference(XmppStream stream, Conference conference_remove) throws IOError { Set? conferences = yield get_conferences(stream); conferences.remove(conference_remove); yield set_conferences(stream, conferences); diff --git a/xmpp-vala/src/module/xep/0049_private_xml_storage.vala b/xmpp-vala/src/module/xep/0049_private_xml_storage.vala index 81ccde9b..a6501505 100644 --- a/xmpp-vala/src/module/xep/0049_private_xml_storage.vala +++ b/xmpp-vala/src/module/xep/0049_private_xml_storage.vala @@ -6,13 +6,13 @@ namespace Xmpp.Xep.PrivateXmlStorage { public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0049_private_xml_storage"); - public async void store(XmppStream stream, StanzaNode node) { + public async void store(XmppStream stream, StanzaNode node) throws IOError { StanzaNode queryNode = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(node); Iq.Stanza iq = new Iq.Stanza.set(queryNode); yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); } - public async StanzaNode? retrieve(XmppStream stream, StanzaNode node) { + public async StanzaNode? retrieve(XmppStream stream, StanzaNode node) throws IOError { StanzaNode queryNode = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(node); Iq.Stanza iq = new Iq.Stanza.get(queryNode); Iq.Stanza iq_result = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); diff --git a/xmpp-vala/src/module/xep/0054_vcard/module.vala b/xmpp-vala/src/module/xep/0054_vcard/module.vala index 4df1d9a5..4c02a2d1 100644 --- a/xmpp-vala/src/module/xep/0054_vcard/module.vala +++ b/xmpp-vala/src/module/xep/0054_vcard/module.vala @@ -2,7 +2,7 @@ namespace Xmpp.Xep.VCard { private const string NS_URI = "vcard-temp"; private const string NS_URI_UPDATE = NS_URI + ":x:update"; -public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) { +public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) throws IOError { Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("vCard", NS_URI).add_self_xmlns()) { to=jid }; Iq.Stanza iq_res = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); diff --git a/xmpp-vala/src/module/xep/0060_pubsub.vala b/xmpp-vala/src/module/xep/0060_pubsub.vala index c9af4263..88b9cd31 100644 --- a/xmpp-vala/src/module/xep/0060_pubsub.vala +++ b/xmpp-vala/src/module/xep/0060_pubsub.vala @@ -41,7 +41,7 @@ namespace Xmpp.Xep.Pubsub { retract_listeners.unset(node); } - public async Gee.List? request_all(XmppStream stream, Jid? jid, string node) { // TODO multiple nodes gehen auch + public async Gee.List? request_all(XmppStream stream, Jid? jid, string node) throws IOError { // TODO multiple nodes gehen auch Iq.Stanza request_iq = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node))); if (jid != null) request_iq.to = jid; @@ -72,7 +72,7 @@ namespace Xmpp.Xep.Pubsub { XmppStream stream, Jid? jid, string node_id, string? item_id, StanzaNode content, PublishOptions? publish_options = null, bool try_reconfiguring = true - ) { + ) throws IOError { StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns(); StanzaNode publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id); pubsub_node.put_node(publish_node); @@ -142,7 +142,7 @@ namespace Xmpp.Xep.Pubsub { stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, null); } - public async DataForms.DataForm? request_node_config(XmppStream stream, Jid? jid, string node_id) { + public async DataForms.DataForm? request_node_config(XmppStream stream, Jid? jid, string node_id) throws IOError { StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI_OWNER).add_self_xmlns(); StanzaNode publish_node = new StanzaNode.build("configure", NS_URI_OWNER).put_attribute("node", node_id); pubsub_node.put_node(publish_node); @@ -154,7 +154,7 @@ namespace Xmpp.Xep.Pubsub { return DataForms.DataForm.create_from_node(data_form_node); } - public async bool submit_node_config(XmppStream stream, DataForms.DataForm data_form, string node_id) { + public async bool submit_node_config(XmppStream stream, DataForms.DataForm data_form, string node_id) throws IOError { StanzaNode submit_node = data_form.get_submit_node(); StanzaNode pubsub_node = new StanzaNode.build("pubsub", Pubsub.NS_URI_OWNER).add_self_xmlns(); @@ -222,7 +222,7 @@ namespace Xmpp.Xep.Pubsub { } } - private async bool change_node_config(XmppStream stream, Jid jid, string node, PublishOptions publish_options) { + private async bool change_node_config(XmppStream stream, Jid jid, string node, PublishOptions publish_options) throws IOError { DataForms.DataForm? data_form = yield stream.get_module(Pubsub.Module.IDENTITY).request_node_config(stream, jid, node); if (data_form == null) return false; diff --git a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala index 9cfb4afc..4bb46837 100644 --- a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala @@ -50,7 +50,7 @@ public class Module : XmppStreamModule, Iq.Handler { return get_local_ip_addresses_impl(); } - private async void query_availability(XmppStream stream) { + private async void query_availability(XmppStream stream) throws IOError { ServiceDiscovery.ItemsResult? items_result = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name); if (items_result == null) return; diff --git a/xmpp-vala/src/module/xep/0077_in_band_registration.vala b/xmpp-vala/src/module/xep/0077_in_band_registration.vala index 12ed2f1c..0d2dae40 100644 --- a/xmpp-vala/src/module/xep/0077_in_band_registration.vala +++ b/xmpp-vala/src/module/xep/0077_in_band_registration.vala @@ -7,7 +7,7 @@ public const string NS_URI = "jabber:iq:register"; public class Module : XmppStreamNegotiationModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0077_in_band_registration"); - public async Form? get_from_server(XmppStream stream, Jid jid) { + public async Form? get_from_server(XmppStream stream, Jid jid) throws IOError { StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns(); Iq.Stanza request_form_iq = new Iq.Stanza.get(query_node) { to=jid }; request_form_iq.to = jid; @@ -16,7 +16,7 @@ public class Module : XmppStreamNegotiationModule { return new Form.from_node(stream, iq_result); } - public async string? submit_to_server(XmppStream stream, Jid jid, Form form) { + public async string? submit_to_server(XmppStream stream, Jid jid, Form form) throws IOError { StanzaNode query_node = new StanzaNode.build("query", NS_URI).add_self_xmlns(); query_node.put_node(form.get_submit_node()); Iq.Stanza iq = new Iq.Stanza.set(query_node) { to=jid }; @@ -30,7 +30,7 @@ public class Module : XmppStreamNegotiationModule { return null; } - public async ErrorStanza? change_password(XmppStream stream, Jid bare_jid, string new_pw) { + public async ErrorStanza? change_password(XmppStream stream, Jid bare_jid, string new_pw) throws IOError { StanzaNode pw_change_node = new StanzaNode.build("query", NS_URI).add_self_xmlns(); StanzaNode username_node = new StanzaNode.build("username", NS_URI); StanzaNode pw_node = new StanzaNode.build("password", NS_URI); diff --git a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala index 3237208e..8cd3cfb1 100644 --- a/xmpp-vala/src/module/xep/0082_date_time_profiles.vala +++ b/xmpp-vala/src/module/xep/0082_date_time_profiles.vala @@ -15,8 +15,8 @@ public TimeZone? parse_tzd(string tzd) { } else { return null; } - int h_offset = unprefixed.substring(0, 2).to_int(); - int m_offset = unprefixed.substring(3, 2).to_int(); + int h_offset = int.parse(unprefixed.substring(0, 2)); + int m_offset = int.parse(unprefixed.substring(3, 2)); int offset = h_offset * 60 * 60 + m_offset * 60; if (tzd.has_prefix("-")) { offset = -offset; diff --git a/xmpp-vala/src/module/xep/0084_user_avatars.vala b/xmpp-vala/src/module/xep/0084_user_avatars.vala index 680c8e78..76ad824b 100644 --- a/xmpp-vala/src/module/xep/0084_user_avatars.vala +++ b/xmpp-vala/src/module/xep/0084_user_avatars.vala @@ -25,7 +25,7 @@ namespace Xmpp.Xep.UserAvatars { stream.get_module(Pubsub.Module.IDENTITY).delete_node(stream, null, NS_URI_METADATA); } - public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) { + public async Bytes? fetch_image(XmppStream stream, Jid jid, string hash) throws IOError { Gee.List? items = yield stream.get_module(Pubsub.Module.IDENTITY).request_all(stream, jid, NS_URI_DATA); if (items == null || items.size == 0 || items[0].sub_nodes.size == 0) return null; diff --git a/xmpp-vala/src/module/xep/0166_jingle/component.vala b/xmpp-vala/src/module/xep/0166_jingle/component.vala index e30175d5..f08ffbce 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/component.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/component.vala @@ -41,7 +41,11 @@ namespace Xmpp.Xep.Jingle { assert(!this.stream.ready); promise.set_value(stream); if (terminated != null) { - yield stream.close_async(); + try { + yield stream.close_async(); + } catch (IOError e) { + warning("Failed closing stream provided for terminated connection: %s", e.message); + } } } @@ -53,7 +57,11 @@ namespace Xmpp.Xep.Jingle { if (terminated == null) { terminated = (reason_name ?? "") + " - " + (reason_text ?? "") + @"we terminated? $we_terminated"; if (stream.ready) { - yield stream.value.close_async(); + try { + yield stream.value.close_async(); + } catch (IOError e) { + warning("Failed closing stream when terminating connection: %s", e.message); + } } else { promise.set_exception(new IOError.FAILED("Jingle connection failed")); } diff --git a/xmpp-vala/src/module/xep/0166_jingle/content.vala b/xmpp-vala/src/module/xep/0166_jingle/content.vala index f6af1b17..07cadc36 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/content.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/content.vala @@ -67,7 +67,7 @@ public class Xmpp.Xep.Jingle.Content : Object { ContentType content_type, ContentParameters content_params, Transport transport, TransportParameters? transport_params, SecurityPrecondition? security_precondition, SecurityParameters? security_params, - Jid local_full_jid, Jid peer_full_jid) throws Error { + Jid local_full_jid, Jid peer_full_jid) { this.content_name = content_name; this.senders = senders; this.role = Role.RESPONDER; @@ -142,7 +142,7 @@ public class Xmpp.Xep.Jingle.Content : Object { this.content_params.accept(stream, session, this); } - internal void handle_accept(XmppStream stream, ContentNode content_node) { + internal void handle_accept(XmppStream stream, ContentNode content_node) throws IqError { this.transport_params.handle_transport_accept(content_node.transport); this.transport_params.create_transport_connection(stream, this); this.content_params.handle_accept(stream, this.session, this, content_node.description); @@ -157,7 +157,13 @@ public class Xmpp.Xep.Jingle.Content : Object { return; } tried_transport_methods.add(new_transport.ns_uri); - transport_params = new_transport.create_transport_parameters(stream, transport_params.components, local_full_jid, peer_full_jid); + try { + transport_params = new_transport.create_transport_parameters(stream, transport_params.components, local_full_jid, peer_full_jid); + } catch (Error e) { + session.terminate(ReasonElement.FAILED_TRANSPORT, null, e.message); + // TODO should we only terminate this content or really the whole session? + return; + } set_transport_params(transport_params); session.send_transport_replace(this, transport_params); state = State.REPLACING_TRANSPORT; diff --git a/xmpp-vala/src/module/xep/0166_jingle/jingle_flag.vala b/xmpp-vala/src/module/xep/0166_jingle/jingle_flag.vala index 9f0acd27..f451f4c9 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/jingle_flag.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/jingle_flag.vala @@ -24,7 +24,12 @@ public class Xmpp.Xep.Jingle.Flag : XmppStreamFlag { public async Session? get_session(string sid) { if (promises.has_key(sid)) { - return yield promises[sid].future.wait_async(); + try { + return yield promises[sid].future.wait_async(); + } catch (FutureError e) { + warning("Failed waiting for session: %s", e.message); + return null; + } } return sessions[sid]; } diff --git a/xmpp-vala/src/module/xep/0166_jingle/session.vala b/xmpp-vala/src/module/xep/0166_jingle/session.vala index 22e8e978..aa8d6656 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/session.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/session.vala @@ -222,7 +222,12 @@ public class Xmpp.Xep.Jingle.Session : Object { .put_node(content.transport_params.to_transport_stanza_node("content-add"))); Iq.Stanza iq = new Iq.Stanza.set(content_add_node) { to=peer_full_jid }; - yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + try { + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + } catch (IOError e) { + warning("Error adding content: %s", e.message); + // FIXME: Probably need to clean up some state + } } private void handle_content_accept(ContentNode content_node) throws IqError { diff --git a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala index 490cc125..53057d9e 100644 --- a/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala +++ b/xmpp-vala/src/module/xep/0176_jingle_ice_udp/transport_parameters.vala @@ -39,7 +39,11 @@ public abstract class Xmpp.Xep.JingleIceUdp.IceUdpTransportParameters : Jingle.T remote_pwd = node.get_attribute("pwd"); remote_ufrag = node.get_attribute("ufrag"); foreach (StanzaNode candidateNode in node.get_subnodes("candidate")) { - remote_candidates.add(Candidate.parse(candidateNode)); + try { + remote_candidates.add(Candidate.parse(candidateNode)); + } catch (Jingle.IqError e) { + warning("Ignoring invalid candidate %s: %s", candidateNode.to_string(), e.message); + } } StanzaNode? fingerprint_node = node.get_subnode("fingerprint", DTLS_NS_URI); diff --git a/xmpp-vala/src/module/xep/0191_blocking_command.vala b/xmpp-vala/src/module/xep/0191_blocking_command.vala index 9e5ede78..c037c480 100644 --- a/xmpp-vala/src/module/xep/0191_blocking_command.vala +++ b/xmpp-vala/src/module/xep/0191_blocking_command.vala @@ -88,11 +88,15 @@ public class Module : XmppStreamModule, Iq.Handler { bool has_feature = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, stream.remote_name, NS_URI); if (has_feature) { stream.add_flag(new Flag()); - stream.get_flag(Flag.IDENTITY).blocklist = yield get_blocklist(stream); + try { + stream.get_flag(Flag.IDENTITY).blocklist = yield get_blocklist(stream); + } catch (IOError e) { + warning("Failed to retrieve blocklist: %s", e.message); + } } } - private async Gee.List get_blocklist(XmppStream stream) { + private async Gee.List get_blocklist(XmppStream stream) throws IOError { StanzaNode blocklist_node = new StanzaNode.build("blocklist", NS_URI).add_self_xmlns(); Iq.Stanza iq = new Iq.Stanza.get(blocklist_node); diff --git a/xmpp-vala/src/module/xep/0199_ping.vala b/xmpp-vala/src/module/xep/0199_ping.vala index 18fa538c..7bc234bc 100644 --- a/xmpp-vala/src/module/xep/0199_ping.vala +++ b/xmpp-vala/src/module/xep/0199_ping.vala @@ -6,7 +6,7 @@ namespace Xmpp.Xep.Ping { public class Module : XmppStreamModule, Iq.Handler { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0199_ping"); - public async Iq.Stanza send_ping(XmppStream stream, Jid jid) { + public async Iq.Stanza send_ping(XmppStream stream, Jid jid) throws IOError { StanzaNode ping_node = new StanzaNode.build("ping", NS_URI).add_self_xmlns(); Iq.Stanza iq = new Iq.Stanza.get(ping_node) { to=jid }; return yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq, Priority.HIGH); diff --git a/xmpp-vala/src/module/xep/0215_external_service_discovery.vala b/xmpp-vala/src/module/xep/0215_external_service_discovery.vala index 5bad71cd..67f67c05 100644 --- a/xmpp-vala/src/module/xep/0215_external_service_discovery.vala +++ b/xmpp-vala/src/module/xep/0215_external_service_discovery.vala @@ -4,7 +4,7 @@ namespace Xmpp.Xep.ExternalServiceDiscovery { private const string NS_URI = "urn:xmpp:extdisco:2"; - public static async Gee.List request_services(XmppStream stream) { + public static async Gee.List request_services(XmppStream stream) throws IOError { Iq.Stanza request_iq = new Iq.Stanza.get((new StanzaNode.build("services", NS_URI)).add_self_xmlns()) { to=stream.remote_name }; Iq.Stanza response_iq = yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, request_iq); diff --git a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala index f85049ac..43edefad 100644 --- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala +++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala @@ -42,7 +42,7 @@ public class Module : Jingle.ContentType, XmppStreamModule { return yield stream.get_module(Jingle.Module.IDENTITY).is_available(stream, required_transport_type, required_components, full_jid); } - public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws Jingle.Error { + public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws Jingle.Error, Jingle.IqError { StanzaNode file_node; StanzaNode description = new StanzaNode.build("description", NS_URI) .add_self_xmlns() diff --git a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala index 852cc7eb..7f44bda2 100644 --- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala @@ -328,9 +328,13 @@ class LocalListener { } } catch (Error e) { warning("Jingle SOCKS5 bytestream establish error cid %s: %s", cid, e.message); - // Send a "general SOCKS server failure" (0x01) - yield conn.output_stream.write_all_async({0x05, 0x01}, GLib.Priority.DEFAULT, null, out written); - yield conn.output_stream.close_async(); + try { + // Send a "general SOCKS server failure" (0x01) + yield conn.output_stream.write_all_async({0x05, 0x01}, GLib.Priority.DEFAULT, null, out written); + yield conn.output_stream.close_async(); + } catch (Error sub) { + // Ignoring + } } } diff --git a/xmpp-vala/src/module/xep/0272_muji.vala b/xmpp-vala/src/module/xep/0272_muji.vala index 8226da10..7fd0da19 100644 --- a/xmpp-vala/src/module/xep/0272_muji.vala +++ b/xmpp-vala/src/module/xep/0272_muji.vala @@ -86,7 +86,12 @@ namespace Xmpp.Xep.Muji { GroupCall group_call = stream.get_flag(Flag.IDENTITY).calls[muc_jid]; group_call.waiting_for_finish_prepares[promise] = preparing_peers; - return yield promise.future.wait_async(); + try { + return yield promise.future.wait_async(); + } catch (FutureError e) { + warning("Failed waiting for preparing peers: %s", e.message); + return new ArrayList(); + } } private async void compute_payload_intersection(XmppStream stream, GroupCall group_call, string media) { diff --git a/xmpp-vala/src/module/xep/0280_message_carbons.vala b/xmpp-vala/src/module/xep/0280_message_carbons.vala index b396ee54..f8008bfe 100644 --- a/xmpp-vala/src/module/xep/0280_message_carbons.vala +++ b/xmpp-vala/src/module/xep/0280_message_carbons.vala @@ -10,13 +10,21 @@ public class Module : XmppStreamModule { public async void enable(XmppStream stream) { if (stream.get_flag(Flag.IDENTITY) == null) { Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("enable", NS_URI).add_self_xmlns()); - yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + try { + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + } catch (IOError e) { + warning("Failed enabling carbons: %s", e.message); + } } } public async void disable(XmppStream stream) { Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("disable", NS_URI).add_self_xmlns()); - yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + try { + yield stream.get_module(Iq.Module.IDENTITY).send_iq_async(stream, iq); + } catch (IOError e) { + warning("Failed enabling carbons: %s", e.message); + } } public override void attach(XmppStream stream) { diff --git a/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala b/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala index 298a1c61..17b93d55 100644 --- a/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala +++ b/xmpp-vala/src/module/xep/0313_2_message_archive_management.vala @@ -61,7 +61,7 @@ namespace Xmpp.MessageArchiveManagement.V2 { return MessageArchiveManagement.create_base_query(stream, mam_params.query_id, fields); } - public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) { + public async QueryResult query_archive(XmppStream stream, MamQueryParams mam_params, Cancellable? cancellable = null) throws IOError { var query_node = create_base_query(stream, mam_params); if (!mam_params.use_ns2_extended) { query_node.put_node(ResultSetManagement.create_set_rsm_node_before(mam_params.end_id)); @@ -70,7 +70,7 @@ namespace Xmpp.MessageArchiveManagement.V2 { return yield MessageArchiveManagement.query_archive(stream, mam_params.mam_server, query_node, cancellable); } - public async QueryResult page_through_results(XmppStream stream, MamQueryParams mam_params, QueryResult prev_result, Cancellable? cancellable = null) { + public async QueryResult page_through_results(XmppStream stream, MamQueryParams mam_params, QueryResult prev_result, Cancellable? cancellable = null) throws IOError { var query_node = create_base_query(stream, mam_params); query_node.put_node(ResultSetManagement.create_set_rsm_node_before(prev_result.first)); diff --git a/xmpp-vala/src/module/xep/0313_message_archive_management.vala b/xmpp-vala/src/module/xep/0313_message_archive_management.vala index a9149d4e..f054a047 100644 --- a/xmpp-vala/src/module/xep/0313_message_archive_management.vala +++ b/xmpp-vala/src/module/xep/0313_message_archive_management.vala @@ -57,7 +57,7 @@ public class Module : XmppStreamModule { return query_node; } - internal async QueryResult query_archive(XmppStream stream, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) { + internal async QueryResult query_archive(XmppStream stream, Jid? mam_server, StanzaNode query_node, Cancellable? cancellable = null) throws IOError { var res = new QueryResult(); Flag flag = Flag.get_flag(stream); diff --git a/xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala b/xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala index b45d84de..10aa5d9f 100644 --- a/xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala +++ b/xmpp-vala/src/module/xep/0392_consistent_color/hsluv.vala @@ -340,9 +340,9 @@ internal string rgb_to_hex(double[] tuple) { internal double[] hex_to_tgb(string hex) { return new double[] { - hex.substring(1, 2).to_long(null, 16) / 255.0, - hex.substring(3, 2).to_long(null, 16) / 255.0, - hex.substring(5, 2).to_long(null, 16) / 255.0 + long.parse(hex.substring(1, 2), 16) / 255.0, + long.parse(hex.substring(3, 2), 16) / 255.0, + long.parse(hex.substring(5, 2), 16) / 255.0 }; } diff --git a/xmpp-vala/src/module/xep/0402_bookmarks2.vala b/xmpp-vala/src/module/xep/0402_bookmarks2.vala index 938af0e9..3a7e5211 100644 --- a/xmpp-vala/src/module/xep/0402_bookmarks2.vala +++ b/xmpp-vala/src/module/xep/0402_bookmarks2.vala @@ -8,7 +8,7 @@ public const string NS_URI_COMPAT = NS_URI + "#compat"; public class Module : BookmarksProvider, XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "0402_bookmarks2"); - public async Set? get_conferences(XmppStream stream) { + public async Set? get_conferences(XmppStream stream) throws IOError { HashMap? hm = null; Flag? flag = stream.get_flag(Flag.IDENTITY); @@ -35,7 +35,7 @@ public class Module : BookmarksProvider, XmppStreamModule { return ret; } - public async void add_conference(XmppStream stream, Conference conference) { + public async void add_conference(XmppStream stream, Conference conference) throws IOError { StanzaNode conference_node = new StanzaNode.build("conference", NS_URI).add_self_xmlns() .put_attribute("autojoin", conference.autojoin.to_string()); if (conference.name != null) { @@ -56,7 +56,7 @@ public class Module : BookmarksProvider, XmppStreamModule { yield stream.get_module(Pubsub.Module.IDENTITY).publish(stream, stream.get_flag(Bind.Flag.IDENTITY).my_jid.bare_jid, NS_URI, conference.jid.to_string(), conference_node, publish_options); } - public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) { + public async void replace_conference(XmppStream stream, Jid muc_jid, Conference modified_conference) throws IOError { yield add_conference(stream, modified_conference); } diff --git a/xmpp-vala/src/module/xep/0461_replies.vala b/xmpp-vala/src/module/xep/0461_replies.vala index af84aa3d..23849d69 100644 --- a/xmpp-vala/src/module/xep/0461_replies.vala +++ b/xmpp-vala/src/module/xep/0461_replies.vala @@ -36,6 +36,5 @@ namespace Xmpp.Xep.Replies { } catch (InvalidJidError e) { return null; } - return null; } } \ No newline at end of file diff --git a/xmpp-vala/tests/color.vala b/xmpp-vala/tests/color.vala index ded67d53..a00710c5 100644 --- a/xmpp-vala/tests/color.vala +++ b/xmpp-vala/tests/color.vala @@ -39,9 +39,9 @@ class ColorTest : Gee.TestCase { public void test_rgb_to_angle() { string[] colors = {"e57373", "f06292", "ba68c8", "9575cd", "7986cb", "64b5f6", "4fc3f7", "4dd0e1", "4db6ac", "81c784", "aed581", "dce775", "fff176", "ffd54f", "ffb74d", "ff8a65"}; foreach(string hex_color in colors) { - uint8 r = (uint8) ((double) hex_color.substring(0, 2).to_long(null, 16)); - uint8 g = (uint8) ((double) hex_color.substring(2, 2).to_long(null, 16)); - uint8 b = (uint8) ((double) hex_color.substring(4, 2).to_long(null, 16)); + uint8 r = (uint8) ((double) long.parse(hex_color.substring(0, 2), 16)); + uint8 g = (uint8) ((double) long.parse(hex_color.substring(2, 2), 16)); + uint8 b = (uint8) ((double) long.parse(hex_color.substring(4, 2), 16)); //print(@"$hex_color, $r, $g, $b, $(ConsistentColor.rgb_to_angle(r, g, b))\n"); } } diff --git a/xmpp-vala/tests/stanza.vala b/xmpp-vala/tests/stanza.vala index 42e19f9a..b573995f 100644 --- a/xmpp-vala/tests/stanza.vala +++ b/xmpp-vala/tests/stanza.vala @@ -21,11 +21,15 @@ class StanzaTest : Gee.TestCase { .put_node(new StanzaNode.build("testbb", "ns3_uri") .add_self_xmlns()); - var xml1 = node1.to_xml(); - var node2 = yield new StanzaReader.for_string(xml1).read_node(); - fail_if_not(node1.equals(node2)); - fail_if_not_eq_str(node1.to_string(), node2.to_string()); - cb(); + try { + var xml1 = node1.to_xml(); + var node2 = yield new StanzaReader.for_string(xml1).read_node(); + fail_if_not(node1.equals(node2)); + fail_if_not_eq_str(node1.to_string(), node2.to_string()); + cb(); + } catch (GLib.Error e) { + cb(e); + } } private async void test_typical_stream(Gee.TestFinishedCallback cb) { @@ -55,12 +59,16 @@ class StanzaTest : Gee.TestCase { .put_node(new StanzaNode.build("body") .put_node(new StanzaNode.text(" I'll send a friar with speed, to Mantua, with my letters to thy lord."))); - var reader = new StanzaReader.for_string(stream); - fail_if_not_eq_node(root_node_cmp, yield reader.read_root_node()); - fail_if_not_eq_node(node_cmp, yield reader.read_node()); - yield reader.read_node(); - yield fail_if_not_end_of_stream(reader); - cb(); + try { + var reader = new StanzaReader.for_string(stream); + fail_if_not_eq_node(root_node_cmp, yield reader.read_root_node()); + fail_if_not_eq_node(node_cmp, yield reader.read_node()); + yield reader.read_node(); + yield fail_if_not_end_of_stream(reader); + cb(); + } catch (GLib.Error e) { + cb(e); + } } private async void fail_if_not_end_of_stream(StanzaReader reader) { @@ -105,13 +113,17 @@ class StanzaTest : Gee.TestCase { .put_node(new StanzaNode.build("required", "urn:ietf:params:xml:ns:xmpp-bind"))); var node2_cmp = new StanzaNode.build("r", "http://jabber.org/protocol/ack"); - var reader = new StanzaReader.for_string(stream); - fail_if_not_eq_node(root_node_cmp, yield reader.read_root_node()); - fail_if_not_eq_node(node_cmp, yield reader.read_node()); - fail_if_not_eq_node(node2_cmp, yield reader.read_node()); - yield reader.read_node(); - yield fail_if_not_end_of_stream(reader); - cb(); + try { + var reader = new StanzaReader.for_string(stream); + fail_if_not_eq_node(root_node_cmp, yield reader.read_root_node()); + fail_if_not_eq_node(node_cmp, yield reader.read_node()); + fail_if_not_eq_node(node2_cmp, yield reader.read_node()); + yield reader.read_node(); + yield fail_if_not_end_of_stream(reader); + cb(); + } catch (GLib.Error e) { + cb(e); + } } private void test_encoded_val() { diff --git a/xmpp-vala/tests/testcase.vala b/xmpp-vala/tests/testcase.vala index 178b6353..10da78f6 100644 --- a/xmpp-vala/tests/testcase.vala +++ b/xmpp-vala/tests/testcase.vala @@ -62,7 +62,7 @@ public abstract class Gee.TestCase : Object { namespace Gee { public delegate void TestMethod (); - public delegate void TestFinishedCallback (); + public delegate void TestFinishedCallback (GLib.Error? error = null); public delegate void AsyncTestMethod (TestFinishedCallback cb); } @@ -128,8 +128,15 @@ private class Gee.TestAsyncAdaptor : Object, TestAdaptor { main_loop.run (); } - public void finish () { - Idle.add (() => { main_loop.quit (); return false; }); + public void finish (GLib.Error? error = null) { + Idle.add(() => { + main_loop.quit(); + if (error != null) { + Test.fail(); + Test.message("Error: %s", error.message); + } + return false; + }); } public bool finish_timeout () { -- cgit v1.3