Mercurial > prosody-modules
comparison mod_bob/mod_bob.lua @ 3342:3aa5419e3576
mod_bob: Add this new module.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Fri, 28 Sep 2018 16:12:18 +0200 |
parents | |
children | 2e65160187a4 |
comparison
equal
deleted
inserted
replaced
3341:1e1dbd7e5b6c | 3342:3aa5419e3576 |
---|---|
1 module:depends("cache_c2s_caps"); | |
2 | |
3 local st = require "util.stanza"; | |
4 local encodings = require "util.encodings"; | |
5 local b64_encode = encodings.base64.encode; | |
6 local b64_decode = encodings.base64.decode; | |
7 local sha1 = require"util.hashes".sha1; | |
8 | |
9 -- TODO: Move that to storage. | |
10 local cache = {}; | |
11 -- TODO: use util.cache for this. | |
12 local in_flight = {}; | |
13 | |
14 local function check_cid(src) | |
15 -- TODO: figure out why ^ can’t be used at the beginning of the pattern. | |
16 for cid in src:gmatch("cid:(%w+%+%w+@bob%.xmpp%.org)$") do | |
17 return cid; | |
18 end | |
19 return nil; | |
20 end | |
21 | |
22 local function handle_data_carrier(tag) | |
23 if tag.name ~= "data" or tag.attr.xmlns ~= "urn:xmpp:bob" then | |
24 return tag; | |
25 end | |
26 local cid = tag.attr.cid; | |
27 local media_type = tag.attr.type; | |
28 local max_age = tag.attr['max-age']; | |
29 local b64_content = tag:get_text(); | |
30 local content = b64_decode(b64_content); | |
31 local hash = sha1(content, true); | |
32 if cid ~= "sha1+"..hash.."@bob.xmpp.org" then | |
33 module:log("debug", "Invalid BoB cid, %s ~= %s", cid, "sha1+"..hash.."@bob.xmpp.org"); | |
34 return nil; | |
35 end | |
36 cache[cid] = { media_type = media_type, max_age = max_age, content = content }; | |
37 if in_flight[cid] then | |
38 local iq = st.iq({ type = "result", id = "fixme" }); | |
39 iq:add_direct_child(tag); | |
40 for jid, data in pairs(in_flight[cid]) do | |
41 iq.attr.from = data.from; | |
42 iq.attr.to = jid; | |
43 iq.attr.id = data.id; | |
44 module:send(iq); | |
45 end | |
46 in_flight[cid] = nil; | |
47 end | |
48 return nil; | |
49 end | |
50 | |
51 local current_id = 0; | |
52 local function send_iq(room_jid, jid, cid, log) | |
53 local iq = st.iq({ type = "get", from = room_jid, to = jid, id = "bob-"..current_id }) | |
54 :tag("data", { xmlns = "urn:xmpp:bob", cid = cid }); | |
55 log("debug", "found BoB image in XHTML-IM, asking %s for cid %s", jid, cid); | |
56 module:send(iq); | |
57 in_flight[cid] = {}; | |
58 end | |
59 | |
60 local function find_images(tag, jid, room_jid, log) | |
61 if tag.name == "img" and tag.attr.xmlns == "http://www.w3.org/1999/xhtml" then | |
62 local src = tag.attr.src; | |
63 local cid = check_cid(src); | |
64 if not cid then | |
65 return; | |
66 end | |
67 if cache[cid] then | |
68 log("debug", "cid %s already found in cache", cid); | |
69 return; | |
70 end | |
71 if in_flight[cid] then | |
72 log("debug", "cid %s already queried", cid); | |
73 return; | |
74 end | |
75 send_iq(room_jid, jid, cid, log); | |
76 return; | |
77 end | |
78 for child in tag:childtags(nil, "http://www.w3.org/1999/xhtml") do | |
79 find_images(child, jid, room_jid, log); | |
80 end | |
81 end | |
82 | |
83 local function message_handler(event) | |
84 local stanza, origin = event.stanza, event.origin; | |
85 local jid = stanza.attr.from; | |
86 local room_jid = stanza.attr.to; | |
87 local log = origin.log or module._log; | |
88 | |
89 -- Remove and cache all <data/> elements embedded here. | |
90 stanza:maptags(handle_data_carrier); | |
91 | |
92 -- Find and query all of the cids not already cached. | |
93 local tag = stanza:get_child("html", "http://jabber.org/protocol/xhtml-im"); | |
94 for body in tag:childtags("body", "http://www.w3.org/1999/xhtml") do | |
95 find_images(body, jid, room_jid, log); | |
96 end | |
97 end | |
98 | |
99 local function handle_data_get(stanza, cid, log) | |
100 local data = cache[cid]; | |
101 if not data then | |
102 log("debug", "BoB requested for data not in cache (cid %s), falling through.", cid); | |
103 if in_flight[cid] then | |
104 log("debug", "But an iq has already been sent, let’s wait…"); | |
105 in_flight[cid][stanza.attr.from] = { id = stanza.attr.id, from = stanza.attr.to }; | |
106 return true; | |
107 end | |
108 return nil; | |
109 end | |
110 | |
111 local iq = st.reply(stanza); | |
112 iq:text_tag("data", b64_encode(data.content), { | |
113 xmlns = "urn:xmpp:bob", | |
114 cid = cid, | |
115 type = data.media_type, | |
116 ['max-age'] = data.max_age, | |
117 }); | |
118 log("debug", "Answering BoB request for cid %s on the behalf of %s", cid, stanza.attr.to); | |
119 module:send(iq); | |
120 return true; | |
121 end | |
122 | |
123 local function iq_handler(event) | |
124 local stanza, origin = event.stanza, event.origin; | |
125 local tag = stanza.tags[1]; | |
126 if tag.name ~= "data" or tag.attr.xmlns ~= "urn:xmpp:bob" then | |
127 return nil; | |
128 end | |
129 local log = origin.log or module._log; | |
130 local cid = tag.attr.cid; | |
131 if not cid then | |
132 log("debug", "BoB iq doesn’t contain a cid attribute."); | |
133 return; | |
134 end | |
135 if stanza.attr.type == "get" then | |
136 return handle_data_get(stanza, cid, log); | |
137 elseif stanza.attr.type == "result" then | |
138 handle_data_carrier(tag); | |
139 return true; | |
140 end | |
141 -- TODO: also handle error iqs. | |
142 end | |
143 | |
144 module:hook("message/bare", message_handler, 1); | |
145 module:hook("iq/full", iq_handler, 1); | |
146 module:hook("iq/bare", iq_handler, 1); |