mod_post_msg/mod_post_msg.lua
changeset 661 a6c8f252e5fa
parent 321 661f64627fed
child 1302 e556219cb43d
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