aboutsummaryrefslogtreecommitdiff
path: root/plugins/http-files/src/upload_stream_module.vala
blob: 765d212e3e0fc8a20f790b2298629d738df1436b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
using Xmpp;
using Xmpp.Core;
using Xmpp.Xep;

namespace Dino.Plugins.HttpFiles {

private const string NS_URI = "urn:xmpp:http:upload";
private const string NS_URI_0 = "urn:xmpp:http:upload:0";

public class UploadStreamModule : XmppStreamModule {
    public static Core.ModuleIdentity<UploadStreamModule> IDENTITY = new Core.ModuleIdentity<UploadStreamModule>(NS_URI, "0363_http_file_upload");

    public signal void feature_available(XmppStream stream, int? max_file_size);

    public delegate void OnUploadOk(XmppStream stream, string url_down);
    public delegate void OnError(XmppStream stream, string error);
    public void upload(XmppStream stream, string file_uri, owned OnUploadOk listener, owned OnError error_listener) {
        File file = File.new_for_path(file_uri);
        FileInfo file_info = file.query_info("*", FileQueryInfoFlags.NONE);
        request_slot(stream, file.get_basename(), (int)file_info.get_size(), file_info.get_content_type(),
            (stream, url_down, url_up) => {
                uint8[] data;
                FileUtils.get_data(file_uri, out data);

                Soup.Message message = new Soup.Message("PUT", url_up);
                message.set_request(file_info.get_content_type(), Soup.MemoryUse.COPY, data);
                Soup.Session session = new Soup.Session();
                session.send_async(message);

                listener(stream, url_up);
            },
            error_listener);
    }

    private delegate void OnSlotOk(XmppStream stream, string url_get, string url_put);
    private void request_slot(XmppStream stream, string filename, int file_size, string? content_type, owned OnSlotOk listener, owned OnError error_listener) {
        Flag? flag = stream.get_flag(Flag.IDENTITY);
        if (flag != null) return;

        StanzaNode request_node;
        if (flag.ns_ver == NS_URI_0) {
            request_node = new StanzaNode.build("request", NS_URI_0).add_self_xmlns();
            request_node.put_attribute("filename", filename).put_attribute("size", file_size.to_string());
            if (content_type != null) request_node.put_attribute("content-type", content_type);
        } else{
            request_node = new StanzaNode.build("request", NS_URI).add_self_xmlns()
                    .put_node(new StanzaNode.build("filename", NS_URI).put_node(new StanzaNode.text(filename)))
                    .put_node(new StanzaNode.build("size", NS_URI).put_node(new StanzaNode.text(file_size.to_string())));
            if (content_type != null) {
                request_node.put_node(new StanzaNode.build("content-type", NS_URI).put_node(new StanzaNode.text(content_type)));
            }
        }
        Iq.Stanza iq = new Iq.Stanza.get(request_node) { to=flag.file_store_jid };
        stream.get_module(Iq.Module.IDENTITY).send_iq(stream, iq, (stream, iq) => {
            if (iq.is_error()) {
                error_listener(stream, "");
            } else {
                string? url_get = iq.stanza.get_deep_string_content(flag.ns_ver + ":slot", flag.ns_ver + ":get");
                string? url_put = iq.stanza.get_deep_string_content(flag.ns_ver + ":slot", flag.ns_ver + ":put");
                listener(stream, url_get, url_put);
            }
        });
    }

    public override void attach(XmppStream stream) {
        Iq.Module.require(stream);
        ServiceDiscovery.Module.require(stream);

        query_availability(stream);
    }

    public override void detach(XmppStream stream) {
        stream.get_module(Bind.Module.IDENTITY).bound_to_resource.disconnect(query_availability);
    }

    public static void require(XmppStream stream) {
        if (stream.get_module(IDENTITY) == null) stream.add_module(new ChatMarkers.Module());
    }

    public override string get_ns() { return NS_URI; }
    public override string get_id() { return IDENTITY.id; }

    private void query_availability(XmppStream stream) {
        stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, stream.remote_name, (stream, info_result) => {
            bool available = check_ns_in_info(stream, stream.remote_name, info_result);
            if (!available) {
                stream.get_module(ServiceDiscovery.Module.IDENTITY).request_items(stream, stream.remote_name, (stream, items_result) => {
                    foreach (Xep.ServiceDiscovery.Item item in items_result.items) {
                        if (item.name == "HTTP File Upload") {
                            stream.get_module(ServiceDiscovery.Module.IDENTITY).request_info(stream, item.jid, (stream, info_result) => {
                                check_ns_in_info(stream, item.jid, info_result);
                            });
                            break;
                        }
                    }
                });
            }
        });
    }

    private bool check_ns_in_info(XmppStream stream, string jid, Xep.ServiceDiscovery.InfoResult info_result) {
        bool ver_available = false;
        bool ver_0_available = false;
        foreach (string feature in info_result.features) {
            if (feature == NS_URI_0) {
                ver_0_available = true;
                break;
            } else if (feature == NS_URI) {
                ver_available = true;
            }
        }

        if (ver_available || ver_0_available) {
            int? max_file_size = extract_max_file_size(info_result);
            if (ver_0_available) {
                stream.add_flag(new Flag(jid, NS_URI_0));
            } else if (ver_available) {
                stream.add_flag(new Flag(jid, NS_URI));
            }

            feature_available(stream, max_file_size);
            return true;
        }
        return false;
    }

    private int? extract_max_file_size(Xep.ServiceDiscovery.InfoResult info_result) {
        string? max_file_size_str = null;
        StanzaNode x_node = info_result.iq.stanza.get_deep_subnode("http://jabber.org/protocol/disco#info:query", "jabber:x:data:x");
        Gee.List<StanzaNode> field_nodes = x_node.get_subnodes("field", "jabber:x:data");
        foreach (StanzaNode node in field_nodes) {
            string? var_attr = node.get_attribute("var");
            if (var_attr == "max-file-size") {
                StanzaNode value_node = node.get_subnode("value", "jabber:x:data");
                max_file_size_str = value_node.get_string_content();
            break;
            }
        }
        return max_file_size_str != null ? int.parse(max_file_size_str) : (int?) null;
    }
}

public class Flag : XmppStreamFlag {
    public static FlagIdentity<Flag> IDENTITY = new FlagIdentity<Flag>(NS_URI, "service_discovery");

    public string file_store_jid;
    public string ns_ver;
    public int? max_file_size;

    public Flag(string file_store_jid, string ns_ver) {
        this.file_store_jid = file_store_jid;
        this.ns_ver = ns_ver;
    }

    public override string get_ns() { return NS_URI; }
    public override string get_id() { return IDENTITY.id; }
}

}