From a0a956ee0878d24bd06be7f5d75dc4ccd4e7901d Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 22 Dec 2019 04:10:53 +0100 Subject: Properly check Jids everywhere --- xmpp-vala/src/module/jid.vala | 155 ++++++++++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 35 deletions(-) (limited to 'xmpp-vala/src/module/jid.vala') 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 +} + } -- cgit v1.2.3-54-g00ecf