From 53d467938cb1d3fb73bab3707319bc740eda9b10 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 23 Dec 2019 04:01:25 +0100 Subject: [PATCH] Use UTS46 instead of IDNA2003 --- xmpp-vala/src/module/jid.vala | 54 ++++++++++++----------------------- xmpp-vala/tests/jid.vala | 6 ++-- xmpp-vala/vapi/icu-uc.vapi | 16 +++++++++++ 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/xmpp-vala/src/module/jid.vala b/xmpp-vala/src/module/jid.vala index 569be54f..f8d09e7e 100644 --- a/xmpp-vala/src/module/jid.vala +++ b/xmpp-vala/src/module/jid.vala @@ -62,47 +62,31 @@ public class Jid { } 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)"); + 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); + if (status == ICU.ErrorCode.INVALID_CHAR_FOUND) { + throw new InvalidJidError.INVALID_CHAR("Found invalid character"); + } else if (status.is_failure() || info.errors > 0) { + throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())"); } + return (string) dest; } 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)"); + 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).nameToASCII_UTF8(src, -1, 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) { + throw new InvalidJidError.UNKNOWN(@"Unknown error: $(status.errorName())"); } } - private static string? prepare(string? src, ICU.PrepType type) throws InvalidJidError { + private static string? prepare(string? src, ICU.PrepType type, bool strict = false) throws InvalidJidError { if (src == null) return src; try { ICU.ErrorCode status = ICU.ErrorCode.ZERO_ERROR; @@ -111,7 +95,7 @@ public class Jid { 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); + long dest16_length = profile.prepare((ICU.Char*) src16, (int32) src16_length, dest16, dest16.length, strict ? ICU.PrepOptions.DEFAULT : 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) { diff --git a/xmpp-vala/tests/jid.vala b/xmpp-vala/tests/jid.vala index 8928dc97..75eb38ce 100644 --- a/xmpp-vala/tests/jid.vala +++ b/xmpp-vala/tests/jid.vala @@ -9,11 +9,11 @@ class JidTest : Gee.TestCase { add_test("jid_valid_domain_with_resource", () => { test_jid_valid("example.com/test"); }); add_test("jid_valid_full", () => { test_jid_valid("test@example.com/test"); }); - // Should those actually be valid? + // These should not be valid in "strict-mode" add_test("jid_valid_emoji_local", () => { test_jid_valid("😅@example.com"); }); add_test("jid_valid_emoji_resource", () => { test_jid_valid("test@example.com/😅"); }); + add_test("jid_valid_emoji_domain", () => { test_jid_valid("test@😅.com"); }); - add_test("jid_invalid_emoji_domain", () => { test_jid_invalid("test@😅.com"); }); add_test("jid_invalid_bidi_local", () => { test_jid_invalid("te‏st@example.com"); }); add_test("jid_invalid_bidi_resource", () => { test_jid_invalid("test@example.com/te‏st"); }); add_test("jid_invalid_bidi_domain", () => { test_jid_invalid("test@exa‏mple.com"); }); @@ -43,7 +43,7 @@ class JidTest : Gee.TestCase { try { new Jid(jid); } catch (Error e) { - fail_if_reached(); + fail_if_reached(@"Throws $(e.message)"); } } diff --git a/xmpp-vala/vapi/icu-uc.vapi b/xmpp-vala/vapi/icu-uc.vapi index 14764440..328523d1 100644 --- a/xmpp-vala/vapi/icu-uc.vapi +++ b/xmpp-vala/vapi/icu-uc.vapi @@ -15,6 +15,10 @@ enum ErrorCode { ; [CCode (cname = "u_errorName")] public unowned string errorName(); + [CCode (cname = "U_SUCCESS")] + public bool is_success(); + [CCode (cname = "U_FAILURE")] + public bool is_failure(); } [CCode (cname = "UErrorCode", cprefix = "U_", cheader_filename = "unicode/parseerr.h")] @@ -42,8 +46,20 @@ enum PrepOptions { [CCode (cname = "UIDNA", cprefix = "uidna_", free_function = "uidna_close", cheader_filename = "unicode/uidna.h")] [Compact] class IDNA { + public static IDNA openUTS46(IDNAOptions options, ref ErrorCode status); public static int32 IDNToUnicode(Char* src, int32 src_length, Char* dest, int32 dest_capacity, IDNAOptions options, out ParseError parse_error, ref ErrorCode status); public static int32 IDNToASCII(Char* src, int32 src_length, Char* dest, int32 dest_capacity, IDNAOptions options, out ParseError parse_error, ref ErrorCode status); + public int32 nameToUnicode(Char* src, int32 src_length, Char* dest, int32 dest_capacity, out IDNAInfo info, ref ErrorCode status); + public int32 nameToASCII(Char* src, int32 src_length, Char* dest, int32 dest_capacity, out IDNAInfo info, ref ErrorCode status); + public int32 nameToASCII_UTF8(string name, int32 name_length, char[] dest, out IDNAInfo info, ref ErrorCode status); + public int32 nameToUnicodeUTF8(string name, int32 name_length, char[] dest, out IDNAInfo info, ref ErrorCode status); +} + +[CCode (cname = "UIDNAInfo", default_value = "UIDNA_INFO_INITIALIZER", has_type_id = false, cheader_filename = "unicode/uidna.h")] +struct IDNAInfo { + public static IDNAInfo INITIAL; + public uint32 errors; + public bool isTransitionalDifferent; } [CCode (cname = "uint32_t", cprefix = "UIDNA_")]