diff --git a/apisix/plugins/proxy-rewrite.lua b/apisix/plugins/proxy-rewrite.lua index c1d7ec4f5d54..f53918003f95 100644 --- a/apisix/plugins/proxy-rewrite.lua +++ b/apisix/plugins/proxy-rewrite.lua @@ -78,6 +78,11 @@ local schema = { type = "object", minProperties = 1, }, + use_real_request_uri_unsafe = { + description = "use real_request_uri instead, THIS IS VERY UNSAFE.", + type = "boolean", + default = false, + }, }, minProperties = 1, } @@ -161,7 +166,9 @@ function _M.rewrite(conf, ctx) end local upstream_uri = ctx.var.uri - if conf.uri ~= nil then + if conf.use_real_request_uri_unsafe then + upstream_uri = ctx.var.real_request_uri + elseif conf.uri ~= nil then upstream_uri = core.utils.resolve_var(conf.uri, ctx.var) elseif conf.regex_uri ~= nil then local uri, _, err = re_sub(ctx.var.uri, conf.regex_uri[1], @@ -177,22 +184,24 @@ function _M.rewrite(conf, ctx) end end - local index = str_find(upstream_uri, "?") - if index then - upstream_uri = core.utils.uri_safe_encode(sub_str(upstream_uri, 1, index-1)) .. - sub_str(upstream_uri, index) - else - upstream_uri = core.utils.uri_safe_encode(upstream_uri) - end - - if ctx.var.is_args == "?" then + if not conf.use_real_request_uri_unsafe then + local index = str_find(upstream_uri, "?") if index then - ctx.var.upstream_uri = upstream_uri .. "&" .. (ctx.var.args or "") + upstream_uri = core.utils.uri_safe_encode(sub_str(upstream_uri, 1, index-1)) .. + sub_str(upstream_uri, index) + else + upstream_uri = core.utils.uri_safe_encode(upstream_uri) + end + + if ctx.var.is_args == "?" then + if index then + ctx.var.upstream_uri = upstream_uri .. "&" .. (ctx.var.args or "") + else + ctx.var.upstream_uri = upstream_uri .. "?" .. (ctx.var.args or "") + end else - ctx.var.upstream_uri = upstream_uri .. "?" .. (ctx.var.args or "") + ctx.var.upstream_uri = upstream_uri end - else - ctx.var.upstream_uri = upstream_uri end if conf.headers then diff --git a/docs/en/latest/plugins/proxy-rewrite.md b/docs/en/latest/plugins/proxy-rewrite.md index 1d1a87a174bb..6e2e71d62661 100644 --- a/docs/en/latest/plugins/proxy-rewrite.md +++ b/docs/en/latest/plugins/proxy-rewrite.md @@ -33,14 +33,15 @@ The `proxy-rewrite` Plugin rewrites Upstream proxy information such as `scheme`, ## Attributes -| Name | Type | Required | Default | Valid values | Description | -|-----------|---------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| scheme | string | False | "http" | ["http", "https"] | New upstream protocol scheme. This option is deprecated. Instead, it is recommended to set the `scheme` field in the Upstream. | -| uri | string | False | | | New Upstream forwarding address. Value supports [Nginx variables](https://nginx.org/en/docs/http/ngx_http_core_module.html). For example, `$arg_name`. | -| method | string | False | | ["GET", "POST", "PUT", "HEAD", "DELETE", "OPTIONS","MKCOL", "COPY", "MOVE", "PROPFIND", "PROPFIND","LOCK", "UNLOCK", "PATCH", "TRACE"] | Rewrites the HTTP method. | -| regex_uri | array[string] | False | | | New upstream forwarding address. Regular expressions can be used to match the URL from client. If it matches, the URL template is forwarded to the Upstream otherwise, the URL from the client is forwarded. When both `uri` and `regex_uri` are configured, `uri` is used first. For example, `[" ^/iresty/(.*)/(.*)/(.*)", "/$1-$2-$3"]`. Here, the first element is the regular expression to match and the second element is the URL template forwarded to the Upstream. | -| host | string | False | | | New Upstream host address. | -| headers | object | False | | | New Upstream headers. Headers are overwritten if they are already present otherwise, they are added to the present headers. To remove a header, set the header value to an empty string. The values in the header can contain Nginx variables like `$remote_addr` and `$client_addr`. | +| Name | Type | Required | Default | Valid values | Description | +|-----------------------------|---------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| scheme | string | False | "http" | ["http", "https"] | New upstream protocol scheme. This option is deprecated. Instead, it is recommended to set the `scheme` field in the Upstream. | +| uri | string | False | | | New Upstream forwarding address. Value supports [Nginx variables](https://nginx.org/en/docs/http/ngx_http_core_module.html). For example, `$arg_name`. | +| method | string | False | | ["GET", "POST", "PUT", "HEAD", "DELETE", "OPTIONS","MKCOL", "COPY", "MOVE", "PROPFIND", "PROPFIND","LOCK", "UNLOCK", "PATCH", "TRACE"] | Rewrites the HTTP method. | +| regex_uri | array[string] | False | | | New upstream forwarding address. Regular expressions can be used to match the URL from client. If it matches, the URL template is forwarded to the Upstream otherwise, the URL from the client is forwarded. When both `uri` and `regex_uri` are configured, `uri` is used first. For example, `[" ^/iresty/(.*)/(.*)/(.*)", "/$1-$2-$3"]`. Here, the first element is the regular expression to match and the second element is the URL template forwarded to the Upstream. | +| host | string | False | | | New Upstream host address. | +| headers | object | False | | | New Upstream headers. Headers are overwritten if they are already present otherwise, they are added to the present headers. To remove a header, set the header value to an empty string. The values in the header can contain Nginx variables like `$remote_addr` and `$client_addr`. | +| use_real_request_uri_unsafe | boolean | False | false | | Use real_request_uri (original $request_uri in nginx) to bypass URI normalization. **Enabling this is considered unsafe as it bypasses all URI normalization steps**. | ## Enabling the Plugin diff --git a/t/admin/global-rules.t b/t/admin/global-rules.t index 422652a46c74..b153aeee1913 100644 --- a/t/admin/global-rules.t +++ b/t/admin/global-rules.t @@ -447,7 +447,7 @@ passed } } --- response_body -{"action":"set","node":{"key":"/apisix/global_rules/1","value":{"id":"1","plugins":{"proxy-rewrite":{"uri":"/"}}}}} +{"action":"set","node":{"key":"/apisix/global_rules/1","value":{"id":"1","plugins":{"proxy-rewrite":{"uri":"/","use_real_request_uri_unsafe":false}}}}} --- request GET /t --- no_error_log @@ -485,7 +485,7 @@ GET /t } } --- response_body -{"action":"compareAndSwap","node":{"key":"/apisix/global_rules/1","value":{"id":"1","plugins":{"proxy-rewrite":{"uri":"/"}}}}} +{"action":"compareAndSwap","node":{"key":"/apisix/global_rules/1","value":{"id":"1","plugins":{"proxy-rewrite":{"uri":"/","use_real_request_uri_unsafe":false}}}}} --- request GET /t --- no_error_log @@ -521,7 +521,7 @@ GET /t } } --- response_body -{"action":"get","node":{"key":"/apisix/global_rules/1","value":{"id":"1","plugins":{"proxy-rewrite":{"uri":"/"}}}}} +{"action":"get","node":{"key":"/apisix/global_rules/1","value":{"id":"1","plugins":{"proxy-rewrite":{"uri":"/","use_real_request_uri_unsafe":false}}}}} --- request GET /t --- no_error_log diff --git a/t/admin/global-rules2.t b/t/admin/global-rules2.t index 6ff033b52f43..b84a80123407 100644 --- a/t/admin/global-rules2.t +++ b/t/admin/global-rules2.t @@ -94,7 +94,7 @@ __DATA__ } } --- response_body -{"action":"set","node":{"key":"/apisix/global_rules/1","value":{"id":"1","plugins":{"proxy-rewrite":{"uri":"/"}}}}} +{"action":"set","node":{"key":"/apisix/global_rules/1","value":{"id":"1","plugins":{"proxy-rewrite":{"uri":"/","use_real_request_uri_unsafe":false}}}}} diff --git a/t/lib/server.lua b/t/lib/server.lua index 029f463e534f..a08ad227dcaf 100644 --- a/t/lib/server.lua +++ b/t/lib/server.lua @@ -377,6 +377,10 @@ for i = 1, 100 do _M["print_uri_" .. i] = print_uri end +function _M.print_uri_detailed() + ngx.say("ngx.var.uri: ", ngx.var.uri) + ngx.say("ngx.var.request_uri: ", ngx.var.request_uri) +end function _M.headers() local args = ngx.req.get_uri_args() diff --git a/t/plugin/proxy-rewrite.t b/t/plugin/proxy-rewrite.t index fbca1b621b51..3070e631df66 100644 --- a/t/plugin/proxy-rewrite.t +++ b/t/plugin/proxy-rewrite.t @@ -1097,7 +1097,7 @@ q: apisix) --- request GET /t --- response_body -{"proxy-rewrite":{"headers":{"X-Api":"v2"},"uri":"/uri/plugin_proxy_rewrite"}} +{"proxy-rewrite":{"headers":{"X-Api":"v2"},"uri":"/uri/plugin_proxy_rewrite","use_real_request_uri_unsafe":false}} --- no_error_log [error] diff --git a/t/plugin/proxy-rewrite3.t b/t/plugin/proxy-rewrite3.t index f98de527fa3f..b67ef379e341 100644 --- a/t/plugin/proxy-rewrite3.t +++ b/t/plugin/proxy-rewrite3.t @@ -200,3 +200,91 @@ passed GET /hello --- error_log plugin_proxy_rewrite get method: POST + + + +=== TEST 8: set route(unsafe uri not normalized at request) +--- 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, + [[{ + "methods": ["GET"], + "plugins": { + "proxy-rewrite": { + "use_real_request_uri_unsafe": true + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/print_uri_detailed" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 9: unsafe uri not normalized at request +--- request +GET /print%5Furi%5Fdetailed HTTP/1.1 +--- response_body +ngx.var.uri: /print_uri_detailed +ngx.var.request_uri: /print%5Furi%5Fdetailed + + + +=== TEST 10: set route(safe uri not normalized at request) +--- 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, + [[{ + "methods": ["GET"], + "plugins": { + "proxy-rewrite": { + "use_real_request_uri_unsafe": true + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/print_uri_detailed" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 11: safe uri not normalized at request +--- request +GET /print_uri_detailed HTTP/1.1 +--- response_body +ngx.var.uri: /print_uri_detailed +ngx.var.request_uri: /print_uri_detailed