mod_http_xep227: Add support for import via APIs rather than direct store manipulation
In particular this transitions PEP nodes and data to be imported via mod_pep's
APIs, fixing issues with importing at runtime while PEP data may already be
live in RAM.
Next obvious candidate for this approach is rosters, so clients get immediate
roster pushes and other special handling (such as emitting subscribes to reach
the desired subscription state).
--
-- XEP-0295: JSON Encodings for XMPP
--
module.host = "*"
local httpserver = require "net.httpserver";
local filters = require "util.filters"
local json = require "util.json"
local json_escapes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"};
local s_char = string.char;
for i=0,31 do
local ch = s_char(i);
if not json_escapes[ch] then json_escapes[ch] = ("\\u%.4X"):format(i); end
end
local state_out = 0;
local state_key_before = 1;
local state_key_in = 2;
local state_key_escape = 3;
local state_key_after = 4;
local state_val_before = 5;
local state_val_in = 6;
local state_val_escape = 7;
local state_val_after = 8;
local whitespace = { [" "] = true, ["\n"] = true, ["\r"] = true, ["\t"] = true };
function json_decoder()
local state = state_out;
local quote;
local output = "";
local buffer = "";
return function(input)
for ch in input:gmatch(".") do
module:log("debug", "%s | %d", ch, state)
local final = false;
if state == state_out then
if whitespace[ch] then
elseif ch ~= "{" then return nil, "{ expected";
else state = state_key_before end
elseif state == state_key_before then
if whitespace[ch] then
elseif ch ~= "'" and ch ~= "\"" then return nil, "\" expected";
else quote = ch; state = state_key_in; end
elseif state == state_key_in then
if ch == quote then state = state_key_after;
elseif ch ~= "s" then return nil, "invalid key, 's' expected"; -- only s as key allowed
else end -- ignore key
elseif state == state_key_after then
if whitespace[ch] then
elseif ch ~= ":" then return nil, ": expected";
else state = state_val_before; end
elseif state == state_val_before then
if whitespace[ch] then
elseif ch ~= "'" and ch ~= "\"" then return nil, "\" expected";
else quote = ch; state = state_val_in; end
elseif state == state_val_in then
if ch == quote then state = state_val_after;
elseif ch == "\\" then state = state_val_escape;
else end
elseif state == state_val_after then
if whitespace[ch] then
elseif ch ~= "}" then return nil, "} expected";
else state = state_out;
final = true;
end
elseif state == state_val_escape then
state = state_val_in;
else
module:log("error", "Unhandled state: "..state);
return nil, "Unhandled state in parser"
end
buffer = buffer..ch;
if final then
module:log("debug", "%s", buffer)
local tmp;
pcall(function() tmp = json.decode(buffer); end);
if not tmp then return nil, "Invalid JSON"; end
output, buffer = output..tmp.s, "";
end
end
local _ = output; output = "";
return _;
end;
end
function filter_hook(session)
local determined = false;
local is_json = false;
local function in_filter(t)
if not determined then
is_json = (t:sub(1,1) == "{") and json_decoder();
determined = true;
end
if is_json then
local s, err = is_json(t);
if not err then return s; end
session:close("not-well-formed");
return;
end
return t;
end
local function out_filter(t)
if is_json then
return '{"s":"' .. t:gsub(".", json_escapes) .. '"}'; -- encode
end
return t;
end
filters.add_filter(session, "bytes/in", in_filter, 100);
filters.add_filter(session, "bytes/out", out_filter, 100);
end
function module.load()
filters.add_filter_hook(filter_hook);
end
function module.unload()
filters.remove_filter_hook(filter_hook);
end
function encode(data)
if type(data) == "string" then
data = json.encode({ s = data });
elseif type(data) == "table" and data.body then
data.body = json.encode({ s = data.body });
data.headers["Content-Type"] = "application/json";
end
return data;
end
function handle_request(method, body, request)
local mod_bosh = modulemanager.get_module("*", "bosh")
if mod_bosh then
if body and method == "POST" then
pcall(function() body = json.decode(body).s; end);
end
local _send = request.send;
function request:send(data) return _send(self, encode(data)); end
return encode(mod_bosh.handle_request(method, body, request));
end
return "<html><body>mod_bosh not loaded</body></html>";
end
local function setup()
local ports = module:get_option("jsonstreams_ports") or { 5280 };
httpserver.new_from_config(ports, handle_request, { base = "jsonstreams" });
end
if prosody.start_time then -- already started
setup();
else
prosody.events.add_handler("server-started", setup);
end