From 89a9066624f0131115d7efb4acc4a0a1fa130f74 Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Fri, 7 Jul 2017 12:39:56 -0700 Subject: [PATCH] feat(apis) allow for specifying user-specified regexes APIs can now receive user-specified regexes in their `uris` property. Those regexes will be treated as such by the router. A URI is considered a regex when its character set does not respect the reserved list from RFC 3986. Implements: #677 (partially) --- kong/dao/schemas/apis.lua | 9 +++- spec/01-unit/007-entities_schemas_spec.lua | 44 ++++++++++++------- .../05-proxy/01-router_spec.lua | 31 +++++++++++-- 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/kong/dao/schemas/apis.lua b/kong/dao/schemas/apis.lua index 91c2629838c1..37cf857a6133 100644 --- a/kong/dao/schemas/apis.lua +++ b/kong/dao/schemas/apis.lua @@ -83,8 +83,13 @@ local function check_uri(uri) return false, "invalid" elseif not match(uri, "^/[%w%.%-%_~%/%%]*$") then - -- Check if characters are in RFC 3986 unreserved list, and % for percent encoding - return false, "must only contain alphanumeric and '., -, _, ~, /, %' characters" + -- URI contains characters outside of the reserved list of + -- RFC 3986: the value will be interpreted as a regex; + -- but is it a valid one? + local _, _, err = ngx.re.find("", uri, "aj") + if err then + return false, "invalid regex '" .. uri .. "' PCRE returned: " .. err + end end local esc = uri:gsub("%%%x%x", "___") -- drop all proper %-encodings diff --git a/spec/01-unit/007-entities_schemas_spec.lua b/spec/01-unit/007-entities_schemas_spec.lua index 89f5116dac64..1a1abb9498f4 100644 --- a/spec/01-unit/007-entities_schemas_spec.lua +++ b/spec/01-unit/007-entities_schemas_spec.lua @@ -270,6 +270,18 @@ describe("Entities Schemas", function() assert.is_true(ok) end) + it("accepts reserved characters from RFC 3986 (considered as a regex)", function() + local t = { + name = "httpbin", + upstream_url = "http://httpbin.org", + uris = { "/users/[a-z]+/" }, + } + + local ok, errors = validate_entity(t, api_schema) + assert.is_nil(errors) + assert.is_true(ok) + end) + it("accepts properly %-encoded characters", function() local valids = {"/abcd%aa%10%ff%AA%FF"} @@ -359,22 +371,6 @@ describe("Entities Schemas", function() end end) - it("rejects reserved characters from RFC 3986", function() - local invalids = { "/[a-z]{3}" } - - for _, v in ipairs(invalids) do - local t = { - name = "mockbin", - upstream_url = "http://mockbin.com", - uris = { v }, - } - - local ok, errors = validate_entity(t, api_schema) - assert.is_false(ok) - assert.matches("must only contain alphanumeric and '., -, _, ~, /, %' characters", errors.uris, nil, true) - end - end) - it("rejects bad %-encoded characters", function() local invalids = { "/some%2words", @@ -431,6 +427,22 @@ describe("Entities Schemas", function() assert.matches("invalid", errors.uris, nil, true) end end) + + it("rejects regex URIs that are invalid regexes", function() + local invalids = { [[/users/(foo/profile]] } + + for _, v in ipairs(invalids) do + local t = { + name = "mockbin", + upstream_url = "http://mockbin.com", + uris = { v }, + } + + local ok, errors = validate_entity(t, api_schema) + assert.is_false(ok) + assert.matches("invalid regex", errors.uris, nil, true) + end + end) end) end) diff --git a/spec/02-integration/05-proxy/01-router_spec.lua b/spec/02-integration/05-proxy/01-router_spec.lua index baa20d41f231..8c5837e6a5ab 100644 --- a/spec/02-integration/05-proxy/01-router_spec.lua +++ b/spec/02-integration/05-proxy/01-router_spec.lua @@ -77,9 +77,15 @@ describe("Router", function() { name = "api-4", upstream_url = "http://httpbin.org/basic-auth", - uris = { "/user" }, + uris = { "/private" }, strip_uri = false, - } + }, + { + name = "api-5", + upstream_url = "http://httpbin.org/anything", + uris = { [[/users/\d+/profile]] }, + strip_uri = true, + }, } assert(helpers.start_kong()) @@ -140,13 +146,32 @@ describe("Router", function() it("with strip_uri = false", function() local res = assert(client:send { method = "GET", - path = "/user/passwd", + path = "/private/passwd", headers = { ["kong-debug"] = 1 }, }) assert.res_status(401, res) assert.equal("api-4", res.headers["kong-api-name"]) end) + + it("[uri] with a regex", function() + local res = assert(client:send { + method = "GET", + path = "/users/foo/profile", + headers = { ["kong-debug"] = 1 }, + }) + + assert.res_status(404, res) + + res = assert(client:send { + method = "GET", + path = "/users/123/profile", + headers = { ["kong-debug"] = 1 }, + }) + + assert.res_status(200, res) + assert.equal("api-5", res.headers["kong-api-name"]) + end) end) describe("URI arguments (querystring)", function()