Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions lib/resty/ldap/filter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -199,21 +199,26 @@ local ESCAPE_MAP = {
["="] = "\\3d",
["<"] = "\\3c",
[">"] = "\\3e",
["~"] = "\\7e",
}

-- RFC 4515 s3: escape the octets that are special in an assertion value so that
-- untrusted input cannot change the filter's structure. The compiler un-escapes
-- \HH back to raw bytes, so escape() is its exact inverse for these octets.
-- The five RFC-mandated specials are `* ( ) \ NUL`; `= < >` are not RFC-special
-- The five RFC-mandated specials are `* ( ) \ NUL`; `= < > ~` are not RFC-special
-- but the grammar above is stricter and rejects them raw inside a value, so we
-- escape them too (escaping any octet is RFC-legal and round-trips). Note: valid
-- multi-byte UTF-8 passes through the grammar unescaped; a lone invalid-UTF-8
-- byte (0x80-0xFF) is not escaped here and fails the filter closed.
-- escape them too (escaping any octet is RFC-legal and round-trips). `~` is the
-- subtler one: UTF8_FILTERED_CHARACTER excludes it so that `~=` can be read as
-- the approx operator, and the Cmt that hands a raw `~` back to the value only
-- fires away from the end of the filter string -- so a leading or trailing `~`
-- would otherwise fail a value RFC 4515 permits (UTF1SUBSET is %x5D-7F).
-- Note: valid multi-byte UTF-8 passes through the grammar unescaped; a lone
-- invalid-UTF-8 byte (0x80-0xFF) is not escaped here and fails the filter closed.
function _M.escape(value)
if type(value) ~= "string" then
return nil, "value must be a string"
end
return (value:gsub("[%z%*%(%)\\=<>]", ESCAPE_MAP))
return (value:gsub("[%z%*%(%)\\=<>~]", ESCAPE_MAP))
end


Expand Down
32 changes: 32 additions & 0 deletions t/filter.t
Original file line number Diff line number Diff line change
Expand Up @@ -519,3 +519,35 @@ GET /t
ok
--- no_error_log
[error]

=== TEST 8: escape covers ~, which RFC 4515 allows but the grammar rejects at the edges
--- http_config eval: $::HttpConfig
--- config
location /t {
content_by_lua_block {
local filter = require("resty.ldap.filter")
assert(filter.escape("a~b") == "a\\7eb", "tilde")
-- RFC 4515 UTF1SUBSET (%x5D-7F) includes ~ (0x7e), so these are all
-- legal assertion values. The grammar only tolerates a raw ~ mid-value
-- and only away from the end of the filter string, so escaping is what
-- makes the leading and trailing forms usable.
for _, raw in ipairs({ "admin~", "~admin", "ad~min", "admin~~", "~" }) do
local ast = assert(filter.compile("(cn=" .. filter.escape(raw) .. ")"),
"value must compile after escaping: " .. raw)
assert(ast.item_type == "simple" and ast.filter_type == "equal",
"simple equality: " .. raw)
assert(ast.attribute_value == raw,
"round-trips to raw: " .. tostring(ast.attribute_value))
end
-- ~= stays an operator: escaping only ever applies to the value
local approx = assert(filter.compile("(cn~=admin)"), "approx filter still parses")
assert(approx.filter_type == "approx", "filter_type != approx, " .. approx.filter_type)
ngx.say("ok")
}
}
--- request
GET /t
--- response_body
ok
--- no_error_log
[error]