Files
2026-07-13 12:32:21 +08:00

95 lines
2.1 KiB
Lua

local rules = require "kong.plugins.bot-detection.rules"
local strip = require("kong.tools.string").strip
local lrucache = require "resty.lrucache"
local kong_meta = require "kong.meta"
local ipairs = ipairs
local re_find = ngx.re.find
local BotDetectionHandler = {}
BotDetectionHandler.PRIORITY = 2500
BotDetectionHandler.VERSION = kong_meta.version
local BAD_REQUEST = 400
local FORBIDDEN = 403
local MATCH_EMPTY = 0
local MATCH_ALLOW = 1
local MATCH_DENY = 2
local MATCH_BOT = 3
-- per-worker cache of matched UAs
-- we use a weak table, index by the `conf` parameter, so once the plugin config
-- is GC'ed, the cache follows automatically
local ua_caches = setmetatable({}, { __mode = "k" })
local UA_CACHE_SIZE = 10 ^ 4
local function get_user_agent()
local user_agent = kong.request.get_headers()["user-agent"]
if type(user_agent) == "table" then
return nil, "Only one User-Agent header allowed"
end
return user_agent
end
local function examine_agent(user_agent, conf)
user_agent = strip(user_agent)
if conf.allow then
for _, rule in ipairs(conf.allow) do
if re_find(user_agent, rule, "jo") then
return MATCH_ALLOW
end
end
end
if conf.deny then
for _, rule in ipairs(conf.deny) do
if re_find(user_agent, rule, "jo") then
return MATCH_DENY
end
end
end
for _, rule in ipairs(rules.bots) do
if re_find(user_agent, rule, "jo") then
return MATCH_BOT
end
end
return MATCH_EMPTY
end
function BotDetectionHandler:access(conf)
local user_agent, err = get_user_agent()
if err then
return kong.response.error(BAD_REQUEST, err)
end
if not user_agent then
return
end
local cache = ua_caches[conf]
if not cache then
cache = lrucache.new(UA_CACHE_SIZE)
ua_caches[conf] = cache
end
local match = cache:get(user_agent)
if not match then
match = examine_agent(user_agent, conf)
cache:set(user_agent, match)
end
-- if we saw a denied UA or bot, return forbidden. otherwise,
-- fall out of our handler
if match > 1 then
return kong.response.error(FORBIDDEN)
end
end
return BotDetectionHandler