aboutsummaryrefslogtreecommitdiff
path: root/xmpp-vala/src/module/jid.vala
diff options
context:
space:
mode:
authorMarvin W <git@larma.de>2019-12-22 04:10:53 +0100
committerMarvin W <git@larma.de>2019-12-23 16:58:53 +0100
commita0a956ee0878d24bd06be7f5d75dc4ccd4e7901d (patch)
treecbb079649066c2001b6d6881137108e70eed9d3f /xmpp-vala/src/module/jid.vala
parent3218dc0211ac717230fe03fad82681a626d968b5 (diff)
downloaddino-a0a956ee0878d24bd06be7f5d75dc4ccd4e7901d.tar.gz
dino-a0a956ee0878d24bd06be7f5d75dc4ccd4e7901d.zip
Properly check Jids everywhere
Diffstat (limited to 'xmpp-vala/src/module/jid.vala')
-rw-r--r--xmpp-vala/src/module/jid.vala155
1 files changed, 120 insertions, 35 deletions
diff --git a/xmpp-vala/src/module/jid.vala b/xmpp-vala/src/module/jid.vala
index c20e0202..569be54f 100644
--- a/xmpp-vala/src/module/jid.vala
+++ b/xmpp-vala/src/module/jid.vala
@@ -6,60 +6,126 @@ public class Jid {
public string? resourcepart;
public Jid bare_jid {
- owned get { return is_bare() ? this : new Jid.components(localpart, domainpart, null); }
+ owned get { return is_bare() ? this : new Jid.intern(null, localpart, domainpart, null); }
}
public Jid domain_jid {
- owned get { return is_domain() ? this : new Jid.components(null, domainpart, null); }
+ owned get { return is_domain() ? this : new Jid.intern(domainpart, null, domainpart, null); }
}
private string jid;
- public Jid(string jid) {
- Jid? parsed = Jid.parse(jid);
- string? localpart = parsed != null ? (owned) parsed.localpart : null;
- string domainpart = parsed != null ? (owned) parsed.domainpart : jid;
- string? resourcepart = parsed != null ? (owned) parsed.resourcepart : null;
- this.intern(jid, (owned) localpart, (owned) domainpart, (owned) resourcepart);
+ 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;
+ string resourcepart = slash_index < 0 ? null : jid.slice(slash_index + 1, jid.length);
+ string localpart = at_index < 0 ? null : jid.slice(0, at_index);
+ string domainpart;
+ if (at_index < 0) {
+ if (slash_index < 0) {
+ domainpart = jid;
+ } else {
+ domainpart = jid.slice(0, slash_index);
+ }
+ } else {
+ if (slash_index < 0) {
+ domainpart = jid.slice(at_index + 1, jid.length);
+ } else {
+ domainpart = jid.slice(at_index + 1, slash_index);
+ }
+ }
+
+ this.components(localpart, domainpart, resourcepart);
}
- private Jid.intern(owned string jid, owned string? localpart, owned string domainpart, owned string? resourcepart) {
+ 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(owned string? localpart, owned string domainpart, owned string? resourcepart) {
- string jid = domainpart;
- if (localpart != null) {
- jid = @"$localpart@$jid";
+ 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);
}
- if (resourcepart != null) {
- jid = @"$jid/$resourcepart";
+ 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);
+ }
+
+ private static string idna_decode(string src) throws InvalidJidError {
+ try {
+ ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR;
+ long src16_length = 0;
+ string16 src16 = src.to_utf16(-1, null, out src16_length);
+ ICU.Char[] dest16 = new ICU.Char[src16_length];
+ ICU.ParseError error;
+ long dest16_length = ICU.IDNA.IDNToUnicode(src16, (int32) src16_length, dest16, dest16.length, ICU.IDNAOptions.DEFAULT, out error, ref status);
+ if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) {
+ throw new InvalidJidError.INVALID_CHAR("Found invalid character");
+ } else if (status != ICU.ErrorCode.ZERO_ERROR) {
+ throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())");
+ } else if (dest16_length < 0) {
+ throw new InvalidJidError.UNKNOWN("Unknown error");
+ }
+ return ((string16) dest16).to_utf8(dest16_length, null, null);
+ } catch (ConvertError e) {
+ throw new InvalidJidError.INVALID_CHAR(@"Conversion error: $(e.message)");
}
- this.jid = jid;
- this.localpart = (owned) localpart;
- this.domainpart = (owned) domainpart;
- this.resourcepart = (owned) resourcepart;
}
- public static Jid? parse(string jid) {
- int slash_index = jid.index_of("/");
- string resourcepart = slash_index == -1 ? null : jid.slice(slash_index + 1, jid.length);
- string bare_jid = slash_index == -1 ? jid : jid.slice(0, slash_index);
- int at_index = bare_jid.index_of("@");
- string localpart = at_index == -1 ? null : bare_jid.slice(0, at_index);
- string domainpart = at_index == -1 ? bare_jid : bare_jid.slice(at_index + 1, bare_jid.length);
-
- if (domainpart == "") return null;
- if (slash_index != -1 && resourcepart == "") return null;
- if (at_index != -1 && localpart == "") return null;
+ private static void idna_verify(string src) throws InvalidJidError {
+ try {
+ ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR;
+ long src16_length = 0;
+ string16 src16 = src.to_utf16(-1, null, out src16_length);
+ ICU.Char[] dest16 = new ICU.Char[256];
+ ICU.ParseError error;
+ long dest16_length = ICU.IDNA.IDNToASCII(src16, (int32) src16_length, dest16, dest16.length, ICU.IDNAOptions.DEFAULT, out error, ref status);
+ if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) {
+ throw new InvalidJidError.INVALID_CHAR("Found invalid character");
+ } else if (status != ICU.ErrorCode.ZERO_ERROR) {
+ throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())");
+ } else if (dest16_length < 0) {
+ throw new InvalidJidError.UNKNOWN("Unknown error");
+ }
+ } catch (ConvertError e) {
+ throw new InvalidJidError.INVALID_CHAR(@"Conversion error: $(e.message)");
+ }
+ }
- return new Jid.intern(jid, (owned) localpart, (owned) domainpart, (owned) resourcepart);
+ private static string? prepare(string? src, ICU.PrepType type) throws InvalidJidError {
+ if (src == null) return src;
+ try {
+ ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR;
+ ICU.PrepProfile profile = ICU.PrepProfile.openByType(type, ref status);
+ long src16_length = 0;
+ string16 src16 = src.to_utf16(-1, null, out src16_length);
+ ICU.Char[] dest16 = new ICU.Char[src16_length * 2];
+ ICU.ParseError error;
+ long dest16_length = profile.prepare((ICU.Char*) src16, (int32) src16_length, dest16, dest16.length, ICU.PrepOptions.ALLOW_UNASSIGNED, out error, ref status);
+ if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) {
+ throw new InvalidJidError.INVALID_CHAR("Found invalid character");
+ } else if (status != ICU.ErrorCode.ZERO_ERROR) {
+ throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())");
+ } else if (dest16_length < 0) {
+ throw new InvalidJidError.UNKNOWN("Unknown error");
+ }
+ return ((string16) dest16).to_utf8(dest16_length, null, null);
+ } catch (ConvertError e) {
+ throw new InvalidJidError.INVALID_CHAR(@"Conversion error: $(e.message)");
+ }
}
- public Jid with_resource(string? resourcepart) {
+ public Jid with_resource(string? resourcepart) throws InvalidJidError {
return new Jid.components(localpart, domainpart, resourcepart);
}
@@ -68,7 +134,7 @@ public class Jid {
}
public bool is_bare() {
- return localpart != null && resourcepart == null;
+ return resourcepart == null;
}
public bool is_full() {
@@ -76,6 +142,17 @@ public class Jid {
}
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;
+ }
+ }
return jid;
}
@@ -88,11 +165,11 @@ public class Jid {
}
public static new bool equals_bare_func(Jid jid1, Jid jid2) {
- return jid1.bare_jid.to_string() == jid2.bare_jid.to_string();
+ return jid1.localpart == jid2.localpart && jid1.domainpart == jid2.domainpart;
}
public static bool equals_func(Jid jid1, Jid jid2) {
- return jid1.to_string() == jid2.to_string();
+ return equals_bare_func(jid1, jid2) && jid1.resourcepart == jid2.resourcepart;
}
public static new uint hash_bare_func(Jid jid) {
@@ -104,4 +181,12 @@ public class Jid {
}
}
+public errordomain InvalidJidError {
+ EMPTY_DOMAIN,
+ EMPTY_RESOURCE,
+ EMPTY_LOCAL,
+ INVALID_CHAR,
+ UNKNOWN
+}
+
}