| author | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-09-14 03:34:40 UTC |
| committer | Stephen Paul Weber
<singpolyma@singpolyma.net> 2023-09-25 17:10:23 UTC |
| parent | 1e511c07721767f888d5091a2531d4201e91ebb4 |
| 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; + } +}