This is an automated email from the ASF dual-hosted git repository.

nic-6443 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 09b19c609 fix: preserve servlet upstream URI boundaries (#13914)
09b19c609 is described below

commit 09b19c609c3021c72b9cde5b71fff090634b5299
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Wed Sep 9 13:37:37 2026 +0800

    fix: preserve servlet upstream URI boundaries (#13914)
---
 apisix/init.lua                 |   7 +-
 t/router/servlet-upstream-uri.t | 172 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 175 insertions(+), 4 deletions(-)

diff --git a/apisix/init.lua b/apisix/init.lua
index eb96f7a29..20d0caa1f 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -797,10 +797,9 @@ function _M.http_access_phase()
             end
 
             api_ctx.var.uri = new_uri
-            -- forward the original uri so the servlet upstream
-            -- can consume the param after ';'. Encode control characters so a
-            -- CR/LF in the decoded uri cannot inject into the upstream 
request line.
-            api_ctx.var.upstream_uri = core.utils.escape_uri_control_chars(uri)
+            -- Forward the original path so servlet upstreams can consume 
params
+            -- after ';'. URI-encode it before proxying to keep delimiters as 
path data.
+            api_ctx.var.upstream_uri = core.utils.uri_safe_encode(uri)
         end
     end
 
diff --git a/t/router/servlet-upstream-uri.t b/t/router/servlet-upstream-uri.t
new file mode 100644
index 000000000..a816cd06e
--- /dev/null
+++ b/t/router/servlet-upstream-uri.t
@@ -0,0 +1,172 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+log_level('info');
+worker_connections(256);
+no_root_location();
+no_shuffle();
+
+our $yaml_config = <<_EOC_;
+apisix:
+    node_listen: 1984
+    normalize_uri_like_servlet: true
+    router:
+        http: 'radixtree_uri'
+_EOC_
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->yaml_config) {
+        $block->set_value("yaml_config", $yaml_config);
+    }
+
+    if (!$block->upstream_server_config) {
+        $block->set_value("upstream_server_config", <<'_EOC_');
+        location = /anything {
+            content_by_lua_block {
+                ngx.say("protected-handler")
+                ngx.say("route-marker=", ngx.var.http_x_route_marker or "")
+            }
+        }
+
+        location /anything {
+            content_by_lua_block {
+                ngx.say("path-handler")
+                ngx.say("route-marker=", ngx.var.http_x_route_marker or "")
+                ngx.say("request-uri=", ngx.var.request_uri)
+            }
+        }
+_EOC_
+    }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: set overlapping exact and wildcard routes
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local routes = {
+                {
+                    id = "protected-exact-path",
+                    host = "servlet-uri.test",
+                    priority = 100000,
+                    uri = "/anything",
+                    plugins = {
+                        ["key-auth"] = {},
+                    },
+                },
+                {
+                    id = "public-wildcard-fallback",
+                    host = "servlet-uri.test",
+                    priority = 100000,
+                    uri = "/*",
+                    plugins = {
+                        ["request-id"] = {
+                            algorithm = "range_id",
+                            header_name = "X-Route-Marker",
+                            include_in_response = false,
+                            range_id = {
+                                char_set = "abcdef",
+                                length = 6,
+                            },
+                        },
+                    },
+                },
+            }
+
+            for _, route in ipairs(routes) do
+                route.upstream = {
+                    nodes = { ["127.0.0.1:1980"] = 1 },
+                    type = "roundrobin",
+                }
+
+                local code, body = t("/apisix/admin/routes/" .. route.id,
+                    ngx.HTTP_PUT, route)
+                if code >= 300 then
+                    ngx.status = code
+                    ngx.say(body)
+                    return
+                end
+            end
+
+            ngx.say("passed")
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 2: exact route requires an API key
+--- request
+GET /anything
+--- more_headers
+Host: servlet-uri.test
+--- error_code: 401
+--- response_body
+{"message":"Missing API key in request"}
+
+
+
+=== TEST 3: encoded question mark remains path data upstream
+--- request
+GET /anything%3Fprobe;jsessionid=x
+--- more_headers
+Host: servlet-uri.test
+--- response_body_like eval
+qr/^path-handler\nroute-marker=[a-f]{6}\nrequest-uri=\/anything%3Fprobe;jsessionid=x\n$/
+--- no_error_log
+[error]
+
+
+
+=== TEST 4: delete routes
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local route_ids = {
+                "protected-exact-path",
+                "public-wildcard-fallback",
+            }
+
+            for _, route_id in ipairs(route_ids) do
+                local code, body = t("/apisix/admin/routes/" .. route_id,
+                    ngx.HTTP_DELETE)
+                if code >= 300 then
+                    ngx.status = code
+                    ngx.say(body)
+                    return
+                end
+            end
+
+            ngx.say("passed")
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed

Reply via email to