refactoring and road to nginx 1.24.0

This commit is contained in:
florian 2023-04-17 02:24:19 +02:00
parent 666b7a1bac
commit 928ed2d6ce
No known key found for this signature in database
GPG Key ID: 3D80806F12602A7C
700 changed files with 39377 additions and 8510 deletions

View File

@ -1,4 +1,4 @@
FROM nginx:1.22.1-alpine AS builder
FROM nginx:1.24.0-alpine AS builder
# Copy dependencies sources folder
COPY src/deps /tmp/bunkerweb/deps
@ -21,7 +21,7 @@ RUN apk add --no-cache --virtual .build-deps py3-pip && \
pip install --no-cache-dir --require-hashes --target /usr/share/bunkerweb/deps/python -r /usr/share/bunkerweb/deps/requirements.txt && \
apk del .build-deps
FROM nginx:1.22.1-alpine
FROM nginx:1.24.0-alpine
# Copy dependencies
COPY --from=builder /usr/share/bunkerweb /usr/share/bunkerweb

View File

@ -1,6 +1,7 @@
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local datastore = require "bunkerweb.datastore"
local clusterstore = require "bunkerweb.clusterstore"
local badbehavior = class("badbehavior", plugin)

View File

@ -71,7 +71,7 @@ function blacklist:init()
if not ok then
return self:ret(false, "can't store blacklist list into datastore : " .. err)
end
return self:ret(true, "successfully loaded " .. tostring(i) .. " bad IP/network/rDNS/ASN/User-Agent/URI")
return self:ret(true, "successfully loaded " .. tostring(i) .. " IP/network/rDNS/ASN/User-Agent/URI")
end
end
@ -96,9 +96,9 @@ function blacklist:access()
["UA"] = false
}
for k, v in pairs(checks) do
local cached, err = self:is_in_cache(v)
if not cached and err ~= "success" then
self.logger:log(ngx.ERR, "error while checking cache : " .. err)
local ok, cached = self:is_in_cache(v)
if not cached then
self.logger:log(ngx.ERR, "error while checking cache : " .. cached)
elseif cached and cached ~= "ok" then
return self:ret(true, k + " is in cached blacklist (info : " .. cached .. ")", utils.get_deny_status())
end
@ -113,16 +113,16 @@ function blacklist:access()
-- Perform checks
for k, v in pairs(checks) do
if not already_cached[k] then
local blacklisted, err = self:is_blacklisted(k)
if blacklisted == nil then
local ok, blacklisted = self:is_blacklisted(k)
if ok == nil then
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is blacklisted : " .. err)
else
local ok, err = self:add_to_cache(v, blacklisted or "ok")
local ok, err = self:add_to_cache(v, blacklisted)
if not ok then
self.logger:log(ngx.ERR, "error while adding element to cache : " .. err)
end
if blacklisted ~= "ok" then
return self:ret(true, k + " is in cached blacklist (info : " .. blacklisted .. ")", utils.get_deny_status())
return self:ret(true, k + " is blacklisted (info : " .. blacklisted .. ")", utils.get_deny_status())
end
end
end

View File

@ -97,7 +97,7 @@ function country:preread()
end
function country:is_in_cache(ip)
local ok, data = cachestore:get("plugin_country_" .. ip)
local ok, data = cachestore:get("plugin_country_cache_" .. ip)
if not ok then then
return false, data
end
@ -105,7 +105,7 @@ function country:is_in_cache(ip)
end
function country:add_to_cache(ip, country, result)
local ok, err = cachestore:set("plugin_country_" .. ip, cjson.encode({country = country, result = result}))
local ok, err = cachestore:set("plugin_country_cache_" .. ip, cjson.encode({country = country, result = result}))
if not ok then then
return false, err
end

View File

@ -25,21 +25,15 @@ location = {{ page }} {
internal;
modsecurity off;
default_type 'text/html';
root /usr/share/bunkerweb/core/files;
content_by_lua_block {
local logger = require "bunkerweb.logger"
local errors = require "errors.errors"
local html, err
logger:new("errors")
errors:new()
if ngx.status == 200 then
html, err = errors:error_html(tostring(405))
errors:render_template(tostring(405))
else
html, err = errors:error_html(tostring(ngx.status))
end
if not html then
logger:log(ngx.ERR, "error while computing HTML error template for {{ intercepted_error_code }} : " .. err)
else
ngx.say(html)
errors:render_template(tostring(ngx.status))
end
}
}

View File

