This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 63f69fe11 feat: add option to normalize uri like servlet (#6984)
63f69fe11 is described below
commit 63f69fe116bd0b83c3b411dd42baa528e5be6950
Author: 罗泽轩 <[email protected]>
AuthorDate: Sat May 7 10:25:01 2022 +0800
feat: add option to normalize uri like servlet (#6984)
Signed-off-by: spacewander <[email protected]>
---
apisix/init.lua | 63 +++++++++++++++++++++++--
conf/config-default.yaml | 5 ++
t/router/radixtree-uri-sanity.t | 102 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 165 insertions(+), 5 deletions(-)
diff --git a/apisix/init.lua b/apisix/init.lua
index 12fa94037..4306809d0 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -50,6 +50,7 @@ local error = error
local ipairs = ipairs
local ngx_now = ngx.now
local ngx_var = ngx.var
+local re_split = require("ngx.re").split
local str_byte = string.byte
local str_sub = string.sub
local tonumber = tonumber
@@ -262,6 +263,44 @@ local function verify_tls_client(ctx)
end
+local function normalize_uri_like_servlet(uri)
+ local found = core.string.find(uri, ';')
+ if not found then
+ return uri
+ end
+
+ local segs, err = re_split(uri, "/", "jo")
+ if not segs then
+ return nil, err
+ end
+
+ local len = #segs
+ for i = 1, len do
+ local seg = segs[i]
+ local pos = core.string.find(seg, ';')
+ if pos then
+ seg = seg:sub(1, pos - 1)
+ -- reject bad uri which bypasses with ';'
+ if seg == "." or seg == ".." then
+ return nil, "dot segment with parameter"
+ end
+ if seg == "" and i < len then
+ return nil, "empty segment with parameters"
+ end
+
+ segs[i] = seg
+
+ seg = seg:lower()
+ if seg == "%2e" or seg == "%2e%2e" then
+ return nil, "encoded dot segment"
+ end
+ end
+ end
+
+ return core.table.concat(segs, '/')
+end
+
+
local function common_phase(phase_name)
local api_ctx = ngx.ctx.api_ctx
if not api_ctx then
@@ -295,11 +334,25 @@ function _M.http_access_phase()
debug.dynamic_debug(api_ctx)
local uri = api_ctx.var.uri
- if local_conf.apisix and local_conf.apisix.delete_uri_tail_slash then
- if str_byte(uri, #uri) == str_byte("/") then
- api_ctx.var.uri = str_sub(api_ctx.var.uri, 1, #uri - 1)
- core.log.info("remove the end of uri '/', current uri: ",
- api_ctx.var.uri)
+ if local_conf.apisix then
+ if local_conf.apisix.delete_uri_tail_slash then
+ if str_byte(uri, #uri) == str_byte("/") then
+ api_ctx.var.uri = str_sub(api_ctx.var.uri, 1, #uri - 1)
+ core.log.info("remove the end of uri '/', current uri: ",
api_ctx.var.uri)
+ end
+ end
+
+ if local_conf.apisix.normalize_uri_like_servlet then
+ local new_uri, err = normalize_uri_like_servlet(uri)
+ if not new_uri then
+ core.log.error("failed to normalize: ", err)
+ return core.response.exit(400)
+ end
+
+ api_ctx.var.uri = new_uri
+ -- forward the original uri so the servlet upstream
+ -- can consume the param after ';'
+ api_ctx.var.upstream_uri = uri
end
end
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 0bda63548..2d266b5b7 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -101,6 +101,11 @@ apisix:
role: viewer
delete_uri_tail_slash: false # delete the '/' at the end of the URI
+ # The URI normalization in servlet is a little different from the RFC's.
+ # See
https://github.com/jakartaee/servlet/blob/master/spec/src/main/asciidoc/servlet-spec-body.adoc#352-uri-path-canonicalization,
+ # which is used under Tomcat.
+ # Turn this option on if you want to be compatible with servlet when
matching URI path.
+ normalize_uri_like_servlet: false
router:
http: radixtree_uri # radixtree_uri: match route by uri(base on
radixtree)
# radixtree_host_uri: match route by host +
uri(base on radixtree)
diff --git a/t/router/radixtree-uri-sanity.t b/t/router/radixtree-uri-sanity.t
index e91834dec..d49285ff1 100644
--- a/t/router/radixtree-uri-sanity.t
+++ b/t/router/radixtree-uri-sanity.t
@@ -22,6 +22,13 @@ worker_connections(256);
no_root_location();
no_shuffle();
+our $servlet_yaml_config = <<_EOC_;
+apisix:
+ node_listen: 1984
+ admin_key: null
+ normalize_uri_like_servlet: true
+_EOC_
+
run_tests();
__DATA__
@@ -295,3 +302,98 @@ GET /hello/
--- error_code: 404
--- no_error_log
[error]
+--- response_body
+{"error_msg":"404 Route Not Found"}
+
+
+
+=== TEST 16: match route like servlet
+--- yaml_config eval: $::servlet_yaml_config
+--- request
+GET /hello;world
+--- response_body eval
+qr/404 Not Found/
+--- error_code: 404
+--- no_error_log
+[error]
+
+
+
+=== TEST 17: plugin should work on the normalized url
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/*",
+ "plugins": {
+ "uri-blocker": {
+ "block_rules": ["/hello/world"]
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 18: hit
+--- yaml_config eval: $::servlet_yaml_config
+--- request
+GET /hello;a=b/world;a/;
+--- error_code: 403
+--- no_error_log
+[error]
+
+
+
+=== TEST 19: reject bad uri
+--- yaml_config eval: $::servlet_yaml_config
+--- config
+ location /t {
+ content_by_lua_block {
+ local http = require "resty.http"
+ local uri = "http://127.0.0.1:" .. ngx.var.server_port
+ for _, path in ipairs({
+ "/;/a", "/%2e;", "/%2E%2E;", "/.;", "/..;",
+ "/%2E%2e;", "/b/;/c"
+ }) do
+ local httpc = http.new()
+ local res, err = httpc:request_uri(uri .. path)
+ if not res then
+ ngx.say(err)
+ return
+ end
+
+ if res.status ~= 400 then
+ ngx.say(path, " ", res.status)
+ end
+ end
+
+ ngx.say("ok")
+ }
+ }
+--- request
+GET /t
+--- response_body
+ok