diff --git a/kong/db/schema/entities/routes.lua b/kong/db/schema/entities/routes.lua index 313f996b04a..8353bc4bdd9 100644 --- a/kong/db/schema/entities/routes.lua +++ b/kong/db/schema/entities/routes.lua @@ -196,10 +196,7 @@ local routes = { }, }, }, - values = { - type = "array", - elements = typedefs.regex_or_plain_pattern, - } + values = typedefs.header_regex_or_plain_pattern, } }, { regex_priority = { description = "A number used to choose which route resolves a given request when several routes match it using regexes simultaneously.", type = "integer", default = 0 }, }, diff --git a/kong/db/schema/typedefs.lua b/kong/db/schema/typedefs.lua index 2d05a30fcee..ade7b56e8ab 100644 --- a/kong/db/schema/typedefs.lua +++ b/kong/db/schema/typedefs.lua @@ -504,14 +504,13 @@ end local function is_valid_regex_pattern(pattern) - local regex = pattern:sub(2) -- remove the leading "~" -- the value will be interpreted as a regex by the router; but is it a -- valid one? Let's dry-run it with the same options as our router. - local _, _, err = ngx.re.find("", regex, "aj") + local _, _, err = ngx.re.find("", pattern, "a") if err then return nil, string.format("invalid regex: '%s' (PCRE returned: %s)", - regex, err) + pattern, err) end return true @@ -525,8 +524,9 @@ local function validate_path_with_regexes(path) return ok, err, err_code end + -- starts with `~` if is_regex_pattern(path) then - return is_valid_regex_pattern(path) + return is_valid_regex_pattern(path:sub(2)) end -- prefix matching. let's check if it's normalized form @@ -539,12 +539,14 @@ local function validate_path_with_regexes(path) end -local function validate_regex_or_plain_pattern(pattern) - if not is_regex_pattern(pattern) then +local function validate_header_regex_or_plain_pattern(patterns) + -- when there's only 1 pattern and it starts with "~*", it's a regex pattern + if #patterns ~= 1 or patterns[1]:sub(1, 2) ~= "~*" then return true end - return is_valid_regex_pattern(pattern) + -- starts with "~*" + return is_valid_regex_pattern(patterns[1]:sub(3)) end @@ -646,9 +648,12 @@ typedefs.headers = Schema.define { description = "A map of header names to arrays of header values." } -typedefs.regex_or_plain_pattern = Schema.define { - type = "string", - custom_validator = validate_regex_or_plain_pattern, +typedefs.header_regex_or_plain_pattern = Schema.define { + type = "array", + elements = { + type = "string", + }, + custom_validator = validate_header_regex_or_plain_pattern, description = "A string representing a regex or plain pattern." } diff --git a/kong/router/transform.lua b/kong/router/transform.lua index 25c1c3d626d..708faf2f8e0 100644 --- a/kong/router/transform.lua +++ b/kong/router/transform.lua @@ -112,7 +112,6 @@ local OP_IN = "in" local DOT = byte(".") -local TILDE = byte("~") local ASTERISK = byte("*") @@ -424,12 +423,13 @@ local function get_expression(route) for h, v in pairs(headers) do single_header_buf:reset():put("(") + local num_v = #v for i, value in ipairs(v) do local name = "any(lower(http.headers." .. replace_dashes_lower(h) .. "))" local op = OP_EQUAL - -- value starts with "~*" - if byte(value, 1) == TILDE and byte(value, 2) == ASTERISK then + -- the condition: only 1 value and it starts with `~*` + if num_v == 1 and is_regex_magic(value) then value = value:sub(3) op = OP_REGEX end diff --git a/kong/router/utils.lua b/kong/router/utils.lua index 4cd32ed171e..72a96af77e3 100644 --- a/kong/router/utils.lua +++ b/kong/router/utils.lua @@ -262,8 +262,9 @@ do local get_phase = ngx.get_phase local TILDE = byte("~") + local ASTERISK = byte("*") is_regex_magic = function(path) - return byte(path) == TILDE + return byte(path) == TILDE and byte(path, 2) == ASTERISK end local empty_table = {} diff --git a/spec/01-unit/01-db/01-schema/06-routes_spec.lua b/spec/01-unit/01-db/01-schema/06-routes_spec.lua index 9b97a031641..7476cd2a148 100644 --- a/spec/01-unit/01-db/01-schema/06-routes_spec.lua +++ b/spec/01-unit/01-db/01-schema/06-routes_spec.lua @@ -622,16 +622,29 @@ describe("routes schema (flavor = " .. flavor .. ")", function() assert.equal("length must be at least 1", err.headers[1]) end) - it("value must be a plain pattern or a valid regex pattern", function() - local route = { - headers = { location = { "~[" } }, - protocols = { "http" }, - } + if flavor == "traditional_compatible" then + it("value must be a plain pattern or a valid regex pattern", function() + local route = { + headers = { location = { "~*[" } }, + protocols = { "http" }, + } - local ok, err = Routes:validate(route) - assert.falsy(ok) - assert.match("invalid regex", err.headers[1]) - end) + local ok, err = Routes:validate(route) + assert.falsy(ok) + assert.match("invalid regex", err.headers) + end) + + it("multiple patterns are treated as plain patterns", function() + local route = { + headers = { location = { "~*[", "~*" } }, + protocols = { "http" }, + } + + local ok, err = Routes:validate(route) + assert.is_nil(err) + assert.truthy(ok) + end) + end end) describe("methods attribute", function()