Software /
code /
prosody-modules
File
mod_rest/jsonmap.lib.lua @ 3866:c0df50ce96f0
mod_rest: Handle internal http request errors early and then return
Skips over attempted parsing of the payload which usually failed since
the body is an error string like "connection refused", so this produced
useless errors.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sat, 25 Jan 2020 20:22:12 +0100 |
parent | 3860:9752a6f1b9f3 |
child | 3870:3261a82884bb |
line wrap: on
line source
local array = require "util.array"; local jid = require "util.jid"; local json = require "util.json"; local st = require "util.stanza"; local xml = require "util.xml"; local simple_types = { -- basic message body = "text_tag", subject = "text_tag", thread = "text_tag", -- basic presence show = "text_tag", status = "text_tag", priority = "text_tag", state = {"name", "http://jabber.org/protocol/chatstates"}, nick = {"text_tag", "http://jabber.org/protocol/nick", "nick"}, delay = {"attr", "urn:xmpp:delay", "delay", "stamp"}, replace = {"attr", "urn:xmpp:message-correct:0", "replace", "id"}, -- XEP-0045 MUC -- TODO history, password, ??? join = {"bool_tag", "http://jabber.org/protocol/muc", "x"}, -- XEP-0071 html = { "func", "http://jabber.org/protocol/xhtml-im", "html", function (s) --> json string return (tostring(s:get_child("body", "http://www.w3.org/1999/xhtml")):gsub(" xmlns='[^']*'","", 1)); end; function (s) --> xml if type(s) == "string" then return assert(xml.parse([[<x:html xmlns:x='http://jabber.org/protocol/xhtml-im' xmlns='http://www.w3.org/1999/xhtml'>]]..s..[[</x:html>]])); end end; }; -- XEP-0199: XMPP Ping ping = {"bool_tag", "urn:xmpp:ping", "ping"}, -- XEP-0092: Software Version version = {"func", "jabber:iq:version", "query", function (s) return { name = s:get_child_text("name"); version = s:get_child_text("version"); os = s:get_child_text("os"); } end, function (s) local v = st.stanza("query", { xmlns = "jabber:iq:version" }); if type(s) == "table" then v:text_tag("name", s.name); v:text_tag("version", s.version); if s.os then v:text_tag("os", s.os); end end return v; end }; -- XEP-0030 disco = { "func", "http://jabber.org/protocol/disco#info", "query", function (s) --> array of features local identities, features = array(), array(); for tag in s:childtags() do if tag.name == "identity" and tag.attr.category and tag.attr.type then identities:push({ category = tag.attr.category, type = tag.attr.type, name = tag.attr.name }); elseif tag.name == "feature" and tag.attr.var then features:push(tag.attr.var); end end return { node = s.attr.node, identities = identities, features = features, }; end; function (s) if type(s) == "table" and s ~= json.null then local disco = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info", node = s.node }); if s.identities then for _, identity in ipairs(s.identities) do disco:tag("identity", { category = identity.category, type = identity.type, name = identity.name }):up(); end end if s.features then for _, feature in ipairs(s.features) do disco:tag("feature", { var = feature }):up(); end end return disco; else st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#info", }); end end; }; items = { "func", "http://jabber.org/protocol/disco#items", "query", function (s) --> array of features local items = array(); for item in s:childtags("item") do items:push({ jid = item.attr.jid, node = item.attr.node, name = item.attr.name }); end return items; end; function (s) local disco = st.stanza("query", { xmlns = "http://jabber.org/protocol/disco#items" }); if type(s) == "table" and s ~= json.null then for _, item in ipairs(s) do if type(item) == "string" then disco:tag("item", { jid = item }); elseif type(item) == "table" then disco:tag("item", { jid = item.jid, node = item.node, name = item.name }); end end end return disco; end; }; -- XEP-0066: Out of Band Data oob_url = {"func", "jabber:iq:oob", "query", function (s) return s:get_child_text("url"); end; function (s) if type(s) == "string" then return st.stanza("query", { xmlns = "jabber:iq:oob" }):text_tag("url", s); end end; }; -- XEP-XXXX: User-defined Data Transfer payload = {"func", "urn:xmpp:udt:0", "payload", function (s) local rawjson = s:get_child_text("json", "urn:xmpp:json:0"); if not rawjson then return nil, "missing-json-payload"; end local parsed, err = json.decode(rawjson); if not parsed then return nil, err; end return { datatype = s.attr.datatype; data = parsed; }; end; function (s) if type(s) == "table" then return st.stanza("payload", { xmlns = "urn:xmpp:udt:0", datatype = s.datatype }) :tag("json", { xmlns = "urn:xmpp:json:0" }):text(json.encode(s.data)); end; end }; }; local implied_kinds = { disco = "iq", items = "iq", ping = "iq", version = "iq", body = "message", html = "message", replace = "message", state = "message", subject = "message", thread = "message", join = "presence", priority = "presence", show = "presence", status = "presence", } local kind_by_type = { get = "iq", set = "iq", result = "iq", normal = "message", chat = "message", headline = "message", groupchat = "message", available = "presence", unavailable = "presence", subscribe = "presence", unsubscribe = "presence", subscribed = "presence", unsubscribed = "presence", } local function st2json(s) local t = { kind = s.name, type = s.attr.type, to = s.attr.to, from = s.attr.from, id = s.attr.id, }; if s.name == "presence" and not s.attr.type then t.type = "available"; end if t.to then t.to = jid.prep(t.to); if not t.to then return nil, "invalid-jid-to"; end end if t.from then t.from = jid.prep(t.from); if not t.from then return nil, "invalid-jid-from"; end end if t.type == "error" then local err_typ, err_condition, err_text = s:get_error(); t.error = { type = err_typ, condition = err_condition, text = err_text }; return t; end for k, typ in pairs(simple_types) do if typ == "text_tag" then t[k] = s:get_child_text(k); elseif typ[1] == "text_tag" then t[k] = s:get_child_text(typ[3], typ[2]); elseif typ[1] == "name" then local child = s:get_child(nil, typ[2]); if child then t[k] = child.name; end elseif typ[1] == "attr" then local child = s:get_child(typ[3], typ[2]) if child then t[k] = child.attr[typ[4]]; end elseif typ[1] == "bool_tag" then if s:get_child(typ[3], typ[2]) then t[k] = true; end elseif typ[1] == "func" then local child = s:get_child(typ[3], typ[2] or k); -- TODO handle err if child then t[k] = typ[4](child); end end end return t; end local function str(s) if type(s) == "string" then return s; end end local function json2st(t) if type(t) ~= "table" or not str(next(t)) then return nil, "invalid-json"; end local kind = str(t.kind) or kind_by_type[str(t.type)]; if not kind then for k, implied in pairs(implied_kinds) do if t[k] then kind = implied; break end end end local s = st.stanza(kind or "message", { type = t.type ~= "available" and str(t.type) or nil, to = str(t.to) and jid.prep(t.to); from = str(t.to) and jid.prep(t.from); id = str(t.id), }); if t.to and not s.attr.to then return nil, "invalid-jid-to"; end if t.from and not s.attr.from then return nil, "invalid-jid-from"; end if kind == "iq" and not s.attr.type then s.attr.type = "get"; end if type(t.error) == "table" then return st.error_reply(st.reply(s), str(t.error.type), str(t.error.condition), str(t.error.text)); elseif t.type == "error" then s:text_tag("error", t.body, { code = t.error_code and tostring(t.error_code) }); return s; end for k, v in pairs(t) do local typ = simple_types[k]; if typ then if typ == "text_tag" then s:text_tag(k, v); elseif typ[1] == "text_tag" then s:text_tag(typ[3] or k, v, typ[2] and { xmlns = typ[2] }); elseif typ[1] == "name" then s:tag(v, { xmlns = typ[2] }):up(); elseif typ[1] == "attr" then s:tag(typ[3] or k, { xmlns = typ[2], [ typ[4] or k ] = v }):up(); elseif typ[1] == "bool_tag" then s:tag(typ[3] or k, { xmlns = typ[2] }):up(); elseif typ[1] == "func" then s:add_child(typ[5](v)):up(); end end end s:reset(); return s; end return { st2json = st2json; json2st = json2st; };