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); |
