File: hsts.lua

package info (click to toggle)
lua-http 0.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,100 kB
  • sloc: makefile: 60; sh: 16
file content (142 lines) | stat: -rw-r--r-- 3,032 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
--[[
Data structures useful for HSTS (HTTP Strict Transport Security)
HSTS is described in RFC 6797
]]

local binaryheap = require "binaryheap"
local http_util = require "http.util"

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

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 new_store()
	return setmetatable({
		domains = {};
		expiry_heap = binaryheap.minUnique();
		n_items = 0;
	}, store_mt)
end

function store_methods:clone()
	local r = new_store()
	r.time = rawget(self, "time")
	r.n_items = rawget(self, "n_items")
	r.expiry_heap = binaryheap.minUnique()
	for host, item in pairs(self.domains) do
		r.domains[host] = item
		r.expiry_heap:insert(item.expires, 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

	-- Clean now so that we can assume there are no expired items in store
	self:clean()

	if max_age == 0 then
		return self:remove(host)
	else
		if http_util.is_ip(host) then
			return false
		end
		-- add to store
		local old_item = self.domains[host]
		if old_item then
			self.expiry_heap:remove(old_item)
		else
			local n_items = self.n_items
			if n_items >= self.max_items then
				return false
			end
			self.n_items = n_items + 1
		end
		local expires = now + max_age
		local item = setmetatable({
			host = host;
			includeSubdomains = directives.includeSubdomains;
			expires = expires;
		}, store_item_mt)
		self.domains[host] = item
		self.expiry_heap:insert(expires, item)
	end
	return true
end

function store_methods:remove(host)
	local item = self.domains[host]
	if item then
		self.expiry_heap:remove(item)
		self.domains[host] = nil
		self.n_items = self.n_items - 1
	end
	return true
end

function store_methods:check(host)
	if http_util.is_ip(host) then
		return false
	end

	-- Clean now so that we can assume there are no expired items in store
	self:clean()

	local h = host
	repeat
		local item = self.domains[h]
		if item then
			if 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_due()
	local next_expiring = self.expiry_heap:peek()
	if not next_expiring then
		return (1e999)
	end
	return next_expiring.expires
end

function store_methods:clean()
	local now = self.time()
	while self:clean_due() < now do
		local item = self.expiry_heap:pop()
		self.domains[item.host] = nil
		self.n_items = self.n_items - 1
	end
	return true
end

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