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