From 158cff3098e9370696f9250a199c78e4f2d18ce0 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 30 May 2026 16:10:36 +0200 Subject: Add JID interning --- libdino/meson.build | 1 - libdino/src/application.vala | 2 +- libdino/src/entity/account.vala | 6 +- libdino/src/entity/message.vala | 2 +- libdino/src/service/call_state.vala | 2 +- libdino/src/service/calls.vala | 2 +- libdino/src/service/database.vala | 2 +- libdino/src/service/history_sync.vala | 2 +- libdino/src/service/reactions.vala | 2 +- libdino/src/service/roster_manager.vala | 2 +- libdino/src/service/search_processor.vala | 12 +- libdino/src/util/weak_map.vala | 119 ------------ libdino/tests/jid.vala | 2 +- .../ui/add_conversation/add_contact_dialog.vala | 4 +- .../ui/add_conversation/add_groupchat_dialog.vala | 6 +- .../conference_details_fragment.vala | 6 +- .../ui/add_conversation/select_jid_fragment.vala | 2 +- main/src/ui/application.vala | 2 +- main/src/ui/widgets/avatar_picture.vala | 2 +- .../preferences_window/add_account_dialog.vala | 12 +- .../omemo/src/dtls_srtp_verification_draft.vala | 4 +- plugins/omemo/src/logic/decrypt.vala | 4 +- plugins/omemo/src/logic/manager.vala | 2 +- plugins/omemo/src/ui/bad_messages_populator.vala | 2 +- .../omemo/src/ui/encryption_preferences_entry.vala | 2 +- xmpp-vala/meson.build | 1 + xmpp-vala/src/module/bind.vala | 2 +- xmpp-vala/src/module/jid.vala | 200 +++++++++++++++------ xmpp-vala/src/module/roster/item.vala | 2 +- xmpp-vala/src/module/stanza.vala | 80 ++++++--- .../xep/0030_service_discovery/items_result.vala | 2 +- xmpp-vala/src/module/xep/0045_muc/module.vala | 10 +- xmpp-vala/src/module/xep/0048_conference.vala | 2 +- .../src/module/xep/0065_socks5_bytestreams.vala | 2 +- .../src/module/xep/0166_jingle/jingle_module.vala | 2 +- xmpp-vala/src/module/xep/0166_jingle/session.vala | 2 +- .../module/xep/0249_direct_muc_invitations.vala | 2 +- .../module/xep/0260_jingle_socks5_bytestreams.vala | 2 +- xmpp-vala/src/module/xep/0298_coin.vala | 2 +- .../xep/0313_message_archive_management.vala | 2 +- .../module/xep/0388_extensible_sasl_profile.vala | 2 +- xmpp-vala/src/module/xep/0402_bookmarks2.vala | 4 +- xmpp-vala/src/module/xep/0461_replies.vala | 2 +- xmpp-vala/src/weak_map.vala | 119 ++++++++++++ xmpp-vala/tests/jid.vala | 14 +- 45 files changed, 391 insertions(+), 267 deletions(-) delete mode 100644 libdino/src/util/weak_map.vala create mode 100644 xmpp-vala/src/weak_map.vala diff --git a/libdino/meson.build b/libdino/meson.build index 73b1c512..f83878e4 100644 --- a/libdino/meson.build +++ b/libdino/meson.build @@ -81,7 +81,6 @@ sources = files( 'src/util/limit_input_stream.vala', 'src/util/send_message.vala', 'src/util/util.vala', - 'src/util/weak_map.vala', 'src/util/weak_timeout.vala', ) sources += [version_vala] diff --git a/libdino/src/application.vala b/libdino/src/application.vala index 19650495..8571120e 100644 --- a/libdino/src/application.vala +++ b/libdino/src/application.vala @@ -94,7 +94,7 @@ public interface Application : GLib.Application { } jid = Uri.unescape_string(jid); try { - jid = new Xmpp.Jid(jid).to_string(); + jid = Xmpp.Jid.from_string(jid).to_string(); } catch (Xmpp.InvalidJidError e) { warning("Received invalid jid in xmpp:-URI: %s", e.message); } diff --git a/libdino/src/entity/account.vala b/libdino/src/entity/account.vala index e7729fa0..a108af22 100644 --- a/libdino/src/entity/account.vala +++ b/libdino/src/entity/account.vala @@ -10,7 +10,7 @@ public class Account : Object { public string domainpart { get { return full_jid.domainpart; } } public string resourcepart { get { return full_jid.resourcepart; } - private set { full_jid.resourcepart = value; } + private set { full_jid = full_jid.with_resource(value); } } public Jid bare_jid { owned get { return full_jid.bare_jid; } } public Jid full_jid { get; private set; } @@ -37,7 +37,7 @@ public class Account : Object { public Account.from_row(Database db, Qlite.Row row) throws InvalidJidError { this.db = db; id = row[db.account.id]; - full_jid = new Jid(row[db.account.bare_jid]).with_resource(row[db.account.resourcepart]); + full_jid = Jid.from_string(row[db.account.bare_jid]).with_resource(row[db.account.resourcepart]); password = row[db.account.password]; alias = row[db.account.alias]; enabled = row[db.account.enabled]; @@ -82,7 +82,7 @@ public class Account : Object { } public static bool equals_func(Account acc1, Account acc2) { - return acc1.bare_jid.to_string() == acc2.bare_jid.to_string(); + return acc1.bare_jid.equals(acc2.bare_jid); } public static uint hash_func(Account acc) { diff --git a/libdino/src/entity/message.vala b/libdino/src/entity/message.vala index ace59572..16e7ae3f 100644 --- a/libdino/src/entity/message.vala +++ b/libdino/src/entity/message.vala @@ -109,7 +109,7 @@ public class Message : Object { encryption = (Encryption) row[db.message.encryption]; string? real_jid_str = row[db.real_jid.real_jid]; - if (real_jid_str != null) real_jid = new Jid(real_jid_str); + if (real_jid_str != null) real_jid = Jid.from_string(real_jid_str); occupant_db_id = row[db.message_occupant_id.occupant_id]; diff --git a/libdino/src/service/call_state.vala b/libdino/src/service/call_state.vala index a87aee0a..3caf5071 100644 --- a/libdino/src/service/call_state.vala +++ b/libdino/src/service/call_state.vala @@ -354,7 +354,7 @@ public class Dino.CallState : Object { } if (cim_call_id == null) cim_call_id = Xmpp.random_uuid(); - muc_jid = new Jid("%08x@".printf(Random.next_int()) + muc_jid.to_string()); // TODO longer? + muc_jid = Jid.from_string("%08x@".printf(Random.next_int()) + muc_jid.to_string()); // TODO longer? debug("[%s] Converting call to groupcall %s", call.account.bare_jid.to_string(), muc_jid.to_string()); yield join_group_call(muc_jid); diff --git a/libdino/src/service/calls.vala b/libdino/src/service/calls.vala index a2d25655..e0e37ffd 100644 --- a/libdino/src/service/calls.vala +++ b/libdino/src/service/calls.vala @@ -425,7 +425,7 @@ namespace Dino { string? room_jid_str = join_method_node.get_attribute("room"); if (room_jid_str == null) return; - Jid room_jid = new Jid(room_jid_str); + Jid room_jid = Jid.from_string(room_jid_str); call_state = create_recv_muji_call(account, call_id, from_jid, room_jid, message_stanza.type_); multiparty = true; diff --git a/libdino/src/service/database.vala b/libdino/src/service/database.vala index 7a5b940a..847d87f6 100644 --- a/libdino/src/service/database.vala +++ b/libdino/src/service/database.vala @@ -833,7 +833,7 @@ public class Database : Qlite.Database { } else { string? bare_jid = jid.select({jid.bare_jid}).with(jid.id, "=", id)[jid.bare_jid]; if (bare_jid != null) { - Jid jid_parsed = new Jid(bare_jid); + Jid jid_parsed = Jid.from_string(bare_jid); jid_table_cache[id] = jid_parsed; // Only store fully normalized Jids for reverse lookup diff --git a/libdino/src/service/history_sync.vala b/libdino/src/service/history_sync.vala index 190284e0..9bd3f54f 100644 --- a/libdino/src/service/history_sync.vala +++ b/libdino/src/service/history_sync.vala @@ -505,7 +505,7 @@ public class Dino.HistorySync { foreach (Row row in db.mam_catchup.select().with(db.mam_catchup.account_id, "=", account.id)) { var mam_range = new MamRange(); mam_range.id = row[db.mam_catchup.id]; - mam_range.server_jid = new Jid(row[db.mam_catchup.server_jid]); + mam_range.server_jid = Jid.from_string(row[db.mam_catchup.server_jid]); mam_range.from_time = row[db.mam_catchup.from_time]; mam_range.from_id = row[db.mam_catchup.from_id]; mam_range.from_end = row[db.mam_catchup.from_end]; diff --git a/libdino/src/service/reactions.vala b/libdino/src/service/reactions.vala index f6050d28..16849c68 100644 --- a/libdino/src/service/reactions.vala +++ b/libdino/src/service/reactions.vala @@ -210,7 +210,7 @@ public class Dino.Reactions : StreamInteractionModule, Object { Jid jid = null; if (!db.jid.bare_jid.is_null(row)) { - jid = new Jid(row[db.jid.bare_jid]); + jid = Jid.from_string(row[db.jid.bare_jid]); } else if (!db.occupantid.occupant_id.is_null(row)) { if (row[db.occupantid.occupant_id] == own_occupant_id) { jid = account.bare_jid; diff --git a/libdino/src/service/roster_manager.vala b/libdino/src/service/roster_manager.vala index 7cd844da..c9bafb24 100644 --- a/libdino/src/service/roster_manager.vala +++ b/libdino/src/service/roster_manager.vala @@ -93,7 +93,7 @@ public class RosterStoreImpl : Roster.Storage, Object { foreach (Qlite.Row row in db.roster.select().with(db.roster.account_id, "=", account.id)) { try { Roster.Item item = new Roster.Item(); - item.jid = new Jid(row[db.roster.jid]); + item.jid = Jid.from_string(row[db.roster.jid]); item.name = row[db.roster.handle]; item.subscription = row[db.roster.subscription]; items[item.jid] = item; diff --git a/libdino/src/service/search_processor.vala b/libdino/src/service/search_processor.vala index 6e7d4993..2b5ebe49 100644 --- a/libdino/src/service/search_processor.vala +++ b/libdino/src/service/search_processor.vala @@ -134,9 +134,9 @@ public class SearchProcessor : StreamInteractionModule, Object { foreach(Row chat in chats) { try { if (suggestions.size == 0) { - suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "from:"+chat[db.jid.bare_jid], after_prev_space, next_space)); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), Jid.from_string(chat[db.jid.bare_jid]), "from:"+chat[db.jid.bare_jid], after_prev_space, next_space)); } - suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.account.bare_jid]), "from:"+chat[db.account.bare_jid], after_prev_space, next_space)); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), Jid.from_string(chat[db.account.bare_jid]), "from:"+chat[db.account.bare_jid], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } @@ -160,7 +160,7 @@ public class SearchProcessor : StreamInteractionModule, Object { .limit(5); foreach(Row msg in msgs) { try { - suggestions.add(new SearchSuggestion(new Conversation.from_row(db, msg), new Jid(current_in).with_resource(msg[db.message.counterpart_resource]), "from:"+msg[db.message.counterpart_resource], after_prev_space, next_space)); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, msg), Jid.from_string(current_in).with_resource(msg[db.message.counterpart_resource]), "from:"+msg[db.message.counterpart_resource], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } @@ -190,7 +190,7 @@ public class SearchProcessor : StreamInteractionModule, Object { .limit(limit); foreach(Row chat in chats) { try { - suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "with:"+chat[db.jid.bare_jid], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), Jid.from_string(chat[db.jid.bare_jid]), "with:"+chat[db.jid.bare_jid], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } @@ -208,7 +208,7 @@ public class SearchProcessor : StreamInteractionModule, Object { .limit(limit - suggestions.size); foreach(Row chat in chats) { try { - suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]).with_resource(chat[db.conversation.resource]), "with:"+chat[db.jid.bare_jid]+"/"+chat[db.conversation.resource], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), Jid.from_string(chat[db.jid.bare_jid]).with_resource(chat[db.conversation.resource]), "with:"+chat[db.jid.bare_jid]+"/"+chat[db.conversation.resource], after_prev_space, next_space) { order = chat[db.conversation.last_active]}); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } @@ -235,7 +235,7 @@ public class SearchProcessor : StreamInteractionModule, Object { .limit(limit); foreach(Row chat in groupchats) { try { - suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), new Jid(chat[db.jid.bare_jid]), "in:"+chat[db.jid.bare_jid], after_prev_space, next_space)); + suggestions.add(new SearchSuggestion(new Conversation.from_row(db, chat), Jid.from_string(chat[db.jid.bare_jid]), "in:"+chat[db.jid.bare_jid], after_prev_space, next_space)); } catch (InvalidJidError e) { warning("Ignoring search suggestion with invalid Jid: %s", e.message); } diff --git a/libdino/src/util/weak_map.vala b/libdino/src/util/weak_map.vala deleted file mode 100644 index a7f4bc44..00000000 --- a/libdino/src/util/weak_map.vala +++ /dev/null @@ -1,119 +0,0 @@ -using Gee; - -public class WeakMap : Gee.AbstractMap { - - private HashMap hash_map; - private HashMap notify_map; - - public HashDataFunc? key_hash_func = null; - public EqualDataFunc? key_equal_func = null; - public EqualDataFunc? value_equal_func = null; - - public WeakMap(owned HashDataFunc? key_hash_func = null, owned EqualDataFunc? key_equal_func = null, owned EqualDataFunc? value_equal_func = null) { - if (!typeof(V).is_object()) { - error("WeakMap only takes values that are Objects"); - } - - this.key_hash_func = (owned)key_hash_func; - this.key_equal_func = (owned)key_equal_func; - this.value_equal_func = (owned)value_equal_func; - - if (this.key_equal_func == null || this.key_equal_func == null || this.value_equal_func == null) { - hash_map = new HashMap(); - notify_map = new HashMap(); - } else { - hash_map = new HashMap((v) => { return this.key_hash_func != null ? this.key_hash_func(v) : 0; }, - (a, b) => { return this.key_equal_func != null ? this.key_equal_func(a, b) : a == b; }, - (a, b) => { return this.value_equal_func != null ? this.value_equal_func(a, b) : a == b; }); - notify_map = new HashMap((v) => { return this.key_hash_func != null ? this.key_hash_func(v) : 0; }, - (a, b) => { return this.key_equal_func != null ? this.key_equal_func(a, b) : a == b; }); - } - } - - public override void clear() { - foreach (K key in notify_map.keys) { - Object o = (Object) hash_map[key]; - o.weak_unref(notify_map[key].func); - } - hash_map.clear(); - notify_map.clear(); - } - - public override V @get(K key) { - if (!hash_map.has_key(key)) return null; - - var v = hash_map[key]; - - return (owned) v; - } - - public override bool has(K key, V value) { - return has_key(key) && (this.value_equal_func != null ? this.value_equal_func(hash_map[key], value) : hash_map[key] == value); - } - - public override bool has_key(K key) { - return hash_map.has_key(key); - } - - public override Gee.MapIterator map_iterator() { - assert_not_reached(); - } - - public override void @set(K key, V value) { - assert(value != null); - - unset(key); - - Object v_obj = (Object) value; - var notify_wrap = new WeakNotifyWrapper((obj) => { - hash_map.unset(key); - notify_map.unset(key); - }); - notify_map[key] = notify_wrap; - v_obj.weak_ref(notify_wrap.func); - - hash_map[key] = value; - } - - public override bool unset(K key, out V value = null) { - if (!hash_map.has_key(key)) { - value = null; - return false; - } - - Object v_obj = (Object) hash_map[key]; - v_obj.weak_unref(notify_map[key].func); - notify_map.unset(key); - return hash_map.unset(key, out value); - } - public override Gee.Set> entries { owned get; } - - public override Gee.Set keys { - owned get { return hash_map.keys; } - } - - public override bool read_only { get { assert_not_reached(); } } - - public override int size { get { return hash_map.size; } } - - public override Gee.Collection values { - owned get { - assert_not_reached(); - } - } - - public override void dispose() { - foreach (K key in notify_map.keys) { - Object o = (Object) hash_map[key]; - o.weak_unref(notify_map[key].func); - } - } -} - -internal class WeakNotifyWrapper { - public WeakNotify func; - - public WeakNotifyWrapper(owned WeakNotify func) { - this.func = (owned) func; - } -} \ No newline at end of file diff --git a/libdino/tests/jid.vala b/libdino/tests/jid.vala index 08ec5c3a..072d41a5 100644 --- a/libdino/tests/jid.vala +++ b/libdino/tests/jid.vala @@ -12,7 +12,7 @@ class JidTest : Gee.TestCase { } private void test_parse() { - Jid jid = new Jid("user@example.com/res"); + Jid jid = Jid.from_string("user@example.com/res"); fail_if(jid.localpart != "user"); fail_if(jid.domainpart != "example.com"); fail_if(jid.resourcepart != "res"); diff --git a/main/src/ui/add_conversation/add_contact_dialog.vala b/main/src/ui/add_conversation/add_contact_dialog.vala index 7b4f40a0..22bcb771 100644 --- a/main/src/ui/add_conversation/add_contact_dialog.vala +++ b/main/src/ui/add_conversation/add_contact_dialog.vala @@ -40,7 +40,7 @@ protected class AddContactDialog : Gtk.Dialog { private void on_ok_button_clicked() { string? alias = alias_entry.text == "" ? null : alias_entry.text; try { - Jid jid = new Jid(jid_entry.text); + Jid jid = Jid.from_string(jid_entry.text); stream_interactor.get_module(RosterManager.IDENTITY).add_jid(account, jid, alias); stream_interactor.get_module(PresenceManager.IDENTITY).request_subscription(account, jid); close(); @@ -51,7 +51,7 @@ protected class AddContactDialog : Gtk.Dialog { private void on_jid_entry_changed() { try { - Jid parsed_jid = new Jid(jid_entry.text); + Jid parsed_jid = Jid.from_string(jid_entry.text); ok_button. sensitive = parsed_jid != null && parsed_jid.resourcepart == null && stream_interactor.get_module(RosterManager.IDENTITY).get_roster_item(account, parsed_jid) == null; } catch (InvalidJidError e) { diff --git a/main/src/ui/add_conversation/add_groupchat_dialog.vala b/main/src/ui/add_conversation/add_groupchat_dialog.vala index a94b8c86..44ac0cfa 100644 --- a/main/src/ui/add_conversation/add_groupchat_dialog.vala +++ b/main/src/ui/add_conversation/add_groupchat_dialog.vala @@ -39,7 +39,7 @@ protected class AddGroupchatDialog : Gtk.Dialog { check_ok(); if (!alias_entry_changed) { try { - Jid parsed_jid = new Jid(jid_entry.text); + Jid parsed_jid = Jid.from_string(jid_entry.text); alias_entry.text = parsed_jid != null && parsed_jid.localpart != null ? parsed_jid.localpart : jid_entry.text; } catch (InvalidJidError e) { alias_entry.text = jid_entry.text; @@ -49,7 +49,7 @@ protected class AddGroupchatDialog : Gtk.Dialog { private void check_ok() { try { - Jid parsed_jid = new Jid(jid_entry.text); + Jid parsed_jid = Jid.from_string(jid_entry.text); ok_button.sensitive = parsed_jid != null && parsed_jid.localpart != null && parsed_jid.resourcepart == null; } catch (InvalidJidError e) { ok_button.sensitive = false; @@ -59,7 +59,7 @@ protected class AddGroupchatDialog : Gtk.Dialog { private void on_ok_button_clicked() { try { Conference conference = new Conference(); - conference.jid = new Jid(jid_entry.text); + conference.jid = Jid.from_string(jid_entry.text); conference.nick = nick_entry.text != "" ? nick_entry.text : null; conference.name = alias_entry.text; stream_interactor.get_module(MucManager.IDENTITY).add_bookmark(account_combobox.selected, conference); diff --git a/main/src/ui/add_conversation/conference_details_fragment.vala b/main/src/ui/add_conversation/conference_details_fragment.vala index 11374032..71fca841 100644 --- a/main/src/ui/add_conversation/conference_details_fragment.vala +++ b/main/src/ui/add_conversation/conference_details_fragment.vala @@ -145,7 +145,7 @@ protected class ConferenceDetailsFragment : Box { string label_text = ""; try { - Jid parsed_jid = new Jid(jid); + Jid parsed_jid = Jid.from_string(jid); Muc.JoinResult? join_result = yield stream_interactor.get_module(MucManager.IDENTITY).join(account, parsed_jid, nick, password); ok_button.label = _("Join"); @@ -180,7 +180,7 @@ protected class ConferenceDetailsFragment : Box { label_text = _("Too many occupants in room"); break; } } else if (join_result.stanza_error != null) { - label_text = _("Could not connect to %s").printf((new Jid(jid)).domainpart); + label_text = _("Could not connect to %s").printf((Jid.from_string(jid)).domainpart); } } catch (InvalidJidError e) { label_text = _("Invalid address"); @@ -191,7 +191,7 @@ protected class ConferenceDetailsFragment : Box { private void check_if_done() { try { - Jid parsed_jid = new Jid(jid); + Jid parsed_jid = Jid.from_string(jid); done = parsed_jid.localpart != null && parsed_jid.resourcepart == null && nick != null; } catch (InvalidJidError e) { done = false; diff --git a/main/src/ui/add_conversation/select_jid_fragment.vala b/main/src/ui/add_conversation/select_jid_fragment.vala index cea1ff86..c9898e8c 100644 --- a/main/src/ui/add_conversation/select_jid_fragment.vala +++ b/main/src/ui/add_conversation/select_jid_fragment.vala @@ -61,7 +61,7 @@ public class SelectJidFragment : Gtk.Box { list.invalidate_filter(); try { - Jid parsed_jid = new Jid(str); + Jid parsed_jid = Jid.from_string(str); if (parsed_jid != null && parsed_jid.localpart != null) { foreach (Account account in accounts) { var list_row = new Gtk.ListBoxRow(); diff --git a/main/src/ui/application.vala b/main/src/ui/application.vala index 605a7a75..1b4dc049 100644 --- a/main/src/ui/application.vala +++ b/main/src/ui/application.vala @@ -89,7 +89,7 @@ public class Dino.Ui.Application : Adw.Application, Dino.Application { Gee.List accounts = stream_interactor.get_accounts(); Jid parsed_jid = null; try { - parsed_jid = new Jid(jid); + parsed_jid = Jid.from_string(jid); } catch (InvalidJidError ignored) { // Ignored } diff --git a/main/src/ui/widgets/avatar_picture.vala b/main/src/ui/widgets/avatar_picture.vala index bfad4d22..155c9440 100644 --- a/main/src/ui/widgets/avatar_picture.vala +++ b/main/src/ui/widgets/avatar_picture.vala @@ -34,7 +34,7 @@ public class Dino.Ui.ViewModel.ConversationParticipantAvatarPictureTileModel : A secondary_avatar_jid = primary_avatar_jid; primary_avatar_jid = real_jid.bare_jid; color_id = primary_avatar_jid.to_string(); - } else { + } else if (jid.resourcepart != null) { color_id = jid.resourcepart.to_string(); } } else if (conversation.type_ == Conversation.Type.CHAT) { diff --git a/main/src/windows/preferences_window/add_account_dialog.vala b/main/src/windows/preferences_window/add_account_dialog.vala index 2568a01e..a0d42533 100644 --- a/main/src/windows/preferences_window/add_account_dialog.vala +++ b/main/src/windows/preferences_window/add_account_dialog.vala @@ -91,7 +91,7 @@ public class AddAccountDialog : Adw.Dialog { // Select Server server_entry.changed.connect(() => { try { - Jid jid = new Jid(server_entry.text); + Jid jid = Jid.from_string(server_entry.text); select_server_continue.sensitive = jid != null && jid.localpart == null && jid.resourcepart == null; } catch (InvalidJidError e) { select_server_continue.sensitive = false; @@ -182,7 +182,7 @@ public class AddAccountDialog : Adw.Dialog { private void on_jid_entry_changed() { try { - login_jid = new Jid(jid_entry.text); + login_jid = Jid.from_string(jid_entry.text); if (login_jid.localpart != null && login_jid.resourcepart == null) { sign_in_continue_button.sensitive = true; } else { @@ -195,7 +195,7 @@ public class AddAccountDialog : Adw.Dialog { private async void on_sign_in_continue_button_clicked() { try { - login_jid = new Jid(jid_entry.text); + login_jid = Jid.from_string(jid_entry.text); sign_in_tls_label.label = ""; sign_in_error_label.visible = false; sign_in_continue_button.sensitive = false; @@ -258,7 +258,7 @@ public class AddAccountDialog : Adw.Dialog { private void on_select_server_continue() { try { - server_jid = new Jid(server_entry.text); + server_jid = Jid.from_string(server_entry.text); request_show_register_form.begin(server_jid); } catch (InvalidJidError e) { warning("Invalid address from interface allowed server: %s", e.message); @@ -268,7 +268,7 @@ public class AddAccountDialog : Adw.Dialog { private void on_server_list_row_activated(ListBox box, ListBoxRow row) { try { - server_jid = new Jid(list_box_jids[row]); + server_jid = Jid.from_string(list_box_jids[row]); request_show_register_form.begin(server_jid); } catch (InvalidJidError e) { warning("Invalid address from selected server: %s", e.message); @@ -351,7 +351,7 @@ public class AddAccountDialog : Adw.Dialog { } } try { - Account account = new Account(new Jid.components(username, server_jid.domainpart, null), password); + Account account = new Account(Jid.from_components(username, server_jid.domainpart, null), password); add_activate_account(account); show_success(account); } catch (InvalidJidError e) { diff --git a/plugins/omemo/src/dtls_srtp_verification_draft.vala b/plugins/omemo/src/dtls_srtp_verification_draft.vala index c0b76524..09617a2a 100644 --- a/plugins/omemo/src/dtls_srtp_verification_draft.vala +++ b/plugins/omemo/src/dtls_srtp_verification_draft.vala @@ -99,7 +99,7 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft { if (muji_node != null) { string muji_room = muji_node.get_attribute("room"); try { - Jid muji_jid = new Jid(muji_room); + Jid muji_jid = Jid.from_string(muji_room); if (device_id_by_muji_member.has_key(@"$(muji_jid.bare_jid)/$(iq.to)")) { device_id = device_id_by_muji_member[@"$(muji_jid.bare_jid)/$(iq.to)"]; } @@ -203,7 +203,7 @@ namespace Dino.Plugins.Omemo.DtlsSrtpVerificationDraft { try { string jid_attribute = item_node.get_attribute("jid"); if (jid_attribute == null) return; - real_jid = new Jid(jid_attribute); + real_jid = Jid.from_string(jid_attribute); } catch (InvalidJidError e) { // Ignore return; diff --git a/plugins/omemo/src/logic/decrypt.vala b/plugins/omemo/src/logic/decrypt.vala index 9e5b739a..2861b42f 100644 --- a/plugins/omemo/src/logic/decrypt.vala +++ b/plugins/omemo/src/logic/decrypt.vala @@ -95,7 +95,7 @@ namespace Dino.Plugins.Omemo { string identity_key = Base64.encode(msg.identity_key.serialize()); foreach (Row row in db.identity_meta.get_with_device_id(identity_id, data.sid).with(db.identity_meta.identity_key_public_base64, "=", identity_key)) { try { - possible_jids.add(new Jid(row[db.identity_meta.address_name])); + possible_jids.add(Jid.from_string(row[db.identity_meta.address_name])); } catch (InvalidJidError e) { warning("Ignoring invalid jid from database: %s", e.message); } @@ -104,7 +104,7 @@ namespace Dino.Plugins.Omemo { // If we don't know the device name (MUC history w/o MAM), test decryption with all keys with fitting device id foreach (Row row in db.identity_meta.get_with_device_id(identity_id, data.sid)) { try { - possible_jids.add(new Jid(row[db.identity_meta.address_name])); + possible_jids.add(Jid.from_string(row[db.identity_meta.address_name])); } catch (InvalidJidError e) { warning("Ignoring invalid jid from database: %s", e.message); } diff --git a/plugins/omemo/src/logic/manager.vala b/plugins/omemo/src/logic/manager.vala index cad6729e..71f17aae 100644 --- a/plugins/omemo/src/logic/manager.vala +++ b/plugins/omemo/src/logic/manager.vala @@ -218,7 +218,7 @@ public class Manager : StreamInteractionModule, Object { int inc = 0; foreach (Row row in db.identity_meta.get_unknown_devices(identity_id, jid.bare_jid.to_string())) { try { - module.fetch_bundle(stream, new Jid(row[db.identity_meta.address_name]), row[db.identity_meta.device_id], false); + module.fetch_bundle(stream, Jid.from_string(row[db.identity_meta.address_name]), row[db.identity_meta.device_id], false); inc++; } catch (InvalidJidError e) { warning("Ignoring device with invalid Jid: %s", e.message); diff --git a/plugins/omemo/src/ui/bad_messages_populator.vala b/plugins/omemo/src/ui/bad_messages_populator.vala index d87108ea..9dacba95 100644 --- a/plugins/omemo/src/ui/bad_messages_populator.vala +++ b/plugins/omemo/src/ui/bad_messages_populator.vala @@ -74,7 +74,7 @@ public class BadMessagesPopulator : Plugins.ConversationItemPopulator, Plugins.C } foreach (Row row in qry) { - Jid jid = new Jid(row[db.identity_meta.address_name]); + 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); diff --git a/plugins/omemo/src/ui/encryption_preferences_entry.vala b/plugins/omemo/src/ui/encryption_preferences_entry.vala index e06455e7..f0aedb6a 100644 --- a/plugins/omemo/src/ui/encryption_preferences_entry.vala +++ b/plugins/omemo/src/ui/encryption_preferences_entry.vala @@ -138,7 +138,7 @@ public class OmemoPreferencesWidget : Adw.PreferencesGroup { }); foreach (Row device in plugin.db.identity_meta.get_unknown_devices(identity_id, jid.to_string())) { try { - module.fetch_bundle(stream, new Jid(device[plugin.db.identity_meta.address_name]), device[plugin.db.identity_meta.device_id], false); + 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); } diff --git a/xmpp-vala/meson.build b/xmpp-vala/meson.build index f0ba7260..85bdaadc 100644 --- a/xmpp-vala/meson.build +++ b/xmpp-vala/meson.build @@ -138,6 +138,7 @@ sources = files( 'src/module/xep/0482_call_invites.vala', 'src/file_content_type.vala', 'src/util.vala', + 'src/weak_map.vala', ) c_args = [ '-DG_LOG_DOMAIN="xmpp-vala"', diff --git a/xmpp-vala/src/module/bind.vala b/xmpp-vala/src/module/bind.vala index 4df8881a..1b90df92 100644 --- a/xmpp-vala/src/module/bind.vala +++ b/xmpp-vala/src/module/bind.vala @@ -19,7 +19,7 @@ namespace Xmpp.Bind { if (iq.type_ == Iq.Stanza.TYPE_RESULT) { try { - flag.my_jid = new Jid(iq.stanza.get_subnode("jid", NS_URI, true).get_string_content()); + flag.my_jid = Jid.from_string(iq.stanza.get_subnode("jid", NS_URI, true).get_string_content()); flag.finished = true; bound_to_resource(stream, flag.my_jid); } catch (InvalidJidError e) { diff --git a/xmpp-vala/src/module/jid.vala b/xmpp-vala/src/module/jid.vala index d5dea870..39f93825 100644 --- a/xmpp-vala/src/module/jid.vala +++ b/xmpp-vala/src/module/jid.vala @@ -1,21 +1,136 @@ namespace Xmpp { -public class Jid { - public string? localpart; - public string domainpart; - public string? resourcepart; +public class Jid : Object { + public string? localpart { + get { + if (level == Level.BARE) return part; + if (level == Level.FULL && parent.level == Level.BARE) return parent.part; + return null; + } + } + public string domainpart { + get { + if (level == Level.DOMAIN) return part; + return parent.domainpart; + } + } + public string? resourcepart { + get { + if (level == Level.FULL) return part; + return null; + } + } public Jid bare_jid { - owned get { return is_bare() ? this : new Jid.intern(null, localpart, domainpart, null); } + get { + if (level == Level.FULL) return parent; + return this; + } } public Jid domain_jid { - owned get { return is_domain() ? this : new Jid.intern(domainpart, null, domainpart, null); } + get { + if (level == Level.DOMAIN) return this; + return parent.domain_jid; + } + } + + private static WeakMap INTERNED = new WeakMap(); + + enum Level { + DOMAIN, + BARE, + FULL + } + private Level level; + private Jid? parent; + private string part; + private string? jid; + + private Jid() {} + + private Jid.domain(string domainpart) throws InvalidJidError { + this.level = Level.DOMAIN; + this.part = domainpart; + this.jid = this.part; + } + + private Jid.bare(string localpart, Jid parent, string jid) throws InvalidJidError { + this.level = Level.BARE; + this.parent = parent; + this.part = localpart; + this.jid = jid; + } + + private Jid.full(Jid parent, string resourcepart, string jid) throws InvalidJidError { + this.level = Level.FULL; + this.parent = parent; + this.part = resourcepart; + this.jid = jid; } - private string jid; + private static Jid get_interned(string jid) { + if (INTERNED == null) new Jid(); + return INTERNED.get(jid); + } + + private static Jid from_domain(string domainpart) throws InvalidJidError { + if (domainpart.length == 0) throw new InvalidJidError.EMPTY_DOMAIN("Domain is empty"); + + Jid result = get_interned(domainpart); + if (result != null) return result; + + int domain_length = domainpart.length; + if (domainpart[domain_length - 1] == '.') domain_length--; + string prepared_domainpart = prepare(idna_decode(domainpart, domain_length), ICU.PrepType.RFC3491_NAMEPREP); + + result = get_interned(prepared_domainpart); + if (result != null) { + INTERNED.set(domainpart, result); + return result; + } + + idna_verify(prepared_domainpart); + result = new Jid.domain(prepared_domainpart); + INTERNED.set(prepared_domainpart, result); + INTERNED.set(domainpart, result); + return result; + } + + private static Jid from_bare(string localpart, Jid parent) throws InvalidJidError { + if (localpart.length == 0) throw new InvalidJidError.EMPTY_LOCAL("Localpart is empty but non-null"); + if (parent.level != Level.DOMAIN) throw new InvalidJidError.UNKNOWN("Parent of bare jid was not a domain"); + + string prepared_localpart = prepare(localpart, ICU.PrepType.RFC3920_NODEPREP); + string prepared_jid = "%s@%s".printf(prepared_localpart, parent.part); + + Jid result = get_interned(prepared_jid); + if (result != null) return result; + + result = new Jid.bare(prepared_localpart, parent, prepared_jid); + INTERNED.set(prepared_jid, result); + return result; + } + + private static Jid from_full(Jid parent, string resourcepart) throws InvalidJidError { + if (resourcepart.length == 0) throw new InvalidJidError.EMPTY_RESOURCE("Resource is empty but non-null"); + if (parent.level > Level.BARE) throw new InvalidJidError.UNKNOWN("Parent of full jid was not a bare jid or domain"); + + string prepared_resourcepart = prepare(resourcepart, ICU.PrepType.RFC3920_RESOURCEPREP); + string prepared_jid = "%s/%s".printf(parent.jid, prepared_resourcepart); + + Jid result = get_interned(prepared_jid); + if (result != null) return result; + + result = new Jid.full(parent, prepared_resourcepart, prepared_jid); + INTERNED.set(prepared_jid, result); + return result; + } + + public static Jid from_string(string jid) throws InvalidJidError { + Jid result = get_interned(jid); + if (result != null) return result; - public Jid(string jid) throws InvalidJidError { int slash_index = jid.index_of("/"); int at_index = jid.index_of("@"); if (at_index > slash_index && slash_index != -1) at_index = -1; @@ -35,37 +150,27 @@ public class Jid { domainpart = jid.slice(at_index + 1, slash_index); } } - - this.components(localpart, domainpart, resourcepart); + result = from_components(localpart, domainpart, resourcepart); + INTERNED.set(jid, result); + return result; } - private Jid.intern(owned string? jid, owned string? localpart, owned string domainpart, owned string? resourcepart) { - this.jid = (owned) jid; - this.localpart = (owned) localpart; - this.domainpart = (owned) domainpart; - this.resourcepart = (owned) resourcepart; - } - - public Jid.components(string? localpart, string domainpart, string? resourcepart) throws InvalidJidError { - // TODO verify and normalize all parts - if (domainpart.length == 0) throw new InvalidJidError.EMPTY_DOMAIN("Domain is empty"); - if (localpart != null && localpart.length == 0) throw new InvalidJidError.EMPTY_LOCAL("Localpart is empty but non-null"); - if (resourcepart != null && resourcepart.length == 0) throw new InvalidJidError.EMPTY_RESOURCE("Resource is empty but non-null"); - string domain = domainpart[domainpart.length - 1] == '.' ? domainpart.substring(0, domainpart.length - 1) : domainpart; - if (domain.contains("xn--")) { - domain = idna_decode(domain); + public static Jid from_components(string? localpart, string domainpart, string? resourcepart) throws InvalidJidError { + Jid jid = from_domain(domainpart); + if (localpart != null) { + jid = from_bare(localpart, jid); } - this.localpart = prepare(localpart, ICU.PrepType.RFC3920_NODEPREP); - this.domainpart = prepare(domain, ICU.PrepType.RFC3491_NAMEPREP); - this.resourcepart = prepare(resourcepart, ICU.PrepType.RFC3920_RESOURCEPREP); - idna_verify(this.domainpart); + if (resourcepart != null) { + jid = from_full(jid, resourcepart); + } + return jid; } - private static string idna_decode(string src) throws InvalidJidError { + private static string idna_decode(string src, int src_length = -1) throws InvalidJidError { ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR; ICU.IDNAInfo info; char[] dest = new char[src.length * 2]; - ICU.IDNA.openUTS46(ICU.IDNAOptions.DEFAULT, ref status).nameToUnicodeUTF8(src, -1, dest, out info, ref status); + ICU.IDNA.openUTS46(ICU.IDNAOptions.DEFAULT, ref status).nameToUnicodeUTF8(src, src_length, dest, out info, ref status); if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) { throw new InvalidJidError.INVALID_CHAR("Found invalid character"); } else if (status.is_failure() || info.errors > 0) { @@ -112,33 +217,23 @@ public class Jid { } public Jid with_resource(string? resourcepart) throws InvalidJidError { - return new Jid.components(localpart, domainpart, resourcepart); + if (resourcepart == null) return bare_jid; + return Jid.from_full(bare_jid, resourcepart); } public bool is_domain() { - return localpart == null && resourcepart == null; + return level == Level.DOMAIN; } public bool is_bare() { - return resourcepart == null; + return level <= Level.BARE; } public bool is_full() { - return localpart != null && resourcepart != null; + return level == Level.FULL; } - public string to_string() { - if (jid == null) { - if (localpart != null && resourcepart != null) { - jid = @"$localpart@$domainpart/$resourcepart"; - } else if (localpart != null) { - jid = @"$localpart@$domainpart"; - } else if (resourcepart != null) { - jid = @"$domainpart/$resourcepart"; - } else { - jid = domainpart; - } - } + public unowned string to_string() { return jid; } @@ -151,11 +246,16 @@ public class Jid { } public static new bool equals_bare_func(Jid jid1, Jid jid2) { - return jid1.localpart == jid2.localpart && jid1.domainpart == jid2.domainpart; + return equals_func(jid1.bare_jid, jid2.bare_jid); } - public static bool equals_func(Jid jid1, Jid jid2) { - return equals_bare_func(jid1, jid2) && jid1.resourcepart == jid2.resourcepart; + public static bool equals_func(Jid? jid1, Jid? jid2) { + if (jid1 == null && jid2 == null) return true; + if (jid1 == null || jid2 == null) return false; + if (jid1 == jid2) return true; + bool res = jid1.level == jid2.level && jid1.part == jid2.part && equals_func(jid1.parent, jid2.parent); + if (res) warning("JIDs considered equal, but are not same instance"); + return res; } public static new uint hash_bare_func(Jid jid) { diff --git a/xmpp-vala/src/module/roster/item.vala b/xmpp-vala/src/module/roster/item.vala index 4c9f30ef..e388bd06 100644 --- a/xmpp-vala/src/module/roster/item.vala +++ b/xmpp-vala/src/module/roster/item.vala @@ -18,7 +18,7 @@ public class Item { public Jid? jid { get { try { - return jid_ ?? (jid_ = new Jid(stanza_node.get_attribute("jid"))); + return jid_ ?? (jid_ = Jid.from_string(stanza_node.get_attribute("jid"))); } catch (InvalidJidError e) { warning("Ignoring invalid Jid in roster entry: %s", e.message); return null; diff --git a/xmpp-vala/src/module/stanza.vala b/xmpp-vala/src/module/stanza.vala index 3b029197..33816a6b 100644 --- a/xmpp-vala/src/module/stanza.vala +++ b/xmpp-vala/src/module/stanza.vala @@ -13,44 +13,59 @@ public class Stanza : Object { private Jid? from_; private Jid? to_; - public virtual Jid? from { - owned get { - string? from_attribute = stanza.get_attribute(ATTRIBUTE_FROM); - // "when a client receives a stanza that does not include a 'from' attribute, it MUST assume that the stanza - // is from the user's account on the server." (RFC6120 8.1.2.1) - if (from_attribute != null) { - try { - return from_ = new Jid(from_attribute); - } catch (InvalidJidError e) { - warning("Ignoring invalid from Jid: %s", e.message); + public Jid? from { + get { + if (from_ == null) { + string? from_attribute = stanza.get_attribute(ATTRIBUTE_FROM); + if (from_attribute != null) { + try { + from_ = Jid.from_string(from_attribute); + } catch (InvalidJidError e) { + warning("Ignoring invalid from Jid: %s", e.message); + } + } + // "when a client receives a stanza that does not include a 'from' attribute, it MUST assume that the stanza + // is from the user's account on the server." (RFC6120 8.1.2.1) + if (from_ == null && my_jid != null) { + from_ = my_jid.bare_jid; } } - if (my_jid != null) { - return my_jid.bare_jid; - } - return null; + return from_; + } + set { + from_ = value; + stanza.set_attribute(ATTRIBUTE_FROM, value.to_string()); } - set { stanza.set_attribute(ATTRIBUTE_FROM, value.to_string()); } } - public virtual string? id { + public string? id { get { return stanza.get_attribute(ATTRIBUTE_ID); } set { stanza.set_attribute(ATTRIBUTE_ID, value); } } - public virtual Jid? to { - owned get { - string? to_attribute = stanza.get_attribute(ATTRIBUTE_TO); - // "if the stanza does not include a 'to' address then the client MUST treat it as if the 'to' address were - // included with a value of the client's full JID." (RFC6120 8.1.1.1) - try { - return to_attribute == null ? my_jid : to_ = new Jid(to_attribute); - } catch (InvalidJidError e) { - warning("Ignoring invalid to Jid: %s", e.message); + public Jid? to { + get { + if (to_ == null) { + string? to_attribute = stanza.get_attribute(ATTRIBUTE_TO); + if (to_attribute != null) { + try { + to_ = Jid.from_string(to_attribute); + } catch (InvalidJidError e) { + warning("Ignoring invalid to Jid: %s", e.message); + } + } + // "if the stanza does not include a 'to' address then the client MUST treat it as if the 'to' address were + // included with a value of the client's full JID." (RFC6120 8.1.1.1) + if (to_ == null) { + to_ = my_jid; + } } - return my_jid; + return to_; + } + set { + to_ = value; + stanza.set_attribute(ATTRIBUTE_TO, value.to_string()); } - set { stanza.set_attribute(ATTRIBUTE_TO, value.to_string()); } } public virtual string? type_ { @@ -58,7 +73,16 @@ public class Stanza : Object { set { stanza.set_attribute(ATTRIBUTE_TYPE, value); } } - public StanzaNode stanza; + private StanzaNode stanza_; + public StanzaNode stanza { + get { return stanza_; } + set { + stanza_ = value; + // Invalidate caches + from_ = null; + to_ = null; + } + } public Stanza.incoming(StanzaNode stanza, Jid? my_jid) { this.stanza = stanza; diff --git a/xmpp-vala/src/module/xep/0030_service_discovery/items_result.vala b/xmpp-vala/src/module/xep/0030_service_discovery/items_result.vala index 105da893..9fdae90b 100644 --- a/xmpp-vala/src/module/xep/0030_service_discovery/items_result.vala +++ b/xmpp-vala/src/module/xep/0030_service_discovery/items_result.vala @@ -10,7 +10,7 @@ public class ItemsResult { ArrayList ret = new ArrayList(); foreach (StanzaNode feature_node in iq.stanza.get_subnode("query", NS_URI_ITEMS).get_subnodes("item", NS_URI_ITEMS)) { try { - ret.add(new Item(new Jid(feature_node.get_attribute("jid", NS_URI_ITEMS)), + ret.add(new Item(Jid.from_string(feature_node.get_attribute("jid", NS_URI_ITEMS)), feature_node.get_attribute("name", NS_URI_ITEMS), feature_node.get_attribute("node", NS_URI_ITEMS))); } catch (InvalidJidError e) { diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala index bdaabb3e..ad929b00 100644 --- a/xmpp-vala/src/module/xep/0045_muc/module.vala +++ b/xmpp-vala/src/module/xep/0045_muc/module.vala @@ -377,7 +377,7 @@ public class Module : XmppStreamModule { string? jid_ = x_node.get_deep_attribute("item", "jid"); if (jid_ != null) { try { - Jid real_jid = new Jid(jid_); + Jid real_jid = Jid.from_string(jid_); flag.set_real_jid(presence.from, real_jid); if (affiliation != null) { stream.get_flag(Flag.IDENTITY).set_offline_member(presence.from, real_jid, affiliation); @@ -496,7 +496,7 @@ public class Module : XmppStreamModule { string? affiliation_ = item.get_attribute("affiliation"); if (jid__ != null && affiliation_ != null) { try { - Jid jid_ = new Jid(jid__); + Jid jid_ = Jid.from_string(jid__); stream.get_flag(Flag.IDENTITY).set_offline_member(iq_result.from, jid_, parse_affiliation(affiliation_)); ret_jids.add(jid_); } catch (InvalidJidError e) { @@ -574,7 +574,7 @@ public class ReceivedPipelineListener : StanzaListener { Jid? from_jid = null; try { string from = invite_node.get_attribute("from"); - if (from != null) from_jid = new Jid(from); + if (from != null) from_jid = Jid.from_string(from); } catch (InvalidJidError e) { warning("Received invite from invalid jid: %s", e.message); } @@ -594,7 +594,7 @@ public class ReceivedPipelineListener : StanzaListener { 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 = new Jid(jid_str); + 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); @@ -622,7 +622,7 @@ public class ReceivedPipelineListener : StanzaListener { if (var_ == "muc#jid"){ StanzaNode? value_node = field_node.get_subnode("value", DataForms.NS_URI); try { - if (value_node != null) from_jid = new Jid(value_node.get_string_content()); + if (value_node != null) from_jid = Jid.from_string(value_node.get_string_content()); } catch (InvalidJidError e) { return false; } diff --git a/xmpp-vala/src/module/xep/0048_conference.vala b/xmpp-vala/src/module/xep/0048_conference.vala index b61de1c0..ee0188de 100644 --- a/xmpp-vala/src/module/xep/0048_conference.vala +++ b/xmpp-vala/src/module/xep/0048_conference.vala @@ -23,7 +23,7 @@ public class Bookmarks1Conference : Conference { public override Jid? jid { get { try { - return jid_ ?? (jid_ = new Jid(stanza_node.get_attribute(ATTRIBUTE_JID))); + return jid_ ?? (jid_ = Jid.from_string(stanza_node.get_attribute(ATTRIBUTE_JID))); } catch (InvalidJidError e) { return null; } diff --git a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala index c184877c..97674f3f 100644 --- a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala @@ -69,7 +69,7 @@ public class Module : XmppStreamModule, Iq.Handler { string? jid_str = stream_host.get_attribute("jid"); Jid? jid = null; try { - jid = jid_str != null ? new Jid(jid_str) : null; + jid = jid_str != null ? Jid.from_string(jid_str) : null; } catch (InvalidJidError ignored) { } int port = stream_host.get_attribute_int("port"); if (host == null || jid == null || port <= 0 || port > 65535) { diff --git a/xmpp-vala/src/module/xep/0166_jingle/jingle_module.vala b/xmpp-vala/src/module/xep/0166_jingle/jingle_module.vala index cdcb9130..5e3b69c2 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/jingle_module.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/jingle_module.vala @@ -165,7 +165,7 @@ namespace Xmpp.Xep.Jingle { string? muji_room_str = iq.stanza.get_deep_attribute(NS_URI + ":jingle", Xep.Muji.NS_URI + ":muji", "room"); if (muji_room_str != null) { try { - session.muji_room = new Jid(muji_room_str); + session.muji_room = Jid.from_string(muji_room_str); } catch (InvalidJidError e) { // Ignore } diff --git a/xmpp-vala/src/module/xep/0166_jingle/session.vala b/xmpp-vala/src/module/xep/0166_jingle/session.vala index 53a9c58d..22e8e978 100644 --- a/xmpp-vala/src/module/xep/0166_jingle/session.vala +++ b/xmpp-vala/src/module/xep/0166_jingle/session.vala @@ -257,7 +257,7 @@ public class Xmpp.Xep.Jingle.Session : Object { Jid responder = iq.from; if (responder_str != null) { try { - responder = new Jid(responder_str); + responder = Jid.from_string(responder_str); } catch (InvalidJidError e) { warning("Received invalid session accept: %s", e.message); } diff --git a/xmpp-vala/src/module/xep/0249_direct_muc_invitations.vala b/xmpp-vala/src/module/xep/0249_direct_muc_invitations.vala index 2ccab33d..8c2e04fd 100644 --- a/xmpp-vala/src/module/xep/0249_direct_muc_invitations.vala +++ b/xmpp-vala/src/module/xep/0249_direct_muc_invitations.vala @@ -23,7 +23,7 @@ namespace Xmpp.Xep.DirectMucInvitations { if (room_str == null) return; Jid? room_jid = null; try { - room_jid = new Jid(room_str); + room_jid = Jid.from_string(room_str); } catch (Error e) { return; } 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 0fe9ce5f..852cc7eb 100644 --- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala +++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala @@ -162,7 +162,7 @@ public class Candidate : Socks5Bytestreams.Proxy { string? jid_str = candidate.get_attribute("jid"); Jid? jid = null; try { - jid = new Jid(jid_str); + jid = Jid.from_string(jid_str); } catch (InvalidJidError ignored) { } int port = candidate.get_attribute("port") != null ? candidate.get_attribute_int("port") : 1080; diff --git a/xmpp-vala/src/module/xep/0298_coin.vala b/xmpp-vala/src/module/xep/0298_coin.vala index 1a7b6d60..a8c12e07 100644 --- a/xmpp-vala/src/module/xep/0298_coin.vala +++ b/xmpp-vala/src/module/xep/0298_coin.vala @@ -44,7 +44,7 @@ namespace Xmpp.Xep.Coin { // if (!jid_string.has_prefix("xmpp:")) continue; // silk does this wrong Jid? jid = null; try { - jid = new Jid(jid_string.substring(4)); + jid = Jid.from_string(jid_string.substring(4)); } catch (Error e) { continue; } 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 1a7789cb..a9149d4e 100644 --- a/xmpp-vala/src/module/xep/0313_message_archive_management.vala +++ b/xmpp-vala/src/module/xep/0313_message_archive_management.vala @@ -118,7 +118,7 @@ public class ReceivedPipelineListener : StanzaListener { } Jid? inner_from = null; try { - inner_from = new Jid(message_node.get_attribute("from")); + inner_from = Jid.from_string(message_node.get_attribute("from")); } catch (InvalidJidError e) { warning("Received MAM message with invalid from attribute in forwarded message from %s, ignoring", message.from.to_string()); return true; diff --git a/xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala b/xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala index cfeeb567..f7db524d 100644 --- a/xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala +++ b/xmpp-vala/src/module/xep/0388_extensible_sasl_profile.vala @@ -40,7 +40,7 @@ namespace Xmpp.Xep.ExtensibleSaslProfile { var authorisation_identifier = node.get_subnode("authorization-identifier", NS_URI); string jid_string = authorisation_identifier.get_string_content(); - Jid jid = new Jid(jid_string); + Jid jid = Jid.from_string(jid_string); foreach (var inline_provider in inline_activation_providers.values) { inline_provider.on_bound(stream, jid, node); diff --git a/xmpp-vala/src/module/xep/0402_bookmarks2.vala b/xmpp-vala/src/module/xep/0402_bookmarks2.vala index 429f26f5..938af0e9 100644 --- a/xmpp-vala/src/module/xep/0402_bookmarks2.vala +++ b/xmpp-vala/src/module/xep/0402_bookmarks2.vala @@ -88,7 +88,7 @@ public class Module : BookmarksProvider, XmppStreamModule { } try { - Jid jid_parsed = new Jid(id); + Jid jid_parsed = Jid.from_string(id); Flag? flag = stream.get_flag(Flag.IDENTITY); if (flag != null) { flag.conferences.unset(jid_parsed); @@ -102,7 +102,7 @@ public class Module : BookmarksProvider, XmppStreamModule { private Conference? parse_item_node(StanzaNode conference_node, string id) { Conference conference = new Conference(); try { - Jid jid_parsed = new Jid(id); + Jid jid_parsed = Jid.from_string(id); if (jid_parsed.resourcepart != null) return null; conference.jid = jid_parsed; } catch (InvalidJidError e) { diff --git a/xmpp-vala/src/module/xep/0461_replies.vala b/xmpp-vala/src/module/xep/0461_replies.vala index 870df295..af84aa3d 100644 --- a/xmpp-vala/src/module/xep/0461_replies.vala +++ b/xmpp-vala/src/module/xep/0461_replies.vala @@ -27,7 +27,7 @@ namespace Xmpp.Xep.Replies { string? to_str = reply_node.get_attribute("to"); if (to_str == null) return null; try { - Jid to_jid = new Jid(to_str); + Jid to_jid = Jid.from_string(to_str); string? id = reply_node.get_attribute("id"); if (id == null) return null; diff --git a/xmpp-vala/src/weak_map.vala b/xmpp-vala/src/weak_map.vala new file mode 100644 index 00000000..a7f4bc44 --- /dev/null +++ b/xmpp-vala/src/weak_map.vala @@ -0,0 +1,119 @@ +using Gee; + +public class WeakMap : Gee.AbstractMap { + + private HashMap hash_map; + private HashMap notify_map; + + public HashDataFunc? key_hash_func = null; + public EqualDataFunc? key_equal_func = null; + public EqualDataFunc? value_equal_func = null; + + public WeakMap(owned HashDataFunc? key_hash_func = null, owned EqualDataFunc? key_equal_func = null, owned EqualDataFunc? value_equal_func = null) { + if (!typeof(V).is_object()) { + error("WeakMap only takes values that are Objects"); + } + + this.key_hash_func = (owned)key_hash_func; + this.key_equal_func = (owned)key_equal_func; + this.value_equal_func = (owned)value_equal_func; + + if (this.key_equal_func == null || this.key_equal_func == null || this.value_equal_func == null) { + hash_map = new HashMap(); + notify_map = new HashMap(); + } else { + hash_map = new HashMap((v) => { return this.key_hash_func != null ? this.key_hash_func(v) : 0; }, + (a, b) => { return this.key_equal_func != null ? this.key_equal_func(a, b) : a == b; }, + (a, b) => { return this.value_equal_func != null ? this.value_equal_func(a, b) : a == b; }); + notify_map = new HashMap((v) => { return this.key_hash_func != null ? this.key_hash_func(v) : 0; }, + (a, b) => { return this.key_equal_func != null ? this.key_equal_func(a, b) : a == b; }); + } + } + + public override void clear() { + foreach (K key in notify_map.keys) { + Object o = (Object) hash_map[key]; + o.weak_unref(notify_map[key].func); + } + hash_map.clear(); + notify_map.clear(); + } + + public override V @get(K key) { + if (!hash_map.has_key(key)) return null; + + var v = hash_map[key]; + + return (owned) v; + } + + public override bool has(K key, V value) { + return has_key(key) && (this.value_equal_func != null ? this.value_equal_func(hash_map[key], value) : hash_map[key] == value); + } + + public override bool has_key(K key) { + return hash_map.has_key(key); + } + + public override Gee.MapIterator map_iterator() { + assert_not_reached(); + } + + public override void @set(K key, V value) { + assert(value != null); + + unset(key); + + Object v_obj = (Object) value; + var notify_wrap = new WeakNotifyWrapper((obj) => { + hash_map.unset(key); + notify_map.unset(key); + }); + notify_map[key] = notify_wrap; + v_obj.weak_ref(notify_wrap.func); + + hash_map[key] = value; + } + + public override bool unset(K key, out V value = null) { + if (!hash_map.has_key(key)) { + value = null; + return false; + } + + Object v_obj = (Object) hash_map[key]; + v_obj.weak_unref(notify_map[key].func); + notify_map.unset(key); + return hash_map.unset(key, out value); + } + public override Gee.Set> entries { owned get; } + + public override Gee.Set keys { + owned get { return hash_map.keys; } + } + + public override bool read_only { get { assert_not_reached(); } } + + public override int size { get { return hash_map.size; } } + + public override Gee.Collection values { + owned get { + assert_not_reached(); + } + } + + public override void dispose() { + foreach (K key in notify_map.keys) { + Object o = (Object) hash_map[key]; + o.weak_unref(notify_map[key].func); + } + } +} + +internal class WeakNotifyWrapper { + public WeakNotify func; + + public WeakNotifyWrapper(owned WeakNotify func) { + this.func = (owned) func; + } +} \ No newline at end of file diff --git a/xmpp-vala/tests/jid.vala b/xmpp-vala/tests/jid.vala index 90420056..72b8e906 100644 --- a/xmpp-vala/tests/jid.vala +++ b/xmpp-vala/tests/jid.vala @@ -42,7 +42,7 @@ class JidTest : Gee.TestCase { private void test_jid_valid(string jid) { try { - new Jid(jid); + Jid.from_string(jid); } catch (Error e) { fail_if_reached(@"Throws $(e.message)"); } @@ -50,7 +50,7 @@ class JidTest : Gee.TestCase { private void test_jid_invalid(string jid) { try { - new Jid(jid); + Jid.from_string(jid); fail_if_reached(); } catch (Error e) { // try { @@ -63,8 +63,8 @@ class JidTest : Gee.TestCase { private void test_jids_equal(string jid1, string jid2) { try { - var t1 = new Jid(jid1); - var t2 = new Jid(jid2); + var t1 = Jid.from_string(jid1); + var t2 = Jid.from_string(jid2); fail_if_not_eq_str(t1.to_string(), t2.to_string()); } catch (Error e) { fail_if_reached(@"Throws $(e.message)"); @@ -73,7 +73,7 @@ class JidTest : Gee.TestCase { private void test_jid_to_string(string jid1, string jid2) { try { - var t1 = new Jid(jid1); + var t1 = Jid.from_string(jid1); fail_if_not_eq_str(t1.to_string(), jid2); } catch (Error e) { fail_if_reached(@"Throws $(e.message)"); @@ -82,8 +82,8 @@ class JidTest : Gee.TestCase { private void test_jids_unequal(string jid1, string jid2) { try { - var t1 = new Jid(jid1); - var t2 = new Jid(jid2); + var t1 = Jid.from_string(jid1); + var t2 = Jid.from_string(jid2); fail_if_eq_str(t1.to_string(), t2.to_string()); } catch (Error e) { fail_if_reached(@"Throws $(e.message)"); -- cgit v1.3