Software / code / prosody-modules
Comparison
mod_json_streams/mod_json_streams.lua @ 353:8ef36af30181
merge with upstream
| author | Phil Stewart <phil.stewart@lichp.co.uk> |
|---|---|
| date | Sun, 03 Apr 2011 22:49:36 +0100 |
| parent | 350:98569ec25ac2 |
comparison
equal
deleted
inserted
replaced
| 347:cd838419a85d | 353:8ef36af30181 |
|---|---|
| 1 -- | |
| 2 -- XEP-0295: JSON Encodings for XMPP | |
| 3 -- | |
| 4 | |
| 5 module.host = "*" | |
| 6 | |
| 7 local httpserver = require "net.httpserver"; | |
| 8 local filters = require "util.filters" | |
| 9 local json = require "util.json" | |
| 10 | |
| 11 local json_escapes = { | |
| 12 ["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f", | |
| 13 ["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"}; | |
| 14 | |
| 15 local s_char = string.char; | |
| 16 for i=0,31 do | |
| 17 local ch = s_char(i); | |
| 18 if not json_escapes[ch] then json_escapes[ch] = ("\\u%.4X"):format(i); end | |
| 19 end | |
| 20 | |
| 21 local state_out = 0; | |
| 22 local state_key_before = 1; | |
| 23 local state_key_in = 2; | |
| 24 local state_key_escape = 3; | |
| 25 local state_key_after = 4; | |
| 26 local state_val_before = 5; | |
| 27 local state_val_in = 6; | |
| 28 local state_val_escape = 7; | |
| 29 local state_val_after = 8; | |
| 30 | |
| 31 local whitespace = { [" "] = true, ["\n"] = true, ["\r"] = true, ["\t"] = true }; | |
| 32 function json_decoder() | |
| 33 local state = state_out; | |
| 34 local quote; | |
| 35 local output = ""; | |
| 36 local buffer = ""; | |
| 37 return function(input) | |
| 38 for ch in input:gmatch(".") do | |
| 39 module:log("debug", "%s | %d", ch, state) | |
| 40 local final = false; | |
| 41 if state == state_out then | |
| 42 if whitespace[ch] then | |
| 43 elseif ch ~= "{" then return nil, "{ expected"; | |
| 44 else state = state_key_before end | |
| 45 elseif state == state_key_before then | |
| 46 if whitespace[ch] then | |
| 47 elseif ch ~= "'" and ch ~= "\"" then return nil, "\" expected"; | |
| 48 else quote = ch; state = state_key_in; end | |
| 49 elseif state == state_key_in then | |
| 50 if ch == quote then state = state_key_after; | |
| 51 elseif ch ~= "s" then return nil, "invalid key, 's' expected"; -- only s as key allowed | |
| 52 else end -- ignore key | |
| 53 elseif state == state_key_after then | |
| 54 if whitespace[ch] then | |
| 55 elseif ch ~= ":" then return nil, ": expected"; | |
| 56 else state = state_val_before; end | |
| 57 elseif state == state_val_before then | |
| 58 if whitespace[ch] then | |
| 59 elseif ch ~= "'" and ch ~= "\"" then return nil, "\" expected"; | |
| 60 else quote = ch; state = state_val_in; end | |
| 61 elseif state == state_val_in then | |
| 62 if ch == quote then state = state_val_after; | |
| 63 elseif ch == "\\" then state = state_val_escape; | |
| 64 else end | |
| 65 elseif state == state_val_after then | |
| 66 if whitespace[ch] then | |
| 67 elseif ch ~= "}" then return nil, "} expected"; | |
| 68 else state = state_out; | |
| 69 final = true; | |
| 70 end | |
| 71 elseif state == state_val_escape then | |
| 72 state = state_val_in; | |
| 73 else | |
| 74 module:log("error", "Unhandled state: "..state); | |
| 75 return nil, "Unhandled state in parser" | |
| 76 end | |
| 77 buffer = buffer..ch; | |
| 78 if final then | |
| 79 module:log("debug", "%s", buffer) | |
| 80 local tmp; | |
| 81 pcall(function() tmp = json.decode(buffer); end); | |
| 82 if not tmp then return nil, "Invalid JSON"; end | |
| 83 output, buffer = output..tmp.s, ""; | |
| 84 end | |
| 85 end | |
| 86 local _ = output; output = ""; | |
| 87 return _; | |
| 88 end; | |
| 89 end | |
| 90 | |
| 91 function filter_hook(session) | |
| 92 local determined = false; | |
| 93 local is_json = false; | |
| 94 local function in_filter(t) | |
| 95 if not determined then | |
| 96 is_json = (t:sub(1,1) == "{") and json_decoder(); | |
| 97 determined = true; | |
| 98 end | |
| 99 if is_json then | |
| 100 local s, err = is_json(t); | |
| 101 if not err then return s; end | |
| 102 session:close("not-well-formed"); | |
| 103 return; | |
| 104 end | |
| 105 return t; | |
| 106 end | |
| 107 local function out_filter(t) | |
| 108 if is_json then | |
| 109 return '{"s":"' .. t:gsub(".", json_escapes) .. '"}'; -- encode | |
| 110 end | |
| 111 return t; | |
| 112 end | |
| 113 filters.add_filter(session, "bytes/in", in_filter, 100); | |
| 114 filters.add_filter(session, "bytes/out", out_filter, 100); | |
| 115 end | |
| 116 | |
| 117 function module.load() | |
| 118 filters.add_filter_hook(filter_hook); | |
| 119 end | |
| 120 function module.unload() | |
| 121 filters.remove_filter_hook(filter_hook); | |
| 122 end | |
| 123 | |
| 124 function encode(data) | |
| 125 if type(data) == "string" then | |
| 126 data = json.encode({ s = data }); | |
| 127 elseif type(data) == "table" and data.body then | |
| 128 data.body = json.encode({ s = data.body }); | |
| 129 data.headers["Content-Type"] = "application/json"; | |
| 130 end | |
| 131 return data; | |
| 132 end | |
| 133 function handle_request(method, body, request) | |
| 134 local mod_bosh = modulemanager.get_module("*", "bosh") | |
| 135 if mod_bosh then | |
| 136 if body and method == "POST" then | |
| 137 pcall(function() body = json.decode(body).s; end); | |
| 138 end | |
| 139 local _send = request.send; | |
| 140 function request:send(data) return _send(self, encode(data)); end | |
| 141 return encode(mod_bosh.handle_request(method, body, request)); | |
| 142 end | |
| 143 return "<html><body>mod_bosh not loaded</body></html>"; | |
| 144 end | |
| 145 | |
| 146 local function setup() | |
| 147 local ports = module:get_option("jsonstreams_ports") or { 5280 }; | |
| 148 httpserver.new_from_config(ports, handle_request, { base = "jsonstreams" }); | |
| 149 end | |
| 150 if prosody.start_time then -- already started | |
| 151 setup(); | |
| 152 else | |
| 153 prosody.events.add_handler("server-started", setup); | |
| 154 end |