--[[
Data structures useful for HSTS (HTTP Strict Transport Security)
HSTS is described in RFC 6797
]]

local EOF = require "lpeg".P(-1)
local IPv4address = require "lpeg_patterns.IPv4".IPv4address
local IPv6address = require "lpeg_patterns.IPv6".IPv6address
local IPaddress = (IPv4address + IPv6address) * EOF

local store_methods = {
	time = function() return os.time() end;
}

local store_mt = {
	__name = "http.hsts.store";
	__index = store_methods;
}

local store_item_methods = {}
local store_item_mt = {
	__name = "http.hsts.store_item";
	__index = store_item_methods;
}

local function host_is_ip(host)
	if IPaddress:match(host) then
		return true
	else
		return false
	end
end

local function new_store()
	return setmetatable({
		domains = {};
	}, store_mt)
end

function store_methods:clone()
	local r = new_store()
	r.time = rawget(self, "time")
	for host, item in pairs(self.domains) do
		r.domains[host] = item
	end
	return r
end

function store_methods:store(host, directives)
	local now = self.time()
	local max_age = directives["max-age"]
	if max_age == nil then
		return nil, "max-age directive is required"
	elseif type(max_age) ~= "string" or max_age:match("[^0-9]") then
		return nil, "max-age directive does not match grammar"
	else
		max_age = tonumber(max_age, 10)
	end
	if host_is_ip(host) then
		return false
	end
	if max_age == 0 then
		-- delete from store
		self.domains[host] = nil
	else
		-- add to store
		self.domains[host] = setmetatable({
			includeSubdomains = directives.includeSubdomains;
			expires = now + max_age;
		}, store_item_mt)
	end
	return true
end

function store_methods:check(host)
	if host_is_ip(host) then
		return false
	end
	local now = self.time()
	local h = host
	repeat
		local item = self.domains[h]
		if item then
			if item.expires < now then
				self:clean()
			elseif host == h or item.includeSubdomains then
				return true
			end
		end
		local n
		h, n = h:gsub("^[^%.]+%.", "", 1)
	until n == 0
	return false
end

function store_methods:clean()
	local now = self.time()
	for host, item in pairs(self.domains) do
		if item.expires < now then
			self.domains[host] = nil
		end
	end
	return true
end

return {
	new_store = new_store;
	store_mt = store_mt;
	store_methods = store_methods;
}
