Changeset

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 660:aa3fbb33700d
children 662:b3d130e4b3ae
files mod_post_msg/mod_post_msg.lua
diffstat 1 files changed, 61 insertions(+), 112 deletions(-) [+]
line wrap: on
line diff
--- a/mod_post_msg/mod_post_msg.lua	Sun Apr 29 20:43:40 2012 +0000
+++ b/mod_post_msg/mod_post_msg.lua	Mon Apr 30 13:08:40 2012 +0200
@@ -1,124 +1,73 @@
--- Recive a HTTP POST and relay it
--- By Kim Alvefur <zash@zash.se>
--- Some code borrowed from mod_webpresence
---
--- Example usage:
---     curl http://example.com:5280/msg/user -u me@example.com:mypassword -d "Hello there"
--- This would send a message to user@example.com from me@example.com
-
+module:depends"http"
 
 local jid_split = require "util.jid".split;
 local jid_prep = require "util.jid".prep;
 local msg = require "util.stanza".message;
 local test_password = require "core.usermanager".test_password;
 local b64_decode = require "util.encodings".base64.decode;
-local urldecode = require "net.http".urldecode;
-local urlparams = --require "net.http".getQueryParams or whatever MattJ names it
-function(s)
-	if not s:match("=") then return urldecode(s); end
-	local r = {}
-	s:gsub("([^=&]*)=([^&]*)", function(k,v)
-		r[ urldecode(k) ] = urldecode(v);
-		return nil
-	end)
-	return r
-end;
+local formdecode = require "net.http".formdecode;
 
---COMPAT 0.7
-if not test_password then
-	local validate_credentials = require "core.usermanager".validate_credentials;
-	test_password = function(user, host, password)
-		return validate_credentials(host, user, password)
+local function require_valid_user(f)
+	return function(event, path)
+		local request = event.request;
+		local response = event.response;
+		local headers = request.headers;
+		if not headers.authorization then
+			response.headers.www_authenticate = ("Basic realm=%q"):format(module.host.."/"..module.name);
+			return 401
+		end
+		local from_jid, password = b64_decode(headers.authorization:match"[^ ]*$"):match"([^:]*):(.*)";
+		from_jid = jid_prep(from_jid);
+		if from_jid and password then
+			local user, host = jid_split(from_jid);
+			local ok, err = test_password(user, host, password);
+			if ok and user and host then
+				module:log("debug", "Authed as %s", from_jid);
+				return f(event, path, from_jid);
+			elseif err then
+				module:log("debug", "User failed authentication: %s", err);
+			end
+		end
+		return 401
 	end
 end
 
-local function http_response(code, message, extra_headers)
-	local response = {
-		status = code .. " " .. message;
-		body = message .. "\n"; }
-	if extra_headers then response.headers = extra_headers; end
-	return response
+local function handle_post(event, path, authed_user)
+	local request = event.request;
+	local response = event.response;
+	local headers = request.headers;
+
+	local body_type = headers.content_type;
+	local to = jid_prep(path);
+	local message;
+	if body_type == "text/plain" then
+		if to and request.body then
+			message = msg({ to = to, from = authed_user, type = "chat"},request.body);
+		end
+	elseif body_type == "application/x-www-form-urlencoded" then
+		local post_body = formdecode(request.body);
+			message = msg({ to = post_body.to or to, from = authed_user,
+				type = post_body.type or "chat"}, post_body.body);
+	else
+		return 415;
+	end
+	if message and message.attr.to then
+		module:log("debug", "Sending %s", tostring(message));
+		module:send(message);
+		return 201;
+	end
+	return 422;
 end
 
-local function handle_request(method, body, request)
-	if request.method == "BREW" then return http_response(418, "I'm a teapot"); end
-	if request.method ~= "POST" then
-		return http_response(405, "Method Not Allowed", {["Allow"] = "POST"}); end
-
-	-- message to?
-	local path_jid = request.url.path:match("[^/]+$");
-	if not path_jid or not body then return http_response(400, "Bad Request"); end
-	local to_user, to_host = jid_split(urldecode(path_jid));
-	if to_host and not to_user and request.headers.host then
-		to_user, to_host = to_host, request.headers.host;
-		if to_host then to_host = to_host:gsub(":%d+$", ""); end
-	end
-	if not to_host or not to_user then return http_response(400, "Bad Request"); end 
-	local to_jid = jid_prep(to_user .. "@" .. to_host)
-	if not to_jid then return http_response(400, "Bad Request"); end 
-
-	-- message from?
-	if not request.headers["authorization"] then
-		return http_response(401, "Unauthorized",
-			{["WWW-Authenticate"]='Basic realm="WallyWorld"'})
-	end
-	local from_jid, password = b64_decode(request.headers.authorization
-			:match("[^ ]*$") or ""):match("([^:]*):(.*)");
-	from_jid = jid_prep(from_jid)
-	if not from_jid or not password then return http_response(400, "Bad Request"); end
-	local from_user, from_host = jid_split(from_jid)
-	if not hosts[from_host] then return http_response(401, "Unauthorized"); end
-
-	-- auth
-	module:log("debug", "testing authz %s", from_jid)
-	if not test_password(from_user, from_host, password) then
-		return http_response(401, "Unauthorized")
-	end
-
-	-- parse body
-	local message = {}
-	local body_type = request.headers["content-type"]
-	if body_type == "text/plain" then
-		message = {["body"] = body}
-	elseif body_type == "application/x-www-form-urlencoded" then
-		message = urlparams(body)
-		if type(message) == "string" then
-			message = {["body"] = message}
-		end
-	else
-		return http_response(415, "Unsupported Media Type")
-	end
-
-	-- guess type if not set
-	if not message["type"] then
-		if message["body"] then 
-			if message["subject"] then
-				message["type"] = "normal"
-			else
-				message["type"] = "chat"
-			end
-		elseif not message["body"] and message["subject"] then
-			message["type"] = "headline"
-		end
-	end
-
-	-- build stanza
-	local stanza = msg({["to"]=to_jid, ["from"]=from_jid, ["type"]=message["type"]})
-	if message["body"] then stanza:tag("body"):text(message["body"]):up(); end
-	if message["subject"] then stanza:tag("subject"):text(message["subject"]):up(); end
-
-	-- and finaly post it
-	module:log("debug", "message for %s", to_jid)
-	core_post_stanza(hosts[module.host], stanza)
-	return http_response(202, "Accepted")
-end
-
-local function setup()
-	local ports = module:get_option("post_msg_ports") or { 5280 };
-	require "net.httpserver".new_from_config(ports, handle_request, { base = "msg" });
-end
-if prosody.start_time then -- already started
-	setup();
-else
-	prosody.events.add_handler("server-started", setup);
-end
+module:provides("http", {
+	default_path = "/msg";
+	route = {
+		["POST /*"] = require_valid_user(handle_post);
+		OPTIONS = function(e)
+			local headers = e.response.headers;
+			headers.allow = "POST";
+			headers.accept = "application/x-www-form-urlencoded, text/plain";
+			return 200;
+		end;
+	}
+});