Mercurial > prosody-modules
comparison mod_post_msg/mod_post_msg.lua @ 661:a6c8f252e5fa
mod_post_msg: Update to the new HTTP API
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Mon, 30 Apr 2012 13:08:40 +0200 |
| parents | 661f64627fed |
| children | e556219cb43d |
comparison
equal
deleted
inserted
replaced
| 660:aa3fbb33700d | 661:a6c8f252e5fa |
|---|---|
| 1 -- Recive a HTTP POST and relay it | 1 module:depends"http" |
| 2 -- By Kim Alvefur <zash@zash.se> | |
| 3 -- Some code borrowed from mod_webpresence | |
| 4 -- | |
| 5 -- Example usage: | |
| 6 -- curl http://example.com:5280/msg/user -u me@example.com:mypassword -d "Hello there" | |
| 7 -- This would send a message to user@example.com from me@example.com | |
| 8 | |
| 9 | 2 |
| 10 local jid_split = require "util.jid".split; | 3 local jid_split = require "util.jid".split; |
| 11 local jid_prep = require "util.jid".prep; | 4 local jid_prep = require "util.jid".prep; |
| 12 local msg = require "util.stanza".message; | 5 local msg = require "util.stanza".message; |
| 13 local test_password = require "core.usermanager".test_password; | 6 local test_password = require "core.usermanager".test_password; |
| 14 local b64_decode = require "util.encodings".base64.decode; | 7 local b64_decode = require "util.encodings".base64.decode; |
| 15 local urldecode = require "net.http".urldecode; | 8 local formdecode = require "net.http".formdecode; |
| 16 local urlparams = --require "net.http".getQueryParams or whatever MattJ names it | |
| 17 function(s) | |
| 18 if not s:match("=") then return urldecode(s); end | |
| 19 local r = {} | |
| 20 s:gsub("([^=&]*)=([^&]*)", function(k,v) | |
| 21 r[ urldecode(k) ] = urldecode(v); | |
| 22 return nil | |
| 23 end) | |
| 24 return r | |
| 25 end; | |
| 26 | 9 |
| 27 --COMPAT 0.7 | 10 local function require_valid_user(f) |
| 28 if not test_password then | 11 return function(event, path) |
| 29 local validate_credentials = require "core.usermanager".validate_credentials; | 12 local request = event.request; |
| 30 test_password = function(user, host, password) | 13 local response = event.response; |
| 31 return validate_credentials(host, user, password) | 14 local headers = request.headers; |
| 15 if not headers.authorization then | |
| 16 response.headers.www_authenticate = ("Basic realm=%q"):format(module.host.."/"..module.name); | |
| 17 return 401 | |
| 18 end | |
| 19 local from_jid, password = b64_decode(headers.authorization:match"[^ ]*$"):match"([^:]*):(.*)"; | |
| 20 from_jid = jid_prep(from_jid); | |
| 21 if from_jid and password then | |
| 22 local user, host = jid_split(from_jid); | |
| 23 local ok, err = test_password(user, host, password); | |
| 24 if ok and user and host then | |
| 25 module:log("debug", "Authed as %s", from_jid); | |
| 26 return f(event, path, from_jid); | |
| 27 elseif err then | |
| 28 module:log("debug", "User failed authentication: %s", err); | |
| 29 end | |
| 30 end | |
| 31 return 401 | |
| 32 end | 32 end |
| 33 end | 33 end |
| 34 | 34 |
| 35 local function http_response(code, message, extra_headers) | 35 local function handle_post(event, path, authed_user) |
| 36 local response = { | 36 local request = event.request; |
| 37 status = code .. " " .. message; | 37 local response = event.response; |
| 38 body = message .. "\n"; } | 38 local headers = request.headers; |
| 39 if extra_headers then response.headers = extra_headers; end | 39 |
| 40 return response | 40 local body_type = headers.content_type; |
| 41 local to = jid_prep(path); | |
| 42 local message; | |
| 43 if body_type == "text/plain" then | |
| 44 if to and request.body then | |
| 45 message = msg({ to = to, from = authed_user, type = "chat"},request.body); | |
| 46 end | |
| 47 elseif body_type == "application/x-www-form-urlencoded" then | |
| 48 local post_body = formdecode(request.body); | |
| 49 message = msg({ to = post_body.to or to, from = authed_user, | |
| 50 type = post_body.type or "chat"}, post_body.body); | |
| 51 else | |
| 52 return 415; | |
| 53 end | |
| 54 if message and message.attr.to then | |
| 55 module:log("debug", "Sending %s", tostring(message)); | |
| 56 module:send(message); | |
| 57 return 201; | |
| 58 end | |
| 59 return 422; | |
| 41 end | 60 end |
| 42 | 61 |
| 43 local function handle_request(method, body, request) | 62 module:provides("http", { |
| 44 if request.method == "BREW" then return http_response(418, "I'm a teapot"); end | 63 default_path = "/msg"; |
| 45 if request.method ~= "POST" then | 64 route = { |
| 46 return http_response(405, "Method Not Allowed", {["Allow"] = "POST"}); end | 65 ["POST /*"] = require_valid_user(handle_post); |
| 47 | 66 OPTIONS = function(e) |
| 48 -- message to? | 67 local headers = e.response.headers; |
| 49 local path_jid = request.url.path:match("[^/]+$"); | 68 headers.allow = "POST"; |
| 50 if not path_jid or not body then return http_response(400, "Bad Request"); end | 69 headers.accept = "application/x-www-form-urlencoded, text/plain"; |
| 51 local to_user, to_host = jid_split(urldecode(path_jid)); | 70 return 200; |
| 52 if to_host and not to_user and request.headers.host then | 71 end; |
| 53 to_user, to_host = to_host, request.headers.host; | 72 } |
| 54 if to_host then to_host = to_host:gsub(":%d+$", ""); end | 73 }); |
| 55 end | |
| 56 if not to_host or not to_user then return http_response(400, "Bad Request"); end | |
| 57 local to_jid = jid_prep(to_user .. "@" .. to_host) | |
| 58 if not to_jid then return http_response(400, "Bad Request"); end | |
| 59 | |
| 60 -- message from? | |
| 61 if not request.headers["authorization"] then | |
| 62 return http_response(401, "Unauthorized", | |
| 63 {["WWW-Authenticate"]='Basic realm="WallyWorld"'}) | |
| 64 end | |
| 65 local from_jid, password = b64_decode(request.headers.authorization | |
| 66 :match("[^ ]*$") or ""):match("([^:]*):(.*)"); | |
| 67 from_jid = jid_prep(from_jid) | |
| 68 if not from_jid or not password then return http_response(400, "Bad Request"); end | |
| 69 local from_user, from_host = jid_split(from_jid) | |
| 70 if not hosts[from_host] then return http_response(401, "Unauthorized"); end | |
| 71 | |
| 72 -- auth | |
| 73 module:log("debug", "testing authz %s", from_jid) | |
| 74 if not test_password(from_user, from_host, password) then | |
| 75 return http_response(401, "Unauthorized") | |
| 76 end | |
| 77 | |
| 78 -- parse body | |
| 79 local message = {} | |
| 80 local body_type = request.headers["content-type"] | |
| 81 if body_type == "text/plain" then | |
| 82 message = {["body"] = body} | |
| 83 elseif body_type == "application/x-www-form-urlencoded" then | |
| 84 message = urlparams(body) | |
| 85 if type(message) == "string" then | |
| 86 message = {["body"] = message} | |
| 87 end | |
| 88 else | |
| 89 return http_response(415, "Unsupported Media Type") | |
| 90 end | |
| 91 | |
| 92 -- guess type if not set | |
| 93 if not message["type"] then | |
| 94 if message["body"] then | |
| 95 if message["subject"] then | |
| 96 message["type"] = "normal" | |
| 97 else | |
| 98 message["type"] = "chat" | |
| 99 end | |
| 100 elseif not message["body"] and message["subject"] then | |
| 101 message["type"] = "headline" | |
| 102 end | |
| 103 end | |
| 104 | |
| 105 -- build stanza | |
| 106 local stanza = msg({["to"]=to_jid, ["from"]=from_jid, ["type"]=message["type"]}) | |
| 107 if message["body"] then stanza:tag("body"):text(message["body"]):up(); end | |
| 108 if message["subject"] then stanza:tag("subject"):text(message["subject"]):up(); end | |
| 109 | |
| 110 -- and finaly post it | |
| 111 module:log("debug", "message for %s", to_jid) | |
| 112 core_post_stanza(hosts[module.host], stanza) | |
| 113 return http_response(202, "Accepted") | |
| 114 end | |
| 115 | |
| 116 local function setup() | |
| 117 local ports = module:get_option("post_msg_ports") or { 5280 }; | |
| 118 require "net.httpserver".new_from_config(ports, handle_request, { base = "msg" }); | |
| 119 end | |
| 120 if prosody.start_time then -- already started | |
| 121 setup(); | |
| 122 else | |
| 123 prosody.events.add_handler("server-started", setup); | |
| 124 end |
