src/web/web.lua

Tue, 09 Mar 2021 12:16:56 +0000

author
Matthew Wild <mwild1@gmail.com>
date
Tue, 09 Mar 2021 12:16:56 +0000
changeset 0
6279a7d40ae7
child 17
b284dc4816cd
permissions
-rw-r--r--

Initial commit

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