diff -r 000000000000 -r 6279a7d40ae7 src/web/web.lua --- /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; +};