feat: adds module to obtain registered domain name using PSL

This commit is contained in:
Iain Learmonth 2025-05-03 15:19:47 +01:00
parent 3f0d12c266
commit 1577679053

105
src/lua/psl.lua Normal file
View file

@ -0,0 +1,105 @@
local http = require "resty.http"
local _M = {}
local function fetch_psl()
local httpc = http.new()
local res, err = httpc:request_uri("https://publicsuffix.org/list/public_suffix_list.dat", {
method = "GET",
ssl_verify = true
})
if not res then
return nil, "Failed to fetch PSL: " .. err
end
local rules = {}
for line in res.body:gmatch("[^\r\n]+") do
line = line:match("^%s*(.-)%s*$")
if line ~= "" and not line:match("^//") then
table.insert(rules, line)
end
end
return rules
end
local function load_psl()
local cache = ngx.shared.jasima_cache
local cached = cache:get("psl")
if cached then return cached end
local rules, err = fetch_psl()
if rules then
cache:set("psl", rules, 86400 * 7) -- 1 week
return rules
else
ngx.log(ngx.ERR, err or "Unknown error loading PSL")
return nil
end
end
local function split(domain)
local parts = {}
for part in domain:gmatch("[^%.]+") do table.insert(parts, part) end
return parts
end
local function domain_matches_rule(domain_parts, rule_parts)
for i = 1, #rule_parts do
local rule_part = rule_parts[#rule_parts - i + 1]
local domain_part = domain_parts[#domain_parts - i + 1]
if rule_part == "*" then
-- wildcard match
elseif not domain_part or rule_part ~= domain_part then
return false
end
end
return true
end
local function find_matching_rule(domain, rules)
local domain_parts = split(domain)
local match = nil
local max_len = 0
for _, rule in ipairs(rules) do
local is_exception = rule:sub(1, 1) == "!"
local clean_rule = is_exception and rule:sub(2) or rule
local rule_parts = split(clean_rule)
if domain_matches_rule(domain_parts, rule_parts) then
if #rule_parts > max_len then
match = { rule = rule, is_exception = is_exception }
max_len = #rule_parts
end
end
end
return match
end
function _M.get_registered_domain(domain)
if not domain or domain == "" then return nil end
local rules = load_psl()
if not rules then return nil end
local domain_parts = split(domain:lower())
local match = find_matching_rule(domain, rules)
if not match then return domain end
local rule = match.rule
local is_exception = match.is_exception
local rule_parts = split(rule:gsub("^!", ""))
local offset = is_exception and (#rule_parts + 1) or #rule_parts
local start_idx = #domain_parts - offset
if start_idx < 1 then return domain end
local reg_parts = {}
for i = start_idx, #domain_parts do
table.insert(reg_parts, domain_parts[i])
end
return table.concat(reg_parts, ".")
end
return _M