aboutsummaryrefslogtreecommitdiff
path: root/xmpp-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
parent3218dc0211ac717230fe03fad82681a626d968b5 (diff)
downloaddino-a0a956ee0878d24bd06be7f5d75dc4ccd4e7901d.tar.gz
dino-a0a956ee0878d24bd06be7f5d75dc4ccd4e7901d.zip
Properly check Jids everywhere
Diffstat (limited to 'xmpp-vala')
-rw-r--r--xmpp-vala/CMakeLists.txt7
-rw-r--r--xmpp-vala/src/core/xmpp_stream.vala6
-rw-r--r--xmpp-vala/src/module/bind.vala18
-rw-r--r--xmpp-vala/src/module/conference.vala4
-rw-r--r--xmpp-vala/src/module/jid.vala155
-rw-r--r--xmpp-vala/src/module/roster/item.vala11
-rw-r--r--xmpp-vala/src/module/stanza.vala15
-rw-r--r--xmpp-vala/src/module/xep/0030_service_discovery/items_result.vala10
-rw-r--r--xmpp-vala/src/module/xep/0045_muc/module.vala146
-rw-r--r--xmpp-vala/src/module/xep/0048_conference.vala10
-rw-r--r--xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala6
-rw-r--r--xmpp-vala/src/module/xep/0166_jingle.vala12
-rw-r--r--xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala6
-rw-r--r--xmpp-vala/src/module/xep/0402_bookmarks2.vala25
-rw-r--r--xmpp-vala/tests/common.vala8
-rw-r--r--xmpp-vala/tests/jid.vala93
-rw-r--r--xmpp-vala/vapi/icu-uc.vapi56
17 files changed, 464 insertions, 124 deletions
diff --git a/xmpp-vala/CMakeLists.txt b/xmpp-vala/CMakeLists.txt
index e059b068..246f0108 100644
--- a/xmpp-vala/CMakeLists.txt
+++ b/xmpp-vala/CMakeLists.txt
@@ -4,6 +4,7 @@ find_packages(ENGINE_PACKAGES REQUIRED
GIO
GLib
GObject
+ ICU
)
set(ENGINE_DEFINITIONS "")
@@ -15,6 +16,7 @@ if(GIO_VERSION VERSION_GREATER "2.60")
else()
message(STATUS "No ALPN support, needs GIO >= 2.60")
endif()
+set(ENGINE_EXTRA_OPTIONS ${MAIN_EXTRA_OPTIONS} --vapidir=${CMAKE_CURRENT_SOURCE_DIR}/vapi)
vala_precompile(ENGINE_VALA_C
SOURCES
@@ -104,6 +106,8 @@ CUSTOM_VAPIS
"${CMAKE_CURRENT_SOURCE_DIR}/src/glib_fixes.vapi"
DEFINITIONS
${ENGINE_DEFINITIONS}
+OPTIONS
+ ${ENGINE_EXTRA_OPTIONS}
)
add_custom_target(xmpp-vala-vapi
@@ -128,12 +132,15 @@ if(BUILD_TESTS)
"tests/common.vala"
"tests/testcase.vala"
+ "tests/jid.vala"
"tests/stanza.vala"
"tests/util.vala"
CUSTOM_VAPIS
${CMAKE_BINARY_DIR}/exports/xmpp-vala_internal.vapi
PACKAGES
${ENGINE_PACKAGES}
+ OPTIONS
+ ${ENGINE_EXTRA_OPTIONS}
)
add_definitions(${VALA_CFLAGS})
diff --git a/xmpp-vala/src/core/xmpp_stream.vala b/xmpp-vala/src/core/xmpp_stream.vala
index 97d00004..39754ba1 100644
--- a/xmpp-vala/src/core/xmpp_stream.vala
+++ b/xmpp-vala/src/core/xmpp_stream.vala
@@ -44,7 +44,11 @@ public class XmppStream {
}
public async void connect(string? remote_name = null) throws IOStreamError {
- if (remote_name != null) this.remote_name = Jid.parse(remote_name);
+ try {
+ if (remote_name != null) this.remote_name = new Jid(remote_name);
+ } catch (InvalidJidError e) {
+ throw new IOStreamError.CONNECT(@"Invalid remote name \"$remote_name\": $(e.message)");
+ }
attach_negotation_modules();
try {
int min_priority = -1;
diff --git a/xmpp-vala/src/module/bind.vala b/xmpp-vala/src/module/bind.vala
index a3b0762b..89398bfb 100644
--- a/xmpp-vala/src/module/bind.vala
+++ b/xmpp-vala/src/module/bind.vala
@@ -5,11 +5,11 @@ namespace Xmpp.Bind {
public class Module : XmppStreamNegotiationModule {
public static ModuleIdentity<Module> IDENTITY = new ModuleIdentity<Module>(NS_URI, "bind_module");
- public string requested_resource { get; set; }
+ public string? requested_resource { get; set; }
public signal void bound_to_resource(XmppStream stream, Jid my_jid);
- public Module(string requested_resource) {
+ public Module(string? requested_resource) {
this.requested_resource = requested_resource;
}
@@ -18,9 +18,13 @@ namespace Xmpp.Bind {
if (flag == null || flag.finished) return;
if (iq.type_ == Iq.Stanza.TYPE_RESULT) {
- flag.my_jid = Jid.parse(iq.stanza.get_subnode("jid", NS_URI, true).get_string_content());
- flag.finished = true;
- bound_to_resource(stream, flag.my_jid);
+ try {
+ flag.my_jid = new Jid(iq.stanza.get_subnode("jid", NS_URI, true).get_string_content());
+ flag.finished = true;
+ bound_to_resource(stream, flag.my_jid);
+ } catch (InvalidJidError e) {
+ warning("Received invalid Jid when binding: %s", e.message);
+ }
}
}
@@ -32,7 +36,9 @@ namespace Xmpp.Bind {
if (bind != null) {
var flag = new Flag();
StanzaNode bind_node = new StanzaNode.build("bind", NS_URI).add_self_xmlns();
- bind_node.put_node(new StanzaNode.build("resource", NS_URI).put_node(new StanzaNode.text(requested_resource)));
+ if (requested_resource != null) {
+ bind_node.put_node(new StanzaNode.build("resource", NS_URI).put_node(new StanzaNode.text(requested_resource)));
+ }
stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.set(bind_node), iq_response_stanza);
stream.add_flag(flag);
}
diff --git a/xmpp-vala/src/module/conference.vala b/xmpp-vala/src/module/conference.vala
index ee1be6b6..9b1172d8 100644
--- a/xmpp-vala/src/module/conference.vala
+++ b/xmpp-vala/src/module/conference.vala
@@ -1,14 +1,14 @@
namespace Xmpp {
public class Conference : Object {
- public virtual Jid jid { get; set; }
+ public virtual Jid? jid { get; set; }
public virtual bool autojoin { get; set; }
public virtual string? nick { get; set; }
public virtual string? name { get; set; }
public virtual string? password { get; set; }
public static bool equal_func(Conference a, Conference b) {
- return a.jid.equals(b.jid);
+ return Jid.equals_func(a.jid, b.jid);
}
public static uint hash_func(Conference a) {
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
+}
+
}
diff --git a/xmpp-vala/src/module/roster/item.vala b/xmpp-vala/src/module/roster/item.vala
index 2fbf03ef..78974a35 100644
--- a/xmpp-vala/src/module/roster/item.vala
+++ b/xmpp-vala/src/module/roster/item.vala
@@ -17,8 +17,15 @@ public class Item {
public StanzaNode stanza_node;
private Jid jid_;
- public Jid jid {
- get { return jid_ ?? (jid_ = Jid.parse(stanza_node.get_attribute(NODE_JID))); }
+ public Jid? jid {
+ get {
+ try {
+ return jid_ ?? (jid_ = new Jid(stanza_node.get_attribute(NODE_JID)));
+ } catch (InvalidJidError e) {
+ warning("Ignoring invalid Jid in roster entry: %s", e.message);
+ return null;
+ }
+ }
set { stanza_node.set_attribute(NODE_JID, value.to_string()); }
}
diff --git a/xmpp-vala/src/module/stanza.vala b/xmpp-vala/src/module/stanza.vala
index abdc33e9..f4e40f84 100644
--- a/xmpp-vala/src/module/stanza.vala
+++ b/xmpp-vala/src/module/stanza.vala
@@ -18,7 +18,13 @@ public class Stanza : Object {
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) return from_ = Jid.parse(from_attribute);
+ if (from_attribute != null) {
+ try {
+ return from_ = new Jid(from_attribute);
+ } catch (InvalidJidError e) {
+ warning("Ignoring invalid from Jid: %s", e.message);
+ }
+ }
if (my_jid != null) {
return my_jid.bare_jid;
}
@@ -37,7 +43,12 @@ public class Stanza : Object {
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)
- return to_attribute == null ? my_jid : to_ = Jid.parse(to_attribute);
+ try {
+ return to_attribute == null ? my_jid : to_ = new Jid(to_attribute);
+ } catch (InvalidJidError e) {
+ warning("Ignoring invalid to Jid: %s", e.message);
+ }
+ return my_jid;
}
set { stanza.set_attribute(ATTRIBUTE_TO, value.to_string()); }
}
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 16a9f5ec..233a0c06 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
@@ -9,9 +9,13 @@ public class ItemsResult {
owned get {
ArrayList<Item> ret = new ArrayList<Item>();
foreach (StanzaNode feature_node in iq.stanza.get_subnode("query", NS_URI_ITEMS).get_subnodes("item", NS_URI_ITEMS)) {
- ret.add(new Item(Jid.parse(feature_node.get_attribute("jid", NS_URI_ITEMS)),
- feature_node.get_attribute("name", NS_URI_ITEMS),
- feature_node.get_attribute("node", NS_URI_ITEMS)));
+ try {
+ ret.add(new Item(new Jid(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) {
+ warning("Ignoring service at invalid Jid: %s", e.message);
+ }
}
return ret;
}
diff --git a/xmpp-vala/src/module/xep/0045_muc/module.vala b/xmpp-vala/src/module/xep/0045_muc/module.vala
index 59d61f3d..ec10d500 100644
--- a/xmpp-vala/src/module/xep/0045_muc/module.vala
+++ b/xmpp-vala/src/module/xep/0045_muc/module.vala
@@ -82,41 +82,50 @@ public class Module : XmppStreamModule {
}
public async JoinResult? enter(XmppStream stream, Jid bare_jid, string nick, string? password, DateTime? history_since) {
- Presence.Stanza presence = new Presence.Stanza();
- presence.to = bare_jid.with_resource(nick);
- StanzaNode x_node = new StanzaNode.build("x", NS_URI).add_self_xmlns();
- if (password != null) {
- x_node.put_node(new StanzaNode.build("password", NS_URI).put_node(new StanzaNode.text(password)));
- }
- if (history_since != null) {
- StanzaNode history_node = new StanzaNode.build("history", NS_URI);
- history_node.set_attribute("since", DateTimeProfiles.to_datetime(history_since));
- x_node.put_node(history_node);
- }
- presence.stanza.put_node(x_node);
+ try {
+ Presence.Stanza presence = new Presence.Stanza();
+ presence.to = bare_jid.with_resource(nick);
- stream.get_flag(Flag.IDENTITY).start_muc_enter(bare_jid, presence.id);
+ StanzaNode x_node = new StanzaNode.build("x", NS_URI).add_self_xmlns();
+ if (password != null) {
+ x_node.put_node(new StanzaNode.build("password", NS_URI).put_node(new StanzaNode.text(password)));
+ }
+ if (history_since != null) {
+ StanzaNode history_node = new StanzaNode.build("history", NS_URI);
+ history_node.set_attribute("since", DateTimeProfiles.to_datetime(history_since));
+ x_node.put_node(history_node);
+ }
+ presence.stanza.put_node(x_node);
- query_room_info(stream, bare_jid);
- stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
+ stream.get_flag(Flag.IDENTITY).start_muc_enter(bare_jid, presence.id);
- var promise = new Promise<JoinResult?>();
- stream.get_flag(Flag.IDENTITY).enter_futures[bare_jid] = promise;
- try {
- JoinResult? enter_result = yield promise.future.wait_async();
- stream.get_flag(Flag.IDENTITY).enter_futures.unset(bare_jid);
- return enter_result;
- } catch (Gee.FutureError e) {
- return null;
+ query_room_info(stream, bare_jid);
+ stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
+
+ var promise = new Promise<JoinResult?>();
+ stream.get_flag(Flag.IDENTITY).enter_futures[bare_jid] = promise;
+ try {
+ JoinResult? enter_result = yield promise.future.wait_async();
+ stream.get_flag(Flag.IDENTITY).enter_futures.unset(bare_jid);
+ return enter_result;
+ } catch (Gee.FutureError e) {
+ return null;
+ }
+ } catch (InvalidJidError e) {
+ return new JoinResult() { muc_error = MucEnterError.NICK_CONFLICT };
}
}
public void exit(XmppStream stream, Jid jid) {
- string nick = stream.get_flag(Flag.IDENTITY).get_muc_nick(jid);
- Presence.Stanza presence = new Presence.Stanza();
- presence.to = jid.with_resource(nick);
- presence.type_ = Presence.Stanza.TYPE_UNAVAILABLE;
- stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
+ try {
+ string nick = stream.get_flag(Flag.IDENTITY).get_muc_nick(jid);
+ Presence.Stanza presence = new Presence.Stanza();
+ presence.to = jid.with_resource(nick);
+ presence.type_ = Presence.Stanza.TYPE_UNAVAILABLE;
+ stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
+ } catch (InvalidJidError e) {
+ warning("Tried to leave room with invalid nick: %s", e.message);
+ }
}
public void change_subject(XmppStream stream, Jid jid, string subject) {
@@ -128,9 +137,14 @@ public class Module : XmppStreamModule {
}
public void change_nick(XmppStream stream, Jid jid, string new_nick) {
- Presence.Stanza presence = new Presence.Stanza();
- presence.to = jid.with_resource(new_nick);
- stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
+ // TODO: Return if successful
+ try {
+ Presence.Stanza presence = new Presence.Stanza();
+ presence.to = jid.with_resource(new_nick);
+ stream.get_module(Presence.Module.IDENTITY).send_presence(stream, presence);
+ } catch (InvalidJidError e) {
+ warning("Tried to change nick to invalid nick: %s", e.message);
+ }
}
public void invite(XmppStream stream, Jid to_muc, Jid jid) {
@@ -148,20 +162,25 @@ public class Module : XmppStreamModule {
/* XEP 0046: "A user cannot be kicked by a moderator with a lower affiliation." (XEP 0045 8.2) */
public bool kick_possible(XmppStream stream, Jid occupant) {
- Jid muc_jid = occupant.bare_jid;
- Flag flag = stream.get_flag(Flag.IDENTITY);
- string own_nick = flag.get_muc_nick(muc_jid);
- Affiliation my_affiliation = flag.get_affiliation(muc_jid, muc_jid.with_resource(own_nick));
- Affiliation other_affiliation = flag.get_affiliation(muc_jid, occupant);
- switch (my_affiliation) {
- case Affiliation.MEMBER:
- if (other_affiliation == Affiliation.ADMIN || other_affiliation == Affiliation.OWNER) return false;
- break;
- case Affiliation.ADMIN:
- if (other_affiliation == Affiliation.OWNER) return false;
- break;
+ try {
+ Jid muc_jid = occupant.bare_jid;
+ Flag flag = stream.get_flag(Flag.IDENTITY);
+ string own_nick = flag.get_muc_nick(muc_jid);
+ Affiliation my_affiliation = flag.get_affiliation(muc_jid, muc_jid.with_resource(own_nick));
+ Affiliation other_affiliation = flag.get_affiliation(muc_jid, occupant);
+ switch (my_affiliation) {
+ case Affiliation.MEMBER:
+ if (other_affiliation == Affiliation.ADMIN || other_affiliation == Affiliation.OWNER) return false;
+ break;
+ case Affiliation.ADMIN:
+ if (other_affiliation == Affiliation.OWNER) return false;
+ break;
+ }
+ return true;
+ } catch (InvalidJidError e) {
+ warning("Tried to kick with invalid nick: %s", e.message);
+ return false;
}
- return true;
}
public void change_role(XmppStream stream, Jid jid, string nick, string new_role) {
@@ -314,12 +333,16 @@ public class Module : XmppStreamModule {
}
string? jid_ = x_node.get_deep_attribute("item", "jid");
if (jid_ != null) {
- Jid? jid = Jid.parse(jid_);
- flag.set_real_jid(presence.from, jid);
- if (affiliation != null) {
- stream.get_flag(Flag.IDENTITY).set_offline_member(presence.from, jid, affiliation);
+ try {
+ Jid jid = new Jid(jid_);
+ flag.set_real_jid(presence.from, jid);
+ if (affiliation != null) {
+ stream.get_flag(Flag.IDENTITY).set_offline_member(presence.from, jid, affiliation);
+ }
+ received_occupant_jid(stream, presence.from, jid);
+ } catch (InvalidJidError e) {
+ warning("Received invalid occupant jid: %s", e.message);
}
- received_occupant_jid(stream, presence.from, jid);
}
string? role_str = x_node.get_deep_attribute("item", "role");
if (role_str != null) {
@@ -414,12 +437,17 @@ public class Module : XmppStreamModule {
Gee.List<StanzaNode> item_nodes = query_node.get_subnodes("item", NS_URI_ADMIN);
Gee.List<Jid> ret_jids = new ArrayList<Jid>(Jid.equals_func);
foreach (StanzaNode item in item_nodes) {
- Jid? jid_ = Jid.parse(item.get_attribute("jid"));
+ string jid__ = item.get_attribute("jid");
string? affiliation_ = item.get_attribute("affiliation");
- if (jid_ != null && affiliation_ != null) {
- stream.get_flag(Flag.IDENTITY).set_offline_member(iq.from, jid_, parse_affiliation(affiliation_));
- ret_jids.add(jid_);
- received_occupant_jid(stream, iq.from, jid_);
+ if (jid__ != null && affiliation_ != null) {
+ try {
+ Jid jid_ = new Jid(jid__);
+ stream.get_flag(Flag.IDENTITY).set_offline_member(iq.from, jid_, parse_affiliation(affiliation_));
+ ret_jids.add(jid_);
+ received_occupant_jid(stream, iq.from, jid_);
+ } catch (InvalidJidError e) {
+ warning("Received invalid occupant jid: %s", e.message);
+ }
}
}
if (listener != null) listener(stream, ret_jids);
@@ -489,13 +517,19 @@ public class ReceivedPipelineListener : StanzaListener<MessageStanza> {
StanzaNode? password_node = x_node.get_subnode("password", NS_URI_USER);
if (password_node != null) password = password_node.get_string_content();
if (invite_node != null) {
- string? from_jid = invite_node.get_attribute("from");
+ Jid? from_jid = null;
+ try {
+ string from = invite_node.get_attribute("from");
+ if (from != null) from_jid = new Jid(from);
+ } catch (InvalidJidError e) {
+ warning("Received invite from invalid jid: %s", e.message);
+ }
if (from_jid != null) {
StanzaNode? reason_node = invite_node.get_subnode("reason", NS_URI_USER);
string? reason = null;
if (reason_node != null) reason = reason_node.get_string_content();
bool is_mam_message = Xep.MessageArchiveManagement.MessageFlag.get_flag(message) != null; // TODO
- if (!is_mam_message) outer.invite_received(stream, message.from, new Jid(from_jid), password, reason);
+ if (!is_mam_message) outer.invite_received(stream, message.from, from_jid, password, reason);
return true;
}
}
diff --git a/xmpp-vala/src/module/xep/0048_conference.vala b/xmpp-vala/src/module/xep/0048_conference.vala
index d78a2685..fdc26152 100644
--- a/xmpp-vala/src/module/xep/0048_conference.vala
+++ b/xmpp-vala/src/module/xep/0048_conference.vala
@@ -20,8 +20,14 @@ public class Bookmarks1Conference : Conference {
}
private Jid jid_;
- public override Jid jid {
- get { return jid_ ?? (jid_ = Jid.parse(stanza_node.get_attribute(ATTRIBUTE_JID))); }
+ public override Jid? jid {
+ get {
+ try {
+ return jid_ ?? (jid_ = new Jid(stanza_node.get_attribute(ATTRIBUTE_JID)));
+ } catch (InvalidJidError e) {
+ return null;
+ }
+ }
set { stanza_node.set_attribute(ATTRIBUTE_JID, value.to_string()); }
}
diff --git a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala
index 1890aac3..a1be00d4 100644
--- a/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala
+++ b/xmpp-vala/src/module/xep/0065_socks5_bytestreams.vala
@@ -52,7 +52,11 @@ public class Module : XmppStreamModule, Iq.Handler {
}
string? host = stream_host.get_attribute("host");
string? jid_str = stream_host.get_attribute("jid");
- Jid? jid = jid_str != null ? Jid.parse(jid_str) : null;
+ Jid? jid = null;
+ try {
+ jid = jid_str != null ? new Jid(jid_str) : null;
+ } catch (InvalidJidError ignored) {
+ }
int port = stream_host.get_attribute_int("port");
if (host == null || jid == null || port <= 0 || port > 65535) {
return;
diff --git a/xmpp-vala/src/module/xep/0166_jingle.vala b/xmpp-vala/src/module/xep/0166_jingle.vala
index 7fc6c929..ca368f00 100644
--- a/xmpp-vala/src/module/xep/0166_jingle.vala
+++ b/xmpp-vala/src/module/xep/0166_jingle.vala
@@ -572,13 +572,15 @@ public class Session {
}
void handle_session_accept(XmppStream stream, ContentNode content, StanzaNode jingle, Iq.Stanza iq) throws IqError {
string? responder_str = jingle.get_attribute("responder");
- Jid responder;
+ Jid responder = iq.from;
if (responder_str != null) {
- responder = Jid.parse(responder_str) ?? iq.from;
- } else {
- responder = iq.from; // TODO(hrxi): and above, can we assume iq.from != null
- // TODO(hrxi): more sanity checking, perhaps replace who we're talking to
+ try {
+ responder = new Jid(responder_str);
+ } catch (InvalidJidError e) {
+ warning("Received invalid session accept: %s", e.message);
+ }
}
+ // TODO(hrxi): more sanity checking, perhaps replace who we're talking to
if (!responder.is_full()) {
throw new IqError.BAD_REQUEST("invalid responder JID");
}
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 991ea141..c6556eb7 100644
--- a/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala
+++ b/xmpp-vala/src/module/xep/0260_jingle_socks5_bytestreams.vala
@@ -116,7 +116,11 @@ public class Candidate : Socks5Bytestreams.Proxy {
string? cid = candidate.get_attribute("cid");
string? host = candidate.get_attribute("host");
string? jid_str = candidate.get_attribute("jid");
- Jid? jid = jid_str != null ? Jid.parse(jid_str) : null;
+ Jid? jid = null;
+ try {
+ jid = new Jid(jid_str);
+ } catch (InvalidJidError ignored) {
+ }
int port = candidate.get_attribute("port") != null ? candidate.get_attribute_int("port") : 1080;
int priority = candidate.get_attribute_int("priority");
string? type_str = candidate.get_attribute("type");
diff --git a/xmpp-vala/src/module/xep/0402_bookmarks2.vala b/xmpp-vala/src/module/xep/0402_bookmarks2.vala
index 15386398..50c52ac2 100644
--- a/xmpp-vala/src/module/xep/0402_bookmarks2.vala
+++ b/xmpp-vala/src/module/xep/0402_bookmarks2.vala
@@ -66,19 +66,28 @@ public class Module : BookmarksProvider, XmppStreamModule {
}
private void on_pupsub_retract(XmppStream stream, Jid jid, string id) {
- Jid jid_parsed = Jid.parse(id);
- Flag? flag = stream.get_flag(Flag.IDENTITY);
- if (flag != null) {
- flag.conferences.unset(jid_parsed);
+ try {
+ Jid jid_parsed = new Jid(id);
+ Flag? flag = stream.get_flag(Flag.IDENTITY);
+ if (flag != null) {
+ flag.conferences.unset(jid_parsed);
+ }
+ conference_removed(stream, jid_parsed);
+ } catch (InvalidJidError e) {
+ warning("Ignoring conference bookmark update with invalid Jid: %s", e.message);
}
- conference_removed(stream, jid_parsed);
}
private Conference? parse_item_node(StanzaNode conference_node, string id) {
Conference conference = new Conference();
- Jid? jid_parsed = Jid.parse(id);
- if (jid_parsed == null || jid_parsed.resourcepart != null) return null;
- conference.jid = jid_parsed;
+ try {
+ Jid jid_parsed = new Jid(id);
+ if (jid_parsed.resourcepart != null) return null;
+ conference.jid = jid_parsed;
+ } catch (InvalidJidError e) {
+ warning("Ignoring conference bookmark update with invalid Jid: %s", e.message);
+ return null;
+ }
if (conference_node.name != "conference" || conference_node.ns_uri != NS_URI) return null;
diff --git a/xmpp-vala/tests/common.vala b/xmpp-vala/tests/common.vala
index b91bbf7c..c616d2e7 100644
--- a/xmpp-vala/tests/common.vala
+++ b/xmpp-vala/tests/common.vala
@@ -5,6 +5,7 @@ int main(string[] args) {
GLib.Test.set_nonfatal_assertions();
TestSuite.get_root().add_suite(new Xmpp.Test.StanzaTest().get_suite());
TestSuite.get_root().add_suite(new Xmpp.Test.UtilTest().get_suite());
+ TestSuite.get_root().add_suite(new Xmpp.Test.JidTest().get_suite());
return GLib.Test.run();
}
@@ -74,6 +75,13 @@ bool fail_if_not_eq_str(string? left, string? right, string? reason = null) {
return fail_if_not(!nullcheck && left == right, @"$(reason + ": " ?? "")$left != $right");
}
+bool fail_if_eq_str(string? left, string? right, string? reason = null) {
+ bool nullcheck = (left == null && right != null) || (left != null && right == null);
+ if (left == null) left = "(null)";
+ if (right == null) right = "(null)";
+ return fail_if(!nullcheck && left == right, @"$(reason + ": " ?? "")$left == $right");
+}
+
bool fail_if_not_eq_uint8_arr(uint8[] left, uint8[] right, string? reason = null) {
if (fail_if_not_eq_int(left.length, right.length, @"$(reason + ": " ?? "")array length not equal")) return true;
return fail_if_not_eq_str(Base64.encode(left), Base64.encode(right), reason);
diff --git a/xmpp-vala/tests/jid.vala b/xmpp-vala/tests/jid.vala
new file mode 100644
index 00000000..8928dc97
--- /dev/null
+++ b/xmpp-vala/tests/jid.vala
@@ -0,0 +1,93 @@
+namespace Xmpp.Test {
+
+class JidTest : Gee.TestCase {
+ public JidTest() {
+ base("Jid");
+
+ add_test("jid_valid_domain_only", () => { test_jid_valid("example.com"); });
+ add_test("jid_valid_bare", () => { test_jid_valid("test@example.com"); });
+ 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?
+ 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_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"); });
+ add_test("jid_invalid_overlong_idn", () => { test_jid_invalid("test@รงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรงรง.com"); });
+
+ add_test("jid_equal_end_domain", () => { test_jids_equal("test@example.com", "test@example.com."); });
+ add_test("jid_equal_case_domain", () => { test_jids_equal("test@example.com", "test@eXample.com"); });
+ add_test("jid_equal_norm_domain", () => { test_jids_equal("test@garรงon.com", "test@garcฬงon.com"); });
+ add_test("jid_equal_puny_domain", () => { test_jids_equal("test@garรงon.com", "test@xn--garon-0ra.com"); });
+ add_test("jid_equal_case_local", () => { test_jids_equal("test@example.com", "tEst@example.com"); });
+ add_test("jid_equal_norm_local", () => { test_jids_equal("garรงon@example.com", "garcฬงon@example.com"); });
+ add_test("jid_equal_norm_resource", () => { test_jids_equal("test@example.com/garรงon", "test@example.com/garcฬงon"); });
+
+ add_test("jid_non_equal_case_resource", () => { test_jids_unequal("example.com/test", "example.com/tEst"); });
+
+ add_test("jid_to_string_end_domain", () => { test_jid_to_string("test@example.com.", "test@example.com"); });
+ add_test("jid_to_string_case_domain", () => { test_jid_to_string("test@eXample.com", "test@example.com"); });
+ add_test("jid_to_string_norm_domain", () => { test_jid_to_string("test@garcฬงon.com", "test@garรงon.com"); });
+ add_test("jid_to_string_puny_domain", () => { test_jid_to_string("test@xn--garon-0ra.com", "test@garรงon.com"); });
+ add_test("jid_to_string_case_local", () => { test_jid_to_string("tEst@example.com", "test@example.com"); });
+ add_test("jid_to_string_norm_local", () => { test_jid_to_string("garcฬงon@example.com", "garรงon@example.com"); });
+ add_test("jid_to_string_case_resource", () => { test_jid_to_string("example.com/tEst", "example.com/tEst"); });
+ add_test("jid_to_string_norm_resource", () => { test_jid_to_string("test@example.com/garcฬงon", "test@example.com/garรงon"); });
+ }
+
+ private void test_jid_valid(string jid) {
+ try {
+ new Jid(jid);
+ } catch (Error e) {
+ fail_if_reached();
+ }
+ }
+
+ private void test_jid_invalid(string jid) {
+ try {
+ new Jid(jid);
+ fail_if_reached();
+ } catch (Error e) {
+// try {
+// fail_if_not_eq_str(Jid.parse(jid).to_string(), jid);
+// } catch (Error e) {
+// fail_if_reached();
+// }
+ }
+ }
+
+ private void test_jids_equal(string jid1, string jid2) {
+ try {
+ var t1 = new Jid(jid1);
+ var t2 = new Jid(jid2);
+ fail_if_not_eq_str(t1.to_string(), t2.to_string());
+ } catch (Error e) {
+ fail_if_reached();
+ }
+ }
+
+ private void test_jid_to_string(string jid1, string jid2) {
+ try {
+ var t1 = new Jid(jid1);
+ fail_if_not_eq_str(t1.to_string(), jid2);
+ } catch (Error e) {
+ fail_if_reached();
+ }
+ }
+
+ private void test_jids_unequal(string jid1, string jid2) {
+ try {
+ var t1 = new Jid(jid1);
+ var t2 = new Jid(jid2);
+ fail_if_eq_str(t1.to_string(), t2.to_string());
+ } catch (Error e) {
+ fail_if_reached();
+ }
+ }
+}
+
+} \ No newline at end of file
diff --git a/xmpp-vala/vapi/icu-uc.vapi b/xmpp-vala/vapi/icu-uc.vapi
new file mode 100644
index 00000000..14764440
--- /dev/null
+++ b/xmpp-vala/vapi/icu-uc.vapi
@@ -0,0 +1,56 @@
+namespace ICU {
+
+[CCode (cname = "UChar")]
+[IntegerType (rank = 5, min = 0, max = 65535)]
+struct Char {}
+
+[CCode (cname = "UErrorCode", cprefix = "U_", cheader_filename = "unicode/utypes.h")]
+enum ErrorCode {
+ ZERO_ERROR,
+ INVALID_CHAR_FOUND,
+ INDEX_OUTOFBOUNDS_ERROR,
+ BUFFER_OVERFLOW_ERROR,
+ UNASSIGNED_CODE_POINT_FOUND,
+ IDNA_STD3_ASCII_RULES_ERROR
+ ;
+ [CCode (cname = "u_errorName")]
+ public unowned string errorName();
+}
+
+[CCode (cname = "UErrorCode", cprefix = "U_", cheader_filename = "unicode/parseerr.h")]
+struct ParseError {}
+
+[CCode (cname = "UStringPrepProfile", cprefix = "usprep_", free_function = "usprep_close", cheader_filename = "unicode/usprep.h")]
+[Compact]
+class PrepProfile {
+ public static PrepProfile open(string path, string file_name, ref ErrorCode status);
+ public static PrepProfile openByType(PrepType type, ref ErrorCode status);
+ public int32 prepare(Char* src, int32 src_length, Char* dest, int32 dest_capacity, PrepOptions options, out ParseError parse_error, ref ErrorCode status);
+}
+[CCode (cname = "UStringPrepProfileType", cprefix = "USPREP_")]
+enum PrepType {
+ RFC3491_NAMEPREP,
+ RFC3920_NODEPREP,
+ RFC3920_RESOURCEPREP
+}
+[CCode (cname = "int32_t", cprefix = "USPREP_")]
+enum PrepOptions {
+ DEFAULT,
+ ALLOW_UNASSIGNED
+}
+
+[CCode (cname = "UIDNA", cprefix = "uidna_", free_function = "uidna_close", cheader_filename = "unicode/uidna.h")]
+[Compact]
+class IDNA {
+ 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);
+}
+
+[CCode (cname = "uint32_t", cprefix = "UIDNA_")]
+enum IDNAOptions {
+ DEFAULT,
+ ALLOW_UNASSIGNED,
+ USE_STD3_RULES
+}
+
+}