Comparison

mod_rest/mod_rest.lua @ 3810:91ff86fc3b20

mod_rest: Factor out payload parsing To make it easier to add new format parsers in a single place
author Kim Alvefur <zash@zash.se>
date Wed, 01 Jan 2020 15:43:47 +0100
parent 3807:b0449faca52b
child 3811:eb25110696cd
comparison
equal deleted inserted replaced
3809:a70f5a6c7f01 3810:91ff86fc3b20
18 assert(auth_type == "Bearer", "Only 'Bearer' is supported in rest_credentials"); 18 assert(auth_type == "Bearer", "Only 'Bearer' is supported in rest_credentials");
19 19
20 -- Bearer token 20 -- Bearer token
21 local function check_credentials(request) 21 local function check_credentials(request)
22 return request.headers.authorization == secret; 22 return request.headers.authorization == secret;
23 end
24
25 local function parse(mimetype, data)
26 mimetype = mimetype:match("^[^; ]*");
27 if mimetype == "application/xmpp+xml" then
28 return xml.parse(data);
29 elseif mimetype == "text/plain" then
30 return st.message({ type = "chat" }, data);
31 end
32 return nil, "unknown-payload-type";
23 end 33 end
24 34
25 local function handle_post(event) 35 local function handle_post(event)
26 local request, response = event.request, event.response; 36 local request, response = event.request, event.response;
27 if not request.headers.authorization then 37 if not request.headers.authorization then
28 response.headers.www_authenticate = ("%s realm=%q"):format(auth_type, module.host.."/"..module.name); 38 response.headers.www_authenticate = ("%s realm=%q"):format(auth_type, module.host.."/"..module.name);
29 return 401; 39 return 401;
30 elseif not check_credentials(request) then 40 elseif not check_credentials(request) then
31 return 401; 41 return 401;
32 end 42 end
33 if request.headers.content_type ~= "application/xmpp+xml" then 43 local payload, err = parse(request.headers.content_type, request.body);
34 return errors.new({ code = 415, text = "'application/xmpp+xml' expected" });
35 end
36 local payload, err = xml.parse(request.body);
37 if not payload then 44 if not payload then
38 -- parse fail 45 -- parse fail
39 return errors.new({ code = 400, text = err }); 46 return errors.new({ code = 400, text = err });
40 end 47 end
41 if payload.attr.xmlns then 48 if payload.attr.xmlns then
164 }, function (body, code, response) 171 }, function (body, code, response)
165 if (code == 202 or code == 204) and not reply_needed then 172 if (code == 202 or code == 204) and not reply_needed then
166 -- Delivered, no reply 173 -- Delivered, no reply
167 return; 174 return;
168 end 175 end
169 local reply, reply_text; 176 local reply;
170 177
171 if response.headers["content-type"] == "application/xmpp+xml" then 178 local parsed, err = parse(response.headers["content-type"], body);
172 local parsed, err = xml.parse(body); 179 if not parsed then
173 if not parsed then 180 module:log("warn", "Failed parsing data from REST callback: %s, %q", err, body);
174 module:log("warn", "REST callback responded with invalid XML: %s, %q", err, body); 181 elseif parsed.name ~= stanza.name then
175 elseif parsed.name ~= stanza.name then 182 module:log("warn", "REST callback responded with the wrong stanza type, got %s but expected %s", parsed.name, stanza.name);
176 module:log("warn", "REST callback responded with the wrong stanza type, got %s but expected %s", parsed.name, stanza.name); 183 else
177 else 184 parsed.attr = {
178 parsed.attr = { 185 from = stanza.attr.to,
179 from = stanza.attr.to, 186 to = stanza.attr.from,
180 to = stanza.attr.from, 187 id = parsed.attr.id or id.medium();
181 id = parsed.attr.id or id.medium(); 188 type = parsed.attr.type,
182 type = parsed.attr.type, 189 ["xml:lang"] = parsed.attr["xml:lang"],
183 ["xml:lang"] = parsed.attr["xml:lang"], 190 };
184 }; 191 if parsed.name == "iq" or parsed.attr.type == "error" then
185 if parsed.name == "iq" or parsed.attr.type == "error" then 192 parsed.attr.id = stanza.attr.id;
186 parsed.attr.id = stanza.attr.id;
187 end
188 reply = parsed;
189 end 193 end
190 elseif response.headers["content-type"] == "text/plain" then 194 reply = parsed;
191 reply = st.reply(stanza);
192 if body ~= "" then
193 reply_text = body;
194 end
195 elseif body ~= "" then -- ignore empty body
196 module:log("debug", "Callback returned response of unhandled type %q", response.headers["content-type"]);
197 end 195 end
198 196
199 if not reply then 197 if not reply then
200 local code_hundreds = code - (code % 100); 198 local code_hundreds = code - (code % 100);
201 if code_hundreds == 200 then 199 if code_hundreds == 200 then
202 reply = st.reply(stanza); 200 reply = st.reply(stanza);
203 if stanza.name ~= "iq" then 201 if stanza.name ~= "iq" then
204 reply.attr.id = id.medium(); 202 reply.attr.id = id.medium();
205 end 203 end
206 if reply_text and reply.name == "message" then
207 reply:body(reply_text, { ["xml:lang"] = response.headers["content-language"] });
208 end
209 -- TODO presence/status=body ? 204 -- TODO presence/status=body ?
210 elseif code2err[code] then 205 elseif code2err[code] then
211 reply = st.error_reply(stanza, errors.new(code, nil, code2err)); 206 reply = st.error_reply(stanza, errors.new(code, nil, code2err));
212 elseif code_hundreds == 400 then 207 elseif code_hundreds == 400 then
213 reply = st.error_reply(stanza, "modify", "bad-request", reply_text); 208 reply = st.error_reply(stanza, "modify", "bad-request", body);
214 elseif code_hundreds == 500 then 209 elseif code_hundreds == 500 then
215 reply = st.error_reply(stanza, "cancel", "internal-server-error", reply_text); 210 reply = st.error_reply(stanza, "cancel", "internal-server-error", body);
216 else 211 else
217 reply = st.error_reply(stanza, "cancel", "undefined-condition", reply_text); 212 reply = st.error_reply(stanza, "cancel", "undefined-condition", body);
218 end 213 end
219 end 214 end
220 215
221 if receipt then 216 if receipt then
222 reply:add_direct_child(receipt); 217 reply:add_direct_child(receipt);