Skip to content

Commit

Permalink
feat(apis) allow for specifying user-specified regexes
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
thibaultcha committed Jul 17, 2017
1 parent 2d041f1 commit dfb91b7
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 21 deletions.
9 changes: 7 additions & 2 deletions kong/dao/schemas/apis.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 28 additions & 16 deletions spec/01-unit/07-entities_schemas_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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"}

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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)

Expand Down
31 changes: 28 additions & 3 deletions spec/02-integration/05-proxy/01-router_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit dfb91b7

Please sign in to comment.