using Xmpp; namespace Xmpp.Xep.StatelessFileSharing { public const string STANZA_NAME = "file-transfer"; public interface SfsSource: Object { public abstract string type(); public abstract string serialize(); public abstract StanzaNode to_stanza_node(); } public class HttpSource: Object, SfsSource { public string url; public const string HTTP_NS_URI = "http://jabber.org/protocol/url-data"; public const string HTTP_STANZA_NAME = "url-data"; public const string HTTP_URL_ATTRIBUTE = "target"; public const string SOURCE_TYPE = "http"; public string type() { return SOURCE_TYPE; } public string serialize() { return this.to_stanza_node().to_xml(); } public StanzaNode to_stanza_node() { StanzaNode node = new StanzaNode.build(HTTP_STANZA_NAME, HTTP_NS_URI).add_self_xmlns(); node.put_attribute(HTTP_URL_ATTRIBUTE, this.url); return node; } public static async HttpSource deserialize(string data) { StanzaNode node = yield new StanzaReader.for_string(data).read_stanza_node(); HttpSource source = HttpSource.from_stanza_node(node); assert(source != null); return source; } public static HttpSource? from_stanza_node(StanzaNode node) { string? url = node.get_attribute(HTTP_URL_ATTRIBUTE); if (url == null) { return null; } HttpSource source = new HttpSource(); source.url = url; return source; } public static Gee.List extract_sources(StanzaNode node) { Gee.List sources = new Gee.ArrayList(); foreach (StanzaNode http_node in node.get_subnodes(HTTP_STANZA_NAME, HTTP_NS_URI)) { HttpSource? source = HttpSource.from_stanza_node(http_node); if (source != null) { sources.add(source); } } return sources; } } public class SfsElement { public Xep.FileMetadataElement.FileMetadata metadata = new Xep.FileMetadataElement.FileMetadata(); public Gee.List sources = new Gee.ArrayList(); public static SfsElement? from_stanza_node(StanzaNode node) { SfsElement element = new SfsElement(); StanzaNode? metadata_node = node.get_subnode("file", Xep.FileMetadataElement.NS_URI); if (metadata_node == null) { return null; } Xep.FileMetadataElement.FileMetadata metadata = Xep.FileMetadataElement.FileMetadata.from_stanza_node(metadata_node); if (metadata == null) { return null; } element.metadata = metadata; StanzaNode? sources_node = node.get_subnode("sources"); if (sources_node == null) { return null; } Gee.List sources = HttpSource.extract_sources(sources_node); if (sources.is_empty) { return null; } element.sources = sources; return element; } public StanzaNode to_stanza_node() { StanzaNode node = new StanzaNode.build(STANZA_NAME, NS_URI).add_self_xmlns(); node.put_node(this.metadata.to_stanza_node()); StanzaNode sources_node = new StanzaNode.build("sources", NS_URI); Gee.List sources = new Gee.ArrayList(); foreach (SfsSource source in this.sources) { sources.add(source.to_stanza_node()); } sources_node.sub_nodes = sources; node.put_node(sources_node); return node; } } public class SfsSourceAttachment { public string sfs_id; public Gee.List sources = new Gee.ArrayList(); public const string ATTACHMENT_NS_URI = "urn:xmpp:message-attaching:1"; public const string ATTACH_TO_STANZA_NAME = "attach-to"; public const string SOURCES_STANZA_NAME = "sources"; public const string ID_ATTRIBUTE_NAME = "id"; public static SfsSourceAttachment? from_message_stanza(MessageStanza stanza) { StanzaNode? attach_to = stanza.stanza.get_subnode(ATTACH_TO_STANZA_NAME, ATTACHMENT_NS_URI); StanzaNode? sources = stanza.stanza.get_subnode(SOURCES_STANZA_NAME, NS_URI); if (attach_to == null || sources == null) { return null; } string? id = attach_to.get_attribute(ID_ATTRIBUTE_NAME, ATTACHMENT_NS_URI); if (id == null) { return null; } SfsSourceAttachment attachment = new SfsSourceAttachment(); attachment.sfs_id = id; Gee.List http_sources = HttpSource.extract_sources(sources); if (http_sources.is_empty) { return null; } attachment.sources = http_sources; return attachment; } public MessageStanza to_message_stanza(Jid to, string message_type) { MessageStanza stanza = new MessageStanza() { to=to, type_=message_type }; Xep.MessageProcessingHints.set_message_hint(stanza, Xep.MessageProcessingHints.HINT_STORE); StanzaNode attach_to = new StanzaNode.build(ATTACH_TO_STANZA_NAME, ATTACHMENT_NS_URI); attach_to.add_attribute(new StanzaAttribute.build(ATTACHMENT_NS_URI, "id", this.sfs_id)); stanza.stanza.put_node(attach_to); StanzaNode sources = new StanzaNode.build(SOURCES_STANZA_NAME, NS_URI); Gee.List sources_nodes = new Gee.ArrayList(); foreach (SfsSource source in this.sources) { sources_nodes.add(source.to_stanza_node()); } sources.sub_nodes = sources_nodes; stanza.stanza.put_node(sources); return stanza; } } public class MessageFlag : Xmpp.MessageFlag { public const string ID = "stateless_file_sharing"; public static MessageFlag? get_flag(MessageStanza message) { return (MessageFlag) message.get_flag(NS_URI, ID); } public override string get_ns() { return NS_URI; } public override string get_id() { return ID; } } public class Module : XmppStreamModule { public static ModuleIdentity IDENTITY = new ModuleIdentity(NS_URI, "stateless_file_sharing"); public signal void received_sfs(Jid from, Jid to, SfsElement sfs_element, MessageStanza message); public signal void received_sfs_attachment(Jid from, Jid to, SfsSourceAttachment attachment, MessageStanza message); public void send_stateless_file_transfer(XmppStream stream, MessageStanza sfs_message, SfsElement sfs_element) { StanzaNode sfs_node = sfs_element.to_stanza_node(); printerr(sfs_node.to_ansi_string(true)); sfs_message.stanza.put_node(sfs_node); printerr("Sending message:\n"); printerr(sfs_message.stanza.to_ansi_string(true)); stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, sfs_message); } public void send_stateless_file_transfer_attachment(XmppStream stream, Jid to, string message_type, SfsSourceAttachment attachment) { MessageStanza message = attachment.to_message_stanza(to, message_type); printerr("Sending message:\n"); printerr(message.stanza.to_ansi_string(true)); stream.get_module(MessageModule.IDENTITY).send_message.begin(stream, message); } private void on_received_message(XmppStream stream, MessageStanza message) { StanzaNode? sfs_node = message.stanza.get_subnode(STANZA_NAME, NS_URI); if (sfs_node != null) { SfsElement? sfs_element = SfsElement.from_stanza_node(sfs_node); if (sfs_element == null) { return; } message.add_flag(new MessageFlag()); received_sfs(message.from, message.to, sfs_element, message); } SfsSourceAttachment? attachment = SfsSourceAttachment.from_message_stanza(message); if (attachment != null) { received_sfs_attachment(message.from, message.to, attachment, message); } } public override void attach(XmppStream stream) { stream.get_module(MessageModule.IDENTITY).received_message.connect(on_received_message); } public override void detach(XmppStream stream) { stream.get_module(MessageModule.IDENTITY).received_message.disconnect(on_received_message); } public override string get_ns() { return NS_URI; } public override string get_id() { return IDENTITY.id; } } }