Mercurial > prosody-modules
comparison mod_rest/mod_rest.lua @ 4938:bc8832c6696b
upstream merge
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 11 May 2022 12:44:32 +0200 |
parents | c83b009b5bc5 |
children | e7b9bc629ecc |
comparison
equal
deleted
inserted
replaced
4913:3ddab718f717 | 4938:bc8832c6696b |
---|---|
1 -- RESTful API | 1 -- RESTful API |
2 -- | 2 -- |
3 -- Copyright (c) 2019-2020 Kim Alvefur | 3 -- Copyright (c) 2019-2022 Kim Alvefur |
4 -- | 4 -- |
5 -- This file is MIT/X11 licensed. | 5 -- This file is MIT/X11 licensed. |
6 | 6 |
7 local encodings = require "util.encodings"; | 7 local encodings = require "util.encodings"; |
8 local base64 = encodings.base64; | 8 local base64 = encodings.base64; |
228 end | 228 end |
229 return form; | 229 return form; |
230 end | 230 end |
231 | 231 |
232 local function encode(type, s) | 232 local function encode(type, s) |
233 if type == "text/plain" then | |
234 return s:get_child_text("body") or ""; | |
235 elseif type == "application/xmpp+xml" then | |
236 return tostring(s); | |
237 end | |
238 local mapped, err = jsonmap.st2json(s); | |
239 if not mapped then return mapped, err; end | |
233 if type == "application/json" then | 240 if type == "application/json" then |
234 return json.encode(jsonmap.st2json(s)); | 241 return json.encode(mapped); |
235 elseif type == "application/x-www-form-urlencoded" then | 242 elseif type == "application/x-www-form-urlencoded" then |
236 return http.formencode(flatten(jsonmap.st2json(s))); | 243 return http.formencode(flatten(mapped)); |
237 elseif type == "application/cbor" then | 244 elseif type == "application/cbor" then |
238 return cbor.encode(jsonmap.st2json(s)); | 245 return cbor.encode(mapped); |
239 elseif type == "text/plain" then | 246 end |
240 return s:get_child_text("body") or ""; | 247 error "unsupported encoding"; |
241 end | |
242 return tostring(s); | |
243 end | 248 end |
244 | 249 |
245 local post_errors = errors.init("mod_rest", { | 250 local post_errors = errors.init("mod_rest", { |
246 noauthz = { code = 401, type = "auth", condition = "not-authorized", text = "No credentials provided" }, | 251 noauthz = { code = 401, type = "auth", condition = "not-authorized", text = "No credentials provided" }, |
247 unauthz = { code = 403, type = "auth", condition = "not-authorized", text = "Credentials not accepted" }, | 252 unauthz = { code = 403, type = "auth", condition = "not-authorized", text = "Credentials not accepted" }, |
330 | 335 |
331 module:log("debug", "Received[rest]: %s", payload:top_tag()); | 336 module:log("debug", "Received[rest]: %s", payload:top_tag()); |
332 local send_type = decide_type((request.headers.accept or "") ..",".. (request.headers.content_type or ""), supported_outputs) | 337 local send_type = decide_type((request.headers.accept or "") ..",".. (request.headers.content_type or ""), supported_outputs) |
333 | 338 |
334 if echo then | 339 if echo then |
340 local ret, err = errors.coerce(encode(send_type, payload)); | |
341 if not ret then return err; end | |
335 response.headers.content_type = send_type; | 342 response.headers.content_type = send_type; |
336 return encode(send_type, payload); | 343 return ret; |
337 end | 344 end |
338 | 345 |
339 if payload.name == "iq" then | 346 if payload.name == "iq" then |
340 local responses = st.stanza("xmpp"); | 347 local responses = st.stanza("xmpp"); |
341 function origin.send(stanza) | 348 function origin.send(stanza) |
407 }); | 414 }); |
408 | 415 |
409 -- Forward stanzas from XMPP to HTTP and return any reply | 416 -- Forward stanzas from XMPP to HTTP and return any reply |
410 local rest_url = module:get_option_string("rest_callback_url", nil); | 417 local rest_url = module:get_option_string("rest_callback_url", nil); |
411 if rest_url then | 418 if rest_url then |
419 local function get_url() return rest_url; end | |
420 if rest_url:find("%b{}") then | |
421 local httputil = require "util.http"; | |
422 local render_url = require"util.interpolation".new("%b{}", httputil.urlencode); | |
423 function get_url(stanza) | |
424 local at = stanza.attr; | |
425 return render_url(rest_url, { kind = stanza.name, type = at.type, to = at.to, from = at.from }); | |
426 end | |
427 end | |
412 local send_type = module:get_option_string("rest_callback_content_type", "application/xmpp+xml"); | 428 local send_type = module:get_option_string("rest_callback_content_type", "application/xmpp+xml"); |
413 if send_type == "json" then | 429 if send_type == "json" then |
414 send_type = "application/json"; | 430 send_type = "application/json"; |
415 end | 431 end |
416 | 432 |
417 module:set_status("info", "Not yet connected"); | 433 module:set_status("info", "Not yet connected"); |
418 http.request(rest_url, { | 434 http.request(get_url(st.stanza("meta", { type = "info", to = module.host, from = module.host })), { |
419 method = "OPTIONS", | 435 method = "OPTIONS", |
420 }, function (body, code, response) | 436 }, function (body, code, response) |
421 if code == 0 then | 437 if code == 0 then |
422 return module:log_status("error", "Could not connect to callback URL %q: %s", rest_url, body); | 438 module:log_status("error", "Could not connect to callback URL %q: %s", rest_url, body); |
439 elseif code == 200 then | |
440 module:set_status("info", "Connected"); | |
441 if response.headers.accept then | |
442 send_type = decide_type(response.headers.accept, supported_outputs); | |
443 module:log("debug", "Set 'rest_callback_content_type' = %q based on Accept header", send_type); | |
444 end | |
423 else | 445 else |
424 module:set_status("info", "Connected"); | 446 module:log_status("warn", "Unexpected response code %d from OPTIONS probe", code); |
425 end | 447 module:log("warn", "Endpoint said: %s", body); |
426 if code == 200 and response.headers.accept then | |
427 send_type = decide_type(response.headers.accept, supported_outputs); | |
428 module:log("debug", "Set 'rest_callback_content_type' = %q based on Accept header", send_type); | |
429 end | 448 end |
430 end); | 449 end); |
431 | 450 |
432 local code2err = require "net.http.errors".registry; | 451 local code2err = require "net.http.errors".registry; |
433 | 452 |
446 | 465 |
447 -- Keep only the top level element and let the rest be GC'd | 466 -- Keep only the top level element and let the rest be GC'd |
448 stanza = st.clone(stanza, true); | 467 stanza = st.clone(stanza, true); |
449 | 468 |
450 module:log("debug", "Sending[rest]: %s", stanza:top_tag()); | 469 module:log("debug", "Sending[rest]: %s", stanza:top_tag()); |
451 http.request(rest_url, { | 470 http.request(get_url(stanza), { |
452 body = request_body, | 471 body = request_body, |
453 headers = { | 472 headers = { |
454 ["Content-Type"] = send_type, | 473 ["Content-Type"] = send_type, |
455 ["Content-Language"] = stanza.attr["xml:lang"], | 474 ["Content-Language"] = stanza.attr["xml:lang"], |
456 Accept = table.concat(supported_inputs, ", "); | 475 Accept = table.concat(supported_inputs, ", "); |
532 end); | 551 end); |
533 | 552 |
534 return true; | 553 return true; |
535 end | 554 end |
536 | 555 |
537 if module:get_host_type() == "component" then | 556 local send_kinds = module:get_option_set("rest_callback_stanzas", { "message", "presence", "iq" }); |
538 module:hook("iq/bare", handle_stanza, -1); | 557 |
539 module:hook("message/bare", handle_stanza, -1); | 558 local event_presets = { |
540 module:hook("presence/bare", handle_stanza, -1); | 559 -- Don't override everything on normal VirtualHosts by default |
541 module:hook("iq/full", handle_stanza, -1); | 560 ["local"] = { "host" }, |
542 module:hook("message/full", handle_stanza, -1); | 561 -- Comonents get to handle all kinds of stanzas |
543 module:hook("presence/full", handle_stanza, -1); | 562 ["component"] = { "bare", "full", "host" }, |
544 module:hook("iq/host", handle_stanza, -1); | 563 }; |
545 module:hook("message/host", handle_stanza, -1); | 564 local hook_events = module:get_option_set("rest_callback_events", event_presets[module:get_host_type()]); |
546 module:hook("presence/host", handle_stanza, -1); | 565 for kind in send_kinds do |
547 else | 566 for event in hook_events do |
548 -- Don't override everything on normal VirtualHosts | 567 module:hook(kind.."/"..event, handle_stanza, -1); |
549 module:hook("iq/host", handle_stanza, -1); | 568 end |
550 module:hook("message/host", handle_stanza, -1); | |
551 module:hook("presence/host", handle_stanza, -1); | |
552 end | 569 end |
553 end | 570 end |
554 | 571 |
555 local supported_errors = { | 572 local supported_errors = { |
556 "text/html", | 573 "text/html", |