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
| 4937: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", |
