aboutsummaryrefslogtreecommitdiff
path: root/vala-xmpp/src/module/xep
diff options
context:
space:
mode:
Diffstat (limited to 'vala-xmpp/src/module/xep')
-rw-r--r--vala-xmpp/src/module/xep/0027_pgp/flag.vala24
-rw-r--r--vala-xmpp/src/module/xep/0027_pgp/module.vala206
-rw-r--r--vala-xmpp/src/module/xep/0030_service_discovery/flag.vala33
-rw-r--r--vala-xmpp/src/module/xep/0030_service_discovery/info_result.vala78
-rw-r--r--vala-xmpp/src/module/xep/0030_service_discovery/items_result.vala27
-rw-r--r--vala-xmpp/src/module/xep/0030_service_discovery/module.vala137
-rw-r--r--vala-xmpp/src/module/xep/0045_muc/flag.vala80
-rw-r--r--vala-xmpp/src/module/xep/0045_muc/module.vala244
-rw-r--r--vala-xmpp/src/module/xep/0048_bookmarks/conference.vala74
-rw-r--r--vala-xmpp/src/module/xep/0048_bookmarks/module.vala137
-rw-r--r--vala-xmpp/src/module/xep/0049_private_xml_storage.vala65
-rw-r--r--vala-xmpp/src/module/xep/0054_vcard/module.vala87
-rw-r--r--vala-xmpp/src/module/xep/0060_pubsub.vala107
-rw-r--r--vala-xmpp/src/module/xep/0084_user_avatars.vala93
-rw-r--r--vala-xmpp/src/module/xep/0085_chat_state_notifications.vala74
-rw-r--r--vala-xmpp/src/module/xep/0115_entitiy_capabilities.vala125
-rw-r--r--vala-xmpp/src/module/xep/0184_message_delivery_receipts.vala62
-rw-r--r--vala-xmpp/src/module/xep/0199_ping.vala56
-rw-r--r--vala-xmpp/src/module/xep/0203_delayed_delivery.vala70
-rw-r--r--vala-xmpp/src/module/xep/0280_message_carbons.vala91
-rw-r--r--vala-xmpp/src/module/xep/0333_chat_markers.vala81
-rw-r--r--vala-xmpp/src/module/xep/pixbuf_storage.vala9
22 files changed, 1960 insertions, 0 deletions
diff --git a/vala-xmpp/src/module/xep/0027_pgp/flag.vala b/vala-xmpp/src/module/xep/0027_pgp/flag.vala
new file mode 100644
index 00000000..03844afa
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0027_pgp/flag.vala
@@ -0,0 +1,24 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.Pgp {
+
+public class Flag : XmppStreamFlag {
+ public const string ID = "pgp";
+ public HashMap<string, string> key_ids = new HashMap<string, string>();
+
+ public string? get_key_id(string jid) { return key_ids[get_bare_jid(jid)]; }
+
+ public void set_key_id(string jid, string key) { key_ids[get_bare_jid(jid)] = key; }
+
+ public static Flag? get_flag(XmppStream stream) { return (Flag?) stream.get_flag(NS_URI, ID); }
+
+ public static bool has_flag(XmppStream stream) { return get_flag(stream) != null; }
+
+ public override string get_ns() { return NS_URI; }
+
+ public override string get_id() { return ID; }
+}
+
+} \ No newline at end of file
diff --git a/vala-xmpp/src/module/xep/0027_pgp/module.vala b/vala-xmpp/src/module/xep/0027_pgp/module.vala
new file mode 100644
index 00000000..fee6b9e4
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0027_pgp/module.vala
@@ -0,0 +1,206 @@
+using GPG;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.Pgp {
+ private const string NS_URI = "jabber:x";
+ private const string NS_URI_ENCRYPTED = NS_URI + ":encrypted";
+ private const string NS_URI_SIGNED = NS_URI + ":signed";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0027_current_pgp_usage";
+
+ public signal void received_jid_key_id(XmppStream stream, string jid, string key_id);
+
+ private static Object mutex = new Object();
+
+ private string? signed_status;
+ private string? own_key_id;
+
+ public Module() {
+ GPG.check_version();
+ signed_status = gpg_sign("");
+ if (signed_status != null) own_key_id = gpg_verify(signed_status, "");
+ }
+
+ public bool encrypt(Message.Stanza message, string key_id) {
+ string? enc_body = gpg_encrypt(message.body, new string[] {key_id, own_key_id});
+ if (enc_body != null) {
+ message.stanza.put_node(new StanzaNode.build("x", NS_URI_ENCRYPTED).add_self_xmlns().put_node(new StanzaNode.text(enc_body)));
+ message.body = "[This message is OpenPGP encrypted (see XEP-0027)]";
+ return true;
+ }
+ return false;
+ }
+
+ public string? get_cyphertext(Message.Stanza message) {
+ StanzaNode? x_node = message.stanza.get_subnode("x", NS_URI_ENCRYPTED);
+ return x_node == null ? null : x_node.get_string_content();
+ }
+
+ public override void attach(XmppStream stream) {
+ Presence.Module.require(stream);
+ Presence.Module.get_module(stream).received_presence.connect(on_received_presence);
+ Presence.Module.get_module(stream).pre_send_presence_stanza.connect(on_pre_send_presence_stanza);
+ Message.Module.require(stream);
+ Message.Module.get_module(stream).pre_received_message.connect(on_pre_received_message);
+ stream.add_flag(new Flag());
+ }
+
+ public override void detach(XmppStream stream) {
+ Presence.Module.get_module(stream).received_presence.disconnect(on_received_presence);
+ Presence.Module.get_module(stream).pre_send_presence_stanza.disconnect(on_pre_send_presence_stanza);
+ Message.Module.get_module(stream).pre_received_message.disconnect(on_pre_received_message);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void on_received_presence(XmppStream stream, Presence.Stanza presence) {
+ StanzaNode x_node = presence.stanza.get_subnode("x", NS_URI_SIGNED);
+ if (x_node != null) {
+ string? sig = x_node.get_string_content();
+ if (sig != null) {
+ string signed_data = presence.status == null ? "" : presence.status;
+ string? key_id = gpg_verify(sig, signed_data);
+ if (key_id != null) {
+ Flag.get_flag(stream).set_key_id(presence.from, key_id);
+ received_jid_key_id(stream, presence.from, key_id);
+ }
+ }
+ }
+ }
+
+ private void on_pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence) {
+ if (presence.type_ == Presence.Stanza.TYPE_AVAILABLE && signed_status != null) {
+ presence.stanza.put_node(new StanzaNode.build("x", NS_URI_SIGNED).add_self_xmlns().put_node(new StanzaNode.text(signed_status)));
+ }
+ }
+
+ private void on_pre_received_message(XmppStream stream, Message.Stanza message) {
+ string? encrypted = get_cyphertext(message);
+ if (encrypted != null) {
+ MessageFlag flag = new MessageFlag();
+ message.add_flag(flag);
+ string? decrypted = gpg_decrypt(encrypted);
+ if (decrypted != null) {
+ flag.decrypted = true;
+ message.body = decrypted;
+ }
+ }
+ }
+
+ private static string? gpg_encrypt(string plain, string[] key_ids) {
+ lock (mutex) {
+ GPG.Context context;
+ GPGError.ErrorCode e = GPG.Context.Context(out context); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ context.set_armor(true);
+
+ Key[] keys = new Key[key_ids.length];
+ for (int i = 0; i < key_ids.length; i++) {
+ Key key;
+ e = context.get_key(key_ids[i], out key, false); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ keys[i] = key;
+ }
+
+ GPG.Data plain_data;
+ e = GPG.Data.create_from_memory(out plain_data, plain.data, false);
+ GPG.Data enc_data;
+ e = GPG.Data.create(out enc_data);
+ e = context.op_encrypt(keys, GPG.EncryptFlags.ALWAYS_TRUST, plain_data, enc_data);
+
+ string encr = get_string_from_data(enc_data);
+ int encryption_start = encr.index_of("\n\n") + 2;
+ return encr.substring(encryption_start, encr.length - "\n-----END PGP MESSAGE-----".length - encryption_start);
+ }
+ }
+
+ private static string? gpg_decrypt(string enc) {
+ lock (mutex) {
+ string armor = "-----BEGIN PGP MESSAGE-----\n\n" + enc + "\n-----END PGP MESSAGE-----";
+
+ GPG.Data enc_data;
+ GPGError.ErrorCode e = GPG.Data.create_from_memory(out enc_data, armor.data, false); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ GPG.Data dec_data;
+ e = GPG.Data.create(out dec_data); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ GPG.Context context;
+ e = GPG.Context.Context(out context); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ e = context.op_decrypt(enc_data, dec_data); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+
+ string plain = get_string_from_data(dec_data);
+ return plain;
+ }
+ }
+
+ private static string? gpg_verify(string sig, string signed_text) {
+ lock (mutex) {
+ string armor = "-----BEGIN PGP MESSAGE-----\n\n" + sig + "\n-----END PGP MESSAGE-----";
+
+ GPG.Data sig_data;
+ GPGError.ErrorCode e = GPG.Data.create_from_memory(out sig_data, armor.data, false); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ GPG.Data plain_data;
+ e = GPG.Data.create(out plain_data); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ GPG.Context context;
+ e = GPG.Context.Context(out context); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ e = context.op_verify(sig_data, null, plain_data); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ GPG.VerifyResult* verify_res = context.op_verify_result();
+ if (verify_res == null || verify_res.signatures == null) return null;
+ return verify_res.signatures.fpr;
+ }
+ }
+
+ private static string? gpg_sign(string status) {
+ lock (mutex) {
+ GPG.Data status_data;
+ GPGError.ErrorCode e = GPG.Data.create_from_memory(out status_data, status.data, false); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ GPG.Data signed_data;
+ e = GPG.Data.create(out signed_data); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ GPG.Context context;
+ e = GPG.Context.Context(out context); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+ e = context.op_sign(status_data, signed_data, GPG.SigMode.CLEAR); if (e != GPGError.ErrorCode.NO_ERROR) return null;
+
+ string signed = get_string_from_data(signed_data);
+ int signature_start = signed.index_of("-----BEGIN PGP SIGNATURE-----");
+ signature_start = signed.index_of("\n\n", signature_start) + 2;
+ return signed.substring(signature_start, signed.length - "\n-----END PGP SIGNATURE-----".length - signature_start);
+ }
+ }
+
+ private static string get_string_from_data(GPG.Data data) {
+ data.seek(0);
+ uint8[] buf = new uint8[256];
+ ssize_t? len = null;
+ string res = "";
+ do {
+ len = data.read(buf);
+ if (len > 0) {
+ string part = (string) buf;
+ part = part.substring(0, (long) len);
+ res += part;
+ }
+ } while (len > 0);
+ return res;
+ }
+ }
+
+ public class MessageFlag : Message.MessageFlag {
+ public const string id = "pgp";
+
+ public bool decrypted = false;
+
+ public static MessageFlag? get_flag(Message.Stanza message) {
+ return (MessageFlag) message.get_flag(NS_URI, id);
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return id; }
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0030_service_discovery/flag.vala b/vala-xmpp/src/module/xep/0030_service_discovery/flag.vala
new file mode 100644
index 00000000..5be9f2eb
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0030_service_discovery/flag.vala
@@ -0,0 +1,33 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.ServiceDiscovery {
+
+public class Flag : XmppStreamFlag {
+ public const string ID = "service_discovery";
+
+ private HashMap<string, ArrayList<string>> entity_features = new HashMap<string, ArrayList<string>>();
+ public ArrayList<string> features = new ArrayList<string>();
+
+ public bool? has_entity_feature(string jid, string feature) {
+ if (!entity_features.has_key(jid)) return null;
+ return entity_features[jid].contains(feature);
+ }
+
+ public void set_entitiy_features(string jid, ArrayList<string> features) {
+ entity_features[jid] = features;
+ }
+
+ public void add_own_feature(string feature) { features.add(feature); }
+
+ public static Flag? get_flag(XmppStream stream) { return (Flag?) stream.get_flag(NS_URI, ID); }
+
+ public static bool has_flag(XmppStream stream) { return get_flag(stream) != null; }
+
+ public override string get_ns() { return NS_URI; }
+
+ public override string get_id() { return ID; }
+}
+
+} \ No newline at end of file
diff --git a/vala-xmpp/src/module/xep/0030_service_discovery/info_result.vala b/vala-xmpp/src/module/xep/0030_service_discovery/info_result.vala
new file mode 100644
index 00000000..7e0f0ea4
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0030_service_discovery/info_result.vala
@@ -0,0 +1,78 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.ServiceDiscovery {
+
+public class InfoResult {
+ public Iq.Stanza iq { get; private set; }
+
+ public ArrayList<string> features {
+ owned get {
+ ArrayList<string> ret = new ArrayList<string>();
+ foreach (StanzaNode feature_node in iq.stanza.get_subnode("query", NS_URI_INFO).get_subnodes("feature", NS_URI_INFO)) {
+ ret.add(feature_node.get_attribute("var", NS_URI_INFO));
+ }
+ return ret;
+ }
+ set {
+ foreach (string feature in value) {
+ add_feature(feature);
+ }
+ }
+ }
+
+ public ArrayList<Identity> identities {
+ owned get {
+ ArrayList<Identity> ret = new ArrayList<Identity>();
+ foreach (StanzaNode feature_node in iq.stanza.get_subnode("query", NS_URI_INFO).get_subnodes("identity", NS_URI_INFO)) {
+ ret.add(new Identity(feature_node.get_attribute("category", NS_URI_INFO),
+ feature_node.get_attribute("type", NS_URI_INFO),
+ feature_node.get_attribute("name", NS_URI_INFO)));
+ }
+ return ret;
+ }
+ set {
+ foreach (Identity identity in value) {
+ add_identity(identity);
+ }
+ }
+ }
+
+ public void add_feature(string feature) {
+ iq.stanza.get_subnode("query", NS_URI_INFO).put_node(new StanzaNode.build("feature", NS_URI_INFO).put_attribute("var", feature));
+ }
+
+ public void add_identity(Identity identity) {
+ StanzaNode identity_node = new StanzaNode.build("identity", NS_URI_INFO)
+ .put_attribute("category", identity.category)
+ .put_attribute("type", identity.type_);
+ if (identity.name != null) {
+ identity_node.put_attribute("name", identity.name);
+ }
+ iq.stanza.get_subnode("query", NS_URI_INFO).put_node(identity_node);
+ }
+
+ private InfoResult.from_iq(Iq.Stanza iq) {
+ this.iq = iq;
+ }
+
+ public InfoResult(Iq.Stanza iq_request) {
+ iq = new Iq.Stanza.result(iq_request);
+ iq.to = iq_request.from;
+ iq.stanza.put_node(new StanzaNode.build("query", NS_URI_INFO).add_self_xmlns());
+ }
+
+ public static InfoResult? create_from_iq(Iq.Stanza iq) {
+ if (iq.is_error()) return null;
+ StanzaNode query_node = iq.stanza.get_subnode("query", NS_URI_INFO);
+ if (query_node == null) return null;
+ StanzaNode feature_node = query_node.get_subnode("feature", NS_URI_INFO);
+ if (feature_node == null) return null;
+ StanzaNode identity_node = query_node.get_subnode("identity", NS_URI_INFO);
+ if (identity_node == null) return null;
+ return new ServiceDiscovery.InfoResult.from_iq(iq);
+ }
+}
+
+} \ No newline at end of file
diff --git a/vala-xmpp/src/module/xep/0030_service_discovery/items_result.vala b/vala-xmpp/src/module/xep/0030_service_discovery/items_result.vala
new file mode 100644
index 00000000..2c29c320
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0030_service_discovery/items_result.vala
@@ -0,0 +1,27 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.ServiceDiscovery {
+
+public class ItemsResult {
+ public Iq.Stanza iq { get; private set; }
+
+ public ArrayList<Item> items {
+ owned get {
+ ArrayList<Item> ret = new ArrayList<Item>();
+ foreach (StanzaNode feature_node in iq.stanza.get_subnode("query", NS_URI_ITEMS).get_subnodes("identity", NS_URI_INFO)) {
+ ret.add(new Item(feature_node.get_attribute("jid", NS_URI_ITEMS),
+ feature_node.get_attribute("name", NS_URI_ITEMS),
+ feature_node.get_attribute("node", NS_URI_ITEMS)));
+ }
+ return ret;
+ }
+ }
+
+ public ItemsResult.from_iq(Iq.Stanza iq) {
+ this.iq = iq;
+ }
+}
+
+} \ No newline at end of file
diff --git a/vala-xmpp/src/module/xep/0030_service_discovery/module.vala b/vala-xmpp/src/module/xep/0030_service_discovery/module.vala
new file mode 100644
index 00000000..109da897
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0030_service_discovery/module.vala
@@ -0,0 +1,137 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.ServiceDiscovery {
+ private const string NS_URI = "http://jabber.org/protocol/disco";
+ public const string NS_URI_INFO = NS_URI + "#info";
+ public const string NS_URI_ITEMS = NS_URI + "#items";
+
+ public class Module : XmppStreamModule, Iq.Handler {
+ public const string ID = "0030_service_discovery_module";
+
+ public ArrayList<Identity> identities = new ArrayList<Identity>();
+
+ public Module.with_identity(string category, string type, string? name = null) {
+ add_identity(category, type, name);
+ }
+
+ public void add_feature(XmppStream stream, string feature) {
+ Flag.get_flag(stream).add_own_feature(feature);
+ }
+
+ public void add_feature_notify(XmppStream stream, string feature) {
+ add_feature(stream, feature + "+notify");
+ }
+
+ public void add_identity(string category, string type, string? name = null) {
+ identities.add(new Identity(category, type, name));
+ }
+
+ public void request_info(XmppStream stream, string jid, InfoResponseListener response_listener) {
+ Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_INFO).add_self_xmlns());
+ iq.to = jid;
+ Iq.Module.get_module(stream).send_iq(stream, iq, new IqInfoResponseListener(response_listener));
+ }
+
+ private class IqInfoResponseListener : Iq.ResponseListener, Object {
+ InfoResponseListener response_listener;
+ public IqInfoResponseListener(InfoResponseListener response_listener) {
+ this.response_listener = response_listener;
+ }
+ public void on_result(XmppStream stream, Iq.Stanza iq) {
+ InfoResult? result = InfoResult.create_from_iq(iq);
+ if (result != null) {
+ Flag.get_flag(stream).set_entitiy_features(iq.from, result.features);
+ response_listener.on_result(stream, result);
+ } else {
+ response_listener.on_error(stream, iq);
+ }
+ }
+ }
+
+ public void request_items(XmppStream stream, string jid, ItemsResponseListener response_listener) {
+ Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("query", NS_URI_ITEMS).add_self_xmlns());
+ iq.to = jid;
+ Iq.Module.get_module(stream).send_iq(stream, iq, new IqItemsResponseListener(response_listener));
+ }
+
+ private class IqItemsResponseListener : Iq.ResponseListener, Object {
+ ItemsResponseListener response_listener;
+ public IqItemsResponseListener(ItemsResponseListener response_listener) { this.response_listener = response_listener; }
+ public void on_result(XmppStream stream, Iq.Stanza iq) {
+ //response_listener.on_result(stream, new ServiceDiscoveryItemsResult.from_iq(iq));
+ }
+ }
+
+ public void on_iq_get(XmppStream stream, Iq.Stanza iq) {
+ StanzaNode? query_node = iq.stanza.get_subnode("query", NS_URI_INFO);
+ if (query_node != null) {
+ send_query_result(stream, iq);
+ }
+ }
+
+ public void on_iq_set(XmppStream stream, Iq.Stanza iq) { }
+
+ public override void attach(XmppStream stream) {
+ Iq.Module.require(stream);
+ Iq.Module.get_module(stream).register_for_namespace(NS_URI_INFO, this);
+ stream.add_flag(new Flag());
+ add_feature(stream, NS_URI_INFO);
+ }
+
+ public override void detach(XmppStream stream) { }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new ServiceDiscovery.Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void send_query_result(XmppStream stream, Iq.Stanza iq_request) {
+ InfoResult query_result = new ServiceDiscovery.InfoResult(iq_request);
+ query_result.features = Flag.get_flag(stream).features;
+ query_result.identities = identities;
+ Iq.Module.get_module(stream).send_iq(stream, query_result.iq, null);
+ }
+ }
+
+ public class Identity {
+ public string category { get; set; }
+ public string type_ { get; set; }
+ public string? name { get; set; }
+
+ public Identity(string category, string type, string? name = null) {
+ this.category = category;
+ this.type_ = type;
+ this.name = name;
+ }
+ }
+
+ public class Item {
+ public string jid;
+ public string? name;
+ public string? node;
+
+ public Item(string jid, string? name = null, string? node = null) {
+ this.jid = jid;
+ this.name = name;
+ this.node = node;
+ }
+ }
+
+ public interface InfoResponseListener : Object {
+ public abstract void on_result(XmppStream stream, InfoResult query_result);
+ public void on_error(XmppStream stream, Iq.Stanza iq) { }
+ }
+
+ public interface ItemsResponseListener : Object {
+ public abstract void on_result(XmppStream stream, ItemsResult query_result);
+ public void on_error(XmppStream stream, Iq.Stanza iq) { }
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0045_muc/flag.vala b/vala-xmpp/src/module/xep/0045_muc/flag.vala
new file mode 100644
index 00000000..6c1ef508
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0045_muc/flag.vala
@@ -0,0 +1,80 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.Muc {
+
+public class Flag : XmppStreamFlag {
+ public const string ID = "muc";
+
+ private HashMap<string, MucEnterListener> enter_listeners = new HashMap<string, MucEnterListener>();
+ private HashMap<string, string> enter_ids = new HashMap<string, string>();
+ private HashMap<string, string> own_nicks = new HashMap<string, string>();
+ private HashMap<string, string> subjects = new HashMap<string, string>();
+ private HashMap<string, string> subjects_by = new HashMap<string, string>();
+ private HashMap<string, string> occupant_real_jids = new HashMap<string, string>();
+ private HashMap<string, string> occupant_affiliation = new HashMap<string, string>();
+ private HashMap<string, string> occupant_role = new HashMap<string, string>();
+
+ public string? get_real_jid(string full_jid) { return occupant_real_jids[full_jid]; }
+
+ public void set_real_jid(string full_jid, string real_jid) { occupant_real_jids[full_jid] = real_jid; }
+
+ public string? get_occupant_affiliation(string full_jid) { return occupant_affiliation[full_jid]; }
+
+ public void set_occupant_affiliation(string full_jid, string affiliation) { occupant_affiliation[full_jid] = affiliation; }
+
+ public string? get_occupant_role(string full_jid) { return occupant_role[full_jid]; }
+
+ public void set_occupant_role(string full_jid, string role) { occupant_role[full_jid] = role; }
+
+ public string? get_muc_nick(string bare_jid) { return own_nicks[bare_jid]; }
+
+ public string? get_enter_id(string bare_jid) { return enter_ids[bare_jid]; }
+
+ public MucEnterListener? get_enter_listener(string bare_jid) { return enter_listeners[bare_jid]; }
+
+ public bool is_muc(string jid) { return own_nicks[jid] != null; }
+
+ public bool is_occupant(string jid) {
+ string bare_jid = get_bare_jid(jid);
+ return own_nicks.has_key(bare_jid) || enter_ids.has_key(bare_jid);
+ }
+
+ public bool is_muc_enter_outstanding() { return enter_ids.size != 0; }
+
+ public string? get_muc_subject(string bare_jid) { return subjects[bare_jid]; }
+
+ public void set_muc_subject(string full_jid, string subject) {
+ string bare_jid = get_bare_jid(full_jid);
+ subjects[bare_jid] = subject;
+ subjects_by[bare_jid] = full_jid;
+ }
+
+ public void start_muc_enter(string bare_jid, string presence_id, MucEnterListener listener) {
+ enter_listeners[bare_jid] = listener;
+ enter_ids[bare_jid] = presence_id;
+ }
+
+ public void finish_muc_enter(string bare_jid, string? nick = null) {
+ if (nick != null) own_nicks[bare_jid] = nick;
+ enter_listeners.unset(bare_jid);
+ enter_ids.unset(bare_jid);
+ }
+
+ public void remove_occupant_info(string full_jid) {
+ occupant_real_jids.unset(full_jid);
+ occupant_affiliation.unset(full_jid);
+ occupant_role.unset(full_jid);
+ }
+
+ public static Flag? get_flag(XmppStream stream) { return (Flag?) stream.get_flag(NS_URI, ID); }
+
+ public static bool has_flag(XmppStream stream) { return get_flag(stream) != null; }
+
+ public override string get_ns() { return NS_URI; }
+
+ public override string get_id() { return ID; }
+}
+
+} \ No newline at end of file
diff --git a/vala-xmpp/src/module/xep/0045_muc/module.vala b/vala-xmpp/src/module/xep/0045_muc/module.vala
new file mode 100644
index 00000000..f9ed9539
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0045_muc/module.vala
@@ -0,0 +1,244 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.Muc {
+
+private const string NS_URI = "http://jabber.org/protocol/muc";
+private const string NS_URI_ADMIN = NS_URI + "#admin";
+private const string NS_URI_USER = NS_URI + "#user";
+
+public const string AFFILIATION_ADMIN = "admin";
+public const string AFFILIATION_MEMBER = "member";
+public const string AFFILIATION_NONE = "none";
+public const string AFFILIATION_OUTCAST = "outcast";
+public const string AFFILIATION_OWNER = "owner";
+
+public const string ROLE_MODERATOR = "moderator";
+public const string ROLE_NONE = "none";
+public const string ROLE_PARTICIPANT = "participant";
+public const string ROLE_VISITOR = "visitor";
+
+public enum MucEnterError {
+ PASSWORD_REQUIRED,
+ NOT_IN_MEMBER_LIST,
+ BANNED,
+ NICK_CONFLICT,
+ OCCUPANT_LIMIT_REACHED,
+ ROOM_DOESNT_EXIST
+}
+
+public class Module : XmppStreamModule {
+ public const string ID = "0045_muc_module";
+
+ public signal void received_occupant_affiliation(XmppStream stream, string jid, string? affiliation);
+ public signal void received_occupant_jid(XmppStream stream, string jid, string? real_jid);
+ public signal void received_occupant_role(XmppStream stream, string jid, string? role);
+ public signal void subject_set(XmppStream stream, string subject, string jid);
+
+ public void enter(XmppStream stream, string bare_jid, string nick, string? password, MucEnterListener listener) {
+ Presence.Stanza presence = new Presence.Stanza();
+ presence.to = bare_jid + "/" + 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)));
+ }
+ presence.stanza.put_node(x_node);
+
+ Muc.Flag.get_flag(stream).start_muc_enter(bare_jid, presence.id, listener);
+
+ Presence.Module.get_module(stream).send_presence(stream, presence);
+ }
+
+ public void exit(XmppStream stream, string jid) {
+ string nick = Flag.get_flag(stream).get_muc_nick(jid);
+ Presence.Stanza presence = new Presence.Stanza();
+ presence.to = jid + "/" + nick;
+ presence.type_ = Presence.Stanza.TYPE_UNAVAILABLE;
+ Presence.Module.get_module(stream).send_presence(stream, presence);
+ }
+
+ public void change_subject(XmppStream stream, string jid, string subject) {
+ Message.Stanza message = new Message.Stanza();
+ message.to = jid;
+ message.type_ = Message.Stanza.TYPE_GROUPCHAT;
+ message.stanza.put_node((new StanzaNode.build("subject")).put_node(new StanzaNode.text(subject)));
+ Message.Module.get_module(stream).send_message(stream, message);
+ }
+
+ public void change_nick(XmppStream stream, string jid, string new_nick) {
+ Presence.Stanza presence = new Presence.Stanza();
+ presence.to = jid + "/" + new_nick;
+ Presence.Module.get_module(stream).send_presence(stream, presence);
+ }
+
+ public void kick(XmppStream stream, string jid, string nick) {
+ change_role(stream, jid, nick, "none");
+ }
+
+ public override void attach(XmppStream stream) {
+ stream.add_flag(new Muc.Flag());
+ Message.Module.require(stream);
+ Message.Module.get_module(stream).received_message.connect(on_received_message);
+ Presence.Module.require(stream);
+ Presence.Module.get_module(stream).received_presence.connect(on_received_presence);
+ Presence.Module.get_module(stream).received_available.connect(on_received_available);
+ Presence.Module.get_module(stream).received_unavailable.connect(on_received_unavailable);
+ if (ServiceDiscovery.Module.get_module(stream) != null) {
+ ServiceDiscovery.Module.get_module(stream).add_feature(stream, NS_URI);
+ }
+ }
+
+ public override void detach(XmppStream stream) {
+ Message.Module.get_module(stream).received_message.disconnect(on_received_message);
+ Presence.Module.get_module(stream).received_presence.disconnect(on_received_presence);
+ Presence.Module.get_module(stream).received_available.disconnect(on_received_available);
+ Presence.Module.get_module(stream).received_unavailable.disconnect(on_received_unavailable);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ Presence.Module.require(stream);
+ if (get_module(stream) == null) stream.add_module(new Muc.Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void change_role(XmppStream stream, string jid, string nick, string new_role) {
+ StanzaNode query = new StanzaNode.build("query", NS_URI_ADMIN).add_self_xmlns();
+ query.put_node(new StanzaNode.build("item", NS_URI_ADMIN).put_attribute("nick", nick, NS_URI_ADMIN).put_attribute("role", new_role, NS_URI_ADMIN));
+ Iq.Stanza iq = new Iq.Stanza.set(query);
+ iq.to = jid;
+ Iq.Module.get_module(stream).send_iq(stream, iq);
+ }
+
+ private void on_received_message(XmppStream stream, Message.Stanza message) {
+ if (message.type_ == Message.Stanza.TYPE_GROUPCHAT) {
+ StanzaNode? subject_node = message.stanza.get_subnode("subject");
+ if (subject_node != null) {
+ string subject = subject_node.get_string_content();
+ Muc.Flag.get_flag(stream).set_muc_subject(message.from, subject);
+ subject_set(stream, subject, message.from);
+ }
+ }
+ }
+
+ private void on_received_presence(XmppStream stream, Presence.Stanza presence) {
+ Flag flag = Flag.get_flag(stream);
+ if (presence.is_error() && flag.is_muc_enter_outstanding() && flag.is_occupant(presence.from)) {
+ string bare_jid = get_bare_jid(presence.from);
+ ErrorStanza? error_stanza = presence.get_error();
+ if (flag.get_enter_id(bare_jid) == error_stanza.original_id) {
+ MucEnterListener listener = flag.get_enter_listener(bare_jid);
+ if (error_stanza.condition == ErrorStanza.CONDITION_NOT_AUTHORIZED && ErrorStanza.TYPE_AUTH == error_stanza.type_) {
+ listener.on_error(MucEnterError.PASSWORD_REQUIRED);
+ } else if (ErrorStanza.CONDITION_REGISTRATION_REQUIRED == error_stanza.condition && ErrorStanza.TYPE_AUTH == error_stanza.type_) {
+ listener.on_error(MucEnterError.NOT_IN_MEMBER_LIST);
+ } else if (ErrorStanza.CONDITION_FORBIDDEN == error_stanza.condition && ErrorStanza.TYPE_AUTH == error_stanza.type_) {
+ listener.on_error(MucEnterError.BANNED);
+ } else if (ErrorStanza.CONDITION_CONFLICT == error_stanza.condition && ErrorStanza.TYPE_CANCEL == error_stanza.type_) {
+ listener.on_error(MucEnterError.NICK_CONFLICT);
+ } else if (ErrorStanza.CONDITION_SERVICE_UNAVAILABLE == error_stanza.condition && ErrorStanza.TYPE_WAIT == error_stanza.type_) {
+ listener.on_error(MucEnterError.OCCUPANT_LIMIT_REACHED);
+ } else if (ErrorStanza.CONDITION_ITEM_NOT_FOUND == error_stanza.condition && ErrorStanza.TYPE_CANCEL == error_stanza.type_) {
+ listener.on_error(MucEnterError.ROOM_DOESNT_EXIST);
+ }
+ flag.finish_muc_enter(bare_jid);
+ }
+ }
+ }
+
+ private void on_received_available(XmppStream stream, Presence.Stanza presence) {
+ Flag flag = Flag.get_flag(stream);
+ if (flag.is_occupant(presence.from)) {
+ StanzaNode? x_node = presence.stanza.get_subnode("x", NS_URI_USER);
+ if (x_node != null) {
+ ArrayList<int> status_codes = get_status_codes(x_node);
+ if (status_codes.contains(StatusCode.SELF_PRESENCE)) {
+ string bare_jid = get_bare_jid(presence.from);
+ flag.get_enter_listener(bare_jid).on_success();
+ flag.finish_muc_enter(bare_jid, get_resource_part(presence.from));
+ }
+ string? affiliation = x_node["item", "affiliation"].val;
+ if (affiliation != null) {
+ received_occupant_affiliation(stream, presence.from, affiliation);
+ }
+ string? jid = x_node["item", "jid"].val;
+ if (jid != null) {
+ flag.set_real_jid(presence.from, jid);
+ received_occupant_jid(stream, presence.from, jid);
+ }
+ string? role = x_node["item", "role"].val;
+ if (role != null) {
+ received_occupant_role(stream, presence.from, role);
+ }
+ }
+ }
+ }
+
+ private void on_received_unavailable(XmppStream stream, string jid) {
+ Flag flag = Flag.get_flag(stream);
+ if (flag.is_occupant(jid)) {
+ flag.remove_occupant_info(jid);
+ }
+ }
+
+ private ArrayList<int> get_status_codes(StanzaNode x_node) {
+ ArrayList<int> ret = new ArrayList<int>();
+ foreach (StanzaNode status_node in x_node.get_subnodes("status", NS_URI_USER)) {
+ ret.add(int.parse(status_node.get_attribute("code")));
+ }
+ return ret;
+ }
+}
+
+public enum StatusCode {
+ /** Inform user that any occupant is allowed to see the user's full JID */
+ JID_VISIBLE = 100,
+ /** Inform user that his or her affiliation changed while not in the room */
+ AFFILIATION_CHANGED = 101,
+ /** Inform occupants that room now shows unavailable members */
+ SHOWS_UNAVIABLE_MEMBERS = 102,
+ /** Inform occupants that room now does not show unavailable members */
+ SHOWS_UNAVIABLE_MEMBERS_NOT = 103,
+ /** Inform occupants that a non-privacy-related room configuration change has occurred */
+ CONFIG_CHANGE_NON_PRIVACY = 104,
+ /** Inform user that presence refers to itself */
+ SELF_PRESENCE = 110,
+ /** Inform occupants that room logging is now enabled */
+ LOGGING_ENABLED = 170,
+ /** Inform occupants that room logging is now disabled */
+ LOGGING_DISABLED = 171,
+ /** Inform occupants that the room is now non-anonymous */
+ NON_ANONYMOUS = 172,
+ /** Inform occupants that the room is now semi-anonymous */
+ SEMI_ANONYMOUS = 173,
+ /** Inform user that a new room has been created */
+ NEW_ROOM_CREATED = 201,
+ /** Inform user that service has assigned or modified occupant's roomnick */
+ MODIFIED_NICK = 210,
+ /** Inform user that he or she has been banned from the room */
+ BANNED = 301,
+ /** Inform all occupants of new room nickname */
+ ROOM_NICKNAME = 303,
+ /** Inform user that he or she has been kicked from the room */
+ KICKED = 307,
+ /** Inform user that he or she is being removed from the room */
+ REMOVED_AFFILIATION_CHANGE = 321,
+ /** Inform user that he or she is being removed from the room because the room has been changed to members-only
+ and the user is not a member */
+ REMOVED_MEMBERS_ONLY = 322,
+ /** Inform user that he or she is being removed from the room because the MUC service is being shut down */
+ REMOVED_SHUTDOWN = 332
+}
+
+public interface MucEnterListener : Object {
+ public abstract void on_success();
+ public abstract void on_error(MucEnterError error);
+}
+
+}
diff --git a/vala-xmpp/src/module/xep/0048_bookmarks/conference.vala b/vala-xmpp/src/module/xep/0048_bookmarks/conference.vala
new file mode 100644
index 00000000..818ab3d0
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0048_bookmarks/conference.vala
@@ -0,0 +1,74 @@
+using Xmpp.Core;
+
+namespace Xmpp.Xep.Bookmarks {
+
+public class Conference {
+
+ public const string ATTRIBUTE_AUTOJOIN = "autojoin";
+ public const string ATTRIBUTE_JID = "jid";
+ public const string ATTRIBUTE_NAME = "name";
+
+ public const string NODE_NICK = "nick";
+ public const string NODE_PASSWORD = "password";
+
+ public StanzaNode stanza_node;
+
+ public bool autojoin {
+ get {
+ string? attr = stanza_node.get_attribute(ATTRIBUTE_AUTOJOIN);
+ return attr == "true" || attr == "1"; // "1" isn't standard, but it's used
+ }
+ set { stanza_node.set_attribute(ATTRIBUTE_AUTOJOIN, value.to_string()); }
+ }
+
+ public string jid {
+ get { return stanza_node.get_attribute(ATTRIBUTE_JID); }
+ set { stanza_node.set_attribute(ATTRIBUTE_JID, value); }
+ }
+
+ public string? name {
+ get { return stanza_node.get_attribute(ATTRIBUTE_NAME); }
+ set { stanza_node.set_attribute(ATTRIBUTE_NAME, value); }
+ }
+
+ public string? nick {
+ get {
+ StanzaNode? nick_node = stanza_node.get_subnode(NODE_NICK);
+ return nick_node == null? null : nick_node.get_string_content();
+ }
+ set {
+ StanzaNode? nick_node = stanza_node.get_subnode(NODE_NICK);
+ if (nick_node == null) {
+ nick_node = new StanzaNode.build(NODE_NICK, NS_URI);
+ stanza_node.put_node(nick_node);
+ }
+ nick_node.put_node(new StanzaNode.text(value));
+ }
+ }
+
+ public string? password {
+ get {
+ StanzaNode? password_node = stanza_node.get_subnode(NODE_PASSWORD);
+ return password_node == null? null : password_node.get_string_content();
+ }
+ set {
+ StanzaNode? password_node = stanza_node.get_subnode(NODE_PASSWORD);
+ if (password_node == null) {
+ password_node = new StanzaNode.build(NODE_PASSWORD);
+ stanza_node.put_node(password_node);
+ }
+ password_node.put_node(new StanzaNode.text(value));
+ }
+ }
+
+ public Conference.from_stanza_node(StanzaNode stanza_node) {
+ this.stanza_node = stanza_node;
+ }
+
+ public Conference(string jid) {
+ this.stanza_node = new StanzaNode.build("conference", NS_URI);
+ this.jid = jid;
+ }
+}
+
+} \ No newline at end of file
diff --git a/vala-xmpp/src/module/xep/0048_bookmarks/module.vala b/vala-xmpp/src/module/xep/0048_bookmarks/module.vala
new file mode 100644
index 00000000..d7767208
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0048_bookmarks/module.vala
@@ -0,0 +1,137 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.Bookmarks {
+private const string NS_URI = "storage:bookmarks";
+
+public class Module : XmppStreamModule {
+ public const string ID = "0048_bookmarks_module";
+
+ public signal void conferences_updated(XmppStream stream, ArrayList<Conference> conferences);
+
+ public void get_conferences(XmppStream stream, ConferencesRetrieveResponseListener response_listener) {
+ StanzaNode get_node = new StanzaNode.build("storage", NS_URI).add_self_xmlns();
+ PrivateXmlStorage.Module.get_module(stream).retrieve(stream, get_node, new GetConferences(response_listener));
+ }
+
+ public void set_conferences(XmppStream stream, ArrayList<Conference> conferences) {
+ StanzaNode storage_node = (new StanzaNode.build("storage", NS_URI)).add_self_xmlns();
+ foreach (Conference conference in conferences) {
+ storage_node.put_node(conference.stanza_node);
+ }
+ PrivateXmlStorage.Module.get_module(stream).store(stream, storage_node, new StoreResponseListenerImpl(conferences));
+ }
+
+ private class StoreResponseListenerImpl : PrivateXmlStorage.StoreResponseListener, Object {
+ ArrayList<Conference> conferences;
+ public StoreResponseListenerImpl(ArrayList<Conference> conferences) {
+ this.conferences = conferences;
+ }
+ public void on_success(XmppStream stream) {
+ Module.get_module(stream).conferences_updated(stream, conferences);
+ }
+ }
+
+ public void add_conference(XmppStream stream, Conference add) {
+ get_conferences(stream, new AddConference(add));
+ }
+
+ public void replace_conference(XmppStream stream, Conference was, Conference modified) {
+ get_conferences(stream, new ModifyConference(was, modified));
+ }
+
+ public void remove_conference(XmppStream stream, Conference conference) {
+ get_conferences(stream, new RemoveConference(conference));
+ }
+
+ private class GetConferences : PrivateXmlStorage.RetrieveResponseListener, Object {
+ ConferencesRetrieveResponseListener response_listener;
+
+ public GetConferences(ConferencesRetrieveResponseListener response_listener) {
+ this.response_listener = response_listener;
+ }
+
+ public void on_result(XmppStream stream, StanzaNode node) {
+ response_listener.on_result(stream, get_conferences_from_stanza(node));
+ }
+ }
+
+ private class AddConference : ConferencesRetrieveResponseListener, Object {
+ private Conference conference;
+ public AddConference(Conference conference) {
+ this.conference = conference;
+ }
+ public void on_result(XmppStream stream, ArrayList<Conference> conferences) {
+ conferences.add(conference);
+ Module.get_module(stream).set_conferences(stream, conferences);
+ }
+ }
+
+ private class ModifyConference : ConferencesRetrieveResponseListener, Object {
+ private Conference was;
+ private Conference modified;
+ public ModifyConference(Conference was, Conference modified) {
+ this.was = was;
+ this.modified = modified;
+ }
+ public void on_result(XmppStream stream, ArrayList<Conference> conferences) {
+ foreach (Conference conference in conferences) {
+ if (conference.name == was.name && conference.jid == was.jid && conference.autojoin == was.autojoin) {
+ conference.autojoin = modified.autojoin;
+ conference.name = modified.name;
+ conference.jid = modified.jid;
+ break;
+ }
+ }
+ Module.get_module(stream).set_conferences(stream, conferences);
+ }
+ }
+
+ private class RemoveConference : ConferencesRetrieveResponseListener, Object {
+ private Conference remove;
+ public RemoveConference(Conference remove) {
+ this.remove = remove;
+ }
+ public void on_result(XmppStream stream, ArrayList<Conference> conferences) {
+ Conference? rem = null;
+ foreach (Conference conference in conferences) {
+ if (conference.name == remove.name && conference.jid == remove.jid && conference.autojoin == remove.autojoin) {
+ rem = conference;
+ }
+ }
+ if (rem != null) conferences.remove(rem);
+ Module.get_module(stream).set_conferences(stream, conferences);
+ }
+ }
+
+ public override void attach(XmppStream stream) { }
+
+ public override void detach(XmppStream stream) { }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stderr.printf("");
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private static ArrayList<Conference> get_conferences_from_stanza(StanzaNode node) {
+ ArrayList<Conference> conferences = new ArrayList<Conference>();
+ ArrayList<StanzaNode> conferenceNodes = node.get_subnode("storage", NS_URI).get_subnodes("conference", NS_URI);
+ foreach (StanzaNode conferenceNode in conferenceNodes) {
+ conferences.add(new Conference.from_stanza_node(conferenceNode));
+ }
+ return conferences;
+ }
+}
+
+public interface ConferencesRetrieveResponseListener : Object {
+ public abstract void on_result(XmppStream stream, ArrayList<Conference> conferences);
+}
+
+}
diff --git a/vala-xmpp/src/module/xep/0049_private_xml_storage.vala b/vala-xmpp/src/module/xep/0049_private_xml_storage.vala
new file mode 100644
index 00000000..c57acdde
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0049_private_xml_storage.vala
@@ -0,0 +1,65 @@
+using Xmpp.Core;
+
+namespace Xmpp.Xep.PrivateXmlStorage {
+ private const string NS_URI = "jabber:iq:private";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0049_private_xml_storage";
+
+ public void store(XmppStream stream, StanzaNode node, StoreResponseListener listener) {
+ StanzaNode queryNode = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(node);
+ Iq.Stanza iq = new Iq.Stanza.set(queryNode);
+ Iq.Module.get_module(stream).send_iq(stream, iq, new IqStoreResponse(listener));
+ }
+
+ private class IqStoreResponse : Iq.ResponseListener, Object {
+ StoreResponseListener listener;
+ public IqStoreResponse(StoreResponseListener listener) {
+ this.listener = listener;
+ }
+ public void on_result(XmppStream stream, Iq.Stanza iq) {
+ listener.on_success(stream);
+ }
+ }
+
+ public void retrieve(XmppStream stream, StanzaNode node, RetrieveResponseListener responseListener) {
+ StanzaNode queryNode = new StanzaNode.build("query", NS_URI).add_self_xmlns().put_node(node);
+ Iq.Stanza iq = new Iq.Stanza.get(queryNode);
+ Iq.Module.get_module(stream).send_iq(stream, iq, new IqRetrieveResponse(responseListener));
+ }
+
+ private class IqRetrieveResponse : Iq.ResponseListener, Object {
+ RetrieveResponseListener response_listener;
+ public IqRetrieveResponse(RetrieveResponseListener response_listener) { this.response_listener = response_listener; }
+
+ public void on_result(XmppStream stream, Iq.Stanza iq) {
+ response_listener.on_result(stream, iq.stanza.get_subnode("query", NS_URI));
+ }
+ }
+
+ public override void attach(XmppStream stream) {
+ Iq.Module.require(stream);
+ }
+
+ public override void detach(XmppStream stream) { }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new PrivateXmlStorage.Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+ }
+
+ public interface StoreResponseListener : Object {
+ public abstract void on_success(XmppStream stream);
+ }
+
+ public interface RetrieveResponseListener : Object {
+ public abstract void on_result(XmppStream stream, StanzaNode stanzaNode);
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0054_vcard/module.vala b/vala-xmpp/src/module/xep/0054_vcard/module.vala
new file mode 100644
index 00000000..58b71d2c
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0054_vcard/module.vala
@@ -0,0 +1,87 @@
+using Xmpp.Core;
+
+namespace Xmpp.Xep.VCard {
+private const string NS_URI = "vcard-temp";
+private const string NS_URI_UPDATE = NS_URI + ":x:update";
+
+public class Module : XmppStreamModule {
+ public const string ID = "0027_current_pgp_usage";
+
+ public signal void received_avatar(XmppStream stream, string jid, string id);
+
+ private PixbufStorage storage;
+
+ public Module(PixbufStorage storage) {
+ this.storage = storage;
+ }
+
+ public override void attach(XmppStream stream) {
+ Iq.Module.require(stream);
+ Presence.Module.require(stream);
+ Presence.Module.get_module(stream).received_presence.connect(on_received_presence);
+ }
+
+ public override void detach(XmppStream stream) {
+ Presence.Module.get_module(stream).received_presence.disconnect(on_received_presence);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stderr.printf("VCardModule required but not attached!\n"); ;
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void on_received_presence(XmppStream stream, Presence.Stanza presence) {
+ StanzaNode? update_node = presence.stanza.get_subnode("x", NS_URI_UPDATE);
+ if (update_node == null) return;
+ StanzaNode? photo_node = update_node.get_subnode("photo", NS_URI_UPDATE);
+ if (photo_node == null) return;
+ string? sha1 = photo_node.get_string_content();
+ if (sha1 == null) return;
+ if (storage.has_image(sha1)) {
+ if (Muc.Flag.get_flag(stream).is_occupant(presence.from)) {
+ received_avatar(stream, presence.from, sha1);
+ } else {
+ received_avatar(stream, get_bare_jid(presence.from), sha1);
+ }
+ } else {
+ Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("vCard", NS_URI).add_self_xmlns());
+ if (Muc.Flag.get_flag(stream).is_occupant(presence.from)) {
+ iq.to = presence.from;
+ } else {
+ iq.to = get_bare_jid(presence.from);
+ }
+ Iq.Module.get_module(stream).send_iq(stream, iq, new IqResponseListenerImpl(this, storage, sha1));
+ }
+ }
+
+ private class IqResponseListenerImpl : Iq.ResponseListener, Object {
+ Module outer;
+ PixbufStorage storage;
+ string id;
+ public IqResponseListenerImpl(Module outer, PixbufStorage storage, string id) {
+ this.outer = outer;
+ this.id = id;
+ this.storage = storage;
+ }
+ public void on_result(XmppStream stream, Iq.Stanza iq) {
+ if (iq.is_error()) return;
+ StanzaNode? vcard_node = iq.stanza.get_subnode("vCard", NS_URI);
+ if (vcard_node == null) return;
+ StanzaNode? photo_node = vcard_node.get_subnode("PHOTO", NS_URI);
+ if (photo_node == null) return;
+ StanzaNode? binary_node = photo_node.get_subnode("BINVAL", NS_URI);
+ if (binary_node == null) return;
+ string? content = binary_node.get_string_content();
+ if (content == null) return;
+ storage.store(id, Base64.decode(content));
+ outer.received_avatar(stream, iq.from, id);
+ }
+ }
+}
+}
diff --git a/vala-xmpp/src/module/xep/0060_pubsub.vala b/vala-xmpp/src/module/xep/0060_pubsub.vala
new file mode 100644
index 00000000..3f96e7a1
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0060_pubsub.vala
@@ -0,0 +1,107 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.Pubsub {
+ private const string NS_URI = "http://jabber.org/protocol/pubsub";
+ private const string NS_URI_EVENT = NS_URI + "#event";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0060_pubsub_module";
+
+ private HashMap<string, EventListener> event_listeners = new HashMap<string, EventListener>();
+
+ public void add_filtered_notification(XmppStream stream, string node, EventListener listener) {
+ ServiceDiscovery.Module.get_module(stream).add_feature_notify(stream, node);
+ event_listeners[node] = listener;
+ }
+
+ public void request(XmppStream stream, string jid, string node, RequestResponseListener listener) { // TODO multiple nodes gehen auch
+ Iq.Stanza a = new Iq.Stanza.get(new StanzaNode.build("pubsub", NS_URI).add_self_xmlns().put_node(new StanzaNode.build("items", NS_URI).put_attribute("node", node)));
+ a.to = jid;
+ Iq.Module.get_module(stream).send_iq(stream, a, new IqRequestResponseListener(listener));
+ }
+
+ private class IqRequestResponseListener : Iq.ResponseListener, Object {
+ RequestResponseListener response_listener;
+ public IqRequestResponseListener(RequestResponseListener response_listener) { this.response_listener = response_listener; }
+ public void on_result(XmppStream stream, Iq.Stanza iq) {
+ StanzaNode event_node = iq.stanza.get_subnode("pubsub", NS_URI); if (event_node == null) return;
+ StanzaNode items_node = event_node.get_subnode("items", NS_URI); if (items_node == null) return;
+ StanzaNode item_node = items_node.get_subnode("item", NS_URI); if (item_node == null) return;
+ string node = items_node.get_attribute("node", NS_URI);
+ string id = item_node.get_attribute("id", NS_URI);
+ response_listener.on_result(stream, iq.from, id, item_node.sub_nodes[0]);
+ }
+ }
+
+ public void publish(XmppStream stream, string? jid, string node_id, string node, string item_id, StanzaNode content) {
+ StanzaNode pubsub_node = new StanzaNode.build("pubsub", NS_URI).add_self_xmlns();
+ StanzaNode publish_node = new StanzaNode.build("publish", NS_URI).put_attribute("node", node_id);
+ pubsub_node.put_node(publish_node);
+ StanzaNode items_node = new StanzaNode.build("item", NS_URI).put_attribute("id", item_id);
+ items_node.put_node(content);
+ publish_node.put_node(items_node);
+ Iq.Stanza iq = new Iq.Stanza.set(pubsub_node);
+ Iq.Module.get_module(stream).send_iq(stream, iq, null);
+ }
+
+ private class IqPublishResponseListener : Iq.ResponseListener, Object {
+ PublishResponseListener response_listener;
+ public IqPublishResponseListener(PublishResponseListener response_listener) { this.response_listener = response_listener; }
+ public void on_result(XmppStream stream, Iq.Stanza iq) {
+ if (iq.is_error()) {
+ response_listener.on_error(stream);
+ } else {
+ response_listener.on_success(stream);
+ }
+ }
+ }
+
+ public override void attach(XmppStream stream) {
+ Iq.Module.require(stream);
+ Message.Module.require(stream);
+ ServiceDiscovery.Module.require(stream);
+ Message.Module.get_module(stream).received_message.connect(on_received_message);
+ }
+
+ public override void detach(XmppStream stream) {
+ Message.Module.get_module(stream).received_message.disconnect(on_received_message);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void on_received_message(XmppStream stream, Message.Stanza message) {
+ StanzaNode event_node = message.stanza.get_subnode("event", NS_URI_EVENT); if (event_node == null) return;
+ StanzaNode items_node = event_node.get_subnode("items", NS_URI_EVENT); if (items_node == null) return;
+ StanzaNode item_node = items_node.get_subnode("item", NS_URI_EVENT); if (item_node == null) return;
+ string node = items_node.get_attribute("node", NS_URI_EVENT);
+ string id = item_node.get_attribute("id", NS_URI_EVENT);
+ if (event_listeners.has_key(node)) {
+ event_listeners[node].on_result(stream, message.from, id, item_node.sub_nodes[0]);
+ }
+ }
+ }
+
+ public interface RequestResponseListener : Object {
+ public abstract void on_result(XmppStream stream, string jid, string id, StanzaNode node);
+ }
+
+ public interface EventListener : Object {
+ public abstract void on_result(XmppStream stream, string jid, string id, StanzaNode node);
+ }
+
+ public interface PublishResponseListener : Object {
+ public abstract void on_success(XmppStream stream);
+ public abstract void on_error(XmppStream stream);
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0084_user_avatars.vala b/vala-xmpp/src/module/xep/0084_user_avatars.vala
new file mode 100644
index 00000000..13d19674
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0084_user_avatars.vala
@@ -0,0 +1,93 @@
+using Xmpp.Core;
+
+namespace Xmpp.Xep.UserAvatars {
+ private const string NS_URI = "urn:xmpp:avatar";
+ private const string NS_URI_DATA = NS_URI + ":data";
+ private const string NS_URI_METADATA = NS_URI + ":metadata";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0084_user_avatars";
+
+ public signal void received_avatar(XmppStream stream, string jid, string id);
+
+ private PixbufStorage storage;
+
+ public Module(PixbufStorage storage) {
+ this.storage = storage;
+ }
+
+ public void publish_png(XmppStream stream, uint8[] image, int width, int height) {
+ string sha1 = Checksum.compute_for_data(ChecksumType.SHA1, image);
+ StanzaNode data_node = new StanzaNode.build("data", NS_URI_DATA).add_self_xmlns()
+ .put_node(new StanzaNode.text(Base64.encode(image)));
+ Pubsub.Module.get_module(stream).publish(stream, null, NS_URI_DATA, NS_URI_DATA, sha1, data_node);
+
+ StanzaNode metadata_node = new StanzaNode.build("metadata", NS_URI_METADATA).add_self_xmlns();
+ StanzaNode info_node = new StanzaNode.build("info", NS_URI_METADATA)
+ .put_attribute("bytes", image.length.to_string())
+ .put_attribute("id", sha1)
+ .put_attribute("width", width.to_string())
+ .put_attribute("height", height.to_string())
+ .put_attribute("type", "image/png");
+ metadata_node.put_node(info_node);
+ Pubsub.Module.get_module(stream).publish(stream, null, NS_URI_METADATA, NS_URI_METADATA, sha1, metadata_node);
+ }
+
+ private class PublishResponseListenerImpl : Pubsub.PublishResponseListener, Object {
+ PublishResponseListener listener;
+ PublishResponseListenerImpl other;
+ public PublishResponseListenerImpl(PublishResponseListener listener, PublishResponseListenerImpl other) {
+ this.listener = listener;
+ this.other = other;
+ }
+ public void on_success(XmppStream stream) { listener.on_success(stream); }
+ public void on_error(XmppStream stream) { listener.on_error(stream); }
+ }
+
+ public override void attach(XmppStream stream) {
+ Pubsub.Module.require(stream);
+ Pubsub.Module.get_module(stream).add_filtered_notification(stream, NS_URI_METADATA, new PubsubEventListenerImpl(storage));
+ }
+
+ public override void detach(XmppStream stream) { }
+
+ class PubsubEventListenerImpl : Pubsub.EventListener, Object {
+ PixbufStorage storage;
+ public PubsubEventListenerImpl(PixbufStorage storage) { this.storage = storage; }
+ public void on_result(XmppStream stream, string jid, string id, StanzaNode node) {
+ StanzaNode info_node = node.get_subnode("info", NS_URI_METADATA);
+ if (info_node.get_attribute("type") != "image/png") return;
+ if (storage.has_image(id)) {
+ Module.get_module(stream).received_avatar(stream, jid, id);
+ } else {
+ Pubsub.Module.get_module(stream).request(stream, jid, NS_URI_DATA, new PubsubRequestResponseListenerImpl(storage));
+ }
+ }
+ }
+
+ class PubsubRequestResponseListenerImpl : Pubsub.RequestResponseListener, Object {
+ PixbufStorage storage;
+ public PubsubRequestResponseListenerImpl(PixbufStorage storage) { this.storage = storage; }
+ public void on_result(XmppStream stream, string jid, string id, StanzaNode node) {
+ storage.store(id, Base64.decode(node.get_string_content()));
+ Module.get_module(stream).received_avatar(stream, jid, id);
+ }
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stderr.printf("UserAvatarsModule required but not attached!\n");
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+ }
+
+ public interface PublishResponseListener : Object {
+ public abstract void on_success(XmppStream stream);
+ public abstract void on_error(XmppStream stream);
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0085_chat_state_notifications.vala b/vala-xmpp/src/module/xep/0085_chat_state_notifications.vala
new file mode 100644
index 00000000..cefc7a18
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0085_chat_state_notifications.vala
@@ -0,0 +1,74 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.ChatStateNotifications {
+private const string NS_URI = "http://jabber.org/protocol/chatstates";
+
+public const string STATE_ACTIVE = "active";
+public const string STATE_INACTIVE = "inactive";
+public const string STATE_GONE = "gone";
+public const string STATE_COMPOSING = "composing";
+public const string STATE_PAUSED = "paused";
+
+private const string[] STATES = {STATE_ACTIVE, STATE_INACTIVE, STATE_GONE, STATE_COMPOSING, STATE_PAUSED};
+
+public class Module : XmppStreamModule {
+ public const string ID = "0085_chat_state_notifications";
+
+ public signal void chat_state_received(XmppStream stream, string jid, string state);
+
+ /**
+ * "A message stanza that does not contain standard messaging content [...] SHOULD be a state other than <active/>" (0085, 5.6)
+ */
+ public void send_state(XmppStream stream, string jid, string state) {
+ Message.Stanza message = new Message.Stanza();
+ message.to = jid;
+ message.type_ = Message.Stanza.TYPE_CHAT;
+ message.stanza.put_node(new StanzaNode.build(state, NS_URI).add_self_xmlns());
+ Message.Module.get_module(stream).send_message(stream, message);
+ }
+
+ public override void attach(XmppStream stream) {
+ ServiceDiscovery.Module.require(stream);
+ ServiceDiscovery.Module.get_module(stream).add_feature(stream, NS_URI);
+ Message.Module.get_module(stream).pre_send_message.connect(on_pre_send_message);
+ Message.Module.get_module(stream).received_message.connect(on_received_message);
+ }
+
+ public override void detach(XmppStream stream) {
+ Message.Module.get_module(stream).pre_send_message.disconnect(on_pre_send_message);
+ Message.Module.get_module(stream).received_message.disconnect(on_received_message);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new Module()); ;
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void on_pre_send_message(XmppStream stream, Message.Stanza message) {
+ if (message.body == null) return;
+ if (message.type_ != Message.Stanza.TYPE_CHAT) return;
+ message.stanza.put_node(new StanzaNode.build(STATE_ACTIVE, NS_URI).add_self_xmlns());
+ }
+
+ private void on_received_message(XmppStream stream, Message.Stanza message) {
+ if (!message.is_error()) {
+ ArrayList<StanzaNode> nodes = message.stanza.get_all_subnodes();
+ foreach (StanzaNode node in nodes) {
+ if (node.ns_uri == NS_URI &&
+ node.name in STATES) {
+ chat_state_received(stream, message.from, node.name);
+ }
+ }
+ }
+ }
+}
+
+}
diff --git a/vala-xmpp/src/module/xep/0115_entitiy_capabilities.vala b/vala-xmpp/src/module/xep/0115_entitiy_capabilities.vala
new file mode 100644
index 00000000..472eb9bd
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0115_entitiy_capabilities.vala
@@ -0,0 +1,125 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.EntityCapabilities {
+ private const string NS_URI = "http://jabber.org/protocol/caps";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0115_entity_capabilities";
+
+ private string own_ver_hash;
+ private Storage storage;
+
+ public Module(Storage storage) {
+ this.storage = storage;
+ }
+
+ private string get_own_hash(XmppStream stream) {
+ if (own_ver_hash == null) {
+ own_ver_hash = compute_hash(ServiceDiscovery.Module.get_module(stream).identities, ServiceDiscovery.Flag.get_flag(stream).features);
+ }
+ return own_ver_hash;
+ }
+
+ public override void attach(XmppStream stream) {
+ ServiceDiscovery.Module.require(stream);
+ Presence.Module.require(stream);
+ Presence.Module.get_module(stream).pre_send_presence_stanza.connect(on_pre_send_presence_stanza);
+ Presence.Module.get_module(stream).received_presence.connect(on_received_presence);
+ ServiceDiscovery.Module.get_module(stream).add_feature(stream, NS_URI);
+ }
+
+ public override void detach(XmppStream stream) {
+ Presence.Module.get_module(stream).pre_send_presence_stanza.disconnect(on_pre_send_presence_stanza);
+ Presence.Module.get_module(stream).received_presence.disconnect(on_received_presence);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stderr.printf("EntityCapabilitiesModule required but not attached!\n");
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void on_pre_send_presence_stanza(XmppStream stream, Presence.Stanza presence) {
+ if (presence.type_ == Presence.Stanza.TYPE_AVAILABLE) {
+ presence.stanza.put_node(new StanzaNode.build("c", NS_URI).add_self_xmlns()
+ .put_attribute("hash", "sha-1")
+ .put_attribute("node", "http://dino-im.org")
+ .put_attribute("ver", get_own_hash(stream)));
+ }
+ }
+
+ private void on_received_presence(XmppStream stream, Presence.Stanza presence) {
+ StanzaNode? c_node = presence.stanza.get_subnode("c", NS_URI);
+ if (c_node != null) {
+ string ver_attribute = c_node.get_attribute("ver", NS_URI);
+ ArrayList<string> capabilities = storage.get_features(ver_attribute);
+ if (capabilities.size == 0) {
+ ServiceDiscovery.Module.get_module(stream)
+ .request_info(stream, presence.from, new ServiceDiscoveryInfoResponseListenerImpl(storage, ver_attribute));
+ } else {
+ ServiceDiscovery.Flag.get_flag(stream).set_entitiy_features(presence.from, capabilities);
+ }
+ }
+ }
+
+ private class ServiceDiscoveryInfoResponseListenerImpl : ServiceDiscovery.InfoResponseListener, Object {
+ private Storage storage;
+ private string entity;
+
+ public ServiceDiscoveryInfoResponseListenerImpl(Storage storage, string entity) {
+ this.storage = storage;
+ this.entity = entity;
+ }
+ public void on_result(XmppStream stream, ServiceDiscovery.InfoResult query_result) {
+ if (compute_hash(query_result.identities, query_result.features) == entity) {
+ storage.store_features(entity, query_result.features);
+ }
+ }
+ }
+
+ private static string compute_hash(ArrayList<ServiceDiscovery.Identity> identities, ArrayList<string> features) {
+ identities.sort(compare_identities);
+ features.sort();
+
+ string s = "";
+ foreach (ServiceDiscovery.Identity identity in identities) {
+ string s_identity = identity.category + "/" + identity.type_ + "//";
+ if (identity.name != null) s_identity += identity.name;
+ s_identity += "<";
+ s += s_identity;
+ }
+ foreach (string feature in features) {
+ s += feature + "<";
+ }
+
+ Checksum c = new Checksum(ChecksumType.SHA1);
+ c.update(s.data, -1);
+ size_t size = 20;
+ uint8[] buf = new uint8[size];
+ c.get_digest(buf, ref size);
+
+ return Base64.encode(buf);
+ }
+
+ private static int compare_identities(ServiceDiscovery.Identity a, ServiceDiscovery.Identity b) {
+ int category_comp = a.category.collate(b.category);
+ if (category_comp != 0) return category_comp;
+ int type_comp = a.type_.collate(b.type_);
+ if (type_comp != 0) return type_comp;
+ // TODO lang
+ return 0;
+ }
+ }
+
+ public interface Storage : Object {
+ public abstract void store_features(string entitiy, ArrayList<string> capabilities);
+ public abstract ArrayList<string> get_features(string entitiy);
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0184_message_delivery_receipts.vala b/vala-xmpp/src/module/xep/0184_message_delivery_receipts.vala
new file mode 100644
index 00000000..489592fa
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0184_message_delivery_receipts.vala
@@ -0,0 +1,62 @@
+using Gdk;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.MessageDeliveryReceipts {
+ private const string NS_URI = "urn:xmpp:receipts";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0184_message_delivery_receipts";
+
+ public signal void receipt_received(XmppStream stream, string jid, string id);
+
+ public override void attach(XmppStream stream) {
+ ServiceDiscovery.Module.require(stream);
+ Message.Module.require(stream);
+
+ ServiceDiscovery.Module.get_module(stream).add_feature(stream, NS_URI);
+ Message.Module.get_module(stream).received_message.connect(received_message);
+ Message.Module.get_module(stream).pre_send_message.connect(pre_send_message);
+ }
+
+ public override void detach(XmppStream stream) {
+ Message.Module.get_module(stream).received_message.disconnect(received_message);
+ Message.Module.get_module(stream).pre_send_message.disconnect(pre_send_message);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void received_message(XmppStream stream, Message.Stanza message) {
+ StanzaNode? received_node = message.stanza.get_subnode("received", NS_URI);
+ if (received_node != null) {
+ receipt_received(stream, message.from, received_node.get_attribute("id", NS_URI));
+ } else if (message.stanza.get_subnode("request", NS_URI) != null) {
+ send_received(stream, message);
+ }
+ }
+
+ private void send_received(XmppStream stream, Message.Stanza message) {
+ Message.Stanza received_message = new Message.Stanza();
+ received_message.to = message.from;
+ received_message.stanza.put_node(new StanzaNode.build("received", NS_URI).add_self_xmlns().put_attribute("id", message.id));
+ Message.Module.get_module(stream).send_message(stream, received_message);
+ }
+
+ private void pre_send_message(XmppStream stream, Message.Stanza message) {
+ StanzaNode? received_node = message.stanza.get_subnode("received", NS_URI);
+ if (received_node != null) return;
+ if (message.body == null) return;
+ if (message.type_ == Message.Stanza.TYPE_GROUPCHAT) return;
+ message.stanza.put_node(new StanzaNode.build("request", NS_URI).add_self_xmlns());
+ }
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0199_ping.vala b/vala-xmpp/src/module/xep/0199_ping.vala
new file mode 100644
index 00000000..82da1d23
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0199_ping.vala
@@ -0,0 +1,56 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.Ping {
+ private const string NS_URI = "urn:xmpp:ping";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0199_ping";
+
+ public void send_ping(XmppStream stream, string jid, ResponseListener? listener = null) {
+ Iq.Stanza iq = new Iq.Stanza.get(new StanzaNode.build("ping", NS_URI).add_self_xmlns());
+ iq.to = jid;
+ Iq.Module.get_module(stream).send_iq(stream, iq, listener == null? null : new IqResponseListenerImpl(listener));
+ }
+
+ private class IqResponseListenerImpl : Iq.ResponseListener, Object {
+ ResponseListener listener;
+ public IqResponseListenerImpl(ResponseListener listener) {
+ this.listener = listener;
+ }
+ public void on_result(XmppStream stream, Iq.Stanza iq) {
+ listener.on_result(stream);
+ }
+ }
+
+ public override void attach(XmppStream stream) {
+ Iq.Module.require(stream);
+ Iq.Module.get_module(stream).register_for_namespace(NS_URI, new IqHandlerImpl());
+ }
+
+ public override void detach(XmppStream stream) { }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private class IqHandlerImpl : Iq.Handler, Object {
+ public void on_iq_get(XmppStream stream, Iq.Stanza iq) {
+ Iq.Module.get_module(stream).send_iq(stream, new Iq.Stanza.result(iq));
+ }
+ public void on_iq_set(XmppStream stream, Iq.Stanza iq) { }
+ }
+ }
+
+ public interface ResponseListener : Object {
+ public abstract void on_result(XmppStream stream);
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0203_delayed_delivery.vala b/vala-xmpp/src/module/xep/0203_delayed_delivery.vala
new file mode 100644
index 00000000..528b0017
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0203_delayed_delivery.vala
@@ -0,0 +1,70 @@
+using Xmpp.Core;
+
+namespace Xmpp.Xep.DelayedDelivery {
+ private const string NS_URI = "urn:xmpp:delay";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0203_delayed_delivery";
+
+ public static DateTime? get_send_time(Message.Stanza message) {
+ StanzaNode? delay_node = message.stanza.get_subnode("delay", NS_URI);
+ if (delay_node != null) {
+ string time = delay_node.get_attribute("stamp");
+ return new DateTime.utc(int.parse(time.substring(0, 4)),
+ int.parse(time.substring(5, 2)),
+ int.parse(time.substring(8, 2)),
+ int.parse(time.substring(11, 2)),
+ int.parse(time.substring(14, 2)),
+ int.parse(time.substring(17, 2)));
+ } else {
+ return null;
+ }
+ }
+
+ public override void attach(XmppStream stream) {
+ Message.Module.get_module(stream).pre_received_message.connect(on_pre_received_message);
+ }
+
+ public override void detach(XmppStream stream) { }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void on_pre_received_message(XmppStream stream, Message.Stanza message) {
+ StanzaNode? delay_node = message.stanza.get_subnode("delay", NS_URI);
+ if (delay_node != null) {
+ string time = delay_node.get_attribute("stamp");
+ DateTime datetime = new DateTime.utc(int.parse(time.substring(0, 4)),
+ int.parse(time.substring(5, 2)),
+ int.parse(time.substring(8, 2)),
+ int.parse(time.substring(11, 2)),
+ int.parse(time.substring(14, 2)),
+ int.parse(time.substring(17, 2)));
+ message.add_flag(new MessageFlag(datetime));
+ }
+ }
+ }
+
+ public class MessageFlag : Message.MessageFlag {
+ public const string ID = "delayed_delivery";
+
+ DateTime datetime;
+
+ public MessageFlag(DateTime datetime) {
+ this.datetime = datetime;
+ }
+
+ public static MessageFlag? get_flag(Message.Stanza message) { return (MessageFlag) message.get_flag(NS_URI, ID); }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0280_message_carbons.vala b/vala-xmpp/src/module/xep/0280_message_carbons.vala
new file mode 100644
index 00000000..18b2ecdf
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0280_message_carbons.vala
@@ -0,0 +1,91 @@
+using Xmpp.Core;
+
+namespace Xmpp.Xep.MessageCarbons {
+ private const string NS_URI = "urn:xmpp:carbons:2";
+
+ public class Module : XmppStreamModule {
+ public const string ID = "0280_message_carbons_module";
+
+ public void enable(XmppStream stream) {
+ Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("enable", NS_URI).add_self_xmlns());
+ Iq.Module.get_module(stream).send_iq(stream, iq);
+ }
+
+ public void disable(XmppStream stream) {
+ Iq.Stanza iq = new Iq.Stanza.set(new StanzaNode.build("disable", NS_URI).add_self_xmlns());
+ Iq.Module.get_module(stream).send_iq(stream, iq);
+ }
+
+ public override void attach(XmppStream stream) {
+ Bind.Module.require(stream);
+ Iq.Module.require(stream);
+ Message.Module.require(stream);
+ ServiceDiscovery.Module.require(stream);
+
+ stream.stream_negotiated.connect(enable);
+ Message.Module.get_module(stream).pre_received_message.connect(pre_received_message);
+ ServiceDiscovery.Module.get_module(stream).add_feature(stream, NS_URI);
+ }
+
+ public override void detach(XmppStream stream) {
+ stream.stream_negotiated.disconnect(enable);
+ Message.Module.get_module(stream).pre_received_message.disconnect(pre_received_message);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void pre_received_message(XmppStream stream, Message.Stanza message) {
+ StanzaNode? received_node = message.stanza.get_subnode("received", NS_URI);
+ StanzaNode? sent_node = received_node == null ? message.stanza.get_subnode("sent", NS_URI) : null;
+ StanzaNode? carbons_node = received_node != null ? received_node : sent_node;
+ if (carbons_node != null) {
+ StanzaNode? forwarded_node = carbons_node.get_subnode("forwarded", "urn:xmpp:forward:0");
+ if (forwarded_node != null) {
+ StanzaNode? message_node = forwarded_node.get_subnode("message", Message.NS_URI);
+ string? from_attribute = message_node.get_attribute("from", Message.NS_URI);
+ // The security model assumed by this document is that all of the resources for a single user are in the same trust boundary.
+ // Any forwarded copies received by a Carbons-enabled client MUST be from that user's bare JID; any copies that do not meet this requirement MUST be ignored.
+ if (from_attribute != null && from_attribute == get_bare_jid(Bind.Flag.get_flag(stream).my_jid)) {
+ if (received_node != null) {
+ message.add_flag(new MessageFlag(MessageFlag.TYPE_RECEIVED));
+ } else if (sent_node != null) {
+ message.add_flag(new MessageFlag(MessageFlag.TYPE_SENT));
+ }
+ message.stanza = message_node;
+ message.rerun_parsing = true;
+ }
+ message.stanza = message_node;
+ message.rerun_parsing = true;
+ }
+ }
+ }
+ }
+
+ public class MessageFlag : Message.MessageFlag {
+ public const string id = "message_carbons";
+
+ public const string TYPE_RECEIVED = "received";
+ public const string TYPE_SENT = "sent";
+ private string type_;
+
+ public MessageFlag(string type) {
+ this.type_ = type;
+ }
+
+ public static MessageFlag? get_flag(Message.Stanza message) {
+ return (MessageFlag) message.get_flag(NS_URI, id);
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return id; }
+ }
+}
diff --git a/vala-xmpp/src/module/xep/0333_chat_markers.vala b/vala-xmpp/src/module/xep/0333_chat_markers.vala
new file mode 100644
index 00000000..0dc0e637
--- /dev/null
+++ b/vala-xmpp/src/module/xep/0333_chat_markers.vala
@@ -0,0 +1,81 @@
+using Gee;
+
+using Xmpp.Core;
+
+namespace Xmpp.Xep.ChatMarkers {
+private const string NS_URI = "urn:xmpp:chat-markers:0";
+
+public const string MARKER_RECEIVED = "received";
+public const string MARKER_DISPLAYED = "displayed";
+public const string MARKER_ACKNOWLEDGED = "acknowledged";
+
+private const string[] MARKERS = {MARKER_RECEIVED, MARKER_DISPLAYED, MARKER_ACKNOWLEDGED};
+
+public class Module : XmppStreamModule {
+ public const string ID = "0333_chat_markers";
+
+ public signal void marker_received(XmppStream stream, string jid, string marker, string id);
+
+ public void send_marker(XmppStream stream, string jid, string message_id, string type_, string marker) {
+ Message.Stanza received_message = new Message.Stanza();
+ received_message.to = jid;
+ received_message.type_ = type_;
+ received_message.stanza.put_node(new StanzaNode.build(marker, NS_URI).add_self_xmlns().put_attribute("id", message_id));
+ Message.Module.get_module(stream).send_message(stream, received_message);
+ }
+
+ public static bool requests_marking(Message.Stanza message) {
+ StanzaNode markable_node = message.stanza.get_subnode("markable", NS_URI);
+ return markable_node != null;
+ }
+
+ public override void attach(XmppStream stream) {
+ Iq.Module.require(stream);
+ Message.Module.require(stream);
+ ServiceDiscovery.Module.require(stream);
+
+ ServiceDiscovery.Module.get_module(stream).add_feature(stream, NS_URI);
+ Message.Module.get_module(stream).pre_send_message.connect(on_pre_send_message);
+ Message.Module.get_module(stream).received_message.connect(on_received_message);
+ }
+
+ public override void detach(XmppStream stream) {
+ Message.Module.get_module(stream).pre_send_message.disconnect(on_pre_send_message);
+ Message.Module.get_module(stream).received_message.disconnect(on_received_message);
+ }
+
+ public static Module? get_module(XmppStream stream) {
+ return (Module?) stream.get_module(NS_URI, ID);
+ }
+
+ public static void require(XmppStream stream) {
+ if (get_module(stream) == null) stream.add_module(new ChatMarkers.Module());
+ }
+
+ public override string get_ns() { return NS_URI; }
+ public override string get_id() { return ID; }
+
+ private void on_received_message(XmppStream stream, Message.Stanza message) {
+ if (message.type_ != Message.Stanza.TYPE_CHAT) return;
+ if (requests_marking(message)) {
+ send_marker(stream, message.from, message.id, message.type_, MARKER_RECEIVED);
+ return;
+ }
+ ArrayList<StanzaNode> nodes = message.stanza.get_all_subnodes();
+ foreach (StanzaNode node in nodes) {
+ if (node.ns_uri == NS_URI && node.name in MARKERS) {
+ marker_received(stream, message.from, node.name, node.get_attribute("id", NS_URI));
+ }
+ }
+ }
+
+ private void on_pre_send_message(XmppStream stream, Message.Stanza message) {
+ StanzaNode? received_node = message.stanza.get_subnode("received", NS_URI);
+ if (received_node != null) return;
+ if (message.body == null) return;
+ if (message.type_ != Message.Stanza.TYPE_CHAT) return;
+ message.stanza.put_node(new StanzaNode.build("markable", NS_URI).add_self_xmlns());
+ }
+}
+
+}
diff --git a/vala-xmpp/src/module/xep/pixbuf_storage.vala b/vala-xmpp/src/module/xep/pixbuf_storage.vala
new file mode 100644
index 00000000..0caf4924
--- /dev/null
+++ b/vala-xmpp/src/module/xep/pixbuf_storage.vala
@@ -0,0 +1,9 @@
+using Gdk;
+
+namespace Xmpp.Xep {
+public interface PixbufStorage : Object {
+ public abstract void store(string id, uint8[] data);
+ public abstract bool has_image(string id);
+ public abstract Pixbuf? get_image(string id);
+}
+} \ No newline at end of file