@ -3,6 +3,7 @@ local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local template = require "resty.template"
local errors = class("errors", plugin)
@ -12,94 +13,68 @@ function errors:new()
if not ok then
return false, err
end
return true, "success"
end
function errors:init()
-- Save default errors into datastore
local default_errors = {
-- Default error texts
self.default_errors = {
["400"] = {
body1 = "Bad Request",
body2 = "The server did not understand the request."
title = "Bad Request",
text = "The server did not understand the request."
},
["401"] = {
body1 = "Not Authorized",
body2 = "Valid authentication credentials needed for the target resource."
title = "Not Authorized",
text = "Valid authentication credentials needed for the target resource."
},
["403"] = {
body1 = "Forbidden",
body2 = "Access is forbidden to the requested page."
title = "Forbidden",
text = "Access is forbidden to the requested page."
},
["404"] = {
body1 = "Not Found",
body2 = "The server cannot find the requested page."
title = "Not Found",
text = "The server cannot find the requested page."
},
["405"] = {
body1 = "Method Not Allowed",
body2 = "The method specified in the request is not allowed."
title = "Method Not Allowed",
text = "The method specified in the request is not allowed."
},
["413"] = {
body1 = "Request Entity Too Large",
body2 = "The server will not accept the request, because the request entity is too large."
title = "Request Entity Too Large",
text = "The server will not accept the request, because the request entity is too large."
},
["429"] = {
body1 = "Too Many Requests",
body2 = "Too many requests sent in a given amount of time, try again later."
title = "Too Many Requests",
text = "Too many requests sent in a given amount of time, try again later."
},
["500"] = {
body1 = "Internal Server Error",
body2 = "The request was not completed. The server met an unexpected condition."
title = "Internal Server Error",
text = "The request was not completed. The server met an unexpected condition."
},
["501"] = {
body1 = "Not Implemented",
body2 = "The request was not completed. The server did not support the functionality required."
title = "Not Implemented",
text = "The request was not completed. The server did not support the functionality required."
},
["502"] = {
body1 = "Bad Gateway",
body2 = "The request was not completed. The server received an invalid response from the upstream server."
title = "Bad Gateway",
text = "The request was not completed. The server received an invalid response from the upstream server."
},
["503"] = {
body1 = "Service Unavailable",
body2 = "The request was not completed. The server is temporarily overloading or down."
title = "Service Unavailable",
text = "The request was not completed. The server is temporarily overloading or down."
},
["504"] = {
body1 = "Gateway Timeout",
body2 = "The gateway has timed out."
title = "Gateway Timeout",
text = "The gateway has timed out."
}
}
local ok, err = datastore:set("plugin_errors_default_errors", cjson.encode(default_errors))
if not ok then
return self:ret(false, "can't save default errors to datastore : " .. err)
end
-- Save generic template into datastore
local f, err = io.open("/usr/share/bunkerweb/core/errors/files/error.html", "r")
if not f then
return self:ret(false, "can't open error.html : " .. err)
end
local template = f:read("*all")
f:close()
local ok, err = datastore:set("plugin_errors_template", template)
if not ok then
return false, "can't save error.html to datastore : " .. err
end
return true, "success"
end
function errors:error_html(code)
-- Load default errors texts
local default_errors, err = datastore:get("plugin_errors_default_errors")
if not default_errors then
return false, "can't get default errors from datastore : " .. err
end
default_errors = cjson.decode(default_errors)
-- Load template
local template, err = datastore:get("plugin_errors_template")
if not template then
return false, "can't get template from datastore : " .. err
end
-- Compute template
return template:format(code .. " - " .. default_errors[code].body1, code, default_errors[code].body1,
default_errors[code].body2), "success"
function errors:render_template(code)
-- Render template
template.render("error.html", {
title = code .. " - " .. self.default_errors[code].title,
error_title = self.default_errors[code].title,
error_code = code,
error_text = self.default_errors[code].text
})
end
return errors

View File

@ -3,29 +3,29 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>%s</title>
<title>{{title}}</title>
<link
rel="icon"
href="data:image/svg+xml, %%3Csvg version='1.0' xmlns='http://www.w3.org/2000/svg' width='96.000000pt' height='96.000000pt' viewBox='0 0 96.000000 96.000000' preserveAspectRatio='xMidYMid meet'%%3E%%3Cg transform='translate(0.000000,96.000000) scale(0.100000,-0.100000)'%%0Afill='%%23085577' stroke='none'%%3E%%3Cpath d='M535 863 c-22 -2 -139 -17 -260 -34 -228 -31 -267 -43 -272 -85 -2%%0A-10 23 -181 55 -379 l57 -360 400 0 400 0 20 40 c16 31 20 59 19 125 -1 100%%0A-24 165 -73 199 -41 29 -46 57 -22 111 30 67 29 188 -3 256 -13 28 -37 60 -53%%0A72 -55 39 -169 62 -268 55z m-15 -348 c30 -16 60 -61 60 -90 0 -10 -8 -33 -17%%0A-52 -16 -34 -16 -41 0 -116 9 -44 15 -82 12 -85 -6 -7 -92 -21 -131 -21 l-31%%0A-1 -6 85 c-4 75 -8 89 -31 112 -20 20 -26 36 -26 70 0 38 5 50 34 79 39 39 86%%0A45 136 19z'/%%3E%%3C/g%%3E%%3C/svg%%3E"
href="data:image/svg+xml, %3Csvg version='1.0' xmlns='http://www.w3.org/2000/svg' width='96.000000pt' height='96.000000pt' viewBox='0 0 96.000000 96.000000' preserveAspectRatio='xMidYMid meet'%3E%3Cg transform='translate(0.000000,96.000000) scale(0.100000,-0.100000)'%0Afill='%23085577' stroke='none'%3E%3Cpath d='M535 863 c-22 -2 -139 -17 -260 -34 -228 -31 -267 -43 -272 -85 -2%0A-10 23 -181 55 -379 l57 -360 400 0 400 0 20 40 c16 31 20 59 19 125 -1 100%0A-24 165 -73 199 -41 29 -46 57 -22 111 30 67 29 188 -3 256 -13 28 -37 60 -53%0A72 -55 39 -169 62 -268 55z m-15 -348 c30 -16 60 -61 60 -90 0 -10 -8 -33 -17%0A-52 -16 -34 -16 -41 0 -116 9 -44 15 -82 12 -85 -6 -7 -92 -21 -131 -21 l-31%0A-1 -6 85 c-4 75 -8 89 -31 112 -20 20 -26 36 -26 70 0 38 5 50 34 79 39 39 86%0A45 136 19z'/%3E%3C/g%3E%3C/svg%3E"
type="image/svg+xml"
/>
<style type="text/css">
body,
html {
width: 100%%;
height: 100%%;
width: 100%;
height: 100%;
background-color: #125678;
}
body {
color: #fff;
text-align: center;
padding: 0;
min-height: 100%%;
min-height: 100%;
display: table;
font-family: "Open Sans", Arial, sans-serif;
margin: 0;
-ms-text-size-adjust: 100%%;
-webkit-text-size-adjust: 100%%;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
h1 {
display: flex;
@ -63,7 +63,7 @@
}
footer {
position: fixed;
width: 100%%;
width: 100%;
letter-spacing: 1px;
left: 0;
bottom: 0;
@ -89,8 +89,8 @@
<body>
<div class="cover">
<div class="message">
<h1>%s<small>%s</small></h1>
<p class="lead">%s</p>
<h1>{{error_title}}<small>{{error_code}}</small></h1>
<p class="lead">{{error_text}}</p>
</div>
</div>
<footer>

View File

@ -1,29 +1,65 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local datastore = require "bunkerweb.datastore"
local clusterstore = require "bunkerweb.clusterstore"
local cjson = require "cjson"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local limit = class("limit", plugin)
function _M.new()
local self = setmetatable({}, _M)
return self, nil
function limit:new()
-- Call parent new
local ok, err = plugin.new(self, "limit")
if not ok then
return false, err
end
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
return false, err
end
self.use_redis = use_redis == "yes"
-- Load rules if needed
if ngx.get_phase() == "access" then
if self.variables["USE_LIMIT_REQ"] == "yes" then
-- Get all rules from datastore
local limited = false
local all_rules, err = datastore:get("plugin_limit_rules")
if not all_rules then
return false, err
end
all_rules = cjson.decode(all_rules)
self.rules = {}
-- Extract global rules
if all_rules.global then
for k, v in pairs(all_rules.global) do
self.rules[k] = v
end
end
-- Extract and overwrite if needed server rules
if all_rules[ngx.var.server_name] then
for k, v in pairs(all_rules[ngx.var.server_name]) do
self.rules[k] = v
end
end
end
end
return true, "success"
end
function _M:init()
function limit:init()
-- Check if init is needed
local init_needed, err = utils.has_variable("USE_LIMIT_REQ", "yes")
if init_needed == nil then
return false, err
return self:ret(false, err)
end
if not init_needed then
return true, "no service uses Limit for requests, skipping init"
return self:ret(true, "no service uses Limit for requests, skipping init")
end
-- Get variables
local variables, err = utils.get_multiple_variables({"LIMIT_REQ_URL", "LIMIT_REQ_RATE"})
if variables == nil then
return false, err
return self:ret(false, err)
end
-- Store URLs and rates
local data = {}
@ -43,73 +79,142 @@ function _M:init()
end
local ok, err = datastore:set("plugin_limit_rules", cjson.encode(data))
if not ok then
return false, err
return self:ret(false, err)
end
return true, "successfully loaded " .. tostring(i) .. " limit rules for requests"
return self:ret(true, "successfully loaded " .. tostring(i) .. " limit rules for requests")
end
function _M:access()
function limit:access()
-- Check if we are whitelisted
if ngx.var.is_whitelisted == "yes" then
return self:ret(true, "client is whitelisted")
end
-- Check if access is needed
local access_needed, err = utils.get_variable("USE_LIMIT_REQ")
if access_needed == nil then
return false, err, nil, nil
if self.variables["USE_LIMIT_REQ"] ~= "yes" then
return self:ret(true, "limit req is disabled")
end
if access_needed ~= "yes" then
return true, "Limit for request not activated", nil, nil
end
-- Don't go further if URL is not limited
local limited = false
local all_rules, err = datastore:get("plugin_limit_rules")
if not all_rules then
return false, err, nil, nil
end
all_rules = cjson.decode(all_rules)
local limited = false
local rate = ""
if not limited and all_rules[ngx.var.server_name] then
for k, v in pairs(all_rules[ngx.var.server_name]) do
if ngx.var.uri:match(k) and k ~= "/" then
limited = true
rate = all_rules[ngx.var.server_name][k]
break
end
-- Check if URI is limited
local rate = nil
local uri = nil
for k, v in pairs(self.rules) do
if k ~= "/" and ngx.var.uri:match(k) then
rate = v
uri = k
break
end
end
if all_rules.global and not limited then
for k, v in pairs(all_rules.global) do
if ngx.var.uri:match(k) and k ~= "/" then
limited = true
rate = all_rules.global[k]
break
end
if not rate then
if self.rules["/"] then
rate = self.rules["/"]
uri = "/"
else
return self:ret(true, "no rule for " .. ngx.var.uri)
end
end
if not limited then
if all_rules[ngx.var.server_name] and all_rules[ngx.var.server_name]["/"] then
limited = true
rate = all_rules[ngx.var.server_name]["/"]
elseif all_rules.global and all_rules.global["/"] then
limited = true
rate = all_rules.global["/"]
end
if not limited then
return true, "URL " .. ngx.var.uri .. " is not limited by a rule, skipping check", nil, nil
end
end
-- Get the rate
-- Check if limit is reached
local _, _, rate_max, rate_time = rate:find("(%d+)r/(.)")
-- Get current requests timestamps
local requests, err = datastore:get("plugin_limit_cache_" .. ngx.var.server_name .. ngx.var.remote_addr .. ngx.var.uri)
if not requests and err ~= "not found" then
return false, err, nil, nil
elseif err == "not found" then
requests = "{}"
local limited, err, current_rate = self:limit_req(tonumber(rate_max), rate_time)
if limited == nil then
return self:ret(false, err)
end
-- Limit reached
if limited then
return return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is limited for URL " .. ngx.var.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")", ngx.HTTP_TOO_MANY_REQUESTS)
end
-- Limit not reached
return self:ret(true, "client IP " .. ngx.var.remote_addr .. " is not limited for URL " .. ngx.var.uri .. " (current rate = " .. current_rate .. "r/" .. rate_time .. " and max rate = " .. rate .. ")")
end
end
function limit:limit_req(rate_max, rate_time)
local timestamps = nil
-- Redis case
if self.use_redis then
local redis_timestamps, err = self:limit_req_redis(rate_max, rate_time)
if redis_timestamps == nil then
self.logger:log(ngx.ERR, "limit_req_redis failed, falling back to local : " .. err)
else
timestamps = redis_timestamps
-- Save the new timestamps
local ok, err = datastore:set("plugin_limit_cache_" .. ngx.var.server_name .. ngx.var.remote_addr .. ngx.var.uri, cjson.encode(timestamps), delay)
if not ok then
return nil, "can't update timestamps : " .. err
end
end
end
-- Local case (or fallback)
if timestamps == nil then
local local_timestamps, err = self:limit_req_local(rate_max, rate_time)
if local_timestamps == nil then
return nil, "limit_req_local failed : " .. err
end
timestamps = local_timestamps
end
if #timestamps > rate_max then
return true, "success - limited", #timestamps
end
return false, "success - not limited", #timestamps
end
function limit:limit_req_local(rate_max, rate_time)
-- Get timestamps
local timestamps, err = datastore:get("plugin_limit_cache_" .. ngx.var.server_name .. ngx.var.remote_addr .. ngx.var.uri)
if not timestamps and err ~= "not found" then
return nil, err
elseif err == "not found" then
timestamps = "{}"
end
timestamps = cjson.decode(timestamps)
-- Compute new timestamps
local updated, new_timestamps, delay = self:limit_req_timestamps(rate_max, rate_time, timestamps)
-- Save new timestamps if needed
if updated then
local ok, err = datastore:set("plugin_limit_cache_" .. ngx.var.server_name .. ngx.var.remote_addr .. ngx.var.uri, cjson.encode(timestamps), delay)
if not ok then
return nil, err
end
end
return new_timestamps, "success"
end
function limit:limit_req_redis(rate_max, rate_time)
-- Connect to server
local cstore, err = clusterstore:new()
if not cstore then
return nil, err
end
local ok, err = clusterstore:connect()
if not ok then
return nil, err
end
-- Get timestamps
local timestamps, err = clusterstore:call("get", "limit_" .. ngx.var.server_name .. ngx.var.remote_addr .. ngx.var.uri)
if err then
clusterstore:close()
return nil, err
end
if timestamps then
timestamps = cjson.decode(timestamps)
else
timestamps = {}
end
-- Compute new timestamps
local updated, new_timestamps, delay = self:limit_req_timestamps(rate_max, rate_time, timestamps)
-- Save new timestamps if needed
if updated then
local ok, err = clusterstore:call("set", "limit_" .. ngx.var.server_name .. ngx.var.remote_addr .. ngx.var.uri, cjson.encode(new_timestamps), "EX", delay)
if not ok then
clusterstore:close()
return nil, err
end
end
lusterstore:close()
return new_timestamps, "success"
end
function limit:limit_req_timestamps(rate_max, rate_time, timestamps)
-- Compute new timestamps
local updated = false
local new_timestamps = {}
local current_timestamp = os.time(os.date("!*t"))
local delay = 0
@ -122,29 +227,20 @@ function _M:access()
elseif rate_time == "d" then
delay = 86400
end
for i, timestamp in ipairs(cjson.decode(requests)) do
-- Keep only timestamp within the delay
for i, timestamp in ipairs(timestamps) do
if current_timestamp - timestamp <= delay then
table.insert(new_timestamps, timestamp)
else
updated = true
end
end
-- Only insert the new timestamp if client is not limited already to avoid infinite insert
if #new_timestamps <= tonumber(rate_max) then
if #new_timestamps <= rate_max then
table.insert(new_timestamps, current_timestamp)
updated = true
end
-- Save the new timestamps
local ok, err = datastore:set("plugin_limit_cache_" .. ngx.var.server_name .. ngx.var.remote_addr .. ngx.var.uri, cjson.encode(new_timestamps), delay)
if not ok then
return false, "can't update timestamps : " .. err, nil, nil
end
-- Deny if the rate is higher than the one defined in rule
if #new_timestamps > tonumber(rate_max) then
return true, "client IP " .. ngx.var.remote_addr .. " is limited for URL " .. ngx.var.uri .. " (current rate = " .. tostring(#new_timestamps) .. "r/" .. rate_time .. " and max rate = " .. rate .. ")", true, ngx.HTTP_TOO_MANY_REQUESTS
end
-- Limit not reached
return true, "client IP " .. ngx.var.remote_addr .. " is not limited for URL " .. ngx.var.uri .. " (current rate = " .. tostring(#new_timestamps) .. "r/" .. rate_time .. " and max rate = " .. rate .. ")", nil, nil
return updated, new_timestamps, delay
end
return _M
return limit

View File

@ -1,65 +1,58 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local cachestore = require "bunkerweb.cachestore"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local reversescan = class("reversescan", plugin)
function _M.new()
local self = setmetatable({}, _M)
return self, nil
function reversescan:new()
-- Call parent new
local ok, err = plugin.new(self, "reversescan")
if not ok then
return false, err
end
-- Instantiate cachestore
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
return false, err
end
cachestore:new(use_redis)
return true, "success"
end
function _M:access()
function reversescan:access()
-- Check if access is needed
local access_needed, err = utils.get_variable("USE_REVERSE_SCAN")
if access_needed == nil then
return false, "can't get USE_REVERSE_SCAN setting from datastore : " .. err, nil, nil
end
if access_needed ~= "yes" then
return true, "reverse scan not activated", nil, nil
end
-- Get ports
local ports, err = utils.get_variable("REVERSE_SCAN_PORTS")
if ports == nil then
return false, "can't get REVERSE_SCAN_PORTS setting from datastore : " .. err, nil, nil
end
if ports == "" then
return true, "no port defined", nil, nil
end
-- Get timeout
local timeout, err = utils.get_variable("REVERSE_SCAN_TIMEOUT")
if timeout == nil then
return false, "can't get REVERSE_SCAN_TIMEOUT setting from datastore : " .. err, nil, nil
if self.variables["USE_REVERSE_SCAN"] ~= "yes" then
return self:ret(true, "reverse scan not activated")
end
-- Loop on ports
for port in ports:gmatch("%S+") do
for port in self.variables["REVERSE_SCAN_PORTS"]:gmatch("%S+") do
-- Check if the scan is already cached
local cached, err = self:is_in_cache(ngx.var.remote_addr .. ":" .. port)
if cached == nil then
return false, "error getting cache from datastore : " .. err, nil, nil
return self:ret(false, "error getting cache from datastore : " .. err)
end
if cached == "open" then
return true, "port " .. port .. " is opened for IP " .. ngx.var.remote_addr, true, utils.get_deny_status()
return self:ret(true, "port " .. port .. " is opened for IP " .. ngx.var.remote_addr, utils.get_deny_status())
elseif not cached then
-- Do the scan
local res, err = self:scan(ngx.var.remote_addr, tonumber(port), tonumber(timeout))
local res, err = self:scan(ngx.var.remote_addr, tonumber(port), tonumber(self.variables["REVERSE_SCAN_TIMEOUT"]))
-- Cache the result
local ok, err = self:add_to_cache(ngx.var.remote_addr .. ":" .. port, res)
if not ok then
return false, "error updating cache from datastore : " .. err, nil, nil
return self:ret(false, "error updating cache from datastore : " .. err)
end
-- Deny request if port is open
if res == "open" then
return true, "port " .. port .. " is opened for IP " .. ngx.var.remote_addr, true, utils.get_deny_status()
return self:ret(true, "port " .. port .. " is opened for IP " .. ngx.var.remote_addr, utils.get_deny_status())
end
end
end
return nil, "no port open for IP " .. ngx.var.remote_addr, nil, nil
-- No port opened
return self:ret(true, "no port open for IP " .. ngx.var.remote_addr)
end
function _M:scan(ip, port, timeout)
function reversescan:scan(ip, port, timeout)
local tcpsock = ngx.socket.tcp()
tcpsock:settimeout(timeout)
local ok, err = tcpsock:connect(ip, port)
@ -70,24 +63,20 @@ function _M:scan(ip, port, timeout)
return "open", nil
end
function _M:is_in_cache(ele)
local res, err = datastore:get("plugin_reversescan_" .. ele)
if not res then
if err == "not found" then
return false, nil
end
return nil, err
end
return true, res
function reversescan:is_in_cache(ip_port)
local ok, data = cachestore:get("plugin_reversescan_cache_" .. ip_port)
if not ok then then
return false, data
end
return true, data
end
function _M:add_to_cache(ele, value)
local ok, err = datastore:set("plugin_reversescan_" .. ele, value, 86400)
if not ok then
return false, err
end
return true, nil
function reversescan:add_to_cache(ip_port, value)
local ok, err = cachestore:set("plugin_reversescan_cache_" .. ip_port, value)
if not ok then then
return false, err
end
return true
end
return _M
return reversescan

View File

@ -50,7 +50,7 @@ function _M:init()
else
config.storage = "redis"
config.redis = {
prefix = "session_",
prefix = "sessions_",
connect_timeout = tonumber(vars["REDIS_TIMEOUT"]),
send_timeout = tonumber(vars["REDIS_TIMEOUT"]),
read_timeout = tonumber(vars["REDIS_TIMEOUT"]),

View File

@ -1,420 +1,270 @@
local _M = {}
_M.__index = _M
local class = require "middleclass"
local plugin = require "bunkerweb.plugin"
local utils = require "bunkerweb.utils"
local datastore = require "bunkerweb.datastore"
local cachestore = require "bunkerweb.cachestore"
local cjson = require "cjson"
local ipmatcher = require "resty.ipmatcher"
local env = require "resty.env"
local utils = require "utils"
local datastore = require "datastore"
local logger = require "logger"
local cjson = require "cjson"
local ipmatcher = require "resty.ipmatcher"
local env = require "resty.env"
local whitelist = class("whitelist", plugin)
function _M.new()
local self = setmetatable({}, _M)
return self, nil
end
function _M:init()
-- Check if init is needed
local init_needed, err = utils.has_variable("USE_WHITELIST", "yes")
if init_needed == nil then
function whitelist:new()
-- Call parent new
local ok, err = plugin.new(self, "whitelist")
if not ok then
return false, err
end
if not init_needed then
return true, "no service uses Whitelist, skipping init"
-- Check if redis is enabled
local use_redis, err = utils.get_variable("USE_REDIS", false)
if not use_redis then
return false, err
end
-- Read whitelists
local whitelists = {
["IP"] = {},
["RDNS"] = {},
["ASN"] = {},
["USER_AGENT"] = {},
["URI"] = {}
}
local i = 0
for kind, _ in pairs(whitelists) do
local f, err = io.open("/var/cache/bunkerweb/whitelist/" .. kind .. ".list", "r")
if f then
for line in f:lines() do
table.insert(whitelists[kind], line)
i = i + 1
end
f:close()
self.use_redis = use_redis == "yes"
-- Check if init is needed
if ngx.get_phase() == "init" then
local init_needed, err = utils.has_variable("USE_WHITELIST", "yes")
if init_needed == nil then
return false, err
end
self.init_needed = init_needed
-- Decode lists
else
local lists, err = datastore:get("plugin_whitelist_lists")
if not lists then
return false, err
end
self.lists = cjson.decode(lists)
end
-- Load them into datastore
local ok, err = datastore:set("plugin_whitelist_list", cjson.encode(whitelists))
if not ok then
return false, "can't store Whitelist list into datastore : " .. err
end
return true, "successfully loaded " .. tostring(i) .. " whitelisted IP/network/rDNS/ASN/User-Agent/URI"
-- Instantiate cachestore
cachestore:new(use_redis)
return true, "success"
end
function _M:set()
function whitelist:init()
if self.init_needed then
-- Read whitelists
local whitelists = {
["IP"] = {},
["RDNS"] = {},
["ASN"] = {},
["USER_AGENT"] = {},
["URI"] = {}
}
local i = 0
for kind, _ in pairs(whitelists) do
local f, err = io.open("/var/cache/bunkerweb/whitelist/" .. kind .. ".list", "r")
if f then
for line in f:lines() do
table.insert(whitelists[kind], line)
i = i + 1
end
f:close()
end
end
-- Load them into datastore
local ok, err = datastore:set("plugin_whitelist_lists", cjson.encode(whitelists))
if not ok then
return self:ret(false, "can't store whitelist list into datastore : " .. err)
end
return self:ret(true, "successfully loaded " .. tostring(i) .. " IP/network/rDNS/ASN/User-Agent/URI")
end
end
function whitelist:set()
-- Set default value
ngx.var.is_whitelisted = "no"
env.set("is_whitelisted", "no")
-- Check if set is needed
if self.variables["USE_WHITELIST"] ~= "yes" then
return self:ret(true, "whitelist not activated")
end
-- Check cache
local whitelisted, err = self:check_cache()
if whitelisted == nil then
return self:ret(false, err)
elseif whitelisted then
ngx.var.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return self:ret(true, err)
end
return self:ret(true, "not in whitelist cache")
end
function whitelist:access()
-- Check if access is needed
local set_needed, err = utils.get_variable("USE_WHITELIST")
if set_needed == nil then
if self.variables["USE_WHITELIST"] ~= "yes" then
return self:ret(true, "whitelist not activated")
end
-- Check cache
local whitelisted, err, already_cached = self:check_cache()
if whitelisted == nil then
return self:ret(false, err)
elseif whitelisted then
ngx.var.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return self:ret(true, err, ngx.OK)
end
-- Perform checks
for k, v in pairs(already_cached) do
if not already_cached[k] then
local ok, whitelisted = self:is_whitelisted(k)
if ok == nil then
self.logger:log(ngx.ERR, "error while checking if " .. k .. " is whitelisted : " .. err)
else
local ok, err = self:add_to_cache(v, whitelisted)
if not ok then
self.logger:log(ngx.ERR, "error while adding element to cache : " .. err)
end
if whitelisted ~= "ok" then
ngx.var.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return self:ret(true, k + " is whitelisted (info : " .. whitelisted .. ")", ngx.OK)
end
end
end
end
-- Not whitelisted
return self:ret(true, "not whitelisted")
end
function whitelist:preread()
return self:access()
end
function whitelist:check_cache()
-- Check the caches
local checks = {
["IP"] = "ip" .. ngx.var.remote_addr
}
if ngx.var.http_user_agent then
checks["UA"] = "ua" .. ngx.var.http_user_agent
end
if ngx.var.uri then
checks["URI"] = "uri" .. ngx.var.uri
end
local already_cached = {
["IP"] = false,
["URI"] = false,
["UA"] = false
}
for k, v in pairs(checks) do
local ok, cached = self:is_in_cache(v)
if not ok then
self.logger:log(ngx.ERR, "error while checking cache : " .. cached)
elseif cached and cached ~= "ok" then
return true, k + " is in cached whitelist (info : " .. cached .. ")"
end
if cached then
already_cached[k] = true
end
end
-- Check lists
if not self.lists then
return nil, "lists is nil"
end
-- Not cached/whitelisted
return false, "not cached/whitelisted", already_cached
end
function whitelist:is_in_cache(ele)
local ok, data = cachestore:get("plugin_whitelist_" .. ele)
if not ok then then
return false, data
end
return true, data
end
function whitelist:add_to_cache(ele, value)
local ok, err = cachestore:set("plugin_whitelist_" .. ele, value)
if not ok then then
return false, err
end
if set_needed ~= "yes" then
return true, "whitelist not enabled"
return true
end
function whitelist:is_whitelisted(kind)
if kind == "IP" then
return self:is_whitelisted_ip()
elseif kind == "URI"
return self:is_whitelisted_uri()
elseif kind == "UA"
return self:is_whitelisted_ua()
return false, "unknown kind " .. kind
end
function whitelist:is_whitelisted_ip()
-- Check if IP is in whitelist
local ipm, err = ipmatcher.new(self.lists["IP"])
if not ipm then
return nil, err
end
local match, err = ipm:match(ngx.var.remote_addr)
if err then
return nil, err
end
if match then
return true, "ip"
end
-- Check the cache
local cached_ip, err = self:is_in_cache("ip" .. ngx.var.remote_addr)
if cached_ip and cached_ip ~= "ok" then
ngx.var.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return true, "ip whitelisted"
-- Check if rDNS is needed
local check_rdns = true
if self.variables["WHITELIST_RDNS_GLOBAL"] == "yes" then
local is_global, err = utils.ip_is_global(ngx.var.remote_addr)
if is_global == nil then
return nil, err
end
if not is_global then
check_rdns = false
end
end
local cached_uri, err = self:is_in_cache("uri" .. ngx.var.uri)
if cached_uri and cached_uri ~= "ok" then
ngx.var.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return true, "uri whitelisted"
if check_rdns then
-- Get rDNS
local rdns_list, err = utils.get_rdns(ngx.var.remote_addr)
if not rdns_list then
return nil, err
end
-- Check if rDNS is in whitelist
for i, suffix in ipairs(self.lists["RDNS"]) do
for j, rdns in ipairs(rdns_list) do
if rdns:sub(-#suffix) == suffix then
return true, "rDNS " .. suffix
end
end
end
end
local cached_ua = true
if ngx.var.http_user_agent then
cached_ua, err = self:is_in_cache("ua" .. ngx.var.http_user_agent)
if cached_ua and cached_ua ~= "ok" then
ngx.var.is_whitelisted = "yes"
env.set("is_whitelisted", "yes")
return true, "ua whitelisted"
-- Check if ASN is in whitelist
for i, bl_asn in ipairs(self.lists["ASN"]) do
if bl_asn == tostring(asn) then
return true, "ASN " .. bl_asn
end
end
-- Not whitelisted
return true, "not whitelisted"
return false, "ok"
end
function _M:access()
-- Check if access is needed
local access_needed, err = utils.get_variable("USE_WHITELIST")
if access_needed == nil then
return false, err, nil, nil
end
if access_needed ~= "yes" then
return true, "Whitelist not activated", nil, nil
end
-- Check the cache
local cached_ip, err = self:is_in_cache("ip" .. ngx.var.remote_addr)
if cached_ip and cached_ip ~= "ok" then
ngx.var.is_whitelisted = "yes"
return true, "IP is in whitelist cache (info = " .. cached_ip .. ")", true, ngx.OK
end
local cached_uri, err = self:is_in_cache("uri" .. ngx.var.uri)
if cached_uri and cached_uri ~= "ok" then
ngx.var.is_whitelisted = "yes"
return true, "URI is in whitelist cache (info = " .. cached_uri .. ")", true, ngx.OK
end
local cached_ua = true
if ngx.var.http_user_agent then
cached_ua, err = self:is_in_cache("ua" .. ngx.var.http_user_agent)
if cached_ua and cached_ua ~= "ok" then
ngx.var.is_whitelisted = "yes"
return true, "User-Agent is in whitelist cache (info = " .. cached_ua .. ")", true, ngx.OK
end
end
if cached_ip and cached_uri and cached_ua then
return true, "full request is in whitelist cache (not whitelisted)", nil, nil
end
-- Get list
local data, err = datastore:get("plugin_whitelist_list")
if not data then
return false, "can't get Whitelist list : " .. err, false, nil
end
local ok, whitelists = pcall(cjson.decode, data)
if not ok then
return false, "error while decoding whitelists : " .. whitelists, false, nil
end
-- Return value
local ret, ret_err = true, "success"
-- Check if IP is in IP/net whitelist
local ip_net, err = utils.get_variable("WHITELIST_IP")
if ip_net and ip_net ~= "" then
for element in ip_net:gmatch("%S+") do
table.insert(whitelists["IP"], element)
end
end
if not cached_ip then
local ipm, err = ipmatcher.new(whitelists["IP"])
if not ipm then
ret = false
ret_err = "can't instantiate ipmatcher " .. err
else
if ipm:match(ngx.var.remote_addr) then
self:add_to_cache("ip" .. ngx.var.remote_addr, "ip/net")
ngx.var.is_whitelisted = "yes"
return ret, "client IP " .. ngx.var.remote_addr .. " is in whitelist", true, ngx.OK
end
end
end
-- Check if rDNS is in whitelist
local rdns_global, err = utils.get_variable("WHITELIST_RDNS_GLOBAL")
local check = true
if not rdns_global then
logger.log(ngx.ERR, "WHITELIST", "Error while getting WHITELIST_RDNS_GLOBAL variable : " .. err)
elseif rdns_global == "yes" then
check, err = utils.ip_is_global(ngx.var.remote_addr)
if check == nil then
logger.log(ngx.ERR, "WHITELIST", "Error while getting checking if IP is global : " .. err)
end
end
if not cached_ip and check then
local rdns, err = utils.get_rdns(ngx.var.remote_addr)
if not rdns then
ret = false
ret_err = "error while trying to get reverse dns : " .. err
else
local rdns_list, err = utils.get_variable("WHITELIST_RDNS")
if rdns_list and rdns_list ~= "" then
for element in rdns_list:gmatch("%S+") do
table.insert(whitelists["RDNS"], element)
end
end
for i, suffix in ipairs(whitelists["RDNS"]) do
if rdns:sub(- #suffix) == suffix then
self:add_to_cache("ip" .. ngx.var.remote_addr, "rDNS " .. suffix)
ngx.var.is_whitelisted = "yes"
return ret, "client IP " .. ngx.var.remote_addr .. " is in whitelist (info = rDNS " .. suffix .. ")", true, ngx.OK
end
end
end
end
-- Check if ASN is in whitelist
if not cached_ip then
if utils.ip_is_global(ngx.var.remote_addr) then
local asn, err = utils.get_asn(ngx.var.remote_addr)
if not asn then
ret = false
ret_err = "error while trying to get asn number : " .. err
else
local asn_list, err = utils.get_variable("WHITELIST_ASN")
if asn_list and asn_list ~= "" then
for element in asn_list:gmatch("%S+") do
table.insert(whitelists["ASN"], element)
end
end
for i, asn_bl in ipairs(whitelists["ASN"]) do
if tostring(asn) == asn_bl then
self:add_to_cache("ip" .. ngx.var.remote_addr, "ASN " .. tostring(asn))
ngx.var.is_whitelisted = "yes"
return ret, "client IP " .. ngx.var.remote_addr .. " is in whitelist (kind = ASN " .. tostring(asn) .. ")", true,
ngx.OK
end
end
end
end
end
-- IP is not whitelisted
local ok, err = self:add_to_cache("ip" .. ngx.var.remote_addr, "ok")
if not ok then
ret = false
ret_err = err
end
-- Check if User-Agent is in whitelist
if not cached_ua and ngx.var.http_user_agent then
local ua_list, err = utils.get_variable("WHITELIST_USER_AGENT")
if ua_list and ua_list ~= "" then
for element in ua_list:gmatch("%S+") do
table.insert(whitelists["USER_AGENT"], element)
end
end
for i, ua_bl in ipairs(whitelists["USER_AGENT"]) do
if ngx.var.http_user_agent:match(ua_bl) then
self:add_to_cache("ua" .. ngx.var.http_user_agent, "UA " .. ua_bl)
ngx.var.is_whitelisted = "yes"
return ret, "client User-Agent " .. ngx.var.http_user_agent .. " is in whitelist (matched " .. ua_bl .. ")", true,
ngx.OK
end
end
-- UA is not whitelisted
local ok, err = self:add_to_cache("ua" .. ngx.var.http_user_agent, "ok")
if not ok then
ret = false
ret_err = err
end
end
function whitelist:is_whitelisted_uri()
-- Check if URI is in whitelist
if not cached_uri then
local uri_list, err = utils.get_variable("WHITELIST_URI")
if uri_list and uri_list ~= "" then
for element in uri_list:gmatch("%S+") do
table.insert(whitelists["URI"], element)
end
end
for i, uri_bl in ipairs(whitelists["URI"]) do
if ngx.var.uri:match(uri_bl) then
self:add_to_cache("uri" .. ngx.var.uri, "URI " .. uri_bl)
ngx.var.is_whitelisted = "yes"
return ret, "client URI " .. ngx.var.uri .. " is in whitelist (matched " .. uri_bl .. ")", true, ngx.OK
end
for i, uri in ipairs(self.lists["URI"]) do
if ngx.var.uri:match(uri) then
return true, "URI " .. uri
end
end
-- URI is not whitelisted
local ok, err = self:add_to_cache("uri" .. ngx.var.uri, "ok")
if not ok then
ret = false
ret_err = err
end
return ret, "IP is not in list (error = " .. ret_err .. ")", false, nil
return false, "ok"
end
function _M:preread()
-- Check if preread is needed
local preread_needed, err = utils.get_variable("USE_WHITELIST")
if preread_needed == nil then
return false, err, nil, nil
end
if preread_needed ~= "yes" then
return true, "Whitelist not activated", nil, nil
end
-- Check the cache
local cached_ip, err = self:is_in_cache("ip" .. ngx.var.remote_addr)
if cached_ip and cached_ip ~= "ok" then
ngx.var.is_whitelisted = "yes"
return true, "IP is in whitelist cache (info = " .. cached_ip .. ")", true, ngx.OK
end
if cached_ip then
return true, "full request is in whitelist cache (not whitelisted)", nil, nil
end
-- Get list
local data, err = datastore:get("plugin_whitelist_list")
if not data then
return false, "can't get Whitelist list : " .. err, false, nil
end
local ok, whitelists = pcall(cjson.decode, data)
if not ok then
return false, "error while decoding whitelists : " .. whitelists, false, nil
end
-- Return value
local ret, ret_err = true, "success"
-- Check if IP is in IP/net whitelist
local ip_net, err = utils.get_variable("WHITELIST_IP")
if ip_net and ip_net ~= "" then
for element in ip_net:gmatch("%S+") do
table.insert(whitelists["IP"], element)
function whitelist:is_whitelisted_ua()
-- Check if UA is in whitelist
for i, ua in ipairs(self.lists["USER_AGENT"]) do
if ngx.var.http_user_agent:match(ua) then
return true, "UA " .. ua
end
end
if not cached_ip then
local ipm, err = ipmatcher.new(whitelists["IP"])
if not ipm then
ret = false
ret_err = "can't instantiate ipmatcher " .. err
else
if ipm:match(ngx.var.remote_addr) then
self:add_to_cache("ip" .. ngx.var.remote_addr, "ip/net")
ngx.var.is_whitelisted = "yes"
return ret, "client IP " .. ngx.var.remote_addr .. " is in whitelist", true, ngx.OK
end
end
end
-- Check if rDNS is in whitelist
local rdns_global, err = utils.get_variable("WHITELIST_RDNS_GLOBAL")
local check = true
if not rdns_global then
logger.log(ngx.ERR, "WHITELIST", "Error while getting WHITELIST_RDNS_GLOBAL variable : " .. err)
elseif rdns_global == "yes" then
check, err = utils.ip_is_global(ngx.var.remote_addr)
if check == nil then
logger.log(ngx.ERR, "WHITELIST", "Error while getting checking if IP is global : " .. err)
end
end
if not cached_ip and check then
local rdns, err = utils.get_rdns(ngx.var.remote_addr)
if not rdns then
ret = false
ret_err = "error while trying to get reverse dns : " .. err
else
local rdns_list, err = utils.get_variable("WHITELIST_RDNS")
if rdns_list and rdns_list ~= "" then
for element in rdns_list:gmatch("%S+") do
table.insert(whitelists["RDNS"], element)
end
end
for i, suffix in ipairs(whitelists["RDNS"]) do
if rdns:sub(- #suffix) == suffix then
self:add_to_cache("ip" .. ngx.var.remote_addr, "rDNS " .. suffix)
ngx.var.is_whitelisted = "yes"
return ret, "client IP " .. ngx.var.remote_addr .. " is in whitelist (info = rDNS " .. suffix .. ")", true, ngx.OK
end
end
end
end
-- Check if ASN is in whitelist
if not cached_ip then
if utils.ip_is_global(ngx.var.remote_addr) then
local asn, err = utils.get_asn(ngx.var.remote_addr)
if not asn then
ret = false
ret_err = "error while trying to get asn number : " .. err
else
local asn_list, err = utils.get_variable("WHITELIST_ASN")
if asn_list and asn_list ~= "" then
for element in asn_list:gmatch("%S+") do
table.insert(whitelists["ASN"], element)
end
end
for i, asn_bl in ipairs(whitelists["ASN"]) do
if tostring(asn) == asn_bl then
self:add_to_cache("ip" .. ngx.var.remote_addr, "ASN " .. tostring(asn))
ngx.var.is_whitelisted = "yes"
return ret, "client IP " .. ngx.var.remote_addr .. " is in whitelist (kind = ASN " .. tostring(asn) .. ")", true,
ngx.OK
end
end
end
end
end
-- IP is not whitelisted
local ok, err = self:add_to_cache("ip" .. ngx.var.remote_addr, "ok")
if not ok then
ret = false
ret_err = err
end
return ret, "IP is not in list (error = " .. ret_err .. ")", false, nil
-- UA is not whiteklisted
return false, "ok"
end
function _M:is_in_cache(ele)
local kind, err = datastore:get("plugin_whitelist_cache_" .. ngx.var.server_name .. ele)
if not kind then
if err ~= "not found" then
logger.log(ngx.ERR, "WHITELIST", "Error while accessing cache : " .. err)
end
return false, err
end
return kind, "success"
end
function _M:add_to_cache(ele, kind)
local ok, err = datastore:set("plugin_whitelist_cache_" .. ngx.var.server_name .. ele, kind, 3600)
if not ok then
logger.log(ngx.ERR, "WHITELIST", "Error while adding element to cache : " .. err)
return false, err
end
return true, "success"
end
return _M
return whitelist

View File

@ -128,10 +128,10 @@ function do_and_check_cmd() {
return 0
}
# nginx 1.22.1
# nginx 1.24.0
echo " Downloading nginx"
NGINX_VERSION="1.22.1"
secure_download "https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz" "nginx-${NGINX_VERSION}.tar.gz" "1d468dcfa9bbd348b8a5dc514ac1428a789e73a92384c039b73a51ce376785f74bf942872c5594a9fcda6bbf44758bd727ce15ac2395f1aa989c507014647dcc"
NGINX_VERSION="1.24.0"
secure_download "https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz" "nginx-${NGINX_VERSION}.tar.gz" "1114e37de5664a8109c99cfb2faa1f42ff8ac63c932bcf3780d645e5ed32c0b2ac446f80305b4465994c8f9430604968e176ae464fd80f632d1cb2c8f6007ff3"
if [ -f "deps/src/nginx-${NGINX_VERSION}.tar.gz" ] ; then
do_and_check_cmd tar -xvzf deps/src/nginx-${NGINX_VERSION}.tar.gz -C deps/src
do_and_check_cmd rm -f deps/src/nginx-${NGINX_VERSION}.tar.gz
@ -148,21 +148,21 @@ if [ -f "deps/src/lua-${LUA_VERSION}.tar.gz" ] ; then
do_and_check_cmd patch deps/src/lua-${LUA_VERSION}/src/Makefile deps/misc/lua.patch2
fi
# LuaJIT v2.1-20220915
# LuaJIT v2.1-20230410
echo " Downloading LuaJIT"
git_secure_clone "https://github.com/openresty/luajit2.git" "8384278b14988390cf030b787537aa916a9709bb"
git_secure_clone "https://github.com/openresty/luajit2.git" "04f33ff01da97905a1641985fb5c840d234f97f1"
# lua-nginx-module v0.10.23
# lua-nginx-module v0.10.24
echo " Downloading lua-nginx-module"
git_secure_clone "https://github.com/openresty/lua-nginx-module.git" "5e05fa3adb0d2492ecaaf2cb76498e23765aa6ab"
git_secure_clone "https://github.com/openresty/lua-nginx-module.git" "68acad14e4a8f42e31d4a4bb5ed44d6f5b55fc1c"
# lua-resty-core v0.1.25
# lua-resty-core v0.1.26
echo " Downloading lua-resty-core"
git_secure_clone "https://github.com/openresty/lua-resty-core.git" "0173d96c9eb77b513b989b765716fd2498f09dd9"
git_secure_clone "https://github.com/openresty/lua-resty-core.git" "407000a9856d3a5aab34e8c73f6ab0f049f8b8d7"
# lua-resty-lrucache v0.13
echo " Downloading lua-resty-lrucache"
git_secure_clone "https://github.com/openresty/lua-resty-lrucache.git" "2ab2624c841cbf04785cc6384c5e213933d3b5f2"
git_secure_clone "https://github.com/openresty/lua-resty-lrucache.git" "a79615ec9dc547fdb4aaee59ef8f5a50648ce9fd"
# lua-resty-dns v0.22
echo " Downloading lua-resty-dns"
@ -180,29 +180,29 @@ git_secure_clone "https://github.com/bungle/lua-resty-random.git" "17b604f7f7dd2
echo " Downloading lua-resty-string"
git_secure_clone "https://github.com/openresty/lua-resty-string.git" "b192878f6ed31b0af237935bbc5a8110a3c2256c"
# lua-cjson v2.1.0.9
# lua-cjson v2.1.0.12
echo " Downloading lua-cjson"
git_secure_clone "https://github.com/openresty/lua-cjson.git" "891962b11d6d3b1b7275550b5c109e16c73ac94f"
git_secure_clone "https://github.com/openresty/lua-cjson.git" "881accc8fadca5ec02aa34d364df2a1aa25cd2f9"
# lua-gd v2.0.33r3+
echo " Downloading lua-gd"
git_secure_clone "https://github.com/ittner/lua-gd.git" "2ce8e478a8591afd71e607506bc8c64b161bbd30"
# lua-resty-http v0.16.1
# lua-resty-http v0.17.1
echo " Downloading lua-resty-http"
git_secure_clone "https://github.com/ledgetech/lua-resty-http.git" "9bf951dfe162dd9710a0e1f4525738d4902e9d20"
git_secure_clone "https://github.com/ledgetech/lua-resty-http.git" "4ab4269cf442ba52507aa2c718f606054452fcad"
# lualogging v1.8.0
# lualogging v1.8.2
echo " Downloading lualogging"
git_secure_clone "https://github.com/lunarmodules/lualogging.git" "1c6fcf5f68e4d0324c5977f1a27083c06f4d1b8f"
git_secure_clone "https://github.com/lunarmodules/lualogging.git" "465c994788f1bc18fca950934fa5ec9a909f496c"
# luasocket v3.1.0
echo " Downloading luasocket"
git_secure_clone "https://github.com/diegonehab/luasocket.git" "95b7efa9da506ef968c1347edf3fc56370f0deed"
# luasec v1.2.0
# luasec v1.3.1
echo " Downloading luasec"
git_secure_clone "https://github.com/brunoos/luasec.git" "d9215ee00f6694a228daad50ee85827a4cd13583"
git_secure_clone "https://github.com/brunoos/luasec.git" "fddde111f7fe9ad5417d75ebbd70429d13eaad97"
# lua-resty-ipmatcher v0.6.1 (3 commits after just in case)
echo " Downloading lua-resty-ipmatcher"
@ -215,13 +215,13 @@ if [ "$dopatch" = "yes" ] ; then
do_and_check_cmd patch deps/src/lua-resty-ipmatcher/resty/ipmatcher.lua deps/misc/ipmatcher.patch
fi
# lua-resty-redis v0.29
# lua-resty-redis v0.30
echo " Downloading lua-resty-redis"
git_secure_clone "https://github.com/openresty/lua-resty-redis.git" "053f989c7f43d8edc79d5151e73b79249c6b5d94"
git_secure_clone "https://github.com/openresty/lua-resty-redis.git" "d7c25f1b339d79196ff67f061c547a73a920b580"
# lua-resty-upload v0.10 (8 commits after just in case)
# lua-resty-upload v0.11
echo " Downloading lua-resty-upload"
git_secure_clone "https://github.com/openresty/lua-resty-upload.git" "73c89846e866bf5d0660ffa881df37fd63f04391"
git_secure_clone "https://github.com/openresty/lua-resty-upload.git" "03704aee42f7135e7782688d8a9af63a16015edc"
# luajit-geoip v2.1.0
echo " Downloading luajit-geoip"
@ -242,12 +242,24 @@ git_secure_clone "https://github.com/iskolbin/lbase64.git" "c261320edbdf82c16409
echo " Downloading lua-resty-env"
git_secure_clone "https://github.com/3scale/lua-resty-env.git" "adb294def823dd910ffa11972d2c61eab7cfce3e"
# ModSecurity v3.0.8 (19 commits after just in case)
# lua-resty-mlcache v2.6.0
echo " Downloading lua-resty-mlcache"
git_secure_clone "https://github.com/thibaultcha/lua-resty-mlcache.git" "f140f56663cbdb9cdd247d29f75c299c702ff6b4"
# lua-resty-template v2.0
echo " Downloading lua-resty-template"
git_secure_clone "https://github.com/bungle/lua-resty-template.git" "c08c6bc9e27710806990f2dec0f03b19406976ac"
# lua-resty-lock v0.09
echo " Downloading lua-resty-lock"
git_secure_clone "https://github.com/openresty/lua-resty-lock.git" "9dc550e56b6f3b1a2f1a31bb270a91813b5b6861"
# ModSecurity v3.0.9
echo " Downloading ModSecurity"
if [ ! -d "deps/src/ModSecurity" ] ; then
dopatch="yes"
fi
git_secure_clone "https://github.com/SpiderLabs/ModSecurity.git" "40f7a5067c695b1770920b881f30abc09a4e02b3"
git_secure_clone "https://github.com/SpiderLabs/ModSecurity.git" "205dac0e8c675182f96b5c2fb06be7d1cf7af2b2"
if [ "$dopatch" = "yes" ] ; then
do_and_check_cmd patch deps/src/ModSecurity/configure.ac deps/misc/modsecurity.patch
fi
@ -285,10 +297,10 @@ git_secure_clone "https://github.com/AirisX/nginx_cookie_flag_module.git" "4e48a
echo " Downloading ngx_brotli"
git_secure_clone "https://github.com/google/ngx_brotli.git" "6e975bcb015f62e1f303054897783355e2a877dc"
# ngx_devel_kit
# ngx_devel_kit v0.3.2
echo " Downloading ngx_devel_kit"
git_secure_clone "https://github.com/vision5/ngx_devel_kit.git" "b4642d6ca01011bd8cd30b253f5c3872b384fd21"
# stream-lua-nginx-module
# stream-lua-nginx-module v0.0.13
echo " Downloading stream-lua-nginx-module"
git_secure_clone "https://github.com/openresty/stream-lua-nginx-module.git" "2ef14f373b991b911c4eb5d09aa333352be9a756"
git_secure_clone "https://github.com/openresty/stream-lua-nginx-module.git" "309198abf26266f1a3e53c71388ed7bb9d1e5ea2"

View File

@ -123,6 +123,18 @@ do_and_check_cmd cp -r /tmp/bunkerweb/deps/src/lbase64/base64.lua /usr/share/bun
echo " Installing lua-resty-env"
do_and_check_cmd cp -r /tmp/bunkerweb/deps/src/lua-resty-env/src/resty/env.lua /usr/share/bunkerweb/deps/lib/lua/resty
# Installing lua-resty-mlcache
echo " Installing lua-resty-mlcache"
do_and_check_cmd cp -r /tmp/bunkerweb/deps/src/lua-resty-mlcache/lib/resty/* /usr/share/bunkerweb/deps/lib/lua/resty
# Installing lua-resty-template
echo " Installing lua-resty-template"
do_and_check_cmd cp -r /tmp/bunkerweb/deps/src/lua-resty-template/lib/resty/* /usr/share/bunkerweb/deps/lib/lua/resty
# Installing lua-resty-lock
echo " Installing lua-resty-lock"
CHANGE_DIR="/tmp/bunkerweb/deps/src/lua-resty-lock" do_and_check_cmd make PREFIX=/usr/share/bunkerweb/deps LUA_LIB_DIR=/usr/share/bunkerweb/deps/lib/lua install
# Compile dynamic modules
echo " Compiling and installing dynamic modules"
CONFARGS="$(nginx -V 2>&1 | sed -n -e 's/^.*arguments: //p')"

View File

@ -1,6 +1,9 @@
---
name: Bug report for version 2.x
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---

View File

@ -2,6 +2,9 @@
name: Bug report for version 3.x
about: Create a report to help us improve. If you don't know a specific detail or
piece of information leave it blank, if necessary we will help you to figure out.
title: ''
labels: ''
assignees: ''
---
@ -17,7 +20,7 @@ Output of:
3. Error logs
4. If there is a crash, the core dump file.
_Notice:_ Be carefully to not leak any confidential information.
_Notice:_ Be careful to not leak any confidential information.
**To Reproduce**
@ -33,8 +36,8 @@ A **curl** command line that mimics the original request and reproduces the prob
A clear and concise description of what you expected to happen.
**Server (please complete the following information):**
- ModSecurity version (and connector): [e.g. ModSecurity v3.0.1 with nginx-connector v1.0.0]
- WebServer: [e.g. nginx-1.15.5]
- ModSecurity version (and connector): [e.g. ModSecurity v3.0.8 with nginx-connector v1.0.3]
- WebServer: [e.g. nginx-1.18.0]
- OS (and distro): [e.g. Linux, archlinux]

View File

@ -1,6 +1,24 @@
v3.x.y - YYYY-MMM-DD (to be released)
-------------------------------------
v3.0.9 - 2023-Apr-12
--------------------
- Fix: possible segfault on reload if duplicate ip+CIDR in ip match list
[Issue #2877, #2890 - @tomsommer, @martinhsv]
- Add some member variable inits in Transaction class (possible segfault)
[Issue #2886 - @GNU-Plus-Windows-User, @airween, @mdounin, @martinhsv]
- Resolve memory leak on reload (bison-generated variable)
[Issue #2876 - @martinhsv]
- Support equals sign in XPath expressions
[Issue #2328 - @dennus, @martinhsv]
- Encode two special chars in error.log output
[Issue #2854 - @airween, @martinhsv]
- Add JIT support for PCRE2
[Issue #2791 - @wfjsw, @airween, @FireBurn, @martinhsv]
- Support comments in ipMatchFromFile file via '#' token
[Issue #2554 - @tomsommer, @martinhsv]
- Use name package name libmaxminddb with pkg-config
[Issue #2595, #2596 - @frankvanbever, @ffontaine, @arnout]
- Fix: FILES_TMP_CONTENT collection key should use part name
[Issue #2831 - @airween]
- Use AS_HELP_STRING instead of obsolete AC_HELP_STRING macro
[Issue #2806 - @hughmcmaster]
- During configure, do not check for pcre if pcre2 specified

View File

@ -279,6 +279,7 @@ TESTS+=test/test-cases/regression/variable-variation-count.json
TESTS+=test/test-cases/regression/variable-variation-exclusion.json
TESTS+=test/test-cases/regression/variable-WEBAPPID.json
TESTS+=test/test-cases/regression/variable-WEBSERVER_ERROR_LOG.json
TESTS+=test/test-cases/regression/variable-XML.json
TESTS+=test/test-cases/secrules-language-tests/operators/beginsWith.json
TESTS+=test/test-cases/secrules-language-tests/operators/contains.json
TESTS+=test/test-cases/secrules-language-tests/operators/containsWord.json

View File

@ -60,12 +60,10 @@ else
# Nothing about MaxMind was informed, using the pkg-config to figure things out.
if test -n "${PKG_CONFIG}"; then
MAXMIND_PKG_NAME=""
for x in ${MAXMIND_POSSIBLE_LIB_NAMES}; do
if ${PKG_CONFIG} --exists ${x}; then
MAXMIND_PKG_NAME="$x"
break
fi
done
if ${PKG_CONFIG} --exists libmaxminddb; then
MAXMIND_PKG_NAME="libmaxminddb"
break
fi
fi
AC_MSG_NOTICE([Nothing about MaxMind was informed during the configure phase. Trying to detect it on the platform...])
if test -n "${MAXMIND_PKG_NAME}"; then

View File

@ -1,6 +1,6 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
* Copyright (c) 2015 - 2023 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@ -190,7 +190,7 @@ namespace modsecurity {
#define MODSECURITY_MAJOR "3"
#define MODSECURITY_MINOR "0"
#define MODSECURITY_PATCHLEVEL "8"
#define MODSECURITY_PATCHLEVEL "9"
#define MODSECURITY_TAG ""
#define MODSECURITY_TAG_NUM "100"
@ -198,7 +198,7 @@ namespace modsecurity {
MODSECURITY_MINOR "." MODSECURITY_PATCHLEVEL \
MODSECURITY_TAG
#define MODSECURITY_VERSION_NUM 3080100
#define MODSECURITY_VERSION_NUM 3090100
#define MODSECURITY_CHECK_VERSION(a) (MODSECURITY_VERSION_NUM <= a)

View File

@ -105,6 +105,7 @@ bool VerifyCC::init(const std::string &param2, std::string *error) {
if (m_pc == NULL) {
return false;
}
m_pcje = pcre2_jit_compile(m_pc, PCRE2_JIT_COMPLETE);
#else
const char *errptr = NULL;
int erroffset = 0;
@ -142,8 +143,16 @@ bool VerifyCC::evaluate(Transaction *t, RuleWithActions *rule,
PCRE2_SPTR pcre2_i = reinterpret_cast<PCRE2_SPTR>(i.c_str());
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(m_pc, NULL);
int ret;
for (offset = 0; offset < target_length; offset++) {
int ret = pcre2_match(m_pc, pcre2_i, target_length, offset, 0, match_data, NULL);
if (m_pcje == 0) {
ret = pcre2_jit_match(m_pc, pcre2_i, target_length, offset, 0, match_data, NULL);
}
if (m_pcje != 0 || ret == PCRE2_ERROR_JIT_STACKLIMIT) {
ret = pcre2_match(m_pc, pcre2_i, target_length, offset, PCRE2_NO_JIT, match_data, NULL);
}
/* If there was no match, then we are done. */
if (ret < 0) {

View File

@ -39,7 +39,8 @@ class VerifyCC : public Operator {
explicit VerifyCC(std::unique_ptr<RunTimeString> param)
: Operator("VerifyCC", std::move(param)),
#if WITH_PCRE2
m_pc(NULL) { }
m_pc(NULL),
m_pcje(PCRE2_ERROR_JIT_BADOPTION) { }
#else
m_pc(NULL),
m_pce(NULL) { }
@ -53,6 +54,7 @@ class VerifyCC : public Operator {
private:
#if WITH_PCRE2
pcre2_code *m_pc;
int m_pcje;
#else
pcre *m_pc;
pcre_extra *m_pce;

View File

@ -1,6 +1,6 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
* Copyright (c) 2015 - 2023 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@ -34,6 +34,7 @@ Driver::Driver()
Driver::~Driver() {
while (loc.empty() == false) {
yy::location *a = loc.back();
loc.pop_back();
@ -129,9 +130,11 @@ int Driver::parse(const std::string &f, const std::string &ref) {
m_lastRule = nullptr;
loc.push_back(new yy::location());
if (ref.empty()) {
loc.back()->begin.filename = loc.back()->end.filename = new std::string("<<reference missing or not informed>>");
m_filenames.push_back("<<reference missing or not informed>>");
loc.back()->begin.filename = loc.back()->end.filename = &(m_filenames.back());
} else {
loc.back()->begin.filename = loc.back()->end.filename = new std::string(ref);
m_filenames.push_back(ref);
loc.back()->begin.filename = loc.back()->end.filename = &(m_filenames.back());
}
if (f.empty()) {

View File

@ -1,6 +1,6 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
* Copyright (c) 2015 - 2023 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@ -53,14 +53,6 @@ typedef struct Driver_t Driver;
#endif
/**
*
* FIXME: There is a memory leak in the filename at yy::location.
* The filename should be converted into a shared string to
* save memory or be associated with the life cycle of the
* driver class.
*
**/
class Driver : public RulesSetProperties {
public:
Driver();
@ -92,6 +84,13 @@ class Driver : public RulesSetProperties {
RuleWithActions *m_lastRule;
RulesSetPhases m_rulesSetPhases;
// Retain a list of new'd filenames so that they are available during the lifetime
// of the Driver object, but so that they will get cleaned up by the Driver
// destructor. This is to resolve a memory leak of yy.position.filename in location.hh.
// Ordinarily other solutions would have been preferable, but location.hh is a
// bison-generated file, which makes some alternative solutions impractical.
std::list<std::string> m_filenames;
};

File diff suppressed because it is too large Load Diff

View File

@ -897,6 +897,7 @@ namespace yy {
// "RUN_TIME_VAR_TIME_YEAR"
// "VARIABLE"
// "Dictionary element"
// "Dictionary element, with equals"
// "Dictionary element, selected by regexp"
char dummy1[sizeof (std::string)];
@ -1314,7 +1315,8 @@ namespace yy {
TOK_RUN_TIME_VAR_TIME_YEAR = 596, // "RUN_TIME_VAR_TIME_YEAR"
TOK_VARIABLE = 597, // "VARIABLE"
TOK_DICT_ELEMENT = 598, // "Dictionary element"
TOK_DICT_ELEMENT_REGEXP = 599 // "Dictionary element, selected by regexp"
TOK_DICT_ELEMENT_WITH_EQUALS = 599, // "Dictionary element, with equals"
TOK_DICT_ELEMENT_REGEXP = 600 // "Dictionary element, selected by regexp"
};
/// Backward compatibility alias (Bison 3.6).
typedef token_kind_type yytokentype;
@ -1331,7 +1333,7 @@ namespace yy {
{
enum symbol_kind_type
{
YYNTOKENS = 345, ///< Number of tokens.
YYNTOKENS = 346, ///< Number of tokens.
S_YYEMPTY = -2,
S_YYEOF = 0, // "end of file"
S_YYerror = 1, // error
@ -1677,23 +1679,24 @@ namespace yy {
S_RUN_TIME_VAR_TIME_YEAR = 341, // "RUN_TIME_VAR_TIME_YEAR"
S_VARIABLE = 342, // "VARIABLE"
S_DICT_ELEMENT = 343, // "Dictionary element"
S_DICT_ELEMENT_REGEXP = 344, // "Dictionary element, selected by regexp"
S_YYACCEPT = 345, // $accept
S_input = 346, // input
S_line = 347, // line
S_audit_log = 348, // audit_log
S_actions = 349, // actions
S_actions_may_quoted = 350, // actions_may_quoted
S_op = 351, // op
S_op_before_init = 352, // op_before_init
S_expression = 353, // expression
S_variables = 354, // variables
S_variables_pre_process = 355, // variables_pre_process
S_variables_may_be_quoted = 356, // variables_may_be_quoted
S_var = 357, // var
S_act = 358, // act
S_setvar_action = 359, // setvar_action
S_run_time_string = 360 // run_time_string
S_DICT_ELEMENT_WITH_EQUALS = 344, // "Dictionary element, with equals"
S_DICT_ELEMENT_REGEXP = 345, // "Dictionary element, selected by regexp"
S_YYACCEPT = 346, // $accept
S_input = 347, // input
S_line = 348, // line
S_audit_log = 349, // audit_log
S_actions = 350, // actions
S_actions_may_quoted = 351, // actions_may_quoted
S_op = 352, // op
S_op_before_init = 353, // op_before_init
S_expression = 354, // expression
S_variables = 355, // variables
S_variables_pre_process = 356, // variables_pre_process
S_variables_may_be_quoted = 357, // variables_may_be_quoted
S_var = 358, // var
S_act = 359, // act
S_setvar_action = 360, // setvar_action
S_run_time_string = 361 // run_time_string
};
};
@ -1927,6 +1930,7 @@ namespace yy {
case symbol_kind::S_RUN_TIME_VAR_TIME_YEAR: // "RUN_TIME_VAR_TIME_YEAR"
case symbol_kind::S_VARIABLE: // "VARIABLE"
case symbol_kind::S_DICT_ELEMENT: // "Dictionary element"
case symbol_kind::S_DICT_ELEMENT_WITH_EQUALS: // "Dictionary element, with equals"
case symbol_kind::S_DICT_ELEMENT_REGEXP: // "Dictionary element, selected by regexp"
value.move< std::string > (std::move (that.value));
break;
@ -2300,6 +2304,7 @@ switch (yykind)
case symbol_kind::S_RUN_TIME_VAR_TIME_YEAR: // "RUN_TIME_VAR_TIME_YEAR"
case symbol_kind::S_VARIABLE: // "VARIABLE"
case symbol_kind::S_DICT_ELEMENT: // "Dictionary element"
case symbol_kind::S_DICT_ELEMENT_WITH_EQUALS: // "Dictionary element, with equals"
case symbol_kind::S_DICT_ELEMENT_REGEXP: // "Dictionary element, selected by regexp"
value.template destroy< std::string > ();
break;
@ -7648,6 +7653,21 @@ switch (yykind)
return symbol_type (token::TOK_DICT_ELEMENT, v, l);
}
#endif
#if 201103L <= YY_CPLUSPLUS
static
symbol_type
make_DICT_ELEMENT_WITH_EQUALS (std::string v, location_type l)
{
return symbol_type (token::TOK_DICT_ELEMENT_WITH_EQUALS, std::move (v), std::move (l));
}
#else
static
symbol_type
make_DICT_ELEMENT_WITH_EQUALS (const std::string& v, const location_type& l)
{
return symbol_type (token::TOK_DICT_ELEMENT_WITH_EQUALS, v, l);
}
#endif
#if 201103L <= YY_CPLUSPLUS
static
symbol_type
@ -7993,7 +8013,7 @@ switch (yykind)
/// Constants.
enum
{
yylast_ = 3344, ///< Last index in yytable_.
yylast_ = 3346, ///< Last index in yytable_.
yynnts_ = 16, ///< Number of nonterminal symbols.
yyfinal_ = 339 ///< Termination state number.
};
@ -8073,10 +8093,11 @@ switch (yykind)
305, 306, 307, 308, 309, 310, 311, 312, 313, 314,
315, 316, 317, 318, 319, 320, 321, 322, 323, 324,
325, 326, 327, 328, 329, 330, 331, 332, 333, 334,
335, 336, 337, 338, 339, 340, 341, 342, 343, 344
335, 336, 337, 338, 339, 340, 341, 342, 343, 344,
345
};
// Last valid token kind.
const int code_max = 599;
const int code_max = 600;
if (t <= 0)
return symbol_kind::S_YYEOF;
@ -8292,6 +8313,7 @@ switch (yykind)
case symbol_kind::S_RUN_TIME_VAR_TIME_YEAR: // "RUN_TIME_VAR_TIME_YEAR"
case symbol_kind::S_VARIABLE: // "VARIABLE"
case symbol_kind::S_DICT_ELEMENT: // "Dictionary element"
case symbol_kind::S_DICT_ELEMENT_WITH_EQUALS: // "Dictionary element, with equals"
case symbol_kind::S_DICT_ELEMENT_REGEXP: // "Dictionary element, selected by regexp"
value.copy< std::string > (YY_MOVE (that.value));
break;
@ -8551,6 +8573,7 @@ switch (yykind)
case symbol_kind::S_RUN_TIME_VAR_TIME_YEAR: // "RUN_TIME_VAR_TIME_YEAR"
case symbol_kind::S_VARIABLE: // "VARIABLE"
case symbol_kind::S_DICT_ELEMENT: // "Dictionary element"
case symbol_kind::S_DICT_ELEMENT_WITH_EQUALS: // "Dictionary element, with equals"
case symbol_kind::S_DICT_ELEMENT_REGEXP: // "Dictionary element, selected by regexp"
value.move< std::string > (YY_MOVE (s.value));
break;
@ -8646,7 +8669,7 @@ switch (yykind)
}
} // yy
#line 8650 "seclang-parser.hh"
#line 8673 "seclang-parser.hh"

View File

@ -319,7 +319,8 @@ using namespace modsecurity::operators;
%initial-action
{
// Initialize the initial location.
@$.begin.filename = @$.end.filename = new std::string(driver.file);
driver.m_filenames.push_back(driver.file);
@$.begin.filename = @$.end.filename = &(driver.m_filenames.back());
};
%define parse.trace
%define parse.error verbose
@ -680,6 +681,7 @@ using namespace modsecurity::operators;
RUN_TIME_VAR_TIME_YEAR "RUN_TIME_VAR_TIME_YEAR"
VARIABLE "VARIABLE"
DICT_ELEMENT "Dictionary element"
DICT_ELEMENT_WITH_EQUALS "Dictionary element, with equals"
DICT_ELEMENT_REGEXP "Dictionary element, selected by regexp"
;

File diff suppressed because it is too large Load Diff

View File

@ -420,10 +420,8 @@ DICT_ELEMENT ([^\"|,\n \t}=]|([^\\]\\\"))+
DICT_ELEMENT_WITH_PIPE [^ =\t"]+
DICT_ELEMENT_NO_PIPE [^ =\|\t"]+
DICT_ELEMENT_NO_MACRO ([^\"|,%{\n \t}=]|([^\\]\\\"))+
DICT_ELEMENT_WITH_EQUALS ([^\"|,\n \t}]|([^\\]\\\"))+
DICT_ELEMENT_TWO [^\"\=, \t\r\n\\]*
DICT_ELEMENT_TWO_QUOTED [^\"\'\=\r\n\\]*
DICT_ELEMENT_TWO2 [A-Za-z_ -\%\{\.\}\-\/]+
DIRECTIVE (?i:SecRule)
DIRECTIVE_SECRULESCRIPT (?i:SecRuleScript)
FREE_TEXT_NEW_LINE [^\"|\n]+
@ -1068,7 +1066,7 @@ EQUALS_MINUS (?i:=\-)
[\/]{DICT_ELEMENT_NO_PIPE}[\/][|] { BEGIN_PREVIOUS(); yyless(yyleng - 1); return p::make_DICT_ELEMENT_REGEXP(std::string(yytext, 1, yyleng-2), *driver.loc.back()); }
['][\/]{DICT_ELEMENT_WITH_PIPE}[\/]['] { BEGIN_PREVIOUS(); yyless(yyleng - 0); return p::make_DICT_ELEMENT_REGEXP(std::string(yytext, 2, yyleng-4), *driver.loc.back()); }
['][\/]{DICT_ELEMENT_WITH_PIPE}[\/]['][|] { BEGIN_PREVIOUS(); yyless(yyleng - 1); return p::make_DICT_ELEMENT_REGEXP(std::string(yytext, 2, yyleng-4), *driver.loc.back()); }
{DICT_ELEMENT} { BEGIN_PREVIOUS(); return p::make_DICT_ELEMENT(yytext, *driver.loc.back()); }
{DICT_ELEMENT_WITH_EQUALS} { BEGIN_PREVIOUS(); return p::make_DICT_ELEMENT(yytext, *driver.loc.back()); }
[\/]{DICT_ELEMENT_NO_PIPE}[\/][,] { BEGIN_PREVIOUS(); yyless(yyleng - 1); return p::make_DICT_ELEMENT_REGEXP(std::string(yytext, 1, yyleng-2), *driver.loc.back()); }
['][\/]{DICT_ELEMENT_NO_PIPE}[\/]['][,] { BEGIN_PREVIOUS(); yyless(yyleng - 1); return p::make_DICT_ELEMENT_REGEXP(std::string(yytext, 2, yyleng-4), *driver.loc.back()); }
@ -1257,7 +1255,8 @@ EQUALS_MINUS (?i:=\-)
std::string err;
std::string f = modsecurity::utils::find_resource(s, *driver.loc.back()->end.filename, &err);
driver.loc.push_back(new yy::location());
driver.loc.back()->begin.filename = driver.loc.back()->end.filename = new std::string(f);
driver.m_filenames.push_back(f);
driver.loc.back()->begin.filename = driver.loc.back()->end.filename = &(driver.m_filenames.back());
yyin = fopen(f.c_str(), "r" );
if (!yyin) {
BEGIN(INITIAL);
@ -1285,7 +1284,8 @@ EQUALS_MINUS (?i:=\-)
for (auto& s: files) {
std::string f = modsecurity::utils::find_resource(s, *driver.loc.back()->end.filename, &err);
driver.loc.push_back(new yy::location());
driver.loc.back()->begin.filename = driver.loc.back()->end.filename = new std::string(f);
driver.m_filenames.push_back(f);
driver.loc.back()->begin.filename = driver.loc.back()->end.filename = &(driver.m_filenames.back());
yyin = fopen(f.c_str(), "r" );
if (!yyin) {
@ -1314,7 +1314,8 @@ EQUALS_MINUS (?i:=\-)
c.setKey(key);
driver.loc.push_back(new yy::location());
driver.loc.back()->begin.filename = driver.loc.back()->end.filename = new std::string(url);
driver.m_filenames.push_back(url);
driver.loc.back()->begin.filename = driver.loc.back()->end.filename = &(driver.m_filenames.back());
YY_BUFFER_STATE temp = YY_CURRENT_BUFFER;
yypush_buffer_state(temp);

View File

@ -29,17 +29,17 @@ std::string RuleMessage::_details(const RuleMessage *rm) {
msg.append(" [file \"" + std::string(*rm->m_ruleFile.get()) + "\"]");
msg.append(" [line \"" + std::to_string(rm->m_ruleLine) + "\"]");
msg.append(" [id \"" + std::to_string(rm->m_ruleId) + "\"]");
msg.append(" [rev \"" + rm->m_rev + "\"]");
msg.append(" [rev \"" + utils::string::toHexIfNeeded(rm->m_rev, true) + "\"]");
msg.append(" [msg \"" + rm->m_message + "\"]");
msg.append(" [data \"" + utils::string::limitTo(200, rm->m_data) + "\"]");
msg.append(" [data \"" + utils::string::toHexIfNeeded(utils::string::limitTo(200, rm->m_data), true) + "\"]");
msg.append(" [severity \"" +
std::to_string(rm->m_severity) + "\"]");
msg.append(" [ver \"" + rm->m_ver + "\"]");
msg.append(" [ver \"" + utils::string::toHexIfNeeded(rm->m_ver, true) + "\"]");
msg.append(" [maturity \"" + std::to_string(rm->m_maturity) + "\"]");
msg.append(" [accuracy \"" + std::to_string(rm->m_accuracy) + "\"]");
for (auto &a : rm->m_tags) {
msg.append(" [tag \"" + a + "\"]");
msg.append(" [tag \"" + utils::string::toHexIfNeeded(a, true) + "\"]");
}
msg.append(" [hostname \"" + *rm->m_serverIpAddress.get() \

View File

@ -1,6 +1,6 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
* Copyright (c) 2015 - 2023 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@ -101,11 +101,11 @@ namespace modsecurity {
*/
Transaction::Transaction(ModSecurity *ms, RulesSet *rules, void *logCbData)
: m_creationTimeStamp(utils::cpu_seconds()),
/* m_clientIpAddress(nullptr), */
m_clientIpAddress(std::make_shared<std::string>("")),
m_httpVersion(""),
/* m_serverIpAddress(""), */
m_serverIpAddress(std::make_shared<std::string>("")),
m_uri(""),
/* m_uri_no_query_string_decoded(""), */
m_uri_no_query_string_decoded(std::make_shared<std::string>("")),
m_ARGScombinedSizeDouble(0),
m_clientPort(0),
m_highestSeverityAction(255),
@ -175,11 +175,11 @@ Transaction::Transaction(ModSecurity *ms, RulesSet *rules, void *logCbData)
Transaction::Transaction(ModSecurity *ms, RulesSet *rules, char *id, void *logCbData)
: m_creationTimeStamp(utils::cpu_seconds()),
/* m_clientIpAddress(""), */
m_clientIpAddress(std::make_shared<std::string>("")),
m_httpVersion(""),
/* m_serverIpAddress(""), */
m_serverIpAddress(std::make_shared<std::string>("")),
m_uri(""),
/* m_uri_no_query_string_decoded(""), */
m_uri_no_query_string_decoded(std::make_shared<std::string>("")),
m_ARGScombinedSizeDouble(0),
m_clientPort(0),
m_highestSeverityAction(255),
@ -814,7 +814,8 @@ int Transaction::processRequestBody() {
m_variableReqbodyError.set("1", 0);
m_variableReqbodyErrorMsg.set("Request body excluding files is bigger than the maximum expected.", 0);
m_variableInboundDataError.set("1", m_variableOffset);
ms_dbg(5, "Request body excluding files is bigger than the maximum expected.");
ms_dbg(5, "Request body excluding files is bigger than the maximum expected. Limit: " \
+ std::to_string(m_rules->m_requestBodyNoFilesLimit.m_value));
requestBodyNoFilesLimitExceeded = true;
}
}
@ -901,7 +902,8 @@ int Transaction::processRequestBody() {
m_variableReqbodyError.set("1", 0);
m_variableReqbodyErrorMsg.set("Request body excluding files is bigger than the maximum expected.", 0);
m_variableInboundDataError.set("1", m_variableOffset);
ms_dbg(5, "Request body excluding files is bigger than the maximum expected.");
ms_dbg(5, "Request body excluding files is bigger than the maximum expected. Limit: " \
+ std::to_string(m_rules->m_requestBodyNoFilesLimit.m_value));
} else {
m_variableReqbodyError.set("0", m_variableOffset);
m_variableReqbodyProcessorError.set("0", m_variableOffset);

View File

@ -1,6 +1,6 @@
/*
* ModSecurity, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
* Copyright (c) 2015 - 2022 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License. You may obtain a copy of the License at
@ -88,6 +88,10 @@ IpTree::~IpTree() {
bool IpTree::addFromBuffer(std::istream *ss, std::string *error) {
char *error_msg = NULL;
for (std::string line; std::getline(*ss, line); ) {
size_t comment_start = line.find('#');
if (comment_start != std::string::npos) {
line = line.substr(0, comment_start);
}
int res = add_ip_from_param(line.c_str(), &m_tree, &error_msg);
if (res != 0) {
if (error_msg != NULL) {

View File

@ -1,6 +1,6 @@
/*
* ModSecurity for Apache 2.x, http://www.modsecurity.org/
* Copyright (c) 2015 - 2021 Trustwave Holdings, Inc. (http://www.trustwave.com/)
* Copyright (c) 2015 - 2023 Trustwave Holdings, Inc. (http://www.trustwave.com/)
*
* You may not use this file except in compliance with
* the License.  You may obtain a copy of the License at
@ -259,6 +259,7 @@ int InsertNetmask(TreeNode *node, TreeNode *parent, TreeNode *new_node,
node->count++;
node->netmasks = reinterpret_cast<unsigned char *>(malloc(node->count * sizeof(unsigned char)));
memset(node->netmasks, 0, (node->count * sizeof(unsigned char)));
if(node->netmasks == NULL)
return 0;
@ -410,6 +411,7 @@ TreeNode *CPTAddElement(unsigned char *ipdata, unsigned int ip_bitmask, CPTTree
node->count++;
new_node = node;
node->netmasks = reinterpret_cast<unsigned char *>(malloc(node->count * sizeof(unsigned char)));
memset(node->netmasks, 0, (node->count * sizeof(unsigned char)));
if ((node->count -1) == 0) {
node->netmasks[0] = netmask;
@ -418,16 +420,16 @@ TreeNode *CPTAddElement(unsigned char *ipdata, unsigned int ip_bitmask, CPTTree
node->netmasks[node->count - 1] = netmask;
i = node->count - 2;
while (i >= 0) {
if (netmask < node->netmasks[i]) {
node->netmasks[i + 1] = netmask;
int index = node->count - 2;
while (index >= 0) {
if (netmask < node->netmasks[index]) {
node->netmasks[index + 1] = netmask;
break;
}
node->netmasks[i + 1] = node->netmasks[i];
node->netmasks[i] = netmask;
i--;
node->netmasks[index + 1] = node->netmasks[index];
node->netmasks[index] = netmask;
index--;
}
}
} else {
@ -481,6 +483,7 @@ TreeNode *CPTAddElement(unsigned char *ipdata, unsigned int ip_bitmask, CPTTree
}
i_node->netmasks = reinterpret_cast<unsigned char *>(malloc((node->count - i) * sizeof(unsigned char)));
memset(i_node->netmasks, 0, ((node->count - i) * sizeof(unsigned char)));
if(i_node->netmasks == NULL) {
free(new_node->prefix);

View File

@ -73,6 +73,7 @@ Regex::Regex(const std::string& pattern_, bool ignoreCase)
PCRE2_SIZE erroroffset = 0;
m_pc = pcre2_compile(pcre2_pattern, PCRE2_ZERO_TERMINATED,
pcre2_options, &errornumber, &erroroffset, NULL);
m_pcje = pcre2_jit_compile(m_pc, PCRE2_JIT_COMPLETE);
#else
const char *errptr = NULL;
int erroffset;
@ -111,15 +112,22 @@ Regex::~Regex() {
std::list<SMatch> Regex::searchAll(const std::string& s) const {
std::list<SMatch> retList;
int rc;
int rc = 0;
#ifdef WITH_PCRE2
PCRE2_SPTR pcre2_s = reinterpret_cast<PCRE2_SPTR>(s.c_str());
PCRE2_SIZE offset = 0;
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(m_pc, NULL);
do {
rc = pcre2_match(m_pc, pcre2_s, s.length(),
offset, 0, match_data, NULL);
if (m_pcje == 0) {
rc = pcre2_jit_match(m_pc, pcre2_s, s.length(),
offset, 0, match_data, NULL);
}
if (m_pcje != 0 || rc == PCRE2_ERROR_JIT_STACKLIMIT) {
rc = pcre2_match(m_pc, pcre2_s, s.length(),
offset, PCRE2_NO_JIT, match_data, NULL);
}
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
#else
const char *subject = s.c_str();
@ -159,7 +167,14 @@ bool Regex::searchOneMatch(const std::string& s, std::vector<SMatchCapture>& cap
#ifdef WITH_PCRE2
PCRE2_SPTR pcre2_s = reinterpret_cast<PCRE2_SPTR>(s.c_str());
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(m_pc, NULL);
int rc = pcre2_match(m_pc, pcre2_s, s.length(), 0, 0, match_data, NULL);
int rc = 0;
if (m_pcje == 0) {
rc = pcre2_jit_match(m_pc, pcre2_s, s.length(), 0, 0, match_data, NULL);
}
if (m_pcje != 0 || rc == PCRE2_ERROR_JIT_STACKLIMIT) {
rc = pcre2_match(m_pc, pcre2_s, s.length(), 0, PCRE2_NO_JIT, match_data, NULL);
}
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
#else
const char *subject = s.c_str();
@ -198,7 +213,7 @@ bool Regex::searchGlobal(const std::string& s, std::vector<SMatchCapture>& captu
pcre2_options = PCRE2_NOTEMPTY_ATSTART | PCRE2_ANCHORED;
}
int rc = pcre2_match(m_pc, pcre2_s, s.length(),
startOffset, pcre2_options, match_data, NULL);
startOffset, pcre2_options, match_data, NULL);
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
#else
@ -270,9 +285,16 @@ int Regex::search(const std::string& s, SMatch *match) const {
#ifdef WITH_PCRE2
PCRE2_SPTR pcre2_s = reinterpret_cast<PCRE2_SPTR>(s.c_str());
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(m_pc, NULL);
int ret = pcre2_match(m_pc, pcre2_s, s.length(),
0, 0, match_data, NULL) > 0;
int ret = 0;
if (m_pcje == 0) {
ret = pcre2_match(m_pc, pcre2_s, s.length(),
0, 0, match_data, NULL) > 0;
}
if (m_pcje != 0 || ret == PCRE2_ERROR_JIT_STACKLIMIT) {
ret = pcre2_match(m_pc, pcre2_s, s.length(),
0, PCRE2_NO_JIT, match_data, NULL) > 0;
}
if (ret > 0) { // match
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
#else
@ -297,7 +319,14 @@ int Regex::search(const std::string& s) const {
#ifdef WITH_PCRE2
PCRE2_SPTR pcre2_s = reinterpret_cast<PCRE2_SPTR>(s.c_str());
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(m_pc, NULL);
int rc = pcre2_match(m_pc, pcre2_s, s.length(), 0, 0, match_data, NULL);
int rc = 0;
if (m_pcje == 0) {
rc = pcre2_jit_match(m_pc, pcre2_s, s.length(), 0, 0, match_data, NULL);
}
if (m_pcje != 0 || rc == PCRE2_ERROR_JIT_STACKLIMIT) {
rc = pcre2_match(m_pc, pcre2_s, s.length(), 0, PCRE2_NO_JIT, match_data, NULL);
}
pcre2_match_data_free(match_data);
if (rc > 0) {
return 1; // match

View File

@ -85,6 +85,7 @@ class Regex {
private:
#if WITH_PCRE2
pcre2_code *m_pc;
int m_pcje;
#else
pcre *m_pc = NULL;
pcre_extra *m_pce = NULL;

View File

@ -135,13 +135,14 @@ std::string string_to_hex(const std::string& input) {
return output;
}
std::string toHexIfNeeded(const std::string &str) {
std::string toHexIfNeeded(const std::string &str, bool escape_spec) {
// escape_spec: escape special chars or not
// spec chars: '"' (quotation mark, ascii 34), '\' (backslash, ascii 92)
std::stringstream res;
for (int i = 0; i < str.size(); i++) {
int c = (unsigned char)str.at(i);
if (c < 32 || c > 126) {
if (c < 32 || c > 126 || (escape_spec == true && (c == 34 || c == 92))) {
res << "\\x" << std::setw(2) << std::setfill('0') << std::hex << c;
} else {
res << str.at(i);
@ -267,7 +268,6 @@ void replaceAll(std::string *str, const std::string& from,
}
}
} // namespace string
} // namespace utils
} // namespace modsecurity

View File

@ -61,7 +61,7 @@ std::string dash_if_empty(const std::string *str);
std::string limitTo(int amount, const std::string &str);
std::string removeBracketsIfNeeded(std::string a);
std::string string_to_hex(const std::string& input);
std::string toHexIfNeeded(const std::string &str);
std::string toHexIfNeeded(const std::string &str, bool escape_spec = false);
std::string tolower(std::string str);
std::string toupper(std::string str);
std::vector<std::string> ssplit(std::string str, char delimiter);

View File

@ -60,7 +60,7 @@ ctunullpointer:src/rule_with_operator.cc:135
ctunullpointer:src/rule_with_operator.cc:95
passedByValue:src/variables/global.h:109
passedByValue:src/variables/global.h:110
passedByValue:src/parser/driver.cc:45
passedByValue:src/parser/driver.cc:46
passedByValue:test/common/modsecurity_test.cc:49
passedByValue:test/common/modsecurity_test.cc:98
unreadVariable:src/rule_with_operator.cc:219

View File

@ -1,4 +1,5 @@
127.0.0.1
# Comment line
10.10.10.1
::1
200.249.12.31

View File

@ -36,7 +36,7 @@
},
"rules":[
"SecRuleEngine On",
"SecRemoteRules key https://gist.githubusercontent.com/zimmerle/a4c1ec028999f7df71d0cc80f4f271ca/raw/4c74363bf4eae974180f1a82007196e58729dd16/modsecurity-regression-test-secremoterules.txt",
"SecRemoteRules key https://gist.githubusercontent.com/martinhsv/20705a36b7cfa8ff6d0dee0d4efce7e7/raw/faa96c7838b1fe972c1f0881efacbb440f9a4a5e/modsecurity-regression-rules.txt",
"SecRule ARGS \"@contains somethingelse\" \"id:9,pass,t:trim\""
]
},

View File

@ -129,7 +129,7 @@
},
"rules":[
"SecRuleEngine On",
"SecRule REMOTE_ADDR \"@ipMatchFromFile https://www.modsecurity.org/modsecurity-regression-test.txt\" \"id:1,phase:3,pass,t:trim\""
"SecRule REMOTE_ADDR \"@ipMatchFromFile https://gist.githubusercontent.com/martinhsv/20705a36b7cfa8ff6d0dee0d4efce7e7/raw/b9321f190eb0e81b98cb65a56db3d7e0a4f59314/modsecurity-regression-ip-list.txt\" \"id:1,phase:3,pass,t:trim\""
]
}
]

View File

@ -0,0 +1,46 @@
[
{
"enabled":1,
"version_min":300000,
"title":"Testing XPath expression with equals sign",
"expected":{
"http_code": 403
},
"client":{
"ip":"200.249.12.31",
"port":123
},
"request":{
"headers":{
"Host":"localhost",
"User-Agent":"curl/7.38.0",
"Accept":"*/*",
"Content-Type": "text/xml"
},
"uri":"/?key=value&key=other_value",
"method":"POST",
"body": [
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",
"<!DOCTYPE author [",
"<!ELEMENT book ANY>",
"<!ENTITY js SYSTEM \"/etc/passwd\">",
"]>",
"<bookstore>",
"<some-tag>aaa</some-tag><some-tag>bbb</some-tag>",
"</bookstore>"
]
},
"server":{
"ip":"200.249.12.31",
"port":80
},
"rules":[
"SecRuleEngine On",
"SecRequestBodyAccess On",
"SecRule REQUEST_HEADERS:Content-Type \"^text/xml$\" \"id:500011,phase:1,t:none,t:lowercase,nolog,pass,ctl:requestBodyProcessor=XML\"",
"SecRule XML://bookstore/*[local-name()='some-tag'] \"bbb\" \"id:500012,phase:3,t:none,t:lowercase,log,deny,status:403\""
]
}
]

View File

@ -8,7 +8,7 @@ jobs:
fail-fast: false
matrix:
cc: ["gcc", "clang"]
luaVersion: ["5.1", "5.2", "5.3", "luajit", "luajit-openresty"]
luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit", "luajit-openresty"]
include:
- luaVersion: "luajit"
runtestArgs: "LUA_INCLUDE_DIR=.lua/include/luajit-2.1"

View File

@ -1,5 +1,5 @@
sudo: required
dist: trusty
dist: Focal
os: linux
@ -7,7 +7,6 @@ language: c
compiler:
- gcc
- clang
addons:
apt:
@ -18,6 +17,7 @@ addons:
- libipc-run3-perl
- lua5.1
- lua5.1-dev
- cmake
cache:
apt: true
@ -31,6 +31,7 @@ env:
- LUAJIT=1 LUA_DIR=/usr/local LUA_INCLUDE_DIR=$LUA_DIR/include/luajit-2.1 LUA_SUFFIX=--lua-suffix=jit
install:
- sudo ln -s /usr/bin/cmake /usr/local/bin/cmake
- if [ -n "$LUAJIT" ]; then git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git; fi
- if [ -n "$LUAJIT" ]; then cd ./luajit2; fi
- if [ -n "$LUAJIT" ]; then make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1); fi
@ -47,8 +48,14 @@ install:
- cd ..
script:
- cppcheck -i ./luajit2 -i --force --error-exitcode=1 --enable=warning . > build.log 2>&1 || (cat build.log && exit 1)
- sh runtests.sh
- cppcheck -i ./luajit2 --force --error-exitcode=1 --enable=warning . > build.log 2>&1 || (cat build.log && exit 1)
- bash runtests.sh
- make
- prove -Itests tests
- TEST_LUA_USE_VALGRIND=1 prove -Itests tests > build.log 2>&1; export e=$?
- cat build.log
- grep -E '^==[0-9]+==' build.log; if [ "$?" == 0 ]; then exit 1; else exit $e; fi
- cmake -DUSE_INTERNAL_FPCONV=1 .
- make
- prove -Itests tests
- TEST_LUA_USE_VALGRIND=1 prove -Itests tests > build.log 2>&1; export e=$?

View File

@ -4,7 +4,7 @@
# Windows: set LUA_DIR=c:\lua51
project(lua-cjson C)
cmake_minimum_required(VERSION 2.6)
cmake_minimum_required(VERSION 2.8.12)
option(USE_INTERNAL_FPCONV "Use internal strtod() / g_fmt() code for performance")
option(MULTIPLE_THREADS "Support multi-threaded apps with internal fpconv - recommended" ON)

View File

@ -165,7 +165,7 @@ encode_escape_forward_slash
**default:** true
If enabled, forward slash '/' will be encoded as '\/'.
If enabled, forward slash '/' will be encoded as '\\/'.
If disabled, forward slash '/' will be encoded as '/' (no escape is applied).

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@
#define IEEE_8087
#endif
#define MALLOC(n) xmalloc(n)
#define MALLOC xmalloc
static void *xmalloc(size_t size)
{
@ -50,6 +50,10 @@ static pthread_mutex_t private_dtoa_lock[2] = {
PTHREAD_MUTEX_INITIALIZER
};
#define dtoa_get_threadno pthread_self
void
set_max_dtoa_threads(unsigned int n);
#define ACQUIRE_DTOA_LOCK(n) do { \
int r = pthread_mutex_lock(&private_dtoa_lock[n]); \
if (r) { \

View File

@ -55,7 +55,7 @@ static char locale_decimal_point = '.';
* localconv() may not be thread safe (=>crash), and nl_langinfo() is
* not supported on some platforms. Use sprintf() instead - if the
* locale does change, at least Lua CJSON won't crash. */
static void fpconv_update_locale()
static void fpconv_update_locale(void)
{
char buf[8];
@ -202,7 +202,7 @@ int fpconv_g_fmt(char *str, double num, int precision)
return len;
}
void fpconv_init()
void fpconv_init(void)
{
fpconv_update_locale();
}

View File

@ -7,12 +7,22 @@
# define FPCONV_G_FMT_BUFSIZE 32
#ifdef USE_INTERNAL_FPCONV
#ifdef MULTIPLE_THREADS
#include "dtoa_config.h"
#include <unistd.h>
static inline void fpconv_init()
{
// Add one to try and avoid core id multiplier alignment
set_max_dtoa_threads((sysconf(_SC_NPROCESSORS_CONF) + 1) * 3);
}
#else
static inline void fpconv_init()
{
/* Do nothing - not required */
}
#endif
#else
extern void fpconv_init();
extern void fpconv_init(void);
#endif
extern int fpconv_g_fmt(char*, double, int);

View File

@ -1,9 +1,9 @@
package = "lua-cjson"
version = "2.1.0.9-1"
version = "2.1.0.11-1"
source = {
url = "git+https://github.com/openresty/lua-cjson",
tag = "2.1.0.9",
tag = "2.1.0.11",
}
description = {
@ -34,6 +34,9 @@ build = {
-- Uncomment the line below on Solaris platforms if required.
-- "USE_INTERNAL_ISINF"
}
},
["cjson.safe"] = {
sources = { "lua_cjson.c", "strbuf.c", "fpconv.c" }
}
},
install = {

View File

@ -52,7 +52,7 @@
#endif
#ifndef CJSON_VERSION
#define CJSON_VERSION "2.1.0.9"
#define CJSON_VERSION "2.1.0.11"
#endif
#ifdef _MSC_VER
@ -82,6 +82,7 @@
#define DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT 1
#define DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT 0
#define DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH 1
#define DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES 0
#ifdef DISABLE_INVALID_NUMBERS
#undef DEFAULT_DECODE_INVALID_NUMBERS
@ -165,6 +166,7 @@ typedef struct {
int decode_invalid_numbers;
int decode_max_depth;
int decode_array_with_array_mt;
int encode_skip_unsupported_value_types;
} json_config_t;
typedef struct {
@ -356,6 +358,16 @@ static int json_cfg_decode_array_with_array_mt(lua_State *l)
return 1;
}
/* Configure how to treat invalid types */
static int json_cfg_encode_skip_unsupported_value_types(lua_State *l)
{
json_config_t *cfg = json_arg_init(l, 1);
json_enum_option(l, 1, &cfg->encode_skip_unsupported_value_types, NULL, 1);
return 1;
}
/* Configures JSON encoding buffer persistence */
static int json_cfg_encode_keep_buffer(lua_State *l)
{
@ -463,6 +475,7 @@ static void json_create_config(lua_State *l)
cfg->encode_empty_table_as_object = DEFAULT_ENCODE_EMPTY_TABLE_AS_OBJECT;
cfg->decode_array_with_array_mt = DEFAULT_DECODE_ARRAY_WITH_ARRAY_MT;
cfg->encode_escape_forward_slash = DEFAULT_ENCODE_ESCAPE_FORWARD_SLASH;
cfg->encode_skip_unsupported_value_types = DEFAULT_ENCODE_SKIP_UNSUPPORTED_VALUE_TYPES;
#if DEFAULT_ENCODE_KEEP_BUFFER > 0
strbuf_init(&cfg->encode_buf, 0);
@ -627,7 +640,7 @@ static void json_check_encode_depth(lua_State *l, json_config_t *cfg,
current_depth);
}
static void json_append_data(lua_State *l, json_config_t *cfg,
static int json_append_data(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json);
/* json_append_array args:
@ -637,19 +650,24 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth,
strbuf_t *json, int array_length)
{
int comma, i;
int comma, i, json_pos, err;
strbuf_append_char(json, '[');
comma = 0;
for (i = 1; i <= array_length; i++) {
if (comma)
json_pos = strbuf_length(json);
if (comma++ > 0)
strbuf_append_char(json, ',');
else
comma = 1;
lua_rawgeti(l, -1, i);
json_append_data(l, cfg, current_depth, json);
err = json_append_data(l, cfg, current_depth, json);
if (err) {
strbuf_set_length(json, json_pos);
if (comma == 1) {
comma = 0;
}
}
lua_pop(l, 1);
}
@ -697,7 +715,7 @@ static void json_append_number(lua_State *l, json_config_t *cfg,
static void json_append_object(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{
int comma, keytype;
int comma, keytype, json_pos, err;
/* Object */
strbuf_append_char(json, '{');
@ -706,10 +724,9 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
/* table, startkey */
comma = 0;
while (lua_next(l, -2) != 0) {
if (comma)
json_pos = strbuf_length(json);
if (comma++ > 0)
strbuf_append_char(json, ',');
else
comma = 1;
/* table, key, value */
keytype = lua_type(l, -2);
@ -727,7 +744,14 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
}
/* table, key, value */
json_append_data(l, cfg, current_depth, json);
err = json_append_data(l, cfg, current_depth, json);
if (err) {
strbuf_set_length(json, json_pos);
if (comma == 1) {
comma = 0;
}
}
lua_pop(l, 1);
/* table, key */
}
@ -735,8 +759,8 @@ static void json_append_object(lua_State *l, json_config_t *cfg,
strbuf_append_char(json, '}');
}
/* Serialise Lua data into JSON string. */
static void json_append_data(lua_State *l, json_config_t *cfg,
/* Serialise Lua data into JSON string. Return 1 if error an error happened, else 0 */
static int json_append_data(lua_State *l, json_config_t *cfg,
int current_depth, strbuf_t *json)
{
int len;
@ -800,16 +824,22 @@ static void json_append_data(lua_State *l, json_config_t *cfg,
case LUA_TLIGHTUSERDATA:
if (lua_touserdata(l, -1) == NULL) {
strbuf_append_mem(json, "null", 4);
} else if (lua_touserdata(l, -1) == &json_array) {
} else if (lua_touserdata(l, -1) == json_lightudata_mask(&json_array)) {
json_append_array(l, cfg, current_depth, json, 0);
}
break;
default:
/* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD,
* and LUA_TLIGHTUSERDATA) cannot be serialised */
json_encode_exception(l, cfg, json, -1, "type not supported");
if (cfg->encode_skip_unsupported_value_types) {
return 1;
} else {
json_encode_exception(l, cfg, json, -1, "type not supported");
}
/* never returns */
}
return 0;
}
static int json_encode(lua_State *l)
@ -1479,6 +1509,7 @@ static int lua_cjson_new(lua_State *l)
{ "encode_invalid_numbers", json_cfg_encode_invalid_numbers },
{ "decode_invalid_numbers", json_cfg_decode_invalid_numbers },
{ "encode_escape_forward_slash", json_cfg_encode_escape_forward_slash },
{ "encode_skip_unsupported_value_types", json_cfg_encode_skip_unsupported_value_types },
{ "new", lua_cjson_new },
{ NULL, NULL }
};

View File

@ -67,6 +67,16 @@ if [ -z "$SKIP_CMAKE" ]; then
cp -r lua/cjson build/cjson.so tests
do_tests
rm -rf build tests/cjson{,.so}
echo "===== Testing Cmake fpconv build ====="
mkdir build
cd build
cmake -DUSE_INTERNAL_FPCONV=1 ..
make
cd ..
cp -r lua/cjson build/cjson.so tests
do_tests
rm -rf build tests/cjson{,.so}
else
echo "===== Skipping Cmake build ====="
fi

View File

@ -150,7 +150,7 @@ static int calculate_new_size(strbuf_t *s, int len)
/* Exponential sizing */
while (newsize < reqsize)
newsize *= -s->increment;
} else {
} else if (s->increment != 0) {
/* Linear sizing */
newsize = ((newsize + s->increment - 1) / s->increment) * s->increment;
}

View File

@ -70,6 +70,7 @@ static char *strbuf_string(strbuf_t *s, int *len);
static void strbuf_ensure_empty_length(strbuf_t *s, int len);
static char *strbuf_empty_ptr(strbuf_t *s);
static void strbuf_extend_length(strbuf_t *s, int len);
static void strbuf_set_length(strbuf_t *s, int len);
/* Update */
extern void strbuf_append_fmt(strbuf_t *s, int len, const char *fmt, ...);
@ -108,6 +109,11 @@ static inline char *strbuf_empty_ptr(strbuf_t *s)
return s->buf + s->length;
}
static inline void strbuf_set_length(strbuf_t *s, int len)
{
s->length = len;
}
static inline void strbuf_extend_length(strbuf_t *s, int len)
{
s->length += len;

View File

@ -306,3 +306,29 @@ print(b)
{"test":"http:\/\/google.com\/google"}
{"test":"http://google.com/google"}
{"test":"http:\/\/google.com\/google"}
=== TEST 22: disable error on invalid type
--- lua
local cjson = require "cjson"
local f = function (x) return 2*x end
local res, err = pcall(cjson.encode, f)
print(err)
local t = {f = f, valid = "valid"}
local res, err = pcall(cjson.encode, t)
print(err)
local arr = {"one", "two", f, "three"}
local res, err = pcall(cjson.encode, arr)
print(err)
cjson.encode_skip_unsupported_value_types(true)
print(cjson.encode(f))
print(cjson.encode(t))
print(cjson.encode(arr))
--- out
Cannot serialise function: type not supported
Cannot serialise function: type not supported
Cannot serialise function: type not supported
{"valid":"valid"}
["one","two","three"]

View File

@ -93,7 +93,7 @@ local cjson_tests = {
-- Test API variables
{ "Check module name, version",
function () return json._NAME, json._VERSION end, { },
true, { "cjson", "2.1.0.9" } },
true, { "cjson", "2.1.0.11" } },
-- Test decoding simple types
{ "Decode string",

View File

@ -64,8 +64,8 @@ before_install:
- sudo apt install --only-upgrade ca-certificates
- '! grep -n -P ''(?<=.{80}).+'' --color `find src -name ''*.c''` `find . -name ''*.h''` || (echo "ERROR: Found C source lines exceeding 80 columns." > /dev/stderr; exit 1)'
- '! grep -n -P ''\t+'' --color `find src -name ''*.c''` `find . -name ''*.h''` || (echo "ERROR: Cannot use tabs." > /dev/stderr; exit 1)'
- sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
- /usr/bin/env perl $(command -v cpanm) --sudo --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
- pyenv global 2.7
install:
- if [ ! -f download-cache/drizzle7-$DRIZZLE_VER.tar.gz ]; then wget -P download-cache http://openresty.org/download/drizzle7-$DRIZZLE_VER.tar.gz; fi
- if [ ! -f download-cache/pcre-$PCRE_VER.tar.gz ]; then wget -P download-cache https://downloads.sourceforge.net/project/pcre/pcre/${PCRE_VER}/pcre-${PCRE_VER}.tar.gz; fi
@ -143,4 +143,4 @@ script:
- dig +short myip.opendns.com @resolver1.opendns.com || exit 0
- dig +short @$TEST_NGINX_RESOLVER openresty.org || exit 0
- dig +short @$TEST_NGINX_RESOLVER agentzh.org || exit 0
- prove -I. -Itest-nginx/lib -r t/
- /usr/bin/env perl $(command -v prove) -I. -Itest-nginx/lib -r t/

View File

@ -8200,7 +8200,7 @@ tcpsock:setoption
**context:** *rewrite_by_lua&#42;, access_by_lua&#42;, content_by_lua&#42;, ngx.timer.&#42;, ssl_certificate_by_lua&#42;, ssl_session_fetch_by_lua&#42;, ssl_client_hello_by_lua&#42;*
This function is added for [LuaSocket](http://w3.impa.br/~diego/software/luasocket/tcp.html) API compatibility and does nothing for now. Its functionality is implemented `v0.10.18`.
This function is added for [LuaSocket](http://w3.impa.br/~diego/software/luasocket/tcp.html) API compatibility, its functionality is implemented `v0.10.18`.
This feature was first introduced in the `v0.5.0rc1` release.
@ -9034,7 +9034,7 @@ ngx.worker.pids
**context:** *set_by_lua&#42;, rewrite_by_lua&#42;, access_by_lua&#42;, content_by_lua&#42;, header_filter_by_lua&#42;, body_filter_by_lua&#42;, log_by_lua&#42;, ngx.timer.&#42;, exit_worker_by_lua&#42;*
This function returns a Lua table for all Nginx worker process ID (PID). Nginx uses channel to send the current worker PID to another worker in the worker process start or restart. So this API can get all current worker PID.
This function returns a Lua table for all Nginx worker process IDs (PIDs). Nginx uses channel to send the current worker PID to another worker in the worker process start or restart. So this API can get all current worker PIDs. Windows does not have this API.
This API was first introduced in the `0.10.23` release.

View File

@ -94,7 +94,7 @@ END
case "$NGX_PLATFORM" in
Darwin:*)
case "$NGX_MACHINE" in
amd64 | arm64 | x86_64 | i386)
amd64 | x86_64 | i386)
echo "adding extra linking options needed by LuaJIT on $NGX_MACHINE"
luajit_ld_opt="$luajit_ld_opt -pagezero_size 10000 -image_base 100000000"
ngx_feature_libs="$ngx_feature_libs -pagezero_size 10000 -image_base 100000000"

View File

@ -19,7 +19,10 @@
/* Public API for other Nginx modules */
#define ngx_http_lua_version 10023
#define ngx_http_lua_version 10024
typedef struct ngx_http_lua_co_ctx_s ngx_http_lua_co_ctx_t;
typedef struct {
@ -56,6 +59,17 @@ ngx_shm_zone_t *ngx_http_lua_find_zone(u_char *name_data, size_t name_len);
ngx_shm_zone_t *ngx_http_lua_shared_memory_add(ngx_conf_t *cf, ngx_str_t *name,
size_t size, void *tag);
ngx_http_lua_co_ctx_t *ngx_http_lua_get_cur_co_ctx(ngx_http_request_t *r);
void ngx_http_lua_set_cur_co_ctx(ngx_http_request_t *r,
ngx_http_lua_co_ctx_t *coctx);
lua_State *ngx_http_lua_get_co_ctx_vm(ngx_http_lua_co_ctx_t *coctx);
void ngx_http_lua_co_ctx_resume_helper(ngx_http_lua_co_ctx_t *coctx, int nrets);
int ngx_http_lua_get_lua_http10_buffering(ngx_http_request_t *r);
#endif /* _NGX_HTTP_LUA_API_H_INCLUDED_ */

View File

@ -213,4 +213,132 @@ ngx_http_lua_shared_memory_init(ngx_shm_zone_t *shm_zone, void *data)
return NGX_OK;
}
ngx_http_lua_co_ctx_t *
ngx_http_lua_get_cur_co_ctx(ngx_http_request_t *r)
{
ngx_http_lua_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
return ctx->cur_co_ctx;
}
void
ngx_http_lua_set_cur_co_ctx(ngx_http_request_t *r, ngx_http_lua_co_ctx_t *coctx)
{
ngx_http_lua_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
coctx->data = r;
ctx->cur_co_ctx = coctx;
}
lua_State *
ngx_http_lua_get_co_ctx_vm(ngx_http_lua_co_ctx_t *coctx)
{
return coctx->co;
}
static ngx_int_t
ngx_http_lua_co_ctx_resume(ngx_http_request_t *r)
{
lua_State *vm;
ngx_connection_t *c;
ngx_int_t rc;
ngx_uint_t nreqs;
ngx_http_lua_ctx_t *ctx;
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
if (ctx == NULL) {
return NGX_ERROR;
}
ctx->resume_handler = ngx_http_lua_wev_handler;
c = r->connection;
vm = ngx_http_lua_get_lua_vm(r, ctx);
nreqs = c->requests;
rc = ngx_http_lua_run_thread(vm, r, ctx, ctx->cur_co_ctx->nrets);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"lua run thread returned %d", rc);
if (rc == NGX_AGAIN) {
return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs);
}
if (rc == NGX_DONE) {
ngx_http_lua_finalize_request(r, NGX_DONE);
return ngx_http_lua_run_posted_threads(c, vm, r, ctx, nreqs);
}
if (ctx->entered_content_phase) {
ngx_http_lua_finalize_request(r, rc);
return NGX_DONE;
}
return rc;
}
void
ngx_http_lua_co_ctx_resume_helper(ngx_http_lua_co_ctx_t *coctx, int nrets)
{
ngx_connection_t *c;
ngx_http_request_t *r;
ngx_http_lua_ctx_t *ctx;
ngx_http_log_ctx_t *log_ctx;
r = coctx->data;
c = r->connection;
ctx = ngx_http_get_module_ctx(r, ngx_http_lua_module);
if (ctx == NULL) {
return;
}
if (c->fd != (ngx_socket_t) -1) { /* not a fake connection */
log_ctx = c->log->data;
log_ctx->current_request = r;
}
coctx->nrets = nrets;
coctx->cleanup = NULL;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"lua coctx resume handler: \"%V?%V\"", &r->uri, &r->args);
ctx->cur_co_ctx = coctx;
if (ctx->entered_content_phase) {
(void) ngx_http_lua_co_ctx_resume(r);
} else {
ctx->resume_handler = ngx_http_lua_co_ctx_resume;
ngx_http_core_run_phases(r);
}
ngx_http_run_posted_requests(c);
}
int
ngx_http_lua_get_lua_http10_buffering(ngx_http_request_t *r)
{
ngx_http_lua_loc_conf_t *llcf;
llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module);
return llcf->http10_buffering;
}
/* vi:set ft=c ts=4 sw=4 et fdm=marker: */

View File

@ -497,6 +497,7 @@ struct ngx_http_lua_co_ctx_s {
unsigned nresults_from_worker_thread; /* number of results
* from worker
* thread callback */
unsigned nrets; /* ngx_http_lua_run_thread nrets arg. */
unsigned nsubreqs; /* number of subrequests of the
* current request */

View File

@ -1250,8 +1250,10 @@ ngx_http_lua_ngx_raw_header_cleanup(void *data)
int
ngx_http_lua_ffi_set_resp_header_macos(ngx_http_lua_set_resp_header_params_t *p)
{
return ngx_http_lua_ffi_set_resp_header(p->r, p->key_data, p->key_len,
p->is_nil, p->sval, p->sval_len,
return ngx_http_lua_ffi_set_resp_header(p->r, (const u_char *) p->key_data,
p->key_len, p->is_nil,
(const u_char *) p->sval,
p->sval_len,
p->mvals, p->mvals_len,
p->override, p->errmsg);
}

View File

@ -2096,7 +2096,8 @@ ngx_http_lua_ffi_shdict_free_space(ngx_shm_zone_t *zone)
int
ngx_http_lua_ffi_shdict_get_macos(ngx_http_lua_shdict_get_params_t *p)
{
return ngx_http_lua_ffi_shdict_get(p->zone, p->key, p->key_len,
return ngx_http_lua_ffi_shdict_get(p->zone,
(u_char *) p->key, p->key_len,
p->value_type, p->str_value_buf,
p->str_value_len, p->num_value,
p->user_flags, p->get_stale,
@ -2107,8 +2108,10 @@ ngx_http_lua_ffi_shdict_get_macos(ngx_http_lua_shdict_get_params_t *p)
int
ngx_http_lua_ffi_shdict_store_macos(ngx_http_lua_shdict_store_params_t *p)
{
return ngx_http_lua_ffi_shdict_store(p->zone, p->op, p->key, p->key_len,
p->value_type, p->str_value_buf,
return ngx_http_lua_ffi_shdict_store(p->zone, p->op,
(u_char *) p->key, p->key_len,
p->value_type,
(u_char *) p->str_value_buf,
p->str_value_len, p->num_value,
p->exptime, p->user_flags,
p->errmsg, p->forcible);
@ -2118,7 +2121,7 @@ ngx_http_lua_ffi_shdict_store_macos(ngx_http_lua_shdict_store_params_t *p)
int
ngx_http_lua_ffi_shdict_incr_macos(ngx_http_lua_shdict_incr_params_t *p)
{
return ngx_http_lua_ffi_shdict_incr(p->zone, p->key, p->key_len,
return ngx_http_lua_ffi_shdict_incr(p->zone, (u_char *) p->key, p->key_len,
p->num_value, p->errmsg,
p->has_init, p->init, p->init_ttl,
p->forcible);

View File

@ -1744,7 +1744,7 @@ ngx_http_lua_ffi_socket_tcp_sslhandshake(ngx_http_request_t *r,
/* read rest of the chain */
for (i = 1; i < sk_X509_num(chain); i++) {
for (i = 1; i < (ngx_int_t) sk_X509_num(chain); i++) {
x509 = sk_X509_value(chain, i);
if (x509 == NULL) {
ERR_clear_error();
@ -6483,7 +6483,7 @@ ngx_http_lua_ffi_socket_tcp_getoption(ngx_http_lua_socket_tcp_upstream_t *u,
fd = u->peer.connection->fd;
if (fd == (ngx_socket_t) -1) {
if (fd == (int) -1) {
*errlen = ngx_snprintf(err, *errlen, "invalid socket fd") - err;
return NGX_ERROR;
}
@ -6540,7 +6540,7 @@ ngx_http_lua_ffi_socket_tcp_setoption(ngx_http_lua_socket_tcp_upstream_t *u,
fd = u->peer.connection->fd;
if (fd == (ngx_socket_t) -1) {
if (fd == (int) -1) {
*errlen = ngx_snprintf(err, *errlen, "invalid socket fd") - err;
return NGX_ERROR;
}
@ -6601,7 +6601,7 @@ ngx_http_lua_ffi_socket_tcp_hack_fd(ngx_http_lua_socket_tcp_upstream_t *u,
}
rc = u->peer.connection->fd;
if (rc == (ngx_socket_t) -1) {
if (rc == (int) -1) {
*errlen = ngx_snprintf(err, *errlen, "invalid socket fd") - err;
return -1;
}

View File

@ -8,7 +8,10 @@
#define DDEBUG 0
#endif
#include "ddebug.h"
#if !(NGX_WIN32)
#include <ngx_channel.h>
#endif
#define NGX_PROCESS_PRIVILEGED_AGENT 99
@ -21,22 +24,25 @@ ngx_http_lua_ffi_worker_pid(void)
}
#if !(NGX_WIN32)
int
ngx_http_lua_ffi_worker_pids(int *pids, size_t *pids_len)
{
ngx_int_t i, n;
size_t n;
ngx_int_t i;
n = 0;
for (i = 0; i < NGX_MAX_PROCESSES; i++) {
for (i = 0; n < *pids_len && i < NGX_MAX_PROCESSES; i++) {
if (i != ngx_process_slot && ngx_processes[i].pid == 0) {
break;
}
if (i == ngx_process_slot && ngx_processes[i].pid == 0) {
/* The current process */
if (i == ngx_process_slot) {
pids[n++] = ngx_pid;
}
if (ngx_processes[i].pid > 0) {
if (ngx_processes[i].channel[0] > 0 && ngx_processes[i].pid > 0) {
pids[n++] = ngx_processes[i].pid;
}
}
@ -49,6 +55,7 @@ ngx_http_lua_ffi_worker_pids(int *pids, size_t *pids_len)
return NGX_OK;
}
#endif
int

View File

@ -0,0 +1,49 @@
# vim:set ft= ts=4 sw=4 et fdm=marker:
use Test::Nginx::Socket::Lua;
#worker_connections(1014);
master_on();
workers(4);
#log_level('warn');
repeat_each(2);
plan tests => repeat_each() * (blocks() * 3);
#no_diff();
no_long_string();
run_tests();
__DATA__
=== TEST 1: get worker pids with multiple worker
--- config
location /lua {
content_by_lua_block {
local pids, err = ngx.worker.pids()
if err ~= nil then
return
end
local pid = ngx.worker.pid()
ngx.say("worker pid: ", pid)
local count = ngx.worker.count()
ngx.say("worker count: ", count)
ngx.say("worker pids count: ", #pids)
for i = 1, count do
if pids[i] == pid then
ngx.say("worker pid is correct.")
return
end
end
}
}
--- request
GET /lua
--- response_body_like
worker pid: \d+
worker count: 4
worker pids count: 4
worker pid is correct\.
--- no_error_log
[error]

View File

@ -0,0 +1,58 @@
# vim:set ft= ts=4 sw=4 et fdm=marker:
our $SkipReason;
BEGIN {
if ($ENV{TEST_NGINX_CHECK_LEAK}) {
$SkipReason = "unavailable for the hup tests";
} else {
$ENV{TEST_NGINX_USE_HUP} = 1;
undef $ENV{TEST_NGINX_USE_STAP};
}
}
use Test::Nginx::Socket::Lua 'no_plan';
#worker_connections(1014);
master_on();
workers(4);
#log_level('warn');
repeat_each(2);
#no_diff();
no_long_string();
run_tests();
__DATA__
=== TEST 1: get worker pids with multiple worker
--- config
location /lua {
content_by_lua_block {
local pids, err = ngx.worker.pids()
if err ~= nil then
return
end
local pid = ngx.worker.pid()
ngx.say("worker pid: ", pid)
local count = ngx.worker.count()
ngx.say("worker count: ", count)
ngx.say("worker pids count: ", #pids)
for i = 1, count do
if pids[i] == pid then
ngx.say("worker pid is correct.")
return
end
end
}
}
--- request
GET /lua
--- response_body_like
worker pid: \d+
worker count: 4
worker pids count: 4
worker pid is correct\.
--- no_error_log
[error]

View File

@ -18,8 +18,8 @@ jobs:
strategy:
matrix:
openresty_version:
- 1.17.8.1
- 1.19.3.1
- 1.17.8.2
- 1.19.9.1
runs-on: ubuntu-latest
container:
@ -28,9 +28,10 @@ jobs:
options: --init
steps:
- uses: actions/checkout@v2
- name: Install deps
run: |
apk add --no-cache curl perl bash wget git perl-dev libarchive-tools
apk add --no-cache curl perl bash wget git perl-dev libarchive-tools nodejs
ln -s /usr/bin/bsdtar /usr/bin/tar
- name: Install CPAN
@ -48,14 +49,12 @@ jobs:
run: cpanm -q -n Test::Nginx
- name: Install Luacov
run: luarocks install luacov
run: /usr/local/openresty/luajit/bin/luarocks install luacov
- uses: actions/checkout@v2
- name: Run tests
env:
TEST_COVERAGE: '1'
run: /usr/bin/prove -I../test-nginx/lib -r t/
run: make coverage
- name: Coverage
run: |

View File

@ -19,6 +19,7 @@ Production ready.
* Request pipelining
* Trailers
* HTTP proxy connections
* mTLS (requires `ngx_lua_http_module` >= v0.10.23)
## API
@ -91,9 +92,9 @@ local body = res.body
local httpc = require("resty.http").new()
-- First establish a connection
local ok, err = httpc:connect({
local ok, err, ssl_session = httpc:connect({
scheme = "https",
host = "127.0.0.1"
host = "127.0.0.1",
port = 8080,
})
if not ok then
@ -153,7 +154,7 @@ Creates the HTTP connection object. In case of failures, returns `nil` and a str
## connect
`syntax: ok, err = httpc:connect(options)`
`syntax: ok, err, ssl_session = httpc:connect(options)`
Attempts to connect to the web server while incorporating the following activities:
@ -172,9 +173,12 @@ The options table has the following fields:
* `pool_size`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
* `backlog`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
* `proxy_opts`: sub-table, defaults to the global proxy options set, see [set\_proxy\_options](#set_proxy_options).
* `ssl_reused_session`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_verify`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake), except that it defaults to `true`.
* `ssl_server_name`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_send_status_req`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
* `ssl_client_cert`: will be passed to `tcpsock:setclientcert`. Requires `ngx_lua_http_module` >= v0.10.23.
* `ssl_client_priv_key`: as above.
## set\_timeout
@ -251,6 +255,8 @@ When the request is successful, `res` will contain the following fields:
* `read_body`: A method to read the entire body into a string.
* `read_trailers`: A method to merge any trailers underneath the headers, after reading the body.
If the response has a body, then before the same connection can be used for another request, you must read the body using `read_body` or `body_reader`.
## request\_uri
`syntax: res, err = httpc:request_uri(uri, params)`

View File

@ -106,7 +106,7 @@ end
local _M = {
_VERSION = '0.16.1',
_VERSION = '0.17.1',
}
_M._USER_AGENT = "lua-resty-http/" .. _M._VERSION .. " (Lua) ngx_lua/" .. ngx.config.ngx_lua_version
@ -165,7 +165,7 @@ end
do
local aio_connect = require "resty.http_connect"
-- Function signatures to support:
-- ok, err = httpc:connect(options_table)
-- ok, err, ssl_session = httpc:connect(options_table)
-- ok, err = httpc:connect(host, port, options_table?)
-- ok, err = httpc:connect("unix:/path/to/unix.sock", options_table?)
function _M.connect(self, options, ...)
@ -313,8 +313,10 @@ local function _format_request(self, params)
local query = params.query or ""
if type(query) == "table" then
query = "?" .. ngx_encode_args(query)
elseif query ~= "" and str_sub(query, 1, 1) ~= "?" then
query = ngx_encode_args(query)
end
if query ~= "" and str_sub(query, 1, 1) ~= "?" then
query = "?" .. query
end
@ -362,7 +364,21 @@ local function _receive_status(sock)
return nil, nil, nil, err
end
return tonumber(str_sub(line, 10, 12)), tonumber(str_sub(line, 6, 8)), str_sub(line, 14)
local version = tonumber(str_sub(line, 6, 8))
if not version then
return nil, nil, nil,
"couldn't parse HTTP version from response status line: " .. line
end
local status = tonumber(str_sub(line, 10, 12))
if not status then
return nil, nil, nil,
"couldn't parse status code from response status line: " .. line
end
local reason = str_sub(line, 14)
return status, version, reason
end
@ -621,18 +637,23 @@ end
local function _handle_continue(sock, body)
local status, version, reason, err = _receive_status(sock) --luacheck: no unused
if not status then
return nil, nil, err
return nil, nil, nil, err
end
-- Only send body if we receive a 100 Continue
if status == 100 then
local ok, err = sock:receive("*l") -- Read carriage return
if not ok then
return nil, nil, err
-- Read headers
local headers, err = _receive_headers(sock)
if not headers then
return nil, nil, nil, err
end
local ok, err = _send_body(sock, body)
if not ok then
return nil, nil, nil, err
end
_send_body(sock, body)
end
return status, version, err
return status, version, reason, err
end
@ -750,11 +771,11 @@ function _M.read_response(self, params)
-- If we expect: continue, we need to handle this, sending the body if allowed.
-- If we don't get 100 back, then status is the actual status.
if params.headers["Expect"] == "100-continue" then
local _status, _version, _err = _handle_continue(sock, params.body)
local _status, _version, _reason, _err = _handle_continue(sock, params.body)
if not _status then
return nil, _err
elseif _status ~= 100 then
status, version, err = _status, _version, _err -- luacheck: no unused
status, version, reason, err = _status, _version, _reason, _err -- luacheck: no unused
end
end

View File

@ -1,6 +1,8 @@
local ngx_re_gmatch = ngx.re.gmatch
local ngx_re_sub = ngx.re.sub
local ngx_re_find = ngx.re.find
local ngx_log = ngx.log
local ngx_WARN = ngx.WARN
--[[
A connection function that incorporates:
@ -22,11 +24,17 @@ client:connect {
backlog = nil,
-- ssl options as per: https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake
ssl_reused_session = nil
ssl_server_name = nil,
ssl_send_status_req = nil,
ssl_verify = true, -- NOTE: defaults to true
ctx = nil, -- NOTE: not supported
-- mTLS options: These require support for mTLS in cosockets, which first
-- appeared in `ngx_http_lua_module` v0.10.23.
ssl_client_cert = nil,
ssl_client_priv_key = nil,
proxy_opts, -- proxy opts, defaults to global proxy options
}
]]
@ -53,15 +61,19 @@ local function connect(self, options)
end
-- ssl settings
local ssl, ssl_server_name, ssl_verify, ssl_send_status_req
local ssl, ssl_reused_session, ssl_server_name
local ssl_verify, ssl_send_status_req, ssl_client_cert, ssl_client_priv_key
if request_scheme == "https" then
ssl = true
ssl_reused_session = options.ssl_reused_session
ssl_server_name = options.ssl_server_name
ssl_send_status_req = options.ssl_send_status_req
ssl_verify = true -- default
if options.ssl_verify == false then
ssl_verify = false
end
ssl_client_cert = options.ssl_client_cert
ssl_client_priv_key = options.ssl_client_priv_key
end
-- proxy related settings
@ -133,9 +145,10 @@ local function connect(self, options)
end
if proxy then
local proxy_uri_t, err = self:parse_uri(proxy_uri)
local proxy_uri_t
proxy_uri_t, err = self:parse_uri(proxy_uri)
if not proxy_uri_t then
return nil, err
return nil, "uri parse error: ", err
end
local proxy_scheme = proxy_uri_t[1]
@ -169,7 +182,9 @@ local function connect(self, options)
-- proxy based connection
ok, err = sock:connect(proxy_host, proxy_port, tcp_opts)
if not ok then
return nil, err
return nil, "failed to connect to: " .. (proxy_host or "") ..
":" .. (proxy_port or "") ..
": ", err
end
if ssl and sock:getreusedtimes() == 0 then
@ -178,7 +193,8 @@ local function connect(self, options)
-- authority-form of RFC 7230 Section 5.3.3. See also RFC 7231 Section
-- 4.3.6 for more details about the CONNECT request
local destination = request_host .. ":" .. request_port
local res, err = self:request({
local res
res, err = self:request({
method = "CONNECT",
path = destination,
headers = {
@ -188,7 +204,7 @@ local function connect(self, options)
})
if not res then
return nil, err
return nil, "failed to issue CONNECT to proxy:", err
end
if res.status < 200 or res.status > 299 then
@ -211,10 +227,25 @@ local function connect(self, options)
end
end
local ssl_session
-- Now do the ssl handshake
if ssl and sock:getreusedtimes() == 0 then
local ok, err = sock:sslhandshake(nil, ssl_server_name, ssl_verify, ssl_send_status_req)
if not ok then
-- Experimental mTLS support
if ssl_client_cert and ssl_client_priv_key then
if type(sock.setclientcert) ~= "function" then
ngx_log(ngx_WARN, "cannot use SSL client cert and key without mTLS support")
else
ok, err = sock:setclientcert(ssl_client_cert, ssl_client_priv_key)
if not ok then
ngx_log(ngx_WARN, "could not set client certificate: ", err)
end
end
end
ssl_session, err = sock:sslhandshake(ssl_reused_session, ssl_server_name, ssl_verify, ssl_send_status_req)
if not ssl_session then
self:close()
return nil, err
end
@ -228,7 +259,7 @@ local function connect(self, options)
self.http_proxy_auth = request_scheme ~= "https" and proxy_authorization or nil
self.path_prefix = path_prefix
return true
return true, nil, ssl_session
end
return connect

View File

@ -4,7 +4,7 @@ local rawget, rawset, setmetatable =
local str_lower = string.lower
local _M = {
_VERSION = '0.16.1',
_VERSION = '0.17.1',
}

View File

@ -1,8 +1,8 @@
package = "lua-resty-http"
version = "0.16.1-0"
version = "0.17.1-0"
source = {
url = "git://github.com/ledgetech/lua-resty-http",
tag = "v0.16.1"
tag = "v0.17.1"
}
description = {
summary = "Lua HTTP client cosocket driver for OpenResty / ngx_lua.",

View File

@ -341,3 +341,93 @@ OK
--- no_error_log
[error]
[warn]
=== TEST 13: Should return error on invalid HTTP version in response status line
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri("http://127.0.0.1:12345")
assert(err == "couldn't parse HTTP version from response status line: TEAPOT/1.1 OMG")
}
}
--- tcp_listen: 12345
--- tcp_reply
TEAPOT/1.1 OMG
Server: Teapot
OK
--- request
GET /a
--- no_error_log
[error]
[warn]
=== TEST 14: Should return error on invalid status code in response status line
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri("http://127.0.0.1:12345")
assert(err == "couldn't parse status code from response status line: HTTP/1.1 OMG")
}
}
--- tcp_listen: 12345
--- tcp_reply
HTTP/1.1 OMG
Server: Teapot
OK
--- request
GET /a
--- no_error_log
[error]
[warn]
=== TEST 14: Empty query
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua '
local http = require "resty.http"
local httpc = http.new()
httpc:connect{
scheme = "http",
host = "127.0.0.1",
port = ngx.var.server_port
}
local res, err = httpc:request{
query = {},
path = "/b"
}
ngx.status = res.status
ngx.print(ngx.header.test)
httpc:close()
';
}
location = /b {
content_by_lua '
ngx.header.test = ngx.var.request_uri
';
}
--- request
GET /a
--- response_headers
/b
--- no_error_log
[error]
[warn]

View File

@ -205,7 +205,153 @@ Expectation Failed
[warn]
=== TEST 5: Non string request bodies are converted with correct length
=== TEST 5: Return 100 Continue with headers
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
httpc:connect({
scheme = "http",
host = "127.0.0.1",
port = ngx.var.server_port
})
local res, err = httpc:request{
body = "a=1&b=2&c=3",
path = "/b",
headers = {
["Expect"] = "100-continue",
["Content-Type"] = "application/x-www-form-urlencoded",
}
}
if not res then
ngx.log(ngx.ERR, "httpc:request failed: ", err)
end
ngx.say(res.status)
ngx.say(res:read_body())
httpc:close()
}
}
location = /b {
content_by_lua_block {
local len = ngx.req.get_headers()["Content-Length"]
local sock, err = ngx.req.socket(true)
if not sock then
ngx.log(ngx.ERR, "server: failed to get raw req socket: ", err)
return
end
-- with additional header
local ok, err = sock:send("HTTP/1.1 100 Continue\r\nConnection: keep-alive\r\n\r\n")
if not ok then
ngx.log(ngx.ERR, "failed to send 100 response: ", err)
end
local data, err = sock:receive(len)
if not data then
ngx.log(ngx.ERR, "failed to receive: ", err)
return
end
local ok, err = sock:send("HTTP/1.1 200 OK\r\n" ..
"Content-Length: " .. len .. "\r\n" ..
"Content-Type: application/x-www-form-urlencoded\r\n\r\n" ..
data)
if not ok then
ngx.log(ngx.ERR, "failed to send 200 response: ", err)
return
end
}
}
--- request
GET /a
--- response_body
200
a=1&b=2&c=3
--- no_error_log
[error]
[warn]
=== TEST 6: Return 100 Continue without headers
--- http_config eval: $::HttpConfig
--- config
location = /a {
content_by_lua_block {
local http = require "resty.http"
local httpc = http.new()
httpc:connect({
scheme = "http",
host = "127.0.0.1",
port = ngx.var.server_port
})
local res, err = httpc:request{
body = "a=1&b=2&c=3",
path = "/b",
headers = {
["Expect"] = "100-continue",
["Content-Type"] = "application/x-www-form-urlencoded",
}
}
if not res then
ngx.log(ngx.ERR, "httpc:request failed: ", err)
end
ngx.say(res.status)
ngx.say(res:read_body())
httpc:close()
}
}
location = /b {
content_by_lua_block {
local len = ngx.req.get_headers()["Content-Length"]
local sock, err = ngx.req.socket(true)
if not sock then
ngx.log(ngx.ERR, "server: failed to get raw req socket: ", err)
return
end
-- without additional headers
local ok, err = sock:send("HTTP/1.1 100 Continue\r\n\r\n")
if not ok then
ngx.log(ngx.ERR, "failed to send 100 response: ", err)
end
local data, err = sock:receive(len)
if not data then
ngx.log(ngx.ERR, "failed to receive: ", err)
return
end
local ok, err = sock:send("HTTP/1.1 200 OK\r\n" ..
"Content-Length: " .. len .. "\r\n" ..
"Content-Type: application/x-www-form-urlencoded\r\n\r\n" ..
data)
if not ok then
ngx.log(ngx.ERR, "failed to send 200 response: ", err)
return
end
}
}
--- request
GET /a
--- response_body
200
a=1&b=2&c=3
--- no_error_log
[error]
[warn]
=== TEST 7: Non string request bodies are converted with correct length
--- http_config eval: $::HttpConfig
--- config
location = /a {
@ -247,7 +393,7 @@ mix123edtable
[warn]
=== TEST 6: Request body as iterator
=== TEST 8: Request body as iterator
--- http_config eval: $::HttpConfig
--- config
location = /a {
@ -284,7 +430,7 @@ foobar
[warn]
=== TEST 7: Request body as iterator, errors with missing length
=== TEST 9: Request body as iterator, errors with missing length
--- http_config eval: $::HttpConfig
--- config
location = /a {
@ -319,7 +465,7 @@ Request body is a function but a length or chunked encoding is not specified
[warn]
=== TEST 8: Request body as iterator with chunked encoding
=== TEST 10: Request body as iterator with chunked encoding
--- http_config eval: $::HttpConfig
--- config
location = /a {

View File

@ -0,0 +1,136 @@
use Test::Nginx::Socket::Lua 'no_plan';
use Cwd qw(abs_path realpath);
use File::Basename;
$ENV{TEST_NGINX_HTML_DIR} ||= html_dir();
$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';
$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__)));
$ENV{TEST_COVERAGE} ||= 0;
my $realpath = realpath();
our $HttpConfig = qq{
lua_package_path "$realpath/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;";
init_by_lua_block {
if $ENV{TEST_COVERAGE} == 1 then
jit.off()
require("luacov.runner").init()
end
TEST_SERVER_SOCK = "unix:/$ENV{TEST_NGINX_HTML_DIR}/nginx.sock"
num_handshakes = 0
}
server {
listen unix:$ENV{TEST_NGINX_HTML_DIR}/nginx.sock ssl;
server_name example.com;
ssl_certificate $ENV{TEST_NGINX_CERT_DIR}/cert/test.crt;
ssl_certificate_key $ENV{TEST_NGINX_CERT_DIR}/cert/test.key;
ssl_session_tickets off;
server_tokens off;
}
};
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: connect returns session userdata
--- http_config eval: $::HttpConfig
--- config
server_tokens off;
resolver $TEST_NGINX_RESOLVER ipv6=off;
lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt;
location /t {
content_by_lua_block {
local httpc = assert(require("resty.http").new())
local ok, err, session = assert(httpc:connect {
scheme = "https",
host = TEST_SERVER_SOCK,
})
assert(type(session) == "userdata" or type(session) == "cdata", "expected session to be userdata or cdata")
assert(httpc:close())
}
}
--- request
GET /t
--- no_error_log
[error]
[alert]
=== TEST 2: ssl_reused_session false does not return session userdata
--- http_config eval: $::HttpConfig
--- config
server_tokens off;
resolver $TEST_NGINX_RESOLVER ipv6=off;
lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt;
location /t {
content_by_lua_block {
local httpc = assert(require("resty.http").new())
local ok, err, session = assert(httpc:connect {
scheme = "https",
host = TEST_SERVER_SOCK,
ssl_reused_session = false,
})
assert(type(session) == "boolean", "expected session to be a boolean")
assert(session == true, "expected session to be true")
assert(httpc:close())
}
}
--- request
GET /t
--- no_error_log
[error]
[alert]
=== TEST 3: ssl_reused_session accepts userdata
--- http_config eval: $::HttpConfig
--- config
server_tokens off;
resolver $TEST_NGINX_RESOLVER ipv6=off;
lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt;
location /t {
content_by_lua_block {
local httpc = assert(require("resty.http").new())
local ok, err, session = assert(httpc:connect {
scheme = "https",
host = TEST_SERVER_SOCK,
})
assert(type(session) == "userdata" or type(session) == "cdata", "expected session to be userdata or cdata")
local httpc2 = assert(require("resty.http").new())
local ok, err, session2 = assert(httpc2:connect {
scheme = "https",
host = TEST_SERVER_SOCK,
ssl_reused_session = session,
})
assert(type(session2) == "userdata" or type(session2) == "cdata", "expected session2 to be userdata or cdata")
assert(httpc:close())
assert(httpc2:close())
}
}
--- request
GET /t
--- no_error_log
[error]
[alert]

View File

@ -0,0 +1,211 @@
use Test::Nginx::Socket::Lua 'no_plan';
#$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';
#$ENV{TEST_COVERAGE} ||= 0;
log_level 'debug';
no_long_string();
#no_diff();
sub read_file {
my $infile = shift;
open my $in, $infile
or die "cannot open $infile for reading: $!";
my $cert = do { local $/; <$in> };
close $in;
$cert;
}
our $MTLSCA = read_file("t/cert/mtls_ca.crt");
our $MTLSClient = read_file("t/cert/mtls_client.crt");
our $MTLSClientKey = read_file("t/cert/mtls_client.key");
our $TestCert = read_file("t/cert/test.crt");
our $TestKey = read_file("t/cert/test.key");
our $HtmlDir = html_dir;
use Cwd qw(cwd);
my $pwd = cwd();
our $mtls_http_config = <<"_EOC_";
lua_package_path "$pwd/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;";
server {
listen unix:$::HtmlDir/mtls.sock ssl;
ssl_certificate $::HtmlDir/test.crt;
ssl_certificate_key $::HtmlDir/test.key;
ssl_client_certificate $::HtmlDir/mtls_ca.crt;
ssl_verify_client on;
server_tokens off;
server_name example.com;
location / {
echo -n "hello, \$ssl_client_s_dn";
}
}
_EOC_
our $mtls_user_files = <<"_EOC_";
>>> mtls_ca.crt
$::MTLSCA
>>> mtls_client.key
$::MTLSClientKey
>>> mtls_client.crt
$::MTLSClient
>>> test.crt
$::TestCert
>>> test.key
$::TestKey
_EOC_
run_tests();
__DATA__
=== TEST 1: Connection fails during handshake without client cert and key
--- http_config eval: $::mtls_http_config
--- config eval
"
lua_ssl_trusted_certificate $::HtmlDir/test.crt;
location /t {
content_by_lua_block {
local httpc = assert(require('resty.http').new())
local ok, err = httpc:connect {
scheme = 'https',
host = 'unix:$::HtmlDir/mtls.sock',
}
if ok and not err then
local res, err = assert(httpc:request {
method = 'GET',
path = '/',
headers = {
['Host'] = 'example.com',
},
})
ngx.status = res.status -- expect 400
end
httpc:close()
}
}
"
--- user_files eval: $::mtls_user_files
--- request
GET /t
--- error_code: 400
--- no_error_log
[error]
[warn]
=== TEST 2: Connection fails during handshake with not priv_key
--- http_config eval: $::mtls_http_config
--- SKIP
--- config eval
"
lua_ssl_trusted_certificate $::HtmlDir/test.crt;
location /t {
content_by_lua_block {
local f = assert(io.open('$::HtmlDir/mtls_client.crt'))
local cert_data = f:read('*a')
f:close()
local ssl = require('ngx.ssl')
local cert = assert(ssl.parse_pem_cert(cert_data))
local httpc = assert(require('resty.http').new())
local ok, err = httpc:connect {
scheme = 'https',
host = 'unix:$::HtmlDir/mtls.sock',
ssl_client_cert = cert,
ssl_client_priv_key = 'foo',
}
if ok and not err then
local res, err = assert(httpc:request {
method = 'GET',
path = '/',
headers = {
['Host'] = 'example.com',
},
})
ngx.say(res:read_body())
end
httpc:close()
}
}
"
--- user_files eval: $::mtls_user_files
--- request
GET /t
--- error_code: 200
--- error_log
could not set client certificate: bad client pkey type
--- response_body_unlike: hello, CN=foo@example.com,O=OpenResty,ST=California,C=US
=== TEST 3: Connection succeeds with client cert and key. SKIP'd for CI until feature is merged.
--- SKIP
--- http_config eval: $::mtls_http_config
--- config eval
"
lua_ssl_trusted_certificate $::HtmlDir/test.crt;
location /t {
content_by_lua_block {
local f = assert(io.open('$::HtmlDir/mtls_client.crt'))
local cert_data = f:read('*a')
f:close()
f = assert(io.open('$::HtmlDir/mtls_client.key'))
local key_data = f:read('*a')
f:close()
local ssl = require('ngx.ssl')
local cert = assert(ssl.parse_pem_cert(cert_data))
local key = assert(ssl.parse_pem_priv_key(key_data))
local httpc = assert(require('resty.http').new())
local ok, err = httpc:connect {
scheme = 'https',
host = 'unix:$::HtmlDir/mtls.sock',
ssl_client_cert = cert,
ssl_client_priv_key = key,
}
if ok and not err then
local res, err = assert(httpc:request {
method = 'GET',
path = '/',
headers = {
['Host'] = 'example.com',
},
})
ngx.say(res:read_body())
end
httpc:close()
}
}
"
--- user_files eval: $::mtls_user_files
--- request
GET /t
--- no_error_log
[error]
[warn]
--- response_body
hello, CN=foo@example.com,O=OpenResty,ST=California,C=US

View File

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFpTCCA42gAwIBAgIUfTh89NyxxmbVwNZ/YFddssWc+WkwDQYJKoZIhvcNAQEL
BQAwWjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoM
CU9wZW5SZXN0eTEiMCAGA1UEAwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAe
Fw0xOTA5MTMyMjI4MTJaFw0zOTA5MDgyMjI4MTJaMFoxCzAJBgNVBAYTAlVTMRMw
EQYDVQQIDApDYWxpZm9ybmlhMRIwEAYDVQQKDAlPcGVuUmVzdHkxIjAgBgNVBAMM
GU9wZW5SZXN0eSBUZXN0aW5nIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4IC
DwAwggIKAoICAQDcMg2DeV8z+0E2ZiXUax111lKzhAbMCK0RJlV9tAi+YdcsDR/t
zvvAZNGONUoewUuz/7E88oweh+Xi1GJtvd0DjB70y7tgpf5PUXovstWVwy7s5jZo
kgn62yi9ZOOZpjwnYTBviirtRTnZRwkzL6wF0xMyJjAbKBJuPMrMiyFdh82lt7wI
NS4mhyEdM0UiVVxfC2uzsddTOcOJURfGbW7UZm4Xohzq4QZ8geQj2OT5YTqw7dZ7
Xxre5H7IcNcAh+vIk5SEBV1WE+S5MnFly7gaLYNc49OSfz5Hcpv59Vr+4bZ+olbW
nQ/uU8BQovtkW6pjuT8nC4OKs2e8osoAZuk0rFS1uC501C+yES48mzaU8ttAidu6
nb/JgsdkrnJQeTc5rAoER4M2ne5kqtEXN8wzf3/sazo2PLywbfrUXUTV6kJilrGr
RkBN+fr6HTBkf+ooQMBOQPTojUdwbR86CLCyiJov2bzmBfGcOgSakv59S+uvUZFp
FLTiahuzLfcgYsG3UKQA47pYlNdUqP8vCCaf1nwmqjx2KS3Z/YFnO/gQgtY+f0Bh
UpnUDv+zBxpVFfVCyxByEsDPdwDkqLSwB6+YZINl36S48iXpoPhNXIYmO6GnhNWV
k2/RyCDTxEO+MbXHVg6iyIVHJWth7m18vl4uuSK/LbJHV9Q9Z7G99DQ0NwIDAQAB
o2MwYTAdBgNVHQ4EFgQUuoo+ehdlDFcQU+j5qONMKh0NtFQwHwYDVR0jBBgwFoAU
uoo+ehdlDFcQU+j5qONMKh0NtFQwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
BAMCAYYwDQYJKoZIhvcNAQELBQADggIBAM7c9q41K8306lfAVLqtbtaeETy+xxYG
XE2HfFW1IuukrXQ8d/JH4stL/HcHJzzhHPf5p3ja3Snu9zPmTk3pgUPDYPZf57tR
NCqwxjn6blwXWlzQqSavto9KAx3IWHuj0OTrZz/a1KPb9NGvatBhgthyRCRTbvhL
OA5tveuYSHb724cp3NZ1xaTQmDZsSgHCoCJ/7RnlbcJ7RsKCOzCWNFRomH410vdv
TajkUBlEC4OC1RIvxuVePHHb1ogbbe93SA/9mzw/E5SfoeF3mvByN4Ay8awXbNlH
26RfuIdGc4fZRc/87s4yPwhYScZBG+pHO0gn42E0FyiG6Jp3rhHMH5Sa2hNlPMpn
hYMaA6zQI4n/3AeFNM0VGxA+Yg/Al2WpXEJARrZqMW/qcrdMcPj5WeY6Tb6er04S
kfImwhMIajl3nNc9tHoad8r2VuMWMltH/dnWuEdo+pPdIY3fdJdyQeoLQDDLEQwL
AYrFy4uzKfQogfQBIHRdIMZTJh5v3mAFDpK59I5yzSt1GtUnFMC5MVOg+LbOo5UW
FCtwaW5EZiTszmakvvWMMZe9HwZMYNCeSGGtiPA/GA2zNci/n2TEcB11HgiY52y2
E/40nS61oL81zMwhV7l5psgJxQ2ORsKRJPHjADwvwh3xyCEJgVyBRCDX7J3PpAUO
79DprjVU8t7p
-----END CERTIFICATE-----

View File

@ -0,0 +1,111 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4096 (0x1000)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=California, O=OpenResty, CN=OpenResty Testing Root CA
Validity
Not Before: Sep 13 22:30:49 2019 GMT
Not After : Sep 10 22:30:49 2029 GMT
Subject: C=US, ST=California, O=OpenResty, CN=foo@example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:d0:8c:1e:2c:25:7f:00:9d:8a:3d:8a:f3:b5:1d:
6b:24:f5:ac:35:7d:cd:b7:d0:af:db:88:7c:ee:82:
46:16:47:f3:43:08:a1:04:6e:0d:3e:ce:69:fa:d5:
89:14:82:20:f1:47:f2:38:c8:ab:ea:2f:1e:f0:15:
04:c0:f4:8b:3c:c3:d4:78:56:87:4c:f1:70:ac:11:
86:2e:c4:6a:6d:10:84:27:81:ca:2a:8b:85:3e:62:
13:5e:40:6c:19:e4:49:3d:f3:de:aa:e8:5e:11:a1:
f2:66:83:6a:40:d1:34:c5:bf:b8:cb:97:7c:6a:ea:
46:bf:17:be:32:8d:a8:31:56:e5:8b:6d:08:03:d0:
44:69:b9:af:1e:15:1d:a5:64:9e:12:84:83:db:d9:
c6:71:90:3b:c2:7b:41:21:57:af:70:15:0b:56:59:
21:a6:4e:46:71:66:90:f1:ef:bc:b2:48:f9:8b:ea:
e5:72:4a:ba:4a:ae:2d:74:0b:33:03:f6:2e:47:0f:
56:a4:00:e8:1e:62:cb:b8:af:9c:98:1a:89:7c:d0:
a3:7a:5a:e1:84:50:64:e4:5d:a5:70:a4:69:54:c4:
f4:76:44:a2:be:1b:ef:dc:a3:d8:1d:0d:30:a2:d4:
79:fb:39:76:ab:b7:18:f2:f7:92:f8:81:83:94:b8:
11:b1
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Cert Type:
SSL Client, S/MIME
Netscape Comment:
OpenSSL Generated Client Certificate
X509v3 Subject Key Identifier:
D2:E4:F5:21:1C:17:A4:FF:13:F4:1A:28:A8:A7:DC:C6:DE:89:A0:31
X509v3 Authority Key Identifier:
keyid:BA:8A:3E:7A:17:65:0C:57:10:53:E8:F9:A8:E3:4C:2A:1D:0D:B4:54
X509v3 Key Usage: critical
Digital Signature, Non Repudiation, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Client Authentication, E-mail Protection
X509v3 Subject Alternative Name:
email:foo@example.com, email:bar@example.com
Signature Algorithm: sha256WithRSAEncryption
47:77:51:37:00:f7:28:da:c4:d9:4e:38:c4:ec:ea:24:c9:83:
36:4c:90:93:a7:b2:2b:10:bf:75:df:0b:72:d8:e7:4f:4f:68:
e5:32:2f:35:89:17:95:5c:bb:43:fc:70:89:46:08:43:61:ac:
41:62:84:01:94:88:d1:dc:8a:bd:30:2c:18:eb:51:79:0b:b7:
1b:b6:49:df:c9:85:55:f6:73:9f:b7:83:99:52:23:fe:e6:bd:
09:da:90:b9:e2:9b:68:c4:df:bd:fe:23:94:55:34:be:0d:7d:
84:0c:53:69:2a:0f:3c:47:68:34:3f:2a:3f:89:3f:3e:d3:26:
ce:b7:58:bc:d0:6f:ee:f8:bd:5d:c6:48:ae:a0:6c:1f:6d:e0:
66:93:7d:db:3c:07:e6:15:ae:aa:e3:d0:3d:ef:04:b6:dd:53:
16:93:61:70:e9:af:c0:e9:1d:ff:2b:e5:0a:03:56:48:3f:1c:
dc:fe:1b:a6:6d:f6:54:ab:41:e5:3b:5b:ab:f5:81:10:46:26:
bb:ea:d7:0e:33:b1:5e:30:4d:81:86:63:9a:4a:4f:1e:44:b9:
c2:c6:08:4e:da:fa:3a:55:da:96:7c:01:f6:d5:e8:3b:ba:e9:
31:3b:1c:51:39:1a:59:f0:e0:c7:17:2e:f6:18:9d:ec:a7:48:
30:b8:4c:6d:e5:4a:4f:43:41:cb:0e:6b:ac:ad:87:44:90:76:
85:23:2b:eb:8f:97:4b:22:13:60:20:3a:37:a4:dc:74:7d:85:
3d:a1:f5:1a:03:f6:d5:78:c7:bc:9b:09:f2:c8:05:27:43:2a:
ac:50:21:3a:ee:83:2d:db:02:6f:c7:91:de:63:d6:36:7d:7a:
9f:1f:fb:48:62:f4:fb:8e:3a:ea:61:9b:3c:03:f9:f8:a5:df:
1b:02:14:2c:de:e6:e3:47:d2:44:65:94:1a:c6:e1:fd:ba:8d:
b6:f8:93:a9:46:46:26:79:b0:bf:57:a8:a2:20:66:56:7e:c9:
f5:a4:0b:5e:76:70:0a:47:a4:db:45:2e:15:99:69:f9:6b:14:
93:2a:0a:b6:ee:53:a6:b9:02:9b:a2:25:37:1e:37:70:a2:7c:
7f:c3:ce:98:17:2f:9b:5b:fa:6f:ae:d8:0e:d4:6a:b2:03:5a:
fe:ba:4b:7f:f6:98:20:ea:cb:be:17:34:e0:43:74:d1:0c:e5:
d4:cc:5d:13:41:d3:5e:a4:f6:94:f7:15:b8:15:a9:65:f8:28:
3f:da:ef:b2:30:34:6d:96:3a:7a:f4:20:ec:9e:62:13:36:f1:
a7:04:e1:7a:d2:33:20:f6:61:4a:68:44:cb:92:d7:62:f0:e4:
70:f0:a5:e3:dd:2f:e2:a3
-----BEGIN CERTIFICATE-----
MIIFGTCCAwGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEiMCAGA1UE
AwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAeFw0xOTA5MTMyMjMwNDlaFw0y
OTA5MTAyMjMwNDlaMFAxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
MRIwEAYDVQQKDAlPcGVuUmVzdHkxGDAWBgNVBAMMD2Zvb0BleGFtcGxlLmNvbTCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANCMHiwlfwCdij2K87UdayT1
rDV9zbfQr9uIfO6CRhZH80MIoQRuDT7OafrViRSCIPFH8jjIq+ovHvAVBMD0izzD
1HhWh0zxcKwRhi7Eam0QhCeByiqLhT5iE15AbBnkST3z3qroXhGh8maDakDRNMW/
uMuXfGrqRr8XvjKNqDFW5YttCAPQRGm5rx4VHaVknhKEg9vZxnGQO8J7QSFXr3AV
C1ZZIaZORnFmkPHvvLJI+Yvq5XJKukquLXQLMwP2LkcPVqQA6B5iy7ivnJgaiXzQ
o3pa4YRQZORdpXCkaVTE9HZEor4b79yj2B0NMKLUefs5dqu3GPL3kviBg5S4EbEC
AwEAAaOB8jCB7zAJBgNVHRMEAjAAMBEGCWCGSAGG+EIBAQQEAwIFoDAzBglghkgB
hvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0ZWQgQ2xpZW50IENlcnRpZmljYXRlMB0G
A1UdDgQWBBTS5PUhHBek/xP0Giiop9zG3omgMTAfBgNVHSMEGDAWgBS6ij56F2UM
VxBT6Pmo40wqHQ20VDAOBgNVHQ8BAf8EBAMCBeAwHQYDVR0lBBYwFAYIKwYBBQUH
AwIGCCsGAQUFBwMEMCsGA1UdEQQkMCKBD2Zvb0BleGFtcGxlLmNvbYEPYmFyQGV4
YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4ICAQBHd1E3APco2sTZTjjE7OokyYM2
TJCTp7IrEL913wty2OdPT2jlMi81iReVXLtD/HCJRghDYaxBYoQBlIjR3Iq9MCwY
61F5C7cbtknfyYVV9nOft4OZUiP+5r0J2pC54ptoxN+9/iOUVTS+DX2EDFNpKg88
R2g0Pyo/iT8+0ybOt1i80G/u+L1dxkiuoGwfbeBmk33bPAfmFa6q49A97wS23VMW
k2Fw6a/A6R3/K+UKA1ZIPxzc/humbfZUq0HlO1ur9YEQRia76tcOM7FeME2BhmOa
Sk8eRLnCxghO2vo6VdqWfAH21eg7uukxOxxRORpZ8ODHFy72GJ3sp0gwuExt5UpP
Q0HLDmusrYdEkHaFIyvrj5dLIhNgIDo3pNx0fYU9ofUaA/bVeMe8mwnyyAUnQyqs
UCE67oMt2wJvx5HeY9Y2fXqfH/tIYvT7jjrqYZs8A/n4pd8bAhQs3ubjR9JEZZQa
xuH9uo22+JOpRkYmebC/V6iiIGZWfsn1pAtednAKR6TbRS4VmWn5axSTKgq27lOm
uQKboiU3Hjdwonx/w86YFy+bW/pvrtgO1GqyA1r+ukt/9pgg6su+FzTgQ3TRDOXU
zF0TQdNepPaU9xW4Fall+Cg/2u+yMDRtljp69CDsnmITNvGnBOF60jMg9mFKaETL
ktdi8ORw8KXj3S/iow==
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA0IweLCV/AJ2KPYrztR1rJPWsNX3Nt9Cv24h87oJGFkfzQwih
BG4NPs5p+tWJFIIg8UfyOMir6i8e8BUEwPSLPMPUeFaHTPFwrBGGLsRqbRCEJ4HK
KouFPmITXkBsGeRJPfPequheEaHyZoNqQNE0xb+4y5d8aupGvxe+Mo2oMVbli20I
A9BEabmvHhUdpWSeEoSD29nGcZA7wntBIVevcBULVlkhpk5GcWaQ8e+8skj5i+rl
ckq6Sq4tdAszA/YuRw9WpADoHmLLuK+cmBqJfNCjelrhhFBk5F2lcKRpVMT0dkSi
vhvv3KPYHQ0wotR5+zl2q7cY8veS+IGDlLgRsQIDAQABAoIBAEpoY++OZVT74LH6
nN+XIn5qZUokm7yk6cnjVefndUhH3aSiNIkXFwS8sxV7ENDPaR+NcwANoUEKFPjG
Fw8dcXx5xpo1DUtHrdLG4eBX1j0Zsn1CErbBVwYeChkL1UYbrII9O8ow5DdYV9t5
sfR0cGbJ9A43+31OH3XY69SvtD39xItgxK0Wg4Ciz475kCvG51Q1iBWAkm4koXhz
VCha4wghs81wJ28HRMFZAFf2C+72rk6EypMUX2dYirvW/+7zONirk298NDMAOSBh
mRWyPV8qipYx42hBQ9vSVm0UVb0ZbqVomKKUZfj11LL6Ad/OzCyVAiNLXyZREV6r
d324Bu0CgYEA/YPsE6p6H3MTPIoVTnsyyQw0pNmXtIAuYTgSveeuPoTYn3ZBWoGN
iLpbnW4EH3xNKfrdMjbqqLls1iwm7/ZAP5klAuL4s10onrcjMt65fyfa3Lw1gavG
SUFFdsueH2k3FohqNsbQUSXZILVQnXsRoldi38b7NKrAqABcEMAIqXMCgYEA0pde
nt4aMmrGBRPLnjCs1UlC5PbXzCE8XxxQ7HZKx4Sy5ErQ0EW1wzF6c0fEYI7i+j1/
ESKqekzc5ue0T8acoioB+VUybO1oxQZsZUPY7roqXOYwZH9LQOdPYUOh9k33CZHw
6KFfx8bKCpdXn7FkwR2UUtCSp/6CZcyYr89Qn0sCgYAQ0L5I86bUDTL6cgJFyWAt
+7RGNvScEWCCLFD57bMeDHu93/8nvK4hopLPF2wIlpsbrLsdSI06EcqJTjZq9j9+
uG6/CUULyKMYG/emuSU+rOsUdxtpdXZah4zO+2SKmtT/lp7M8VUB/OuxArXNLEuY
JAm35B/nd2f9/MAekE5CxwKBgQCV660w7G0590mB09XhiEWCkeVNm22FpSOVklMK
BCy4XX/9hkWh//6mN1M1PqJPG2n7PEx5pnQ3HQEmYU28fWiFCeLd3glIArvTh/8j
GGoXifEescFByl2IlyOr2roy3s4/weX/tuK5Fow/ff6jcWaJFMXDLzk437d1QXJx
tuVugQKBgByfr2eakXFQvAVGJUfVXA3M2BoBODZEPYTgryVMoEEduFy0HZiw4xKi
Dngwewy6/UJMAGA+8ak9Ca367FxnegZU9knm6ujYVyhU5WzbKpR8v7OaUP8d5icq
rCZZtglG0c8XfVpJjR4FsKA/qrFvKZpu5NdEw3o5/LSrV4HjqZQ6
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,112 @@
Certificate:
Data:
Version: 3 (0x2)
Serial Number: 4097 (0x1001)
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=California, O=OpenResty, CN=OpenResty Testing Root CA
Validity
Not Before: Sep 13 22:32:03 2019 GMT
Not After : Sep 10 22:32:03 2029 GMT
Subject: C=US, ST=California, O=OpenResty, CN=example.com
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public-Key: (2048 bit)
Modulus:
00:bb:69:2b:30:43:b0:b4:cc:84:3f:22:39:65:65:
6a:bd:75:a4:2b:7d:f7:ec:e4:12:e8:d1:c4:ce:7e:
4e:54:5c:22:cc:d2:18:7f:3b:9e:cb:70:d9:7d:79:
8f:05:93:b6:9f:2f:d5:33:d7:98:a2:ed:c5:00:93:
e4:ca:bc:cb:f0:e1:63:3e:07:6b:38:6f:4d:09:45:
f1:a1:3b:a3:ca:c0:47:c1:a1:0a:f8:c9:bb:c7:da:
26:9d:d3:0b:35:24:01:3e:16:14:2e:44:38:8c:c9:
09:02:41:9e:b6:fb:0c:aa:fc:d6:44:5e:27:ab:aa:
d5:c3:68:e1:dd:57:06:6c:4f:f6:24:33:a8:2b:49:
60:82:0e:15:aa:55:9f:61:cc:74:39:7e:9f:a6:4f:
71:4a:8b:eb:43:dd:c2:f7:90:38:df:a6:a6:a8:f6:
77:bc:9e:54:69:30:83:4c:2a:eb:b8:62:7c:c7:14:
84:9e:f3:e1:4a:15:33:51:65:a3:af:9d:09:c6:b8:
89:30:a3:d2:18:e9:dc:5d:6b:ea:68:ca:8b:5c:e4:
3b:fe:32:7f:48:c3:4c:f0:b5:06:f6:23:97:3e:f2:
50:90:68:26:39:6d:b2:e2:53:89:71:6a:48:f0:f1:
fc:89:3c:6d:db:87:6c:79:23:ed:87:5d:c5:fa:8a:
0d:b9
Exponent: 65537 (0x10001)
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Cert Type:
SSL Server
Netscape Comment:
OpenSSL Generated Server Certificate
X509v3 Subject Key Identifier:
8C:8E:18:2C:13:84:C9:2A:61:6B:73:3F:18:76:A4:85:55:5F:5C:5F
X509v3 Authority Key Identifier:
keyid:BA:8A:3E:7A:17:65:0C:57:10:53:E8:F9:A8:E3:4C:2A:1D:0D:B4:54
DirName:/C=US/ST=California/O=OpenResty/CN=OpenResty Testing Root CA
serial:7D:38:7C:F4:DC:B1:C6:66:D5:C0:D6:7F:60:57:5D:B2:C5:9C:F9:69
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
Signature Algorithm: sha256WithRSAEncryption
80:9f:3e:f5:8b:50:ee:cb:e4:c2:f0:16:01:07:a1:af:76:bc:
da:c8:cd:24:e9:63:df:d3:47:28:8c:7f:58:14:5d:d4:fd:44:
16:c3:06:15:be:90:ec:1c:8f:78:34:11:e7:cc:86:d8:2a:a2:
e5:99:70:83:76:4a:65:a4:e1:9a:68:20:29:c0:7a:c6:4a:08:
b3:74:c3:53:b5:7b:79:92:f1:99:b5:a1:a3:90:ce:9a:cb:26:
a5:a6:33:de:74:98:99:ec:18:d1:1e:41:be:f8:c3:d2:8d:aa:
07:de:9a:97:28:0d:bf:70:ac:2b:cf:b7:ff:bc:ac:e4:16:0c:
1c:03:a7:5a:2d:64:0d:90:16:bd:97:c3:1f:f5:bf:a9:fa:15:
d1:e0:d4:0d:f7:b3:51:23:ce:ad:16:4f:41:72:17:aa:01:d5:
44:e2:9e:d5:ce:ea:54:98:04:43:14:2e:51:4b:c7:d9:21:4f:
e1:a4:fa:dd:e0:f0:82:ec:6f:9f:be:a2:3c:3b:85:f7:6d:96:
ee:0d:e6:08:2b:1b:be:06:a4:b7:5f:a3:f2:f2:b9:d0:5a:8f:
90:86:1a:f4:7a:9f:c8:ae:09:1d:60:a2:8b:e0:0b:f6:00:21:
d9:df:33:4b:39:75:b6:64:9b:c7:df:e4:85:7a:ae:df:72:8c:
8b:7e:98:8e:47:0a:27:1f:8e:2c:11:7f:7b:fc:a0:db:1b:6e:
f6:de:4e:85:ac:30:e6:e8:6a:7a:e6:f9:f4:18:0a:c6:ad:1c:
e1:0c:dd:e0:e0:8d:5a:d7:08:34:e7:22:b4:44:bd:99:39:b1:
71:74:3f:7c:aa:65:f5:37:46:85:d3:79:f7:a8:35:8d:2b:30:
99:d2:47:ce:a6:74:eb:f3:9f:d3:9a:4e:99:96:50:7b:ba:22:
c8:72:47:d4:da:6e:9a:73:01:3c:89:e9:3f:56:17:b7:ba:22:
71:db:66:a2:d2:fb:33:51:36:f6:b6:f2:5b:32:70:9d:e7:e3:
36:d6:ae:cb:9b:62:ef:69:c7:f7:ba:95:49:16:f5:7c:d9:29:
bb:0a:02:b1:6b:72:15:ab:2c:27:7b:c8:bc:f6:15:1f:fa:ae:
08:fd:e0:11:36:b1:ab:9c:c8:11:d1:d3:0d:7d:49:4e:ca:e6:
73:ee:0d:c3:8d:6f:f5:a4:fe:a1:af:6b:91:f7:53:fd:10:df:
77:dd:ef:ec:7b:cf:32:75:df:04:8a:d1:a1:f7:36:68:ee:65:
e3:43:90:37:43:e8:d1:a8:e2:90:5c:1c:75:0a:29:94:4a:6a:
9b:89:28:43:bd:85:56:0d:f1:2b:44:bd:e6:7a:4c:b7:85:10:
77:b7:a8:0f:33:29:a7:26
-----BEGIN CERTIFICATE-----
MIIFWTCCA0GgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwWjELMAkGA1UEBhMCVVMx
EzARBgNVBAgMCkNhbGlmb3JuaWExEjAQBgNVBAoMCU9wZW5SZXN0eTEiMCAGA1UE
AwwZT3BlblJlc3R5IFRlc3RpbmcgUm9vdCBDQTAeFw0xOTA5MTMyMjMyMDNaFw0y
OTA5MTAyMjMyMDNaMEwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlh
MRIwEAYDVQQKDAlPcGVuUmVzdHkxFDASBgNVBAMMC2V4YW1wbGUuY29tMIIBIjAN
BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu2krMEOwtMyEPyI5ZWVqvXWkK333
7OQS6NHEzn5OVFwizNIYfzuey3DZfXmPBZO2ny/VM9eYou3FAJPkyrzL8OFjPgdr
OG9NCUXxoTujysBHwaEK+Mm7x9omndMLNSQBPhYULkQ4jMkJAkGetvsMqvzWRF4n
q6rVw2jh3VcGbE/2JDOoK0lggg4VqlWfYcx0OX6fpk9xSovrQ93C95A436amqPZ3
vJ5UaTCDTCrruGJ8xxSEnvPhShUzUWWjr50JxriJMKPSGOncXWvqaMqLXOQ7/jJ/
SMNM8LUG9iOXPvJQkGgmOW2y4lOJcWpI8PH8iTxt24dseSPth13F+ooNuQIDAQAB
o4IBNTCCATEwCQYDVR0TBAIwADARBglghkgBhvhCAQEEBAMCBkAwMwYJYIZIAYb4
QgENBCYWJE9wZW5TU0wgR2VuZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNV
HQ4EFgQUjI4YLBOEySpha3M/GHakhVVfXF8wgZcGA1UdIwSBjzCBjIAUuoo+ehdl
DFcQU+j5qONMKh0NtFShXqRcMFoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxp
Zm9ybmlhMRIwEAYDVQQKDAlPcGVuUmVzdHkxIjAgBgNVBAMMGU9wZW5SZXN0eSBU
ZXN0aW5nIFJvb3QgQ0GCFH04fPTcscZm1cDWf2BXXbLFnPlpMA4GA1UdDwEB/wQE
AwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsFAAOCAgEAgJ8+
9YtQ7svkwvAWAQehr3a82sjNJOlj39NHKIx/WBRd1P1EFsMGFb6Q7ByPeDQR58yG
2Cqi5Zlwg3ZKZaThmmggKcB6xkoIs3TDU7V7eZLxmbWho5DOmssmpaYz3nSYmewY
0R5BvvjD0o2qB96alygNv3CsK8+3/7ys5BYMHAOnWi1kDZAWvZfDH/W/qfoV0eDU
DfezUSPOrRZPQXIXqgHVROKe1c7qVJgEQxQuUUvH2SFP4aT63eDwguxvn76iPDuF
922W7g3mCCsbvgakt1+j8vK50FqPkIYa9HqfyK4JHWCii+AL9gAh2d8zSzl1tmSb
x9/khXqu33KMi36YjkcKJx+OLBF/e/yg2xtu9t5Ohaww5uhqeub59BgKxq0c4Qzd
4OCNWtcINOcitES9mTmxcXQ/fKpl9TdGhdN596g1jSswmdJHzqZ06/Of05pOmZZQ
e7oiyHJH1NpumnMBPInpP1YXt7oicdtmotL7M1E29rbyWzJwnefjNtauy5ti72nH
97qVSRb1fNkpuwoCsWtyFassJ3vIvPYVH/quCP3gETaxq5zIEdHTDX1JTsrmc+4N
w41v9aT+oa9rkfdT/RDfd93v7HvPMnXfBIrRofc2aO5l40OQN0Po0ajikFwcdQop
lEpqm4koQ72FVg3xK0S95npMt4UQd7eoDzMppyY=
-----END CERTIFICATE-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAu2krMEOwtMyEPyI5ZWVqvXWkK3337OQS6NHEzn5OVFwizNIY
fzuey3DZfXmPBZO2ny/VM9eYou3FAJPkyrzL8OFjPgdrOG9NCUXxoTujysBHwaEK
+Mm7x9omndMLNSQBPhYULkQ4jMkJAkGetvsMqvzWRF4nq6rVw2jh3VcGbE/2JDOo
K0lggg4VqlWfYcx0OX6fpk9xSovrQ93C95A436amqPZ3vJ5UaTCDTCrruGJ8xxSE
nvPhShUzUWWjr50JxriJMKPSGOncXWvqaMqLXOQ7/jJ/SMNM8LUG9iOXPvJQkGgm
OW2y4lOJcWpI8PH8iTxt24dseSPth13F+ooNuQIDAQABAoIBABnT/KfCLHA+X1t0
FATtXTCPLfjwe2KibBi6EC2FKrZlnEYuDkI6rT/MZaztO9DA8sItjWx/ogGSUzwp
JbbrHhAsf8jkrNoyPKOyiAJ4fbJLnZgJ4cE3zDFW10uY8kp4k9NCp7VYoZKFgkBV
WtJM9wn5nm39q+n0uVEc+0PN4oy6m54Aqb1HqVCyXFp+/pVhL6PtgaClbqJd3oKV
0/HLWfWaI3nvV6ltAphUfPoCmYIUtSl90sRgSeEaJ61UZXh0OkhhtD7Iw/JUlHDk
a0J7owrh0Wf1kDsaSn+j1ba8MELsspFYYVm0gAMKAvRXbVrYgUMdb+HVdZ0odULl
ezFWeAECgYEA9nnoZs+PzKWNxTzYPtgvvrSLmpXLzNrs/p41JUhy6GjQIixTSBFy
WHkjwu0k2fvRgODfcaetAyK6sV4uTpRgqUhtFSeyNMelZ2yiIEqvhUrtHoVov1C+
BqwwlUnmkQZNQODXOpKCvnqnOaPwMILKLtxDGmPtW0tCTR2dVVaht6ECgYEAwqb/
h0Fh3YtykOnhV8wOZRrpVr8jS1RIgg/hklt2xh6+OYtL16sKFaLBF/BhzZRBapqd
fB2Cx3B6rxZ5PLTse8yjEvjt6Ly7TusYWpaKbYKFnnEbmdsm5sBepuLUv4AoMYbk
99ZejFcQI2gNbzX7eIrFitCQGxT+Wu7Gncv+vxkCgYBvAYCVrS2KcZVkG38Y7qyy
KwYk3QoofQD3u7Eb1YFLAsmaWnQ3pQPmrMhaZguO0UcN0DlSKr5VBzMl5tDcOx89
noziVjqAYtovtlFeUcSzN4eLk3IVl/u9bZeD5QCemEP60Eie7JVNzFe8MgVfE8iT
Skg+fnrL/x0hNhFB+f5jgQKBgDgOEX4o5P8A3nA++gbnm6mgE1xI1OgnkG3sFuCn
+E9boRo/NAsalV/fq82yCuhB7oi9l+abNQMsMBhl12oVDBkmuDuJdjHUz/gNGclU
mu6obMRQ/ErVYqGG+nsCzZOMW4bPuvZoRHgTxnD70QqauB1hkTvFjgpOhGU5Z/cf
PPBZAoGBAJQK7NF6VoF9nm5CT8vufrQ2vvp2aiLdOLLx5JXt/6seEnPZWtEvjp8/
+ExIsfOIaU5elhv8ze8iKmRP9f04XdWpbRm6k6AR5cOkkQQ1oO7N9abU7KbD/gqX
pJIWOlaUrbKO4Dprx7HyMYYPs9mu/UoF0Dvd/+bYXM5ZKiFrQ3Ly
-----END RSA PRIVATE KEY-----

View File

@ -1,24 +1,21 @@
-----BEGIN CERTIFICATE-----
MIID8DCCAtigAwIBAgIJALL9eJPZ6neGMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
BAYTAkdCMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwR0ZXN0MB4XDTE1MTAyMTE2MjQ1
NloXDTE1MTEyMDE2MjQ1NlowWDELMAkGA1UEBhMCR0IxDTALBgNVBAgTBFRlc3Qx
DTALBgNVBAcTBFRlc3QxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxDTAL
BgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDz/AoE
c+TPdm+Aqcchq8fLNWksFQZqbsCBGnq8rUG1b6MsVlAOkDUQGRlNPs9v0/+pzgX7
IYXPCFcV7YONNsTUfvBYTq43mfOycmAdb3SX6kBygxdhYsDRZR+vCAIkjoRmRB20
meh1motqM58spq3IcT8VADTRJl1OI48VTnxmXdCtmkOymU948DcauMoxm03eL/hU
6eniNEujbnbB305noNG0W5c3h6iz9CvqUAD1kwyjick+f1atB2YYn1bymA+db6YN
3iTo0v2raWmIc7D+qqpkNaCRxgMb2HN6X3/SfkijtNJidjqHMbs2ftlKJ5/lODPZ
rCPQOcYK6TT8MIZ1AgMBAAGjgbwwgbkwHQYDVR0OBBYEFFUC1GrAhUp7IvJH5iyf
+fJQliEIMIGJBgNVHSMEgYEwf4AUVQLUasCFSnsi8kfmLJ/58lCWIQihXKRaMFgx
CzAJBgNVBAYTAkdCMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYD
VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwR0ZXN0ggkAsv14k9nq
d4YwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAtaUQOr3Qn87KXmmP
GbSvCLSl+bScE09VYZsYaB6iq0pGN9y+Vh4/HjBUUsFexopw1dY25MEEJXEVi1xV
2krLYAsfKCM6c1QBVmdqfVuxUvxpXwr+CNRNAlzz6PhjkeY/Ds/j4sg7EqN8hMmT
gu8GuogX7+ZCgrzRSMMclWej+W8D1xSIuCC+rqv4w9SZdtVb3XGpCyizpTNsQAuV
ACXvq9KXkEEj+XNvKrNdWd4zG715RdMnVm+WM53d9PLp63P+4/kwhwHULYhXygQ3
DzzVPaojBBdw3VaHbbPHnv73FtAzOb7ky6zJ01DlmEPxEahCFpklMkY9T2uCdpj9
oOzaNA==
MIIDYzCCAkugAwIBAgIUXCmnoPKJ60jFkycVZ04mVj3B8aswDQYJKoZIhvcNAQEL
BQAwQTELMAkGA1UEBhMCUFQxDjAMBgNVBAgMBVBvcnRvMQ4wDAYDVQQHDAVQb3J0
bzESMBAGA1UECgwJbGVkZ2V0ZWNoMB4XDTIyMTIxNTEyMDQzMVoXDTMyMDkxMzEy
MDQzMVowQTELMAkGA1UEBhMCUFQxDjAMBgNVBAgMBVBvcnRvMQ4wDAYDVQQHDAVQ
b3J0bzESMBAGA1UECgwJbGVkZ2V0ZWNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEAqzJINgQtchR799ahPpu2gweHCmx7cytQ59ZFW4J3QBRIB+DFnLSM
fJa797s0Sl6f+t9pT3QLYjbdNK+R60HGd5tM31cEIa518AcDokfkzGo/clY+dbs4
OcFe10HxAlXpu6S5/yiQ0u4CIf1uQSsS53kNpaRWcEz6Z/sw80ksCEnYSfD3YXEh
sJm41i5Hd4F2/Y1WrMg82YGZG7Y81cM9LgUxKcikTm4JnEn9G1yg56hSbKs1a0E0
J9Gk84lufWpiUqpX9ASsUPttnYzgljo24x7zeNEaXsVOKgu+88Cc+bBn7AORGXM4
YtYbxVGhAgPf0vOalNl/kDwfE1jwBz0r6QIDAQABo1MwUTAdBgNVHQ4EFgQUxuo7
FdlD/iQ7cMnORpKFQ0JW9I4wHwYDVR0jBBgwFoAUxuo7FdlD/iQ7cMnORpKFQ0JW
9I4wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAfINyfrlE/f3W
cTNFiVMzqCa+oeNtvlkaCKgtjJdow8xwAc/Mxae76dkN3Mh8yegKV9+QRY6rw6q0
dfma/Rg0tJgrI9El64XdGRcMpwGdtujZbE/bCGTJwLpcIM051cr/NCtYYduM2RU8
i5uc8z5sQbMIJHdwDDm4PevA4WrqteUo5bXFQp9jYessDIkjIg7n5hGNvSNtfpVV
0fGDYPD9yNydgRMe6EwqQ0Z9p6yfq4o60JceYt4MfbbcGxzwrQc41ou2wKM/iKnQ
FBwCOaa+imgn01Qno/PdisV05KM7uSv/dK1v33nrk4xPSVG8u0aGe440U1CcGXbB
jy3ACMw/ZQ==
-----END CERTIFICATE-----

View File

@ -1,27 +1,28 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA8/wKBHPkz3ZvgKnHIavHyzVpLBUGam7AgRp6vK1BtW+jLFZQ
DpA1EBkZTT7Pb9P/qc4F+yGFzwhXFe2DjTbE1H7wWE6uN5nzsnJgHW90l+pAcoMX
YWLA0WUfrwgCJI6EZkQdtJnodZqLajOfLKatyHE/FQA00SZdTiOPFU58Zl3QrZpD
splPePA3GrjKMZtN3i/4VOnp4jRLo252wd9OZ6DRtFuXN4eos/Qr6lAA9ZMMo4nJ
Pn9WrQdmGJ9W8pgPnW+mDd4k6NL9q2lpiHOw/qqqZDWgkcYDG9hzel9/0n5Io7TS
YnY6hzG7Nn7ZSief5Tgz2awj0DnGCuk0/DCGdQIDAQABAoIBAGjKc7L94+SHRdTJ
FtILacCJrCZW0W6dKulIajbnYzV+QWMlnzTiEyha31ciBw5My54u8rqt5z7Ioj60
yK+6OkfaTXhgMsuGv/iAz29VE4q7/fow+7XEKHTHLhiLJAB3hb42u1t6TzFTs1Vl
3pPa8wEIQsPOVuENzT1mYGoST7PW+LBIMr9ScMnRHfC0MNdV/ntQiXideOAd5PkA
4O7fNgYZ8CTAZ8rOLYTMFF76/c/jLiqfeghqbIhqMykk36kd7Lud//FRykVsn1aJ
REUva/SjVEth5kITot1hpMC4SIElWpha2YxiiZFoSXSaUbtHpymiUGV01cYtMWk0
MZ5HN3ECgYEA/74U8DpwPxd4up9syKyNqOqrCrYnhEEC/tdU/W5wECi4y5kppjdd
88lZzICVPzk2fezYXlCO9HiSHU1UfcEsY3u16qNCvylK7Qz1OqXV/Ncj59891Q5Z
K0UBcbnrv+YD6muZuhlHEbyDPqYO091G9Gf/BbL5JIBDzg1qFO9Dh9cCgYEA9Drt
O9PJ5Sjz3mXQVtVHpwyhOVnd7CUv8a1zkUQCK5uQeaiF5kal1FIo7pLOr3KAvG0C
pXbm/TobwlfAfcERQN88aPN8Z/l1CB0oKV6ipBMD2/XLzDRtx8lpTeh/BB8jIhrz
+FDJY54HCzLfW0P5kT+Cyw51ofjziPnFdO/Z6pMCgYEAon17gEchGnUnWCwDSl2Y
hELV+jBSW02TQag/b+bDfQDiqTnfpKR5JXRBghYQveL0JH5f200EB4C0FboUfPJH
6c2ogDTLK/poiMU66tCDbeqj/adx+fTr4votOL0QdRUIV+GWAxAcf8BvA1cvBJ4L
fy60ckKM2gxFCJ6tUC/VkHECgYBoMDNAUItSnXPbrmeAg5/7naGxy6qmsP6RBUPF
9tNOMyEhJUlqAT2BJEOd8zcFFb3hpEd6uwyzfnSVJcZSX2iy2gj1ZNnvqTXJ7lZR
v7N2dz4wOd1lEgC7OCsaN1LoOThNtl3Z0uz2+FVc66jpUEhJNGThpxt7q66JArS/
vAqkzQKBgFkzqA6QpnH5KhOCoZcuLQ4MtvnNHOx1xSm2B0gKDVJzGkHexTmOJvwM
ZhHXRl9txS4icejS+AGUXNBzCWEusfhDaZpZqS6zt6UxEjMsLj/Te7z++2KQn4t/
aI77jClydW1pJvICtqm5v+sukVZvQTTJza9ujta6fj7u2s671np9
-----END RSA PRIVATE KEY-----
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCrMkg2BC1yFHv3
1qE+m7aDB4cKbHtzK1Dn1kVbgndAFEgH4MWctIx8lrv3uzRKXp/632lPdAtiNt00
r5HrQcZ3m0zfVwQhrnXwBwOiR+TMaj9yVj51uzg5wV7XQfECVem7pLn/KJDS7gIh
/W5BKxLneQ2lpFZwTPpn+zDzSSwISdhJ8PdhcSGwmbjWLkd3gXb9jVasyDzZgZkb
tjzVwz0uBTEpyKRObgmcSf0bXKDnqFJsqzVrQTQn0aTziW59amJSqlf0BKxQ+22d
jOCWOjbjHvN40RpexU4qC77zwJz5sGfsA5EZczhi1hvFUaECA9/S85qU2X+QPB8T
WPAHPSvpAgMBAAECggEADrAtoUwCQpRYWIv9LkJ5sJjmPX8DDn9Fnaz099d9qy1L
5GuLRbLF9PCRdKO55k5jenvnOXs5RZym2QNEfHaA9GuKKmvF8N/awmFpSE46M4Gp
xOhHMdG1qFroOLhkv1cNfhB+XTjzrm1GS8VGZU5jr+5Jo4/lUxUZTO+XCq1mAXK0
io58x7K3Z7P26/46yeuuqNggbFCxeHfHSK8SXYg6QxgjlZyA/A8tHDuvhDCH81q/
AISHxIFmly8ZQ2KxuVCSyHXQkPwODPh2F/4UpOq5EdN781CuV/W4e73WKBwcDUQM
aEX5KF8D2C0nXjk592SS9SFmXqw9ZD77sAL2JmbE4wKBgQDGoKT8i3xn3x4nAju0
yTxK6jaV72Lrdn5HmOZyJb8irAUr9B0t33zaubzXlM1LpuS/JAQwtppShGo1jPlt
T8iE2+JR9UKi6siHV/3EKC15i5UT3p30txdR2KS+hAasb7qXvM6sgM0nwzoS9PUy
LbKnRN8UYTvJmEKU1vuyeFQAQwKBgQDcpUQpNhtf4GrT7EoBh8E24yOO2k9XludW
yhaEddTYe2FXmqf9MGuPPX+dBj3G1SY6RNXvjmVjr3uvFN5qz+mtOuAkm7g99KkI
bKjkVuCgx6mpiH89dt7d/3uTcKXEkai0F7JgSPd1mcSxjRybhyna4qC4CuCHjICU
Ug00LAqGYwKBgGjX6N6JPgySABd1HVDrG9ErWc7AwkUpkbR3J8S+2eoSRNSTkUdi
fUPy4JQmrkqteHbQKwoPiNvfmzRTCmHByEUgz5CVViwqo9iVAJUm5AIRRIptapD+
h+ei5CrQA7nHbAWmGq2Be0juytuwwzBOYMvcFahrPqTFovdvlwH4c9aDAoGAYAk6
4qkfPxrhxH3rNEFPUsGIX4wbzqbq6DarmFnlG5iQJN420hf6KO1+luz5hIqPyfre
Fxemf74Imor9yAXY0sJ2fticV7Mew4Dv/frmaHSfHyA/KZSMqpmhwunb7PPtNv29
cPUxaClWmGUwF228RP4xMAnj8nuwF16jSpsEtbsCgYEAxEaEzpuknqV4AOnlJFBQ
7JqSkvW7w4Zy8hnUNmqhOOX1KwB0YudfirHceGtOpjxWQmHqjCHZqau0nEU02+ev
qUQsdiGKeJvWHYyqEldOdNd49qaux19IPQBeKRJQGWheGHDWGlg1aSDw3jY0sESS
3VqdbL5Z3XRXozQdwmNyfbw=
-----END PRIVATE KEY-----

View File

@ -0,0 +1 @@
*.t linguist-language=Text

10
src/deps/src/lua-resty-lock/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
*.swp
*.swo
*~
go
t/servroot/
reindex
nginx
ctags
tags
a.lua

View File

@ -0,0 +1,67 @@
sudo: required
dist: bionic
os: linux
language: c
compiler:
- gcc
cache:
directories:
- download-cache
env:
global:
- JOBS=3
- NGX_BUILD_JOBS=$JOBS
- LUAJIT_PREFIX=/opt/luajit21
- LUAJIT_LIB=$LUAJIT_PREFIX/lib
- LUAJIT_INC=$LUAJIT_PREFIX/include/luajit-2.1
- LUA_INCLUDE_DIR=$LUAJIT_INC
- LUA_CMODULE_DIR=/lib
- OPENSSL_PREFIX=/opt/ssl
- OPENSSL_LIB=$OPENSSL_PREFIX/lib
- OPENSSL_INC=$OPENSSL_PREFIX/include
- OPENSSL_VER=1.1.1k
- LD_LIBRARY_PATH=$LUAJIT_LIB:$LD_LIBRARY_PATH
- TEST_NGINX_SLEEP=0.006
matrix:
- NGINX_VERSION=1.19.9
install:
- if [ ! -d download-cache ]; then mkdir download-cache; fi
- if [ ! -f download-cache/openssl-$OPENSSL_VER.tar.gz ]; then wget -O download-cache/openssl-$OPENSSL_VER.tar.gz https://www.openssl.org/source/openssl-$OPENSSL_VER.tar.gz; fi
- sudo apt-get install -qq -y cpanminus axel
- sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1)
- git clone https://github.com/openresty/openresty.git ../openresty
- git clone https://github.com/openresty/lua-resty-core.git ../lua-resty-core
- git clone https://github.com/openresty/lua-resty-lrucache.git ../lua-resty-lrucache
- git clone https://github.com/openresty/nginx-devel-utils.git
- git clone https://github.com/simpl/ngx_devel_kit.git ../ndk-nginx-module
- git clone https://github.com/openresty/lua-nginx-module.git ../lua-nginx-module
- git clone https://github.com/openresty/no-pool-nginx.git ../no-pool-nginx
- git clone -b v2.1-agentzh https://github.com/openresty/luajit2.git
- git clone https://github.com/openresty/mockeagain.git
script:
- cd luajit2/
- make -j$JOBS CCDEBUG=-g Q= PREFIX=$LUAJIT_PREFIX CC=$CC XCFLAGS='-DLUA_USE_APICHECK -DLUA_USE_ASSERT' > build.log 2>&1 || (cat build.log && exit 1)
- sudo make install PREFIX=$LUAJIT_PREFIX > build.log 2>&1 || (cat build.log && exit 1)
- cd ..
- tar zxf download-cache/openssl-$OPENSSL_VER.tar.gz
- cd openssl-$OPENSSL_VER/
- ./config shared --prefix=$OPENSSL_PREFIX -DPURIFY > build.log 2>&1 || (cat build.log && exit 1)
- make -j$JOBS > build.log 2>&1 || (cat build.log && exit 1)
- sudo make PATH=$PATH install_sw > build.log 2>&1 || (cat build.log && exit 1)
- cd ../mockeagain/ && make CC=$CC -j$JOBS && cd ..
- export PATH=$PWD/work/nginx/sbin:$PWD/nginx-devel-utils:$PATH
- export LD_PRELOAD=$PWD/mockeagain/mockeagain.so
- export LD_LIBRARY_PATH=$PWD/mockeagain:$LD_LIBRARY_PATH
- export TEST_NGINX_RESOLVER=8.8.4.4
- export NGX_BUILD_CC=$CC
- ngx-build $NGINX_VERSION --with-ipv6 --with-http_realip_module --with-http_ssl_module --with-cc-opt="-I$OPENSSL_INC" --with-ld-opt="-L$OPENSSL_LIB -Wl,-rpath,$OPENSSL_LIB" --add-module=../ndk-nginx-module --add-module=../lua-nginx-module --with-debug > build.log 2>&1 || (cat build.log && exit 1)
- nginx -V
- ldd `which nginx`|grep -E 'luajit|ssl|pcre'
- prove -r t

View File

@ -0,0 +1,18 @@
OPENRESTY_PREFIX=/usr/local/openresty
PREFIX ?= /usr/local
LUA_INCLUDE_DIR ?= $(PREFIX)/include
LUA_LIB_DIR ?= $(PREFIX)/lib/lua/$(LUA_VERSION)
INSTALL ?= install
.PHONY: all test install
all: ;
install: all
$(INSTALL) -d $(DESTDIR)/$(LUA_LIB_DIR)/resty/
$(INSTALL) lib/resty/*.lua $(DESTDIR)/$(LUA_LIB_DIR)/resty/
test: all
PATH=$(OPENRESTY_PREFIX)/nginx/sbin:$$PATH prove -I../test-nginx/lib -r t

View File

@ -0,0 +1,419 @@
Name
====
lua-resty-lock - Simple shm-based nonblocking lock API
Table of Contents
=================
* [Name](#name)
* [Status](#status)
* [Synopsis](#synopsis)
* [Description](#description)
* [Methods](#methods)
* [new](#new)
* [lock](#lock)
* [unlock](#unlock)
* [expire](#expire)
* [For Multiple Lua Light Threads](#for-multiple-lua-light-threads)
* [For Cache Locks](#for-cache-locks)
* [Limitations](#limitations)
* [Prerequisites](#prerequisites)
* [Installation](#installation)
* [TODO](#todo)
* [Community](#community)
* [English Mailing List](#english-mailing-list)
* [Chinese Mailing List](#chinese-mailing-list)
* [Bugs and Patches](#bugs-and-patches)
* [Author](#author)
* [Copyright and License](#copyright-and-license)
* [See Also](#see-also)
Status
======
This library is still under early development and is production ready.
Synopsis
========
```lua
# nginx.conf
http {
# you do not need the following line if you are using the
# OpenResty bundle:
lua_package_path "/path/to/lua-resty-core/lib/?.lua;/path/to/lua-resty-lock/lib/?.lua;;";
lua_shared_dict my_locks 100k;
server {
...
location = /t {
content_by_lua '
local resty_lock = require "resty.lock"
for i = 1, 2 do
local lock, err = resty_lock:new("my_locks")
if not lock then
ngx.say("failed to create lock: ", err)
end
local elapsed, err = lock:lock("my_key")
ngx.say("lock: ", elapsed, ", ", err)
local ok, err = lock:unlock()
if not ok then
ngx.say("failed to unlock: ", err)
end
ngx.say("unlock: ", ok)
end
';
}
}
}
```
Description
===========
This library implements a simple mutex lock in a similar way to ngx_proxy module's [proxy_cache_lock directive](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_lock).
Under the hood, this library uses [ngx_lua](https://github.com/openresty/lua-nginx-module) module's shared memory dictionaries. The lock waiting is nonblocking because we use stepwise [ngx.sleep](https://github.com/openresty/lua-nginx-module#ngxsleep) to poll the lock periodically.
[Back to TOC](#table-of-contents)
Methods
=======
To load this library,
1. you need to specify this library's path in ngx_lua's [lua_package_path](https://github.com/openresty/lua-nginx-module#lua_package_path) directive. For example, `lua_package_path "/path/to/lua-resty-lock/lib/?.lua;;";`.
2. you use `require` to load the library into a local Lua variable:
```lua
local lock = require "resty.lock"
```
[Back to TOC](#table-of-contents)
new
---
`syntax: obj, err = lock:new(dict_name)`
`syntax: obj, err = lock:new(dict_name, opts)`
Creates a new lock object instance by specifying the shared dictionary name (created by [lua_shared_dict](http://https://github.com/openresty/lua-nginx-module#lua_shared_dict)) and an optional options table `opts`.
In case of failure, returns `nil` and a string describing the error.
The options table accepts the following options:
* `exptime`
Specifies expiration time (in seconds) for the lock entry in the shared memory dictionary. You can specify up to `0.001` seconds. Default to 30 (seconds). Even if the invoker does not call `unlock` or the object holding the lock is not GC'd, the lock will be released after this time. So deadlock won't happen even when the worker process holding the lock crashes.
* `timeout`
Specifies the maximal waiting time (in seconds) for the [lock](#lock) method calls on the current object instance. You can specify up to `0.001` seconds. Default to 5 (seconds). This option value cannot be bigger than `exptime`. This timeout is to prevent a [lock](#lock) method call from waiting forever.
You can specify `0` to make the [lock](#lock) method return immediately without waiting if it cannot acquire the lock right away.
* `step`
Specifies the initial step (in seconds) of sleeping when waiting for the lock. Default to `0.001` (seconds). When the [lock](#lock) method is waiting on a busy lock, it sleeps by steps. The step size is increased by a ratio (specified by the `ratio` option) until reaching the step size limit (specified by the `max_step` option).
* `ratio`
Specifies the step increasing ratio. Default to 2, that is, the step size doubles at each waiting iteration.
* `max_step`
Specifies the maximal step size (i.e., sleep interval, in seconds) allowed. See also the `step` and `ratio` options). Default to 0.5 (seconds).
[Back to TOC](#table-of-contents)
lock
----
`syntax: elapsed, err = obj:lock(key)`
Tries to lock a key across all the Nginx worker processes in the current Nginx server instance. Different keys are different locks.
The length of the key string must not be larger than 65535 bytes.
Returns the waiting time (in seconds) if the lock is successfully acquired. Otherwise returns `nil` and a string describing the error.
The waiting time is not from the wallclock, but rather is from simply adding up all the waiting "steps". A nonzero `elapsed` return value indicates that someone else has just hold this lock. But a zero return value cannot gurantee that no one else has just acquired and released the lock.
When this method is waiting on fetching the lock, no operating system threads will be blocked and the current Lua "light thread" will be automatically yielded behind the scene.
It is strongly recommended to always call the [unlock()](#unlock) method to actively release the lock as soon as possible.
If the [unlock()](#unlock) method is never called after this method call, the lock will get released when
1. the current `resty.lock` object instance is collected automatically by the Lua GC.
2. the `exptime` for the lock entry is reached.
Common errors for this method call is
* "timeout"
: The timeout threshold specified by the `timeout` option of the [new](#new) method is exceeded.
* "locked"
: The current `resty.lock` object instance is already holding a lock (not necessarily of the same key).
Other possible errors are from ngx_lua's shared dictionary API.
It is required to create different `resty.lock` instances for multiple simultaneous locks (i.e., those around different keys).
[Back to TOC](#table-of-contents)
unlock
------
`syntax: ok, err = obj:unlock()`
Releases the lock held by the current `resty.lock` object instance.
Returns `1` on success. Returns `nil` and a string describing the error otherwise.
If you call `unlock` when no lock is currently held, the error "unlocked" will be returned.
[Back to TOC](#table-of-contents)
expire
------
`syntax: ok, err = obj:expire(timeout)`
Sets the TTL of the lock held by the current `resty.lock` object instance. This will reset the
timeout of the lock to `timeout` seconds if it is given, otherwise the `timeout` provided while
calling [new](#new) will be used.
Note that the `timeout` supplied inside this function is independent from the `timeout` provided while
calling [new](#new). Calling `expire()` will not change the `timeout` value specified inside [new](#new)
and subsequent `expire(nil)` call will still use the `timeout` number from [new](#new).
Returns `true` on success. Returns `nil` and a string describing the error otherwise.
If you call `expire` when no lock is currently held, the error "unlocked" will be returned.
[Back to TOC](#table-of-contents)
For Multiple Lua Light Threads
==============================
It is always a bad idea to share a single `resty.lock` object instance across multiple ngx_lua "light threads" because the object itself is stateful and is vulnerable to race conditions. It is highly recommended to always allocate a separate `resty.lock` object instance for each "light thread" that needs one.
[Back to TOC](#table-of-contents)
For Cache Locks
===============
One common use case for this library is avoid the so-called "dog-pile effect", that is, to limit concurrent backend queries for the same key when a cache miss happens. This usage is similar to the standard ngx_proxy module's [proxy_cache_lock](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_lock) directive.
The basic workflow for a cache lock is as follows:
1. Check the cache for a hit with the key. If a cache miss happens, proceed to step 2.
2. Instantiate a `resty.lock` object, call the [lock](#lock) method on the key, and check the 1st return value, i.e., the lock waiting time. If it is `nil`, handle the error; otherwise proceed to step 3.
3. Check the cache again for a hit. If it is still a miss, proceed to step 4; otherwise release the lock by calling [unlock](#unlock) and then return the cached value.
4. Query the backend (the data source) for the value, put the result into the cache, and then release the lock currently held by calling [unlock](#unlock).
Below is a kinda complete code example that demonstrates the idea.
```lua
local resty_lock = require "resty.lock"
local cache = ngx.shared.my_cache
-- step 1:
local val, err = cache:get(key)
if val then
ngx.say("result: ", val)
return
end
if err then
return fail("failed to get key from shm: ", err)
end
-- cache miss!
-- step 2:
local lock, err = resty_lock:new("my_locks")
if not lock then
return fail("failed to create lock: ", err)
end
local elapsed, err = lock:lock(key)
if not elapsed then
return fail("failed to acquire the lock: ", err)
end
-- lock successfully acquired!
-- step 3:
-- someone might have already put the value into the cache
-- so we check it here again:
val, err = cache:get(key)
if val then
local ok, err = lock:unlock()
if not ok then
return fail("failed to unlock: ", err)
end
ngx.say("result: ", val)
return
end
--- step 4:
local val = fetch_redis(key)
if not val then
local ok, err = lock:unlock()
if not ok then
return fail("failed to unlock: ", err)
end
-- FIXME: we should handle the backend miss more carefully
-- here, like inserting a stub value into the cache.
ngx.say("no value found")
return
end
-- update the shm cache with the newly fetched value
local ok, err = cache:set(key, val, 1)
if not ok then
local ok, err = lock:unlock()
if not ok then
return fail("failed to unlock: ", err)
end
return fail("failed to update shm cache: ", err)
end
local ok, err = lock:unlock()
if not ok then
return fail("failed to unlock: ", err)
end
ngx.say("result: ", val)
```
Here we assume that we use the ngx_lua shared memory dictionary to cache the Redis query results and we have the following configurations in `nginx.conf`:
```nginx
# you may want to change the dictionary size for your cases.
lua_shared_dict my_cache 10m;
lua_shared_dict my_locks 1m;
```
The `my_cache` dictionary is for the data cache while the `my_locks` dictionary is for `resty.lock` itself.
Several important things to note in the example above:
1. You need to release the lock as soon as possible, even when some other unrelated errors happen.
2. You need to update the cache with the result got from the backend *before* releasing the lock so other threads already waiting on the lock can get cached value when they get the lock afterwards.
3. When the backend returns no value at all, we should handle the case carefully by inserting some stub value into the cache.
[Back to TOC](#table-of-contents)
Limitations
===========
Some of this library's API functions may yield. So do not call those functions in `ngx_lua` module contexts where yielding is not supported (yet), like `init_by_lua*`,
`init_worker_by_lua*`, `header_filter_by_lua*`, `body_filter_by_lua*`, `balancer_by_lua*`, and `log_by_lua*`.
[Back to TOC](#table-of-contents)
Prerequisites
=============
* [LuaJIT](http://luajit.org) 2.0+
* [ngx_lua](https://github.com/openresty/lua-nginx-module) 0.8.10+
[Back to TOC](#table-of-contents)
Installation
============
It is recommended to use the latest [OpenResty bundle](http://openresty.org) directly where this library
is bundled and enabled by default. At least OpenResty 1.4.2.9 is required. And you need to enable LuaJIT when building your OpenResty
bundle by passing the `--with-luajit` option to its `./configure` script. No extra Nginx configuration is required.
If you want to use this library with your own Nginx build (with ngx_lua), then you need to
ensure you are using at least ngx_lua 0.8.10. Also, You need to configure
the [lua_package_path](https://github.com/openresty/lua-nginx-module#lua_package_path) directive to
add the path of your lua-resty-lock and lua-resty-core source directories to ngx_lua's Lua module search path, as in
```nginx
# nginx.conf
http {
lua_package_path "/path/to/lua-resty-lock/lib/?.lua;/path/to/lua-resty-core/lib/?.lua;;";
...
}
```
and then load the library in Lua:
```lua
local resty_lock = require "resty.lock"
```
Note that this library depends on the [lua-resty-core](https://github.com/openresty/lua-resty-core) library
which is also enabled by default in the OpenResty bundle.
[Back to TOC](#table-of-contents)
TODO
====
* We should simplify the current implementation when LuaJIT 2.1 gets support for `__gc` metamethod on normal Lua tables. Right now we are using an FFI cdata and a ref/unref memo table to work around this, which is rather ugly and a bit inefficient.
[Back to TOC](#table-of-contents)
Community
=========
[Back to TOC](#table-of-contents)
English Mailing List
--------------------
The [openresty-en](https://groups.google.com/group/openresty-en) mailing list is for English speakers.
[Back to TOC](#table-of-contents)
Chinese Mailing List
--------------------
The [openresty](https://groups.google.com/group/openresty) mailing list is for Chinese speakers.
[Back to TOC](#table-of-contents)
Bugs and Patches
================
Please report bugs or submit patches by
1. creating a ticket on the [GitHub Issue Tracker](https://github.com/openresty/lua-resty-lock/issues),
1. or posting to the [OpenResty community](#community).
[Back to TOC](#table-of-contents)
Author
======
Yichun "agentzh" Zhang (章亦春) <agentzh@gmail.com>, OpenResty Inc.
[Back to TOC](#table-of-contents)
Copyright and License
=====================
This module is licensed under the BSD license.
Copyright (C) 2013-2019, by Yichun "agentzh" Zhang, OpenResty Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
[Back to TOC](#table-of-contents)
See Also
========
* the ngx_lua module: https://github.com/openresty/lua-nginx-module
* OpenResty: http://openresty.org
[Back to TOC](#table-of-contents)

View File

@ -0,0 +1,10 @@
name=lua-resty-lock
abstract=Simple shm-based nonblocking lock API
author=Yichun Zhang (agentzh)
is_original=yes
license=2bsd
lib_dir=lib
doc_dir=lib
repo_link=https://github.com/openresty/lua-resty-lock
main_module=lib/resty/lock.lua
requires = luajit

View File

@ -0,0 +1,222 @@
-- Copyright (C) Yichun Zhang (agentzh)
require "resty.core.shdict" -- enforce this to avoid dead locks
local ffi = require "ffi"
local ffi_new = ffi.new
local shared = ngx.shared
local sleep = ngx.sleep
local log = ngx.log
local max = math.max
local min = math.min
local debug = ngx.config.debug
local setmetatable = setmetatable
local tonumber = tonumber
local _M = { _VERSION = '0.08' }
local mt = { __index = _M }
local ERR = ngx.ERR
local FREE_LIST_REF = 0
-- FIXME: we don't need this when we have __gc metamethod support on Lua
-- tables.
local memo = {}
if debug then _M.memo = memo end
local function ref_obj(key)
if key == nil then
return -1
end
local ref = memo[FREE_LIST_REF]
if ref and ref ~= 0 then
memo[FREE_LIST_REF] = memo[ref]
else
ref = #memo + 1
end
memo[ref] = key
-- print("ref key_id returned ", ref)
return ref
end
if debug then _M.ref_obj = ref_obj end
local function unref_obj(ref)
if ref >= 0 then
memo[ref] = memo[FREE_LIST_REF]
memo[FREE_LIST_REF] = ref
end
end
if debug then _M.unref_obj = unref_obj end
local function gc_lock(cdata)
local dict_id = tonumber(cdata.dict_id)
local key_id = tonumber(cdata.key_id)
-- print("key_id: ", key_id, ", key: ", memo[key_id], "dict: ",
-- type(memo[cdata.dict_id]))
if key_id > 0 then
local key = memo[key_id]
unref_obj(key_id)
local dict = memo[dict_id]
-- print("dict.delete type: ", type(dict.delete))
local ok, err = dict:delete(key)
if not ok then
log(ERR, 'failed to delete key "', key, '": ', err)
end
cdata.key_id = 0
end
unref_obj(dict_id)
end
local ctype = ffi.metatype("struct { int key_id; int dict_id; }",
{ __gc = gc_lock })
function _M.new(_, dict_name, opts)
local dict = shared[dict_name]
if not dict then
return nil, "dictionary not found"
end
local cdata = ffi_new(ctype)
cdata.key_id = 0
cdata.dict_id = ref_obj(dict)
local timeout, exptime, step, ratio, max_step
if opts then
timeout = opts.timeout
exptime = opts.exptime
step = opts.step
ratio = opts.ratio
max_step = opts.max_step
end
if not exptime then
exptime = 30
end
if timeout then
timeout = min(timeout, exptime)
if step then
step = min(step, timeout)
end
end
local self = {
cdata = cdata,
dict = dict,
timeout = timeout or 5,
exptime = exptime,
step = step or 0.001,
ratio = ratio or 2,
max_step = max_step or 0.5,
}
setmetatable(self, mt)
return self
end
function _M.lock(self, key)
if not key then
return nil, "nil key"
end
local dict = self.dict
local cdata = self.cdata
if cdata.key_id > 0 then
return nil, "locked"
end
local exptime = self.exptime
local ok, err = dict:add(key, true, exptime)
if ok then
cdata.key_id = ref_obj(key)
self.key = key
return 0
end
if err ~= "exists" then
return nil, err
end
-- lock held by others
local step = self.step
local ratio = self.ratio
local timeout = self.timeout
local max_step = self.max_step
local elapsed = 0
while timeout > 0 do
sleep(step)
elapsed = elapsed + step
timeout = timeout - step
local ok, err = dict:add(key, true, exptime)
if ok then
cdata.key_id = ref_obj(key)
self.key = key
return elapsed
end
if err ~= "exists" then
return nil, err
end
if timeout <= 0 then
break
end
step = min(max(0.001, step * ratio), timeout, max_step)
end
return nil, "timeout"
end
function _M.unlock(self)
local dict = self.dict
local cdata = self.cdata
local key_id = tonumber(cdata.key_id)
if key_id <= 0 then
return nil, "unlocked"
end
local key = memo[key_id]
unref_obj(key_id)
local ok, err = dict:delete(key)
if not ok then
return nil, err
end
cdata.key_id = 0
return 1
end
function _M.expire(self, time)
local dict = self.dict
local cdata = self.cdata
local key_id = tonumber(cdata.key_id)
if key_id <= 0 then
return nil, "unlocked"
end
if not time then
time = self.exptime
end
local ok, err = dict:replace(self.key, true, time)
if not ok then
return nil, err
end
return true
end
return _M

View File

@ -0,0 +1,546 @@
# vim:set ft= ts=4 sw=4 et:
use Test::Nginx::Socket::Lua;
use Cwd qw(cwd);
repeat_each(2);
plan tests => repeat_each() * (blocks() * 3);
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "../lua-resty-core/lib/?.lua;lib/?.lua;;";
lua_package_cpath "/usr/local/openresty-debug/lualib/?.so;/usr/local/openresty/lualib/?.so;;";
lua_shared_dict cache_locks 100k;
};
$ENV{TEST_NGINX_RESOLVER} = '8.8.8.8';
$ENV{TEST_NGINX_REDIS_PORT} ||= 6379;
no_long_string();
#no_diff();
run_tests();
__DATA__
=== TEST 1: lock is subject to garbage collection
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
for i = 1, 2 do
collectgarbage("collect")
local lock = lock:new("cache_locks")
local elapsed, err = lock:lock("foo")
ngx.say("lock: ", elapsed, ", ", err)
end
collectgarbage("collect")
}
}
--- request
GET /t
--- response_body
lock: 0, nil
lock: 0, nil
--- no_error_log
[error]
=== TEST 2: serial lock and unlock
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
for i = 1, 2 do
local lock = lock:new("cache_locks")
local elapsed, err = lock:lock("foo")
ngx.say("lock: ", elapsed, ", ", err)
local ok, err = lock:unlock()
if not ok then
ngx.say("failed to unlock: ", err)
end
ngx.say("unlock: ", ok)
end
}
}
--- request
GET /t
--- response_body
lock: 0, nil
unlock: 1
lock: 0, nil
unlock: 1
--- no_error_log
[error]
=== TEST 3: timed out locks
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
for i = 1, 2 do
local lock1 = lock:new("cache_locks", { timeout = 0.01 })
local lock2 = lock:new("cache_locks", { timeout = 0.01 })
local elapsed, err = lock1:lock("foo")
ngx.say("lock 1: lock: ", elapsed, ", ", err)
local elapsed, err = lock2:lock("foo")
ngx.say("lock 2: lock: ", elapsed, ", ", err)
local ok, err = lock1:unlock()
ngx.say("lock 1: unlock: ", ok, ", ", err)
local ok, err = lock2:unlock()
ngx.say("lock 2: unlock: ", ok, ", ", err)
end
}
}
--- request
GET /t
--- response_body
lock 1: lock: 0, nil
lock 2: lock: nil, timeout
lock 1: unlock: 1, nil
lock 2: unlock: nil, unlocked
lock 1: lock: 0, nil
lock 2: lock: nil, timeout
lock 1: unlock: 1, nil
lock 2: unlock: nil, unlocked
--- no_error_log
[error]
=== TEST 4: waited locks
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local resty_lock = require "resty.lock"
local key = "blah"
local t, err = ngx.thread.spawn(function ()
local lock = resty_lock:new("cache_locks")
local elapsed, err = lock:lock(key)
ngx.say("sub thread: lock: ", elapsed, " ", err)
ngx.sleep(0.1)
ngx.say("sub thread: unlock: ", lock:unlock(key))
end)
local lock = resty_lock:new("cache_locks")
local elapsed, err = lock:lock(key)
ngx.say("main thread: lock: ", elapsed, " ", err)
ngx.say("main thread: unlock: ", lock:unlock())
}
}
--- request
GET /t
--- response_body_like chop
^sub thread: lock: 0 nil
sub thread: unlock: 1
main thread: lock: 0.12[6-9] nil
main thread: unlock: 1
$
--- no_error_log
[error]
=== TEST 5: waited locks (custom step)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local resty_lock = require "resty.lock"
local key = "blah"
local t, err = ngx.thread.spawn(function ()
local lock = resty_lock:new("cache_locks")
local elapsed, err = lock:lock(key)
ngx.say("sub thread: lock: ", elapsed, " ", err)
ngx.sleep(0.1)
ngx.say("sub thread: unlock: ", lock:unlock(key))
end)
local lock = resty_lock:new("cache_locks", { step = 0.01 })
local elapsed, err = lock:lock(key)
ngx.say("main thread: lock: ", elapsed, " ", err)
ngx.say("main thread: unlock: ", lock:unlock())
}
}
--- request
GET /t
--- response_body_like chop
^sub thread: lock: 0 nil
sub thread: unlock: 1
main thread: lock: 0.1[4-5]\d* nil
main thread: unlock: 1
$
--- no_error_log
[error]
=== TEST 6: waited locks (custom ratio)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local resty_lock = require "resty.lock"
local key = "blah"
local t, err = ngx.thread.spawn(function ()
local lock = resty_lock:new("cache_locks")
local elapsed, err = lock:lock(key)
ngx.say("sub thread: lock: ", elapsed, " ", err)
ngx.sleep(0.1)
ngx.say("sub thread: unlock: ", lock:unlock(key))
end)
local lock = resty_lock:new("cache_locks", { ratio = 3 })
local elapsed, err = lock:lock(key)
ngx.say("main thread: lock: ", elapsed, " ", err)
ngx.say("main thread: unlock: ", lock:unlock())
}
}
--- request
GET /t
--- response_body_like chop
^sub thread: lock: 0 nil
sub thread: unlock: 1
main thread: lock: 0.1[2]\d* nil
main thread: unlock: 1
$
--- no_error_log
[error]
=== TEST 7: waited locks (custom max step)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local resty_lock = require "resty.lock"
local key = "blah"
local t, err = ngx.thread.spawn(function ()
local lock = resty_lock:new("cache_locks")
local elapsed, err = lock:lock(key)
ngx.say("sub thread: lock: ", elapsed, " ", err)
ngx.sleep(0.1)
ngx.say("sub thread: unlock: ", lock:unlock(key))
end)
local lock = resty_lock:new("cache_locks", { max_step = 0.05 })
local elapsed, err = lock:lock(key)
ngx.say("main thread: lock: ", elapsed, " ", err)
ngx.say("main thread: unlock: ", lock:unlock())
}
}
--- request
GET /t
--- response_body_like chop
^sub thread: lock: 0 nil
sub thread: unlock: 1
main thread: lock: 0.11[2-4]\d* nil
main thread: unlock: 1
$
--- no_error_log
[error]
=== TEST 8: lock expired by itself
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local resty_lock = require "resty.lock"
local key = "blah"
local t, err = ngx.thread.spawn(function ()
local lock = resty_lock:new("cache_locks", { exptime = 0.1 })
local elapsed, err = lock:lock(key)
ngx.say("sub thread: lock: ", elapsed, " ", err)
ngx.sleep(0.1)
-- ngx.say("sub thread: unlock: ", lock:unlock(key))
end)
local lock = resty_lock:new("cache_locks", { max_step = 0.05 })
local elapsed, err = lock:lock(key)
ngx.say("main thread: lock: ", elapsed, " ", err)
ngx.say("main thread: unlock: ", lock:unlock())
}
}
--- request
GET /t
--- response_body_like chop
^sub thread: lock: 0 nil
main thread: lock: 0.11[2-4]\d* nil
main thread: unlock: 1
$
--- no_error_log
[error]
=== TEST 9: ref & unref (1 at most)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
local memo = lock.memo
local ref = lock.ref_obj("foo")
ngx.say(#memo)
lock.unref_obj(ref)
ngx.say(#memo)
ref = lock.ref_obj("bar")
ngx.say(#memo)
lock.unref_obj(ref)
ngx.say(#memo)
}
}
--- request
GET /t
--- response_body
1
0
1
0
--- no_error_log
[error]
--- skip_eval: 3: system("$NginxBinary -V 2>&1 | grep -- '--with-debug'") ne 0
=== TEST 10: ref & unref (2 at most)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
local memo = lock.memo
for i = 1, 2 do
local refs = {}
refs[1] = lock.ref_obj("foo")
ngx.say(#memo)
refs[2] = lock.ref_obj("bar")
ngx.say(#memo)
lock.unref_obj(refs[1])
ngx.say(#memo)
lock.unref_obj(refs[2])
ngx.say(#memo)
end
}
}
--- request
GET /t
--- response_body
1
2
2
2
2
2
1
1
--- no_error_log
[error]
--- skip_eval: 3: system("$NginxBinary -V 2>&1 | grep -- '--with-debug'") ne 0
=== TEST 11: lock on a nil key
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
local lock = lock:new("cache_locks")
local elapsed, err = lock:lock(nil)
if elapsed then
ngx.say("lock: ", elapsed, ", ", err)
local ok, err = lock:unlock()
if not ok then
ngx.say("failed to unlock: ", err)
end
else
ngx.say("failed to lock: ", err)
end
}
}
--- request
GET /t
--- response_body
failed to lock: nil key
--- no_error_log
[error]
=== TEST 12: same shdict, multple locks
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
local memo = lock.memo
local lock1 = lock:new("cache_locks", { timeout = 0.01 })
for i = 1, 3 do
lock1:lock("lock_key")
lock1:unlock()
collectgarbage("collect")
end
local lock2 = lock:new("cache_locks", { timeout = 0.01 })
local lock3 = lock:new("cache_locks", { timeout = 0.01 })
lock2:lock("lock_key")
lock3:lock("lock_key")
collectgarbage("collect")
ngx.say(#memo)
lock2:unlock()
lock3:unlock()
collectgarbage("collect")
}
}
--- request
GET /t
--- response_body
4
--- no_error_log
[error]
--- skip_eval: 3: system("$NginxBinary -V 2>&1 | grep -- '--with-debug'") ne 0
=== TEST 13: timed out locks (0 timeout)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
for i = 1, 2 do
local lock1 = lock:new("cache_locks", { timeout = 0 })
local lock2 = lock:new("cache_locks", { timeout = 0 })
local elapsed, err = lock1:lock("foo")
ngx.say("lock 1: lock: ", elapsed, ", ", err)
local elapsed, err = lock2:lock("foo")
ngx.say("lock 2: lock: ", elapsed, ", ", err)
local ok, err = lock1:unlock()
ngx.say("lock 1: unlock: ", ok, ", ", err)
local ok, err = lock2:unlock()
ngx.say("lock 2: unlock: ", ok, ", ", err)
end
}
}
--- request
GET /t
--- response_body
lock 1: lock: 0, nil
lock 2: lock: nil, timeout
lock 1: unlock: 1, nil
lock 2: unlock: nil, unlocked
lock 1: lock: 0, nil
lock 2: lock: nil, timeout
lock 1: unlock: 1, nil
lock 2: unlock: nil, unlocked
--- no_error_log
[error]
=== TEST 13: expire()
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local lock = require "resty.lock"
for i = 1, 2 do
local lock1 = lock:new("cache_locks", { timeout = 0, exptime = 0.1 })
local lock2 = lock:new("cache_locks", { timeout = 0, exptime = 0.1 })
local exp, err = lock1:expire()
ngx.say("lock 1: expire: ", exp, ", ", err)
local elapsed, err = lock1:lock("foo")
ngx.say("lock 1: lock: ", elapsed, ", ", err)
ngx.sleep(0.06)
local exp, err = lock1:expire()
ngx.say("lock 1: expire: ", exp, ", ", err)
ngx.sleep(0.06)
local elapsed, err = lock2:lock("foo")
ngx.say("lock 2: lock: ", elapsed, ", ", err)
local exp, err = lock1:expire(0.2)
ngx.say("lock 1: expire: ", exp, ", ", err)
ngx.sleep(0.15)
local elapsed, err = lock2:lock("foo")
ngx.say("lock 2: lock: ", elapsed, ", ", err)
ngx.sleep(0.1)
local elapsed, err = lock2:lock("foo")
ngx.say("lock 2: lock: ", elapsed, ", ", err)
local ok, err = lock2:unlock()
ngx.say("lock 2: unlock: ", ok, ", ", err)
local exp, err = lock2:expire(0.2)
ngx.say("lock 2: expire: ", exp, ", ", err)
end
}
}
--- request
GET /t
--- response_body
lock 1: expire: nil, unlocked
lock 1: lock: 0, nil
lock 1: expire: true, nil
lock 2: lock: nil, timeout
lock 1: expire: true, nil
lock 2: lock: nil, timeout
lock 2: lock: 0, nil
lock 2: unlock: 1, nil
lock 2: expire: nil, unlocked
lock 1: expire: nil, unlocked
lock 1: lock: 0, nil
lock 1: expire: true, nil
lock 2: lock: nil, timeout
lock 1: expire: true, nil
lock 2: lock: nil, timeout
lock 2: lock: 0, nil
lock 2: unlock: 1, nil
lock 2: expire: nil, unlocked
--- no_error_log
[error]

View File

@ -0,0 +1,69 @@
{
<insert_a_suppression_name_here>
Memcheck:Leak
fun:malloc
fun:ngx_alloc
fun:ngx_event_process_init
}
{
<insert_a_suppression_name_here>
Memcheck:Param
epoll_ctl(event)
fun:epoll_ctl
fun:ngx_epoll_add_event
}
{
<insert_a_suppression_name_here>
Memcheck:Cond
fun:index
fun:expand_dynamic_string_token
fun:_dl_map_object
fun:map_doit
fun:_dl_catch_error
fun:do_preload
fun:dl_main
fun:_dl_sysdep_start
fun:_dl_start
}
{
<insert_a_suppression_name_here>
Memcheck:Param
epoll_ctl(event)
fun:epoll_ctl
fun:ngx_epoll_init
fun:ngx_event_process_init
}
{
<insert_a_suppression_name_here>
Memcheck:Param
epoll_ctl(event)
fun:epoll_ctl
fun:ngx_epoll_notify_init
fun:ngx_epoll_init
}
{
<insert_a_suppression_name_here>
Memcheck:Param
epoll_ctl(event)
fun:epoll_ctl
fun:ngx_epoll_test_rdhup
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:ngx_alloc
fun:ngx_set_environment
fun:ngx_single_process_cycle
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:ngx_alloc
fun:ngx_set_environment
fun:ngx_worker_process_init
fun:ngx_worker_process_cycle
}

View File

@ -141,7 +141,7 @@ end
-- true module stuffs
local _M = {
_VERSION = '0.11'
_VERSION = '0.13'
}
local mt = { __index = _M }

View File

@ -307,7 +307,7 @@ end
--========================================================================
local _M = {
_VERSION = '0.11'
_VERSION = '0.13'
}
local mt = { __index = _M }

View File

@ -0,0 +1,59 @@
name: CI
on:
push:
branches: main
pull_request:
branches: '*'
workflow_dispatch:
inputs:
openresty:
description: 'OpenResty version (e.g. 1.21.4.1rc2)'
required: true
defaults:
run:
shell: bash
jobs:
tests:
name: Tests
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
openresty:
- 1.21.4.1
- 1.19.9.1
- 1.19.3.2
- 1.17.8.2
- 1.15.8.3
- 1.13.6.2
- 1.11.2.5
steps:
- if: ${{ github.event_name == 'workflow_dispatch' }}
run: echo "OPENRESTY_VER=${{ github.event.inputs.openresty }}" >> $GITHUB_ENV
- if: ${{ github.event_name == 'push' || github.event_name == 'pull_request' }}
run: echo "OPENRESTY_VER=${{ matrix.openresty }}" >> $GITHUB_ENV
- uses: actions/checkout@v2
- name: Setup OpenResty
uses: thibaultcha/setup-openresty@main
with:
version: ${{ env.OPENRESTY_VER }}
- run: prove -r t/
lint:
name: Lint
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
openresty: [1.19.9.1]
steps:
- uses: actions/checkout@v2
- name: Setup OpenResty
uses: thibaultcha/setup-openresty@main
with:
version: ${{ matrix.openresty }}
- run: |
echo "luarocks check"

View File

@ -0,0 +1,5 @@
t/servroot*
lua-resty-mlcache-*/
*.tar.gz
*.rock
work/

View File

@ -0,0 +1,3 @@
std = "ngx_lua"
redefined = false
max_line_length = 80

View File

@ -0,0 +1,270 @@
# Table of Contents
- [2.6.0](#2.6.0)
- [2.5.0](#2.5.0)
- [2.4.1](#2.4.1)
- [2.4.0](#2.4.0)
- [2.3.0](#2.3.0)
- [2.2.1](#2.2.1)
- [2.2.0](#2.2.0)
- [2.1.0](#2.1.0)
- [2.0.2](#2.0.2)
- [2.0.1](#2.0.1)
- [2.0.0](#2.0.0)
- [1.0.1](#1.0.1)
- [1.0.0](#1.0.0)
## [2.6.0]
> Released on: 2022/08/22
#### Added
- Use the new LuaJIT `string.buffer` API for L2 (shm layer) encoding/decoding
when available.
[#110](https://github.com/thibaultcha/lua-resty-mlcache/pull/110)
[Back to TOC](#table-of-contents)
## [2.5.0]
> Released on: 2020/11/18
#### Added
- `get()` callback functions are now optional. Without a callback, `get()` now
still performs on-cpu L1/L2 lookups (no yielding). This allows implementing
new cache lookup patterns guaranteed to be on-cpu for a more constant,
smoother latency tail end (e.g. values are refreshed in background timers with
`set()`).
Thanks Hamish Forbes and Corina Purcarea for proposing this feature and
participating in its development!
[#96](https://github.com/thibaultcha/lua-resty-mlcache/pull/96)
#### Fixed
- Improve `update()` robustness to worker crashes. Now, the library behind
`cache:update()` is much more robust to re-spawned workers when initialized in
the `init_by_lua` phase.
[#97](https://github.com/thibaultcha/lua-resty-mlcache/pull/97)
- Document the `peek()` method `stale` argument which was not mentioned, as well
as the possibility of negative TTL return values for expired items.
[Back to TOC](#table-of-contents)
## [2.4.1]
> Released on: 2020/01/17
#### Fixed
- The IPC module now avoids replaying all events when spawning new workers, and
gets initialized with the latest event index instead.
[#88](https://github.com/thibaultcha/lua-resty-mlcache/pull/88)
[Back to TOC](#table-of-contents)
## [2.4.0]
> Released on: 2019/03/28
#### Added
- A new `get_bulk()` API allows for fetching several values from the layered
caches in a single call, and will execute all L3 callback functions
concurrently, in a configurable pool of threads.
[#77](https://github.com/thibaultcha/lua-resty-mlcache/pull/77)
- `purge()` now clears the L1 LRU cache with the new `flush_all()` method when
used in OpenResty >= 1.13.6.2.
Thanks [@Crack](https://github.com/Crack) for the patch!
[#78](https://github.com/thibaultcha/lua-resty-mlcache/pull/78)
#### Fixed
- `get()` is now resilient to L3 callback functions calling `error()` with
non-string arguments. Such functions could result in a runtime error when
LuaJIT is compiled with `-DLUAJIT_ENABLE_LUA52COMPAT`.
Thanks [@MartinAmps](https://github.com/MartinAmps) for the patch!
[#75](https://github.com/thibaultcha/lua-resty-mlcache/pull/75)
- Instances using a custom L1 LRU cache in OpenResty < 1.13.6.2 are now
restricted from calling `purge()`, since doing so would result in the LRU
cache being overwritten.
[#79](https://github.com/thibaultcha/lua-resty-mlcache/pull/79)
[Back to TOC](#table-of-contents)
## [2.3.0]
> Released on: 2019/01/17
#### Added
- Returning a negative `ttl` value from the L3 callback will now make the
fetched data bypass the cache (it will still be returned by `get()`).
This is useful when some fetched data indicates that it is not cacheable.
Thanks [@eaufavor](https://github.com/eaufavor) for the patch!
[#68](https://github.com/thibaultcha/lua-resty-mlcache/pull/68)
[Back to TOC](#table-of-contents)
## [2.2.1]
> Released on: 2018/07/28
#### Fixed
- When `get()` returns a value from L2 (shm) during its last millisecond of
freshness, we do not erroneously cache the value in L1 (LRU) indefinitely
anymore. Thanks [@jdesgats](https://github.com/jdesgats) and
[@javierguerragiraldez](https://github.com/javierguerragiraldez) for the
report and initial fix.
[#58](https://github.com/thibaultcha/lua-resty-mlcache/pull/58)
- When `get()` returns a previously resurrected value from L2 (shm), we now
correctly set the `hit_lvl` return value to `4`, instead of `2`.
[307feca](https://github.com/thibaultcha/lua-resty-mlcache/commit/307fecad6adac8755d4fcd931bbb498da23d069c)
[Back to TOC](#table-of-contents)
## [2.2.0]
> Released on: 2018/06/29
#### Added
- Implement a new `resurrect_ttl` option. When specified, `get()` will behave
in a more resilient way upon errors, and in particular callback errors.
[#52](https://github.com/thibaultcha/lua-resty-mlcache/pull/52)
- New `stale` argument to `peek()`. When specified, `peek()` will return stale
shm values.
[#52](https://github.com/thibaultcha/lua-resty-mlcache/pull/52)
[Back to TOC](#table-of-contents)
## [2.1.0]
> Released on: 2018/06/14
#### Added
- Implement a new `shm_locks` option. This option receives the name of a
lua_shared_dict, and, when specified, the mlcache instance will store
lua-resty-lock objects in it instead of storing them in the cache hits
lua_shared_dict. This can help reducing LRU churning in some workloads.
[#55](https://github.com/thibaultcha/lua-resty-mlcache/pull/55)
- Provide stack traceback in `err` return value when the L3 callback throws an
error.
[#56](https://github.com/thibaultcha/lua-resty-mlcache/pull/56)
#### Fixed
- Ensure `no memory` errors returned by shm insertions are properly returned
by `set()`.
[#53](https://github.com/thibaultcha/lua-resty-mlcache/pull/53)
[Back to TOC](#table-of-contents)
## [2.0.2]
> Released on: 2018/04/09
#### Fixed
- Make `get()` lookup in shm after lock timeout. This prevents a possible (but
rare) race condition under high load. Thanks to
[@jdesgats](https://github.com/jdesgats) for the report and initial fix.
[#49](https://github.com/thibaultcha/lua-resty-mlcache/pull/49)
[Back to TOC](#table-of-contents)
## [2.0.1]
> Released on: 2018/03/27
#### Fixed
- Ensure the `set()`, `delete()`, `peek()`, and `purge()` method properly
support the new `shm_miss` option.
[#45](https://github.com/thibaultcha/lua-resty-mlcache/pull/45)
[Back to TOC](#table-of-contents)
## [2.0.0]
> Released on: 2018/03/18
This release implements numerous new features. The major version digit has been
bumped to ensure that the changes to the interpretation of the callback return
values (documented below) do not break any dependent application.
#### Added
- Implement a new `purge()` method to clear all cached items in both
the L1 and L2 caches.
[#34](https://github.com/thibaultcha/lua-resty-mlcache/pull/34)
- Implement a new `shm_miss` option. This option receives the name
of a lua_shared_dict, and when specified, will cache misses there instead of
the instance's `shm` shared dict. This is particularly useful for certain
types of workload where a large number of misses can be triggered and
eventually evict too many cached values (hits) from the instance's `shm`.
[#42](https://github.com/thibaultcha/lua-resty-mlcache/pull/42)
- Implement a new `l1_serializer` callback option. It allows the
deserialization of data from L2 or L3 into arbitrary Lua data inside the LRU
cache (L1). This includes userdata, cdata, functions, etc...
Thanks to [@jdesgats](https://github.com/jdesgats) for the contribution.
[#29](https://github.com/thibaultcha/lua-resty-mlcache/pull/29)
- Implement a new `shm_set_tries` option to retry `shm:set()`
operations and ensure LRU eviction when caching values of disparate sizes.
[#41](https://github.com/thibaultcha/lua-resty-mlcache/issues/41)
- The L3 callback can now return `nil + err`, which will be bubbled up
to the caller of `get()`. Prior to this change, the second return value of
callbacks was ignored, and users had to throw hard Lua errors from inside
their callbacks.
[#35](https://github.com/thibaultcha/lua-resty-mlcache/pull/35)
- Support for custom IPC module.
[#31](https://github.com/thibaultcha/lua-resty-mlcache/issues/31)
#### Fixed
- In the event of a `no memory` error returned by the L2 lua_shared_dict cache
(after the number of `shm_set_tries` failed), we do not interrupt the `get()`
flow to return an error anymore. Instead, the retrieved value is now bubbled
up for insertion in L1, and returned to the caller. A warning log is (by
default) printed in the nginx error logs.
[#41](https://github.com/thibaultcha/lua-resty-mlcache/issues/41)
[Back to TOC](#table-of-contents)
## [1.0.1]
> Released on: 2017/08/26
#### Fixed
- Do not rely on memory address of mlcache instance in invalidation events
channel names. This ensures invalidation events are properly broadcasted to
sibling instances in other workers.
[#27](https://github.com/thibaultcha/lua-resty-mlcache/pull/27)
[Back to TOC](#table-of-contents)
## [1.0.0]
> Released on: 2017/08/23
Initial release.
[Back to TOC](#table-of-contents)
[2.6.0]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.5.0...2.6.0
[2.5.0]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.4.1...2.5.0
[2.4.1]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.4.0...2.4.1
[2.4.0]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.3.0...2.4.0
[2.3.0]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.2.1...2.3.0
[2.2.1]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.2.0...2.2.1
[2.2.0]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.1.0...2.2.0
[2.1.0]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.0.2...2.1.0
[2.0.2]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.0.1...2.0.2
[2.0.1]: https://github.com/thibaultcha/lua-resty-mlcache/compare/2.0.0...2.0.1
[2.0.0]: https://github.com/thibaultcha/lua-resty-mlcache/compare/1.0.1...2.0.0
[1.0.1]: https://github.com/thibaultcha/lua-resty-mlcache/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/thibaultcha/lua-resty-mlcache/tree/1.0.0

Some files were not shown because too many files have changed in this diff Show More