git » sdk » commit 4b165cb

Search trusted gateways for chats

author Stephen Paul Weber
2023-09-14 03:34:40 UTC
committer Stephen Paul Weber
2023-09-25 17:10:23 UTC
parent 1e511c07721767f888d5091a2531d4201e91ebb4

Search trusted gateways for chats

xmpp/Chat.hx +17 -0
xmpp/Client.hx +47 -5
xmpp/JID.hx +31 -1
xmpp/queries/JabberIqGatewayGet.hx +66 -0

diff --git a/xmpp/Chat.hx b/xmpp/Chat.hx
index 2faf35f..bac2e93 100644
--- a/xmpp/Chat.hx
+++ b/xmpp/Chat.hx
@@ -21,6 +21,7 @@ abstract class Chat {
 	private var persistence:Persistence;
 	private var avatarSha1:Null<BytesData> = null;
 	private var caps:Map<String, Caps> = [];
+	private var trusted:Bool = false;
 	public var chatId(default, null):String;
 	public var type(default, null):Null<ChatType>;
 
@@ -45,6 +46,22 @@ abstract class Chat {
 		this.caps.set(resource, caps);
 	}
 
+	public function getCaps():KeyValueIterator<String, Caps> {
+		return caps.keyValueIterator();
+	}
+
+	public function getResourceCaps(resource:String):Caps {
+		return caps[resource];
+	}
+
+	public function setTrusted(trusted:Bool) {
+		this.trusted = trusted;
+	}
+
+	public function isTrusted():Bool {
+		return this.trusted;
+	}
+
 	public function onMessage(handler:ChatMessage->Void):Void {
 		this.stream.on("message", function(event) {
 			final stanza:Stanza = event.stanza;
diff --git a/xmpp/Client.hx b/xmpp/Client.hx
index 2ac1503..a271840 100644
--- a/xmpp/Client.hx
+++ b/xmpp/Client.hx
@@ -11,6 +11,7 @@ import xmpp.queries.GenericQuery;
 import xmpp.queries.RosterGet;
 import xmpp.queries.PubsubGet;
 import xmpp.queries.DiscoInfoGet;
+import xmpp.queries.JabberIqGatewayGet;
 import xmpp.PubsubEvent;
 
 typedef ChatList = Array<Chat>;
@@ -111,7 +112,10 @@ class Client extends xmpp.EventEmitter {
 			if (items.length == 0) return EventUnhandled;
 
 			for (item in items) {
-				if (item.subscription != "remove") getDirectChat(item.jid, false);
+				if (item.subscription != "remove") {
+					final chat = getDirectChat(item.jid, false);
+					chat.setTrusted(item.subscription == "both" || item.subscription == "from");
+				}
 			}
 			this.trigger("chats/update", chats);
 
@@ -187,12 +191,49 @@ class Client extends xmpp.EventEmitter {
 	}
 
 	public function findAvailableChats(q:String, callback:(q:String, chatIds:Array<String>) -> Void) {
-		var jid = JID.parse(q);
+		var results = [];
+		final query = StringTools.trim(q);
+		final jid = JID.parse(query);
 		if (jid.isValid()) {
-			callback(q, [jid.asBare().asString()]);
-			return;
+			results.push(jid.asBare().asString());
+			callback(q, results); // send some right away
+		}
+		for (chat in chats) {
+			if (chat.isTrusted()) {
+				final resources:Map<String, Bool> = [];
+				for (resource in Caps.withIdentity(chat.getCaps(), "gateway", null)) {
+					resources[resource] = true;
+				}
+				for (resource in Caps.withFeature(chat.getCaps(), "jabber:iq:gateway")) {
+					resources[resource] = true;
+				}
+				for (resource in resources.keys()) {
+					final bareJid = JID.parse(chat.chatId);
+					final fullJid = new JID(bareJid.node, bareJid.domain, resource);
+					final jigGet = new JabberIqGatewayGet(fullJid.asString(), query);
+					jigGet.onFinished(() -> {
+						if (jigGet.getResult() == null) {
+							final caps = chat.getResourceCaps(resource);
+							if (bareJid.isDomain() && caps.features.contains("jid\\20escaping")) {
+								results.push(new JID(query, bareJid.domain).asString());
+								callback(q, results);
+							} else if (bareJid.isDomain()) {
+								results.push(new JID(StringTools.replace(query, "@", "%"), bareJid.domain).asString());
+								callback(q, results);
+							}
+						} else {
+							switch (jigGet.getResult()) {
+								case Left(error): return;
+								case Right(result):
+									results.push(result);
+									callback(q, results);
+							}
+						}
+					});
+					sendQuery(jigGet);
+				}
+			}
 		}
-		callback(q, []);
 	}
 
 	public function chatActivity(chat: Chat) {
@@ -218,6 +259,7 @@ class Client extends xmpp.EventEmitter {
 		rosterGet.onFinished(() -> {
 			for (item in rosterGet.getResult()) {
 				var chat = getDirectChat(item.jid, false);
+				chat.setTrusted(item.subscription == "both" || item.subscription == "from");
 				if (item.fn != null && item.fn != "") chat.setDisplayName(item.fn);
 			}
 			this.trigger("chats/update", chats);
diff --git a/xmpp/JID.hx b/xmpp/JID.hx
index aa49777..a4bd3a5 100644
--- a/xmpp/JID.hx
+++ b/xmpp/JID.hx
@@ -6,7 +6,33 @@ class JID {
 	public final resource : Null<String>;
 
 	public function new(?node:String, domain:String, ?resource:String) {
-		this.node = node;
+		this.node = node == null ? null :
+			StringTools.replace(StringTools.replace(StringTools.replace(
+			StringTools.replace(StringTools.replace(StringTools.replace(
+			StringTools.replace(StringTools.replace(StringTools.replace(
+			StringTools.replace(StringTools.replace(StringTools.replace(
+			StringTools.replace(StringTools.replace(StringTools.replace(
+			StringTools.replace(StringTools.replace(StringTools.replace(
+				StringTools.replace(StringTools.trim(node),
+				"\\5c", "\\5c5c"),
+				"\\20", "\\5c20"),
+				"\\22", "\\5c22"),
+				"\\26", "\\5c26"),
+				"\\27", "\\5c27"),
+				"\\2f", "\\5c2f"),
+				"\\3a", "\\5c3a"),
+				"\\3c", "\\5c3c"),
+				"\\3e", "\\5c3e"),
+				"\\40", "\\5c40"),
+				" ", "\\20"),
+				'"', "\\22"),
+				"&", "\\26"),
+				"'", "\\27"),
+				"/", "\\2f"),
+				":", "\\3a"),
+				"<", "\\3c"),
+				">", "\\3e"),
+				"@", "\\40");
 		this.domain = domain;
 		this.resource = resource;
 	}
@@ -32,6 +58,10 @@ class JID {
 		return domain.indexOf(".") >= 0;
 	}
 
+	public function isDomain():Bool {
+		return node == null;
+	}
+
 	public function equals(rhs:JID):Bool {
 		return (
 			this.node == rhs.node &&
diff --git a/xmpp/queries/JabberIqGatewayGet.hx b/xmpp/queries/JabberIqGatewayGet.hx
new file mode 100644
index 0000000..7eb1d3f
--- /dev/null
+++ b/xmpp/queries/JabberIqGatewayGet.hx
@@ -0,0 +1,66 @@
+package xmpp.queries;
+
+import haxe.DynamicAccess;
+import haxe.Exception;
+import haxe.ds.Either;
+
+import xmpp.ID;
+import xmpp.ResultSet;
+import xmpp.Stanza;
+import xmpp.Stream;
+import xmpp.queries.GenericQuery;
+import xmpp.Caps;
+
+class JabberIqGatewayGet extends GenericQuery {
+	public var xmlns(default, null) = "jabber:iq:gateway";
+	public var queryId:String = null;
+	public var ver:String = null;
+	private var responseStanza:Stanza;
+	private var result:Null<Either<String, String>>;
+
+	public function new(to: String, ?prompt: String) {
+		queryId = ID.short();
+		queryStanza = new Stanza(
+			"iq",
+			{ to: to, type: prompt == null ? "get" : "set", id: queryId }
+		);
+		final query = queryStanza.tag("query", { xmlns: xmlns });
+		if (prompt != null) {
+			query.textTag("prompt", prompt, {});
+		}
+		query.up();
+	}
+
+	public function handleResponse(stanza:Stanza) {
+		responseStanza = stanza;
+		finish();
+	}
+
+	public function getResult() {
+		if (responseStanza == null) {
+			return null;
+		}
+		if(result == null) {
+			final error = responseStanza.getChild("error");
+			if (error == null) {
+				final q = responseStanza.getChild("query", xmlns);
+				if(q == null) {
+					return null;
+				}
+				final prompt = q.getChild("prompt");
+				if (prompt == null) {
+					final jid = q.getChild("jid");
+					if (jid == null) return null;
+					result = Right(jid.getText());
+				} else {
+					result = Right(prompt.getText());
+				}
+			} else {
+				if (error.getChild("service-unavailable", "urn:ietf:params:xml:ns:xmpp-stanzas") != null) return null;
+				if (error.getChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas") != null) return null;
+				result = Left(error.getText());
+			}
+		}
+		return result;
+	}
+}