aboutsummaryrefslogtreecommitdiff
path: root/xmpp-vala/src/module/jid.vala
diff options
context:
space:
mode:
authorMarvin W <git@larma.de>2026-05-30 16:10:36 +0200
committerMarvin W <git@larma.de>2026-06-07 23:13:07 +0200
commit158cff3098e9370696f9250a199c78e4f2d18ce0 (patch)
treebd05a55e5419ef2307c7a7f4d7665f7449c8e8b1 /xmpp-vala/src/module/jid.vala
parentafcf44928748f7ac049f1ba9f5c51c6dedbf4ec2 (diff)
Add JID interning
Diffstat (limited to 'xmpp-vala/src/module/jid.vala')
-rw-r--r--xmpp-vala/src/module/jid.vala200
1 files changed, 150 insertions, 50 deletions
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<string, Jid> INTERNED = new WeakMap<string, Jid>();
+
+ 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) {