src/web/web.lua

changeset 0
6279a7d40ae7
child 17
b284dc4816cd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/web/web.lua	Tue Mar 09 12:16:56 2021 +0000
@@ -0,0 +1,108 @@
+local http_util = require "util.http";
+local usercookie = require"util.usercookie";
+local uuid = require "util.uuid";
+
+local function unpack_cookies(request)
+	if not request.cookies then
+		request.cookies = usercookie.decode(request.headers.cookie);
+	end
+end
+
+local post_parsers = {
+	["application/x-www-form-urlencoded"] = http_util.formdecode;
+};
+
+local function parse_body(request)
+	local content_type = request.headers.content_type;
+	if not content_type then
+		--log("warn", "No Content-Type header sent");
+		return nil, 400;
+	end
+	local post_parser = post_parsers[content_type];
+	if not post_parser then
+		--log("warn", "Don't know how to parse %s", content_type);
+		return nil, 415;
+	end
+	local post_body = post_parser(request.body);
+	if type(post_body) ~= "table" then
+		--log("warn", "Could not parse %s %q, got %s", content_type, request.body, type(post_body));
+		return nil, 415;
+	end
+	return post_body;
+end
+
+local csrf_token_len = #uuid.generate();
+
+local function validate_csrf(csrf_token, request)
+	if request.headers.origin == nil and request.headers.referer == nil then
+		return true; -- Probably a non-browser request
+	end
+	if not (csrf_token and #csrf_token == csrf_token_len) then
+		return false;
+	end
+	unpack_cookies(request);
+	return request.cookies.csrf_token == csrf_token;
+end
+
+local function parse_body_and_csrf(request)
+	local post_body, err = parse_body(request);
+	if not post_body then return nil, err; end
+	if not validate_csrf(post_body.csrf_token, request) then
+		--log("warn", "CSRF error (token: '%s', cookie: '%s')",
+		--	tostring(post_body.csrf_token), tostring(request.headers.cookie));
+		return nil, 400;
+	end
+	return post_body;
+end
+
+-- Cookies
+
+local function add_header(headers, header, value)
+	if headers[header] then
+		headers[header] = headers[header] .. ", " .. value;
+	else
+		headers[header] = value;
+	end
+end
+
+local function prefix_header(headers, header, value)
+	if headers[header] then
+		headers[header] = value .. ", " .. headers[header];
+	else
+		headers[header] = value;
+	end
+end
+
+local function set_cookie(headers, cookie, opts)
+	if opts then
+		local params = {""};
+		if opts.path then
+			table.insert(params, "Path="..opts.path);
+		end
+		if opts.ttl then
+			table.insert(params, ("Max-Age=%d"):format(opts.ttl));
+		end
+		if opts.http_only then
+			table.insert(params, "HttpOnly");
+		end
+		if opts.secure then
+			table.insert(params, "Secure");
+		end
+
+		if #params > 1 then
+			cookie = cookie .. table.concat(params, "; ");
+		end
+	end
+
+	prefix_header(headers, "set_cookie", cookie);
+end
+
+return {
+	unpack_cookies = unpack_cookies;
+	validate_csrf = validate_csrf;
+	parse_body_and_csrf = parse_body_and_csrf;
+	parse_body = parse_body;
+	add_header = add_header;
+	prefix_header = prefix_header;
+	set_cookie = set_cookie;
+};

mercurial