aboutsummaryrefslogblamecommitdiff
path: root/xmpp-vala/src/module/xep/0447_stateless_file_sharing.vala
blob: 285bfe782508460fb9ace749f7676267613b7400 (plain) (tree)































































































































































































































                                                                                                                                             
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<HttpSource> extract_sources(StanzaNode node) {
            Gee.List<HttpSource> sources = new Gee.ArrayList<HttpSource>();
            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<SfsSource> sources = new Gee.ArrayList<SfsSource>();

        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<HttpSource> 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<StanzaNode> sources = new Gee.ArrayList<StanzaNode>();
            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<SfsSource> sources = new Gee.ArrayList<SfsSource>();

        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<HttpSource> 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<StanzaNode> sources_nodes = new Gee.ArrayList<StanzaNode>();
            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<Module> IDENTITY = new ModuleIdentity<Module>(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; }
    }
}