From c629a9895bcff3874bc96f1b48a7f65430d743a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Travassos?= Date: Wed, 28 Oct 2026 11:21:28 +0000 Subject: [PATCH 1/3] fix(routing): keep encoded "?" in path for backends with a path With a Backend that has a path, 3scale generates a routing rule whose replace_path renders the decoded request path ({{uri | remove_first: ...}}). A "%3F" sent by the client is a "?" in ngx.var.uri, so Upstream:append_path split the rendered value there and dropped the rest of the path segment: /REST/svc/files/a%3Fb reached the upstream as /REST/svc/files/a. When the request path itself contains a "?", that "?" is data, so append_path no longer splits on it. The upstream then receives "%3F", since nginx encodes "?" again when the path is set with ngx.req.set_uri. Requests without an encoded "?" behave as before. Fixes #1612 --- CHANGELOG.md | 1 + gateway/src/apicast/upstream.lua | 13 +++++++- spec/upstream_spec.lua | 18 +++++++++++ t/apicast-policy-routing.t | 51 ++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a660ed1f5..f6fd769cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Set request timeout for OIDC query. [PR #1601](https://github.com/3scale/APIcast/pull/1601) [THREESCALE-8006](https://redhat.atlassian.net/browse/THREESCALE-8006) - Set upstream metrics when sending request via proxy. [PR #1598](https://github.com/3scale/APIcast/pull/1598) [THREESCALE-10571](https://redhat.atlassian.net/browse/THREESCALE-15071) - Certificate is never verified in API request for https backend via proxy [PR #1573](https://github.com/3scale/APIcast/pull/1568) [THREESCALE-11944](https://redhat.atlassian.net/browse/THREESCALE-11944) +- Routing Policy: keep an encoded "?" (`%3F`) in the path when the backend has a path [Issue #1612](https://github.com/3scale/APIcast/issues/1612) ### Added - Update APIcast schema manifest [PR #1550](https://github.com/3scale/APIcast/pull/1550) diff --git a/gateway/src/apicast/upstream.lua b/gateway/src/apicast/upstream.lua index b76c196b4..63f791baf 100644 --- a/gateway/src/apicast/upstream.lua +++ b/gateway/src/apicast/upstream.lua @@ -8,6 +8,7 @@ local setmetatable = setmetatable local str_format = string.format +local str_find = string.find local resty_resolver = require('resty.resolver') local resty_url = require('resty.url') @@ -121,7 +122,17 @@ function _M:set_path(path) end function _M:append_path(path) - local tmp_path, tmp_query = url_helper.split_path(path) + local tmp_path, tmp_query + + -- ngx.var.uri is decoded, so a "?" in it was sent as "%3F" and is part of a + -- path segment. A path rendered from it must not be split there. + local uri = ngx.var.uri + if uri and str_find(uri, '?', 1, true) then + tmp_path = path + else + tmp_path, tmp_query = url_helper.split_path(path) + end + if not self.uri.path then self.uri.path = "/" end diff --git a/spec/upstream_spec.lua b/spec/upstream_spec.lua index b24b8f4c3..51374af95 100644 --- a/spec/upstream_spec.lua +++ b/spec/upstream_spec.lua @@ -53,6 +53,8 @@ describe('Upstream', function() end) describe(':append_path', function() + before_each(function() ngx.var = { } end) + it('return valid path when is not set', function() local up = Upstream.new('http://host:8090') up:append_path("/test") @@ -71,6 +73,22 @@ describe('Upstream', function() assert.same(up.uri.path, "/test") end) + it('keeps an encoded "?" of the request path in the path', function() + ngx.var = { uri = '/REST/svc/files/a?b' } + local up = Upstream.new('http://host:8090/REST/svc') + up:append_path("/files/a?b") + assert.same(up.uri.path, "/REST/svc/files/a?b") + assert.is_nil(up.uri.query) + end) + + it('does not use the query of the rendered path', function() + ngx.var = { uri = '/REST/svc/files/a' } + local up = Upstream.new('http://host:8090/REST/svc') + up:append_path("/files/a?b") + assert.same(up.uri.path, "/REST/svc/files/a") + assert.is_nil(up.uri.query) + end) + end) local function stub_ngx_request() diff --git a/t/apicast-policy-routing.t b/t/apicast-policy-routing.t index 86745fb21..6d011747e 100644 --- a/t/apicast-policy-routing.t +++ b/t/apicast-policy-routing.t @@ -2586,3 +2586,54 @@ content_by_lua_block { } --- no_error_log [error] + + +=== TEST 37: Encoded "?" in the path is kept when the backend has a path +The rule is the one 3scale generates for a Backend used with a path. A "%3F" +in the request path must reach the upstream as "%3F" and not cut the path. +--- configuration +{ + "services": [ + { + "id": 42, + "proxy": { + "policy_chain": [ + { + "name": "apicast.policy.routing", + "configuration": { + "rules": [ + { + "url": "http://test:$TEST_NGINX_SERVER_PORT/REST/svc", + "replace_path": "{{uri | remove_first: '/REST/svc'}}", + "condition": { + "operations": [ + { + "match": "path", + "op": "matches", + "value": "^(/REST/svc/.*|/REST/svc/?)" + } + ] + } + } + ] + } + } + ] + } + } + ] +} +--- upstream + location ~ / { + content_by_lua_block { + ngx.say(ngx.var.request_uri) + } + } +--- request eval +["GET /REST/svc/files/a%3Fb", "GET /REST/svc/files/a%3Fb?x=1", "GET /REST/svc/files/a?x=1"] +--- response_body eval +["/REST/svc/files/a%3Fb\n", "/REST/svc/files/a%3Fb?x=1\n", "/REST/svc/files/a?x=1\n"] +--- error_code eval +[200, 200, 200] +--- no_error_log +[error] From 8c0a72626205511d32d2a88b69d38e3abc51db68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Travassos?= Date: Wed, 28 Oct 2026 11:28:30 +0000 Subject: [PATCH 2/3] Add the PR link to the CHANGELOG entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6fd769cf..795bc8a4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Set request timeout for OIDC query. [PR #1601](https://github.com/3scale/APIcast/pull/1601) [THREESCALE-8006](https://redhat.atlassian.net/browse/THREESCALE-8006) - Set upstream metrics when sending request via proxy. [PR #1598](https://github.com/3scale/APIcast/pull/1598) [THREESCALE-10571](https://redhat.atlassian.net/browse/THREESCALE-15071) - Certificate is never verified in API request for https backend via proxy [PR #1573](https://github.com/3scale/APIcast/pull/1568) [THREESCALE-11944](https://redhat.atlassian.net/browse/THREESCALE-11944) -- Routing Policy: keep an encoded "?" (`%3F`) in the path when the backend has a path [Issue #1612](https://github.com/3scale/APIcast/issues/1612) +- Routing Policy: keep an encoded "?" (`%3F`) in the path when the backend has a path [PR #1613](https://github.com/3scale/APIcast/pull/1613) [Issue #1612](https://github.com/3scale/APIcast/issues/1612) ### Added - Update APIcast schema manifest [PR #1550](https://github.com/3scale/APIcast/pull/1550) From 1d0b247f8418bf178f6c41907f21d8732d64f139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Travassos?= Date: Wed, 28 Oct 2026 11:50:46 +0000 Subject: [PATCH 3/3] Retrigger CI