This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new 15fc782f2f fix(common): prevent fallback loops with encoded query
parameters (#7449)
15fc782f2f is described below
commit 15fc782f2fbf44f7ac1be2a316de4e539c6a7caa
Author: lymerin <[email protected]>
AuthorDate: Thu Oct 8 14:28:04 2026 +0800
fix(common): prevent fallback loops with encoded query parameters (#7449)
---
.../org/apache/shenyu/common/utils/UriUtils.java | 3 +-
.../apache/shenyu/common/utils/UriUtilsTest.java | 4 ++
.../plugin/base/fallback/FallbackHandlerTest.java | 43 ++++++++++++++++++++++
3 files changed, 49 insertions(+), 1 deletion(-)
diff --git
a/shenyu-common/src/main/java/org/apache/shenyu/common/utils/UriUtils.java
b/shenyu-common/src/main/java/org/apache/shenyu/common/utils/UriUtils.java
index d5a65c69e4..9b347db9bf 100644
--- a/shenyu-common/src/main/java/org/apache/shenyu/common/utils/UriUtils.java
+++ b/shenyu-common/src/main/java/org/apache/shenyu/common/utils/UriUtils.java
@@ -84,7 +84,8 @@ public class UriUtils {
if (Objects.isNull(uri)) {
return StringUtils.EMPTY;
}
- String params = StringUtils.isEmpty(uri.getQuery()) ? "" : "?" +
uri.getQuery();
+ // Preserve query encoding so fallback self-target checks compare raw
URI values.
+ String params = StringUtils.isEmpty(uri.getRawQuery()) ? "" : "?" +
uri.getRawQuery();
return uri.getRawPath() + params;
}
diff --git
a/shenyu-common/src/test/java/org/apache/shenyu/common/utils/UriUtilsTest.java
b/shenyu-common/src/test/java/org/apache/shenyu/common/utils/UriUtilsTest.java
index b4a686a8a7..663468a75f 100644
---
a/shenyu-common/src/test/java/org/apache/shenyu/common/utils/UriUtilsTest.java
+++
b/shenyu-common/src/test/java/org/apache/shenyu/common/utils/UriUtilsTest.java
@@ -85,6 +85,10 @@ public final class UriUtilsTest {
assertNotNull(uri);
ret = UriUtils.getPathWithParams(uri);
assertEquals("/path?key=val", ret);
+
+ uri = UriUtils.createUri("https://example.com/path?key=a%26b");
+ ret = UriUtils.getPathWithParams(uri);
+ assertEquals("/path?key=a%26b", ret);
}
@Test
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/fallback/FallbackHandlerTest.java
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/fallback/FallbackHandlerTest.java
index b810d5bff4..a1159d5713 100644
---
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/fallback/FallbackHandlerTest.java
+++
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/fallback/FallbackHandlerTest.java
@@ -21,10 +21,14 @@ import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
import org.mockito.quality.Strictness;
import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpStatus;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.reactive.DispatcherHandler;
@@ -35,9 +39,13 @@ import reactor.test.StepVerifier;
import java.net.InetSocketAddress;
import java.net.URI;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
@@ -95,6 +103,41 @@ public final class FallbackHandlerTest {
URI.create("fallback:/SHENYU/SHENYU"),
mock(RuntimeException.class))).expectSubscription().verifyComplete());
}
+ @ParameterizedTest
+ @ValueSource(strings = {"/fallback?reason=a%26b",
"fallback:/fallback?reason=a%26b"})
+ void fallbackToSameEncodedQuery(final String target) {
+ URI current = URI.create("http://localhost/fallback?reason=a%26b");
+ MockServerWebExchange fallbackExchange =
MockServerWebExchange.from(MockServerHttpRequest.method(HttpMethod.GET,
current));
+ RuntimeException exception = new RuntimeException(new
IllegalStateException("upstream failed"));
+ RuntimeException thrown = assertThrows(RuntimeException.class,
+ () ->
testFallbackAvoidRedirectLoopHandler.fallback(fallbackExchange,
URI.create(target), exception));
+ assertSame(exception.getCause(), thrown.getCause());
+ }
+
+ @Test
+ void relativeFallbackToDifferentQuery() {
+ URI current = URI.create("http://localhost/fallback?reason=a%26b");
+ URI target = URI.create("/fallback?reason=a&b");
+ MockServerWebExchange fallbackExchange =
MockServerWebExchange.from(MockServerHttpRequest.method(HttpMethod.GET,
current));
+
+
StepVerifier.create(testFallbackAvoidRedirectLoopHandler.fallback(fallbackExchange,
target, new RuntimeException("upstream failed"))).verifyComplete();
+
+ assertEquals(HttpStatus.FOUND,
fallbackExchange.getResponse().getStatusCode());
+ assertEquals(target,
fallbackExchange.getResponse().getHeaders().getLocation());
+ }
+
+ @Test
+ void internalFallbackToDifferentQuery() {
+ URI current = URI.create("http://localhost/fallback?reason=a%26b");
+ MockServerWebExchange fallbackExchange =
MockServerWebExchange.from(MockServerHttpRequest.method(HttpMethod.GET,
current));
+
+
StepVerifier.create(testFallbackAvoidRedirectLoopHandler.fallback(fallbackExchange,
+ URI.create("fallback:/fallback?reason=a&b"), new
RuntimeException("upstream failed"))).verifyComplete();
+
+
verify(SpringBeanUtils.getInstance().getBean(DispatcherHandler.class)).handle(argThat(dispatched
->
+
"http://localhost/fallback?reason=a&b".equals(dispatched.getRequest().getURI().toString())));
+ }
+
static class TestFallbackHandler implements FallbackHandler {
@Override
public Mono<Void> withoutFallback(final ServerWebExchange exchange,
final Throwable throwable) {