From b84e5c80a4dac29860283aa0cbbfa7cd840eab51 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Sat, 17 Oct 2026 19:33:13 -0400 Subject: [PATCH 1/9] CXF-7986: WebClient.path method overwrites base URL when given absolute URL as input --- .../apache/cxf/jaxrs/impl/UriBuilderImpl.java | 31 +---------------- .../jaxrs/provider/JAXBElementProvider.java | 2 +- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 34 ++++++++++--------- .../cxf/jaxrs/client/WebClientTest.java | 24 +++++++++++++ 4 files changed, 44 insertions(+), 47 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java index 82275162108..caa84b3b51f 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java @@ -548,36 +548,7 @@ private UriBuilder doPath(String path, boolean checkSegments) { if (path == null) { throw new IllegalArgumentException("path is null"); } - if (isAbsoluteUriPath(path)) { - try { - URI uri = URI.create(path); - this.originalPathEmpty = StringUtils.isEmpty(uri.getPath()); - uri(uri); - } catch (IllegalArgumentException ex) { - if (!URITemplate.createExactTemplate(path).getVariables().isEmpty()) { - return uriAsTemplate(path); - } - String pathEncoded = HttpUtils.pathEncode(path); - // Bad hack to bypass the TCK usage of bogus URI with empty paths containing matrix parameters, - // which even URI class chokes upon; cheaper to do the following than try to challenge, - // given that URI RFC mentions the possibility of empty paths, though no word on the possibility of - // such empty paths having matrix parameters... - int schemeIndex = pathEncoded.indexOf("//"); - if (schemeIndex != -1) { - int pathComponentStart = pathEncoded.indexOf('/', schemeIndex + 2); - if (pathComponentStart == -1) { - this.originalPathEmpty = true; - pathComponentStart = pathEncoded.indexOf(';'); - if (pathComponentStart != -1) { - pathEncoded = pathEncoded.substring(0, pathComponentStart) - + "/" + pathEncoded.substring(pathComponentStart); - } - } - } - setUriParts(URI.create(pathEncoded)); - } - return this; - } + if (paths.isEmpty()) { leadingSlash = path.startsWith("/"); diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java index b43152491a9..bcd085bb357 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java @@ -514,7 +514,7 @@ protected String resolveXMLResourceURI(String path) { String httpBasePath = (String)mc.get("http.base.path"); final UriBuilder builder; if (httpBasePath != null) { - builder = UriBuilder.fromPath(httpBasePath); + builder = UriBuilder.fromUri(httpBasePath); } else { builder = mc.getUriInfo().getBaseUriBuilder(); } diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index a5778ee9293..6670437df87 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -512,9 +512,10 @@ public void testMatrixWithSlash() throws Exception { @Test public void replaceMatrixParamWithEmptyPathTest() throws Exception { String name = "name"; - String expected = "http://localhost:8080;name=x;name=y;name=y%20x;name=x%25y;name=%20"; + String expected = "http://localhost:8080/;name=x;name=y;name=y%20x;name=x%25y;name=%20"; - URI uri = UriBuilder.fromPath("http://localhost:8080;name=x=;name=y?;name=x y;name=&") + URI uri = UriBuilder.fromUri("http://localhost:8080") + .path(";name=x=;name=y?;name=x y;name=&") .replaceMatrixParam(name, "x", "y", "y x", "x%y", "%20") .build(); assertEquals(expected, uri.toString()); @@ -522,20 +523,21 @@ public void replaceMatrixParamWithEmptyPathTest() throws Exception { @Test public void replaceMatrixWithEmptyPathTest() throws Exception { - String expected = "http://localhost:8080;name=x;name=y;name=y%20x;name=x%25y;name=%20"; + String expected = "http://localhost:8080/;name=x;name=y;name=y%20x;name=x%25y;name=%20"; String value = "name=x;name=y;name=y x;name=x%y;name= "; - URI uri = UriBuilder.fromPath("http://localhost:8080;name=x=;name=y?;name=x y;name=&") - .replaceMatrix(value).build(); + URI uri = UriBuilder.fromUri("http://localhost:8080") + .path(";name=x=;name=y?;name=x y;name=&") + .replaceMatrix(value).build(); assertEquals(expected, uri.toString()); } @Test public void testAddMatrixToEmptyPath() throws Exception { String name = "name"; - String expected = "http://localhost:8080;name=x;name=y"; + String expected = "http://localhost:8080/;name=x;name=y"; - URI uri = UriBuilder.fromPath("http://localhost:8080").matrixParam(name, "x", "y") + URI uri = UriBuilder.fromUri("http://localhost:8080").matrixParam(name, "x", "y") .build(); assertEquals(expected, uri.toString()); } @@ -885,7 +887,7 @@ public void testReplaceQueryNull() throws Exception { public void testReplaceQueryWithNull2() { String expected = "http://localhost:8080"; - URI uri = UriBuilder.fromPath("http://localhost:8080") + URI uri = UriBuilder.fromUri("http://localhost:8080") .queryParam("name", "x=", "y?", "x y", "&").replaceQuery(null).build(); assertEquals(expected, uri.toString()); } @@ -915,7 +917,7 @@ public void testReplaceQuery2() throws Exception { public void testReplaceQuery3() { String expected = "http://localhost:8080?name1=xyz"; - URI uri = UriBuilder.fromPath("http://localhost:8080") + URI uri = UriBuilder.fromUri("http://localhost:8080") .queryParam("name", "x=", "y?", "x y", "&").replaceQuery("name1=xyz").build(); assertEquals(expected, uri.toString()); } @@ -924,7 +926,7 @@ public void testReplaceQuery3() { public void testFromPathUriOnly() { String expected = "http://localhost:8080"; - URI uri = UriBuilder.fromPath("http://localhost:8080").build(); + URI uri = UriBuilder.fromUri("http://localhost:8080").build(); assertEquals(expected, uri.toString()); } @@ -1346,7 +1348,7 @@ public void testInvalidHost() { @Test public void testFromEncodedDuplicateVar2() { String expected = "http://localhost:8080/xy/%20/%25/xy"; - URI uri = UriBuilder.fromPath("http://localhost:8080") + URI uri = UriBuilder.fromUri("http://localhost:8080") .path("/{x}/{y}/{z}/{x}") .buildFromEncoded("xy", " ", "%"); assertEquals(expected, uri.toString()); @@ -1355,7 +1357,7 @@ public void testFromEncodedDuplicateVar2() { @Test public void testFromEncodedDuplicateVar3() { String expected = "http://localhost:8080/1/2/3/1"; - URI uri = UriBuilder.fromPath("http://localhost:8080") + URI uri = UriBuilder.fromUri("http://localhost:8080") .path("/{a}/{b}/{c}/{a}") .buildFromEncoded("1", "2", "3"); @@ -1426,7 +1428,7 @@ public void testMissingMapValue() { public void testFromEncodedDuplicateVar() { String expected = "http://localhost:8080/a/%25/=/%25G0/%25/="; - URI uri = UriBuilder.fromPath("http://localhost:8080") + URI uri = UriBuilder.fromUri("http://localhost:8080") .path("/{v}/{w}/{x}/{y}/{z}/{x}") .buildFromEncoded("a", "%25", "=", "%G0", "%", "23"); assertEquals(expected, uri.toString()); @@ -1520,7 +1522,7 @@ public void testMultipleUriSchemes() throws Exception { public void testEncodingQueryParamFromBuild() throws Exception { String expectedValue = "http://localhost:8080?name=x%3D&name=y?&name=x+y&name=%26"; - URI uri = UriBuilder.fromPath("http://localhost:8080").queryParam("name", + URI uri = UriBuilder.fromUri("http://localhost:8080").queryParam("name", "x=", "y?", "x y", "&").build(); assertEquals(expectedValue, uri.toString()); } @@ -1529,7 +1531,7 @@ public void testEncodingQueryParamFromBuild() throws Exception { public void testReplaceParamAndEncodeQueryParamFromBuild() throws Exception { String expectedValue = "http://localhost:8080?name=x&name=y&name=y+x&name=x%25y&name=%20"; - URI uri = UriBuilder.fromPath("http://localhost:8080").queryParam("name", + URI uri = UriBuilder.fromUri("http://localhost:8080").queryParam("name", "x=", "y?", "x y", "&").replaceQueryParam("name", "x", "y", "y x", "x%y", "%20").build(); assertEquals(expectedValue, uri.toString()); @@ -1539,7 +1541,7 @@ public void testReplaceParamAndEncodeQueryParamFromBuild() throws Exception { public void testReplaceStringAndEncodeQueryParamFromBuild() { String expected = "http://localhost:8080?name1=x&name2=%20&name3=x+y&name4=23&name5=x%20y"; - URI uri = UriBuilder.fromPath("http://localhost:8080") + URI uri = UriBuilder.fromUri("http://localhost:8080") .queryParam("name", "x=", "y?", "x y", "&") .replaceQuery("name1=x&name2=%20&name3=x+y&name4=23&name5=x y").build(); assertEquals(expected, uri.toString()); diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/WebClientTest.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/WebClientTest.java index e6566de1f51..fc227b48b00 100644 --- a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/WebClientTest.java +++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/WebClientTest.java @@ -48,6 +48,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.collection.IsIterableContainingInAnyOrder.containsInAnyOrder; import static org.junit.Assert.assertEquals; @@ -535,6 +536,29 @@ public void testWebClientFromShare() { assertThat(configuration.getBus().getState(), equalTo(BusState.SHUTDOWN)); } + @Test + public void pathAllowsAddingURLAsPathElement() { + final WebClient webClient = WebClient.create("http://www.foo.com"); + webClient.path("http://www.bar.com"); + assertThat(webClient.getCurrentURI().getHost(), equalTo("www.foo.com")); + } + + @Test + public void preservesPreviouslyAddedPathParameters() { + final WebClient webClient = WebClient.create("http://www.foo.com"); + webClient.path("foo"); + webClient.path("bar"); + webClient.path("http://www.bar.com"); + assertThat(webClient.getCurrentURI().toString(), startsWith("http://www.foo.com/foo/bar")); + } + + @Test + public void outputOfUriUtilsEncodePathSegmentIsSafe() { + final WebClient webClient = WebClient.create("http://www.foo.com"); + webClient.path("http:%2F%2Fwww.bar.com"); + assertThat(webClient.getCurrentURI().getHost(), equalTo("www.foo.com")); + } + private static final class ParamConverterProviderImpl implements ParamConverterProvider { @SuppressWarnings("unchecked") From 2c72be873baf6708e67f980e0ec15da6036d7ac3 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Mon, 19 Oct 2026 07:39:34 -0400 Subject: [PATCH 2/9] Address code review comments --- .../apache/cxf/jaxrs/impl/UriBuilderImpl.java | 33 +++++++++++++++++- .../jaxrs/provider/JAXBElementProvider.java | 2 +- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 34 +++++++++---------- 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java index caa84b3b51f..8f58616558b 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java @@ -548,7 +548,38 @@ private UriBuilder doPath(String path, boolean checkSegments) { if (path == null) { throw new IllegalArgumentException("path is null"); } - + // Only set uri when path is absolute and the current URI's is not set + // (scheme and host are all null). + if (isAbsoluteUriPath(path) && scheme == null && host == null) { + try { + URI uri = URI.create(path); + this.originalPathEmpty = StringUtils.isEmpty(uri.getPath()); + uri(uri); + } catch (IllegalArgumentException ex) { + if (!URITemplate.createExactTemplate(path).getVariables().isEmpty()) { + return uriAsTemplate(path); + } + String pathEncoded = HttpUtils.pathEncode(path); + // Bad hack to bypass the TCK usage of bogus URI with empty paths containing matrix parameters, + // which even URI class chokes upon; cheaper to do the following than try to challenge, + // given that URI RFC mentions the possibility of empty paths, though no word on the possibility of + // such empty paths having matrix parameters... + int schemeIndex = pathEncoded.indexOf("//"); + if (schemeIndex != -1) { + int pathComponentStart = pathEncoded.indexOf('/', schemeIndex + 2); + if (pathComponentStart == -1) { + this.originalPathEmpty = true; + pathComponentStart = pathEncoded.indexOf(';'); + if (pathComponentStart != -1) { + pathEncoded = pathEncoded.substring(0, pathComponentStart) + + "/" + pathEncoded.substring(pathComponentStart); + } + } + } + setUriParts(URI.create(pathEncoded)); + } + return this; + } if (paths.isEmpty()) { leadingSlash = path.startsWith("/"); diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java index bcd085bb357..b43152491a9 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/JAXBElementProvider.java @@ -514,7 +514,7 @@ protected String resolveXMLResourceURI(String path) { String httpBasePath = (String)mc.get("http.base.path"); final UriBuilder builder; if (httpBasePath != null) { - builder = UriBuilder.fromUri(httpBasePath); + builder = UriBuilder.fromPath(httpBasePath); } else { builder = mc.getUriInfo().getBaseUriBuilder(); } diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index 6670437df87..a5778ee9293 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -512,10 +512,9 @@ public void testMatrixWithSlash() throws Exception { @Test public void replaceMatrixParamWithEmptyPathTest() throws Exception { String name = "name"; - String expected = "http://localhost:8080/;name=x;name=y;name=y%20x;name=x%25y;name=%20"; + String expected = "http://localhost:8080;name=x;name=y;name=y%20x;name=x%25y;name=%20"; - URI uri = UriBuilder.fromUri("http://localhost:8080") - .path(";name=x=;name=y?;name=x y;name=&") + URI uri = UriBuilder.fromPath("http://localhost:8080;name=x=;name=y?;name=x y;name=&") .replaceMatrixParam(name, "x", "y", "y x", "x%y", "%20") .build(); assertEquals(expected, uri.toString()); @@ -523,21 +522,20 @@ public void replaceMatrixParamWithEmptyPathTest() throws Exception { @Test public void replaceMatrixWithEmptyPathTest() throws Exception { - String expected = "http://localhost:8080/;name=x;name=y;name=y%20x;name=x%25y;name=%20"; + String expected = "http://localhost:8080;name=x;name=y;name=y%20x;name=x%25y;name=%20"; String value = "name=x;name=y;name=y x;name=x%y;name= "; - URI uri = UriBuilder.fromUri("http://localhost:8080") - .path(";name=x=;name=y?;name=x y;name=&") - .replaceMatrix(value).build(); + URI uri = UriBuilder.fromPath("http://localhost:8080;name=x=;name=y?;name=x y;name=&") + .replaceMatrix(value).build(); assertEquals(expected, uri.toString()); } @Test public void testAddMatrixToEmptyPath() throws Exception { String name = "name"; - String expected = "http://localhost:8080/;name=x;name=y"; + String expected = "http://localhost:8080;name=x;name=y"; - URI uri = UriBuilder.fromUri("http://localhost:8080").matrixParam(name, "x", "y") + URI uri = UriBuilder.fromPath("http://localhost:8080").matrixParam(name, "x", "y") .build(); assertEquals(expected, uri.toString()); } @@ -887,7 +885,7 @@ public void testReplaceQueryNull() throws Exception { public void testReplaceQueryWithNull2() { String expected = "http://localhost:8080"; - URI uri = UriBuilder.fromUri("http://localhost:8080") + URI uri = UriBuilder.fromPath("http://localhost:8080") .queryParam("name", "x=", "y?", "x y", "&").replaceQuery(null).build(); assertEquals(expected, uri.toString()); } @@ -917,7 +915,7 @@ public void testReplaceQuery2() throws Exception { public void testReplaceQuery3() { String expected = "http://localhost:8080?name1=xyz"; - URI uri = UriBuilder.fromUri("http://localhost:8080") + URI uri = UriBuilder.fromPath("http://localhost:8080") .queryParam("name", "x=", "y?", "x y", "&").replaceQuery("name1=xyz").build(); assertEquals(expected, uri.toString()); } @@ -926,7 +924,7 @@ public void testReplaceQuery3() { public void testFromPathUriOnly() { String expected = "http://localhost:8080"; - URI uri = UriBuilder.fromUri("http://localhost:8080").build(); + URI uri = UriBuilder.fromPath("http://localhost:8080").build(); assertEquals(expected, uri.toString()); } @@ -1348,7 +1346,7 @@ public void testInvalidHost() { @Test public void testFromEncodedDuplicateVar2() { String expected = "http://localhost:8080/xy/%20/%25/xy"; - URI uri = UriBuilder.fromUri("http://localhost:8080") + URI uri = UriBuilder.fromPath("http://localhost:8080") .path("/{x}/{y}/{z}/{x}") .buildFromEncoded("xy", " ", "%"); assertEquals(expected, uri.toString()); @@ -1357,7 +1355,7 @@ public void testFromEncodedDuplicateVar2() { @Test public void testFromEncodedDuplicateVar3() { String expected = "http://localhost:8080/1/2/3/1"; - URI uri = UriBuilder.fromUri("http://localhost:8080") + URI uri = UriBuilder.fromPath("http://localhost:8080") .path("/{a}/{b}/{c}/{a}") .buildFromEncoded("1", "2", "3"); @@ -1428,7 +1426,7 @@ public void testMissingMapValue() { public void testFromEncodedDuplicateVar() { String expected = "http://localhost:8080/a/%25/=/%25G0/%25/="; - URI uri = UriBuilder.fromUri("http://localhost:8080") + URI uri = UriBuilder.fromPath("http://localhost:8080") .path("/{v}/{w}/{x}/{y}/{z}/{x}") .buildFromEncoded("a", "%25", "=", "%G0", "%", "23"); assertEquals(expected, uri.toString()); @@ -1522,7 +1520,7 @@ public void testMultipleUriSchemes() throws Exception { public void testEncodingQueryParamFromBuild() throws Exception { String expectedValue = "http://localhost:8080?name=x%3D&name=y?&name=x+y&name=%26"; - URI uri = UriBuilder.fromUri("http://localhost:8080").queryParam("name", + URI uri = UriBuilder.fromPath("http://localhost:8080").queryParam("name", "x=", "y?", "x y", "&").build(); assertEquals(expectedValue, uri.toString()); } @@ -1531,7 +1529,7 @@ public void testEncodingQueryParamFromBuild() throws Exception { public void testReplaceParamAndEncodeQueryParamFromBuild() throws Exception { String expectedValue = "http://localhost:8080?name=x&name=y&name=y+x&name=x%25y&name=%20"; - URI uri = UriBuilder.fromUri("http://localhost:8080").queryParam("name", + URI uri = UriBuilder.fromPath("http://localhost:8080").queryParam("name", "x=", "y?", "x y", "&").replaceQueryParam("name", "x", "y", "y x", "x%y", "%20").build(); assertEquals(expectedValue, uri.toString()); @@ -1541,7 +1539,7 @@ public void testReplaceParamAndEncodeQueryParamFromBuild() throws Exception { public void testReplaceStringAndEncodeQueryParamFromBuild() { String expected = "http://localhost:8080?name1=x&name2=%20&name3=x+y&name4=23&name5=x%20y"; - URI uri = UriBuilder.fromUri("http://localhost:8080") + URI uri = UriBuilder.fromPath("http://localhost:8080") .queryParam("name", "x=", "y?", "x y", "&") .replaceQuery("name1=x&name2=%20&name3=x+y&name4=23&name5=x y").build(); assertEquals(expected, uri.toString()); From 3924ba69af92b5db519e92171298691a090c2f84 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Mon, 19 Oct 2026 07:50:32 -0400 Subject: [PATCH 3/9] Address code review comments --- .../apache/cxf/jaxrs/impl/UriBuilderImpl.java | 4 ++- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java index 8f58616558b..c750c51037b 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java @@ -780,7 +780,9 @@ public UriBuilder replaceMatrixParam(String name, Object... values) throws Illeg public UriBuilder replacePath(String path) { if (path == null) { clearPathAndMatrix(); - } else if (isAbsoluteUriPath(path)) { + } else if (isAbsoluteUriPath(path) && scheme == null && host == null) { + // Only set uri when path is absolute and the current URI's is not set + // (scheme and host are all null). clearPathAndMatrix(); uri(URI.create(path)); } else { diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index a5778ee9293..4a7fed766a3 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -40,6 +40,8 @@ import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import static org.junit.Assert.fail; @@ -134,7 +136,41 @@ public void testBuildWithNonEncodedSubstitutionValue8() { assertEquals("/?a=%25&a2=%7B%7D", uri.toString()); } + @Test + public void testReplaceFullPath() { + UriBuilder ub = UriBuilder.fromPath(""); + URI uri = ub.build(); + assertEquals("", uri.toString()); + uri = ub.replacePath("http://foo.com").build(); + assertEquals("http://foo.com", uri.toString()); + } + + @Test + public void testReplaceFullPathWhenUriIsSet() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.replacePath("http://bar.com/").build(); + assertThat(uri.toString(), startsWith("http://foo.com")); + } + @Test + public void testBuildAndSetFullPath() { + UriBuilder ub = UriBuilder.fromPath(""); + URI uri = ub.build(); + assertEquals("", uri.toString()); + uri = ub.path("http://foo.com").build(); + assertEquals("http://foo.com", uri.toString()); + } + + @Test + public void testBuildAndSetFullPathWhenUriIsSet() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.path("http://bar.com/").build(); + assertThat(uri.toString(), startsWith("http://foo.com")); + } @Test public void testResolveTemplate() { From f1254f2fccd7574b2a5524b8211c9b62d9538be4 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Mon, 19 Oct 2026 12:30:06 -0400 Subject: [PATCH 4/9] Address code review comments --- .../apache/cxf/jaxrs/impl/UriBuilderImpl.java | 15 +++--- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 47 +++++++++++++++++++ 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java index c750c51037b..72bb27a0b87 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java @@ -103,7 +103,7 @@ public UriBuilderImpl(URI uri) throws IllegalArgumentException { @Override public URI build(Object... values) throws IllegalArgumentException, UriBuilderException { - return doBuild(false, true, values); + return build(values, true); } private static Map getResolvedTemplates(Map rtemplates) { @@ -185,13 +185,16 @@ private UriParts doBuildUriParts(boolean fromEncoded, boolean encodePathSlash, private URI buildURI(boolean fromEncoded, String thePath, String theQuery, String theFragment) throws URISyntaxException { + + // Always include the leading slash in order to prevent host overrides + if ((scheme != null || host != null || userInfo != null) + && thePath.length() != 0 && !(thePath.startsWith("/") || thePath.startsWith(";"))) { + thePath = "/" + thePath; + } + if (fromEncoded) { return buildURIFromEncoded(thePath, theQuery, theFragment); } else if (!isSchemeOpaque()) { - if ((scheme != null || host != null || userInfo != null) - && thePath.length() != 0 && !(thePath.startsWith("/") || thePath.startsWith(";"))) { - thePath = "/" + thePath; - } try { return buildURIFromEncoded(thePath, theQuery, theFragment); } catch (Exception ex) { @@ -262,7 +265,7 @@ public URI buildFromEncoded(Object... values) throws IllegalArgumentException, U @Override public URI buildFromMap(Map map) throws IllegalArgumentException, UriBuilderException { - return doBuildFromMap(map, false, true); + return buildFromMap(map, true); } private URI doBuildFromMap(Map map, boolean fromEncoded, diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index 4a7fed766a3..a8a9be30edd 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -41,6 +41,7 @@ import org.junit.Test; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.startsWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; @@ -143,6 +144,9 @@ public void testReplaceFullPath() { assertEquals("", uri.toString()); uri = ub.replacePath("http://foo.com").build(); assertEquals("http://foo.com", uri.toString()); + + uri = ub.buildFromEncoded(); + assertEquals("http://foo.com", uri.toString()); } @Test @@ -152,6 +156,11 @@ public void testReplaceFullPathWhenUriIsSet() { assertEquals("http://foo.com", uri.toString()); uri = ub.replacePath("http://bar.com/").build(); assertThat(uri.toString(), startsWith("http://foo.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), startsWith("http://foo.com")); + assertThat(uri.getHost(), equalTo("foo.com")); } @Test @@ -161,6 +170,10 @@ public void testBuildAndSetFullPath() { assertEquals("", uri.toString()); uri = ub.path("http://foo.com").build(); assertEquals("http://foo.com", uri.toString()); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), startsWith("http://foo.com")); + assertThat(uri.getHost(), equalTo("foo.com")); } @Test @@ -170,6 +183,40 @@ public void testBuildAndSetFullPathWhenUriIsSet() { assertEquals("http://foo.com", uri.toString()); uri = ub.path("http://bar.com/").build(); assertThat(uri.toString(), startsWith("http://foo.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), startsWith("http://foo.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + } + + @Test + public void testPathDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + + uri = ub.path(".org").build(); + assertThat(uri.toString(), equalTo("http://foo.com/.org")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://foo.com/.org")); + assertThat(uri.getHost(), equalTo("foo.com")); + } + + @Test + public void testReplacePathDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.replacePath(".org").build(); + assertThat(uri.toString(), equalTo("http://foo.com/.org")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://foo.com/.org")); + assertThat(uri.getHost(), equalTo("foo.com")); } @Test From 2689864a53e03ae577a67fc766e2414629c25958 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Mon, 19 Oct 2026 21:55:36 -0400 Subject: [PATCH 5/9] Add tests for UriBuilder::toTemplate() --- .../apache/cxf/jaxrs/impl/UriBuilderImpl.java | 2 +- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 36 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java index 72bb27a0b87..5865357df4f 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java @@ -238,7 +238,7 @@ private String buildUriString(String thePath, String theQuery, String theFragmen b.append(':').append(port); } if (thePath != null && thePath.length() > 0) { - b.append(thePath.startsWith("/") || b.length() == 0 || originalPathEmpty + b.append(thePath.startsWith("/") || b.length() == 0 || (originalPathEmpty && thePath.startsWith(";")) ? thePath : '/' + thePath); } if (theQuery != null && theQuery.length() != 0) { diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index a8a9be30edd..c4e3e2a05dc 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -219,6 +219,42 @@ public void testReplacePathDoesNotChangeHost() { assertThat(uri.getHost(), equalTo("foo.com")); } + @Test + public void testTemplateUriDoesNotChangeHost() { + final String template = UriBuilder + .fromUri("http://foo.com") + .path(".org") + .toTemplate(); + assertEquals("http://foo.com/.org", template); + } + + @Test + public void testTemplatePathDoesNotChangeHost() { + final String template = UriBuilder + .fromPath("http://foo.com") + .path(".org") + .toTemplate(); + assertEquals("http://foo.com/.org", template); + } + + @Test + public void testTemplateWithPathDoesNotChangeHost() { + final String template = UriBuilder + .fromPath("http://foo.com/aaa") + .path(".org") + .toTemplate(); + assertEquals("http://foo.com/aaa/.org", template); + } + + @Test + public void testTemplatePathDoesNotReplaceUri() { + final String template = UriBuilder + .fromPath("http://foo.com/") + .path("http://bar.com") + .toTemplate(); + assertThat(template, startsWith("http://foo.com/")); + } + @Test public void testResolveTemplate() { URI uri; From f8957ace258113c5ef2f5ba5b62197ad7c473cd0 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Tue, 20 Oct 2026 07:58:43 -0400 Subject: [PATCH 6/9] Address review comments --- .../cxf/jaxrs/client/WebClientTest.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/WebClientTest.java b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/WebClientTest.java index fc227b48b00..58db96c53f1 100644 --- a/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/WebClientTest.java +++ b/rt/rs/client/src/test/java/org/apache/cxf/jaxrs/client/WebClientTest.java @@ -538,24 +538,25 @@ public void testWebClientFromShare() { @Test public void pathAllowsAddingURLAsPathElement() { - final WebClient webClient = WebClient.create("http://www.foo.com"); - webClient.path("http://www.bar.com"); + final WebClient webClient = WebClient.create("http://www.foo.com").path("http://www.bar.com"); + assertThat(webClient.getCurrentURI().toString(), startsWith("http://www.foo.com/")); assertThat(webClient.getCurrentURI().getHost(), equalTo("www.foo.com")); } @Test public void preservesPreviouslyAddedPathParameters() { - final WebClient webClient = WebClient.create("http://www.foo.com"); - webClient.path("foo"); - webClient.path("bar"); - webClient.path("http://www.bar.com"); - assertThat(webClient.getCurrentURI().toString(), startsWith("http://www.foo.com/foo/bar")); + final WebClient webClient = WebClient.create("http://www.foo.com") + .path("foo") + .path("bar") + .path("http://www.bar.com"); + assertThat(webClient.getCurrentURI().toString(), startsWith("http://www.foo.com/foo/bar/")); + assertThat(webClient.getCurrentURI().getHost(), equalTo("www.foo.com")); } @Test public void outputOfUriUtilsEncodePathSegmentIsSafe() { - final WebClient webClient = WebClient.create("http://www.foo.com"); - webClient.path("http:%2F%2Fwww.bar.com"); + final WebClient webClient = WebClient.create("http://www.foo.com").path("http:%2F%2Fwww.bar.com"); + assertThat(webClient.getCurrentURI().toString(), startsWith("http://www.foo.com/")); assertThat(webClient.getCurrentURI().getHost(), equalTo("www.foo.com")); } From 1b35cad20dbc7e4f29d894d47347f313cc3b4640 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Tue, 20 Oct 2026 21:57:26 -0400 Subject: [PATCH 7/9] Address code review comments --- .../apache/cxf/jaxrs/impl/UriBuilderImpl.java | 28 +++- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 134 ++++++++++++++++++ 2 files changed, 159 insertions(+), 3 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java index 5865357df4f..3e625c8875f 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java @@ -188,7 +188,7 @@ private URI buildURI(boolean fromEncoded, String thePath, String theQuery, Strin // Always include the leading slash in order to prevent host overrides if ((scheme != null || host != null || userInfo != null) - && thePath.length() != 0 && !(thePath.startsWith("/") || thePath.startsWith(";"))) { + && thePath.length() != 0 && !(thePath.startsWith("/") || !needsLeadingSlash(thePath))) { thePath = "/" + thePath; } @@ -238,8 +238,8 @@ private String buildUriString(String thePath, String theQuery, String theFragmen b.append(':').append(port); } if (thePath != null && thePath.length() > 0) { - b.append(thePath.startsWith("/") || b.length() == 0 || (originalPathEmpty && thePath.startsWith(";")) - ? thePath : '/' + thePath); + b.append(thePath.startsWith("/") || b.length() == 0 + || (originalPathEmpty && !needsLeadingSlash(thePath)) ? thePath : '/' + thePath); } if (theQuery != null && theQuery.length() != 0) { b.append('?').append(theQuery); @@ -252,6 +252,28 @@ private String buildUriString(String thePath, String theQuery, String theFragmen } return b.toString(); } + + /** + * The URI parsing logic seems to be flawed and treats everything after @ + * symbol as the host name, for example: + * + * URI.create("http://foo.com;@evil.com").getHost() -> "evil.com" + * URI.create("http://foo.com&@evil.com").getHost() -> "evil.com" + * URI.create("http://foo.com+@evil.com").getHost() -> "evil.com" + * + * The straightforward encoding of @ -> %40 does not work either: + * + * URI.create("http://foo.com;%40evil.com").getHost() -> null + * + * It does not even matter if the userinfo part is already provided: + * + * URI.create("http://a:b@foo.com;%40evil.com").getHost() -> null + * + * To workaround the issue, we have to include leading path slash all the time. + */ + private boolean needsLeadingSlash(String path) { + return !path.startsWith(";") || path.indexOf('@') >= 0; /* check if @ is present */ + } private boolean isSchemeOpaque() { return schemeSpecificPart != null; diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index c4e3e2a05dc..7db8f9556ca 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -219,6 +219,140 @@ public void testReplacePathDoesNotChangeHost() { assertThat(uri.getHost(), equalTo("foo.com")); } + @Test + public void testUserInfoAndUriWithMatrix() { + // The URI will treat `evil.com` as the host name + UriBuilder ub = UriBuilder.fromUri("http://foo.com;@evil.com"); + URI uri = ub.build(); + assertEquals("http://foo.com;@evil.com", uri.toString()); + assertThat(uri.getHost(), equalTo("evil.com")); + } + + @Test + public void testUserInfoAndPathWithMatrix() { + // The URI will treat `evil.com` as the host name + UriBuilder ub = UriBuilder.fromPath("http://foo.com;@evil.com"); + URI uri = ub.build(); + assertEquals("http://foo.com;@evil.com", uri.toString()); + assertThat(uri.getHost(), equalTo("evil.com")); + } + + @Test + public void testPathWithUserInfoDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://a:b@foo.com"); + URI uri = ub.build(); + assertEquals("http://a:b@foo.com", uri.toString()); + uri = ub.path(";@evil.com").build(); + assertThat(uri.toString(), equalTo("http://a:b@foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://a:b@foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + String template = ub.toTemplate(); + assertThat(template, equalTo("http://a:b@foo.com/;@evil.com")); + } + + @Test + public void testUriWithUserInfoDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromUri("http://a:b@foo.com"); + URI uri = ub.build(); + assertEquals("http://a:b@foo.com", uri.toString()); + uri = ub.path(";@evil.com").build(); + assertThat(uri.toString(), equalTo("http://a:b@foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://a:b@foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + String template = ub.toTemplate(); + assertThat(template, equalTo("http://a:b@foo.com/;@evil.com")); + } + + @Test + public void testUserInfoAndPathDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.path(";@evil.com").build(); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + String template = ub.toTemplate(); + assertThat(template, equalTo("http://foo.com/;@evil.com")); + } + + @Test + public void testUserInfoAndReplacePathDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.replacePath(";@evil.com").build(); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + String template = ub.toTemplate(); + assertThat(template, equalTo("http://foo.com/;@evil.com")); + } + + @Test + public void testUserInfoAndReplaceMatrixDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.replaceMatrix("@evil.com").build(); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + String template = ub.toTemplate(); + assertThat(template, equalTo("http://foo.com/;@evil.com")); + } + + @Test + public void testUserInfoAndMatrixDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.matrixParam("@evil.com").build(); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + String template = ub.toTemplate(); + assertThat(template, equalTo("http://foo.com/;@evil.com")); + } + + @Test + public void testUserInfoAndTemplatedPathDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.path("{x}").build(";@evil.com"); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(";@evil.com"); + assertThat(uri.toString(), equalTo("http://foo.com/;@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + } + @Test public void testTemplateUriDoesNotChangeHost() { final String template = UriBuilder From af77a9b7131413002a8c247001cd7b800fc673b9 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Wed, 21 Oct 2026 11:44:47 -0400 Subject: [PATCH 8/9] Address review comments --- .../apache/cxf/jaxrs/impl/UriBuilderImpl.java | 14 ++--- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 53 +++++++++++++++---- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java index 3e625c8875f..942d63e2955 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriBuilderImpl.java @@ -186,9 +186,9 @@ private UriParts doBuildUriParts(boolean fromEncoded, boolean encodePathSlash, private URI buildURI(boolean fromEncoded, String thePath, String theQuery, String theFragment) throws URISyntaxException { - // Always include the leading slash in order to prevent host overrides + // Always include the leading slash in order to prevent host overrides if ((scheme != null || host != null || userInfo != null) - && thePath.length() != 0 && !(thePath.startsWith("/") || !needsLeadingSlash(thePath))) { + && thePath.length() != 0 && (!thePath.startsWith("/") && needsLeadingSlash(thePath))) { thePath = "/" + thePath; } @@ -252,10 +252,10 @@ private String buildUriString(String thePath, String theQuery, String theFragmen } return b.toString(); } - + /** - * The URI parsing logic seems to be flawed and treats everything after @ - * symbol as the host name, for example: + * The `;`, `&` and `+` are valid userinfo characters (RFC 3986), so a path + * glued to the host that contains `@` is parsed as userinfo. * * URI.create("http://foo.com;@evil.com").getHost() -> "evil.com" * URI.create("http://foo.com&@evil.com").getHost() -> "evil.com" @@ -272,7 +272,9 @@ private String buildUriString(String thePath, String theQuery, String theFragmen * To workaround the issue, we have to include leading path slash all the time. */ private boolean needsLeadingSlash(String path) { - return !path.startsWith(";") || path.indexOf('@') >= 0; /* check if @ is present */ + return !path.startsWith(";") + || path.indexOf('@') >= 0 /* check if @ is present */ + || path.indexOf("%40") >= 0; /* @ encoded */ } private boolean isSchemeOpaque() { diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index 7db8f9556ca..de19395ae4b 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -219,9 +219,18 @@ public void testReplacePathDoesNotChangeHost() { assertThat(uri.getHost(), equalTo("foo.com")); } + @Test + public void testEncodedUserInfoAndUriWithMatrix() { + // The UriBuilder::fromUri will treat `evil.com` as the host name + UriBuilder ub = UriBuilder.fromUri("http://foo.com;%40evil.com"); + URI uri = ub.build(); + assertEquals("http://foo.com;@evil.com", uri.toString()); + assertThat(uri.getHost(), equalTo("evil.com")); + } + @Test public void testUserInfoAndUriWithMatrix() { - // The URI will treat `evil.com` as the host name + // The UriBuilder::fromUri will treat `evil.com` as the host name UriBuilder ub = UriBuilder.fromUri("http://foo.com;@evil.com"); URI uri = ub.build(); assertEquals("http://foo.com;@evil.com", uri.toString()); @@ -229,14 +238,23 @@ public void testUserInfoAndUriWithMatrix() { } @Test - public void testUserInfoAndPathWithMatrix() { - // The URI will treat `evil.com` as the host name + public void testEncodedUserInfoSeparatorAndPathWithMatrix() { + // The UriBuilder::fromPath will treat `evil.com` as the host name + UriBuilder ub = UriBuilder.fromPath("http://foo.com;%40evil.com"); + URI uri = ub.build(); + assertEquals("http://foo.com;@evil.com", uri.toString()); + assertThat(uri.getHost(), equalTo("evil.com")); + } + + @Test + public void testUserInfoSeparatorAndPathWithMatrix() { + // The UriBuilder::fromPath will treat `evil.com` as the host name UriBuilder ub = UriBuilder.fromPath("http://foo.com;@evil.com"); URI uri = ub.build(); assertEquals("http://foo.com;@evil.com", uri.toString()); assertThat(uri.getHost(), equalTo("evil.com")); } - + @Test public void testPathWithUserInfoDoesNotChangeHost() { UriBuilder ub = UriBuilder.fromPath("http://a:b@foo.com"); @@ -272,7 +290,24 @@ public void testUriWithUserInfoDoesNotChangeHost() { } @Test - public void testUserInfoAndPathDoesNotChangeHost() { + public void testEncodedUserInfoSeparatorAndPathDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.path(";%40evil.com").build(); + assertThat(uri.toString(), equalTo("http://foo.com/;%40evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://foo.com/;%40evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + String template = ub.toTemplate(); + assertThat(template, equalTo("http://foo.com/;%40evil.com")); + } + + @Test + public void testUserInfoSeparatorAndPathDoesNotChangeHost() { UriBuilder ub = UriBuilder.fromPath("http://foo.com"); URI uri = ub.build(); assertEquals("http://foo.com", uri.toString()); @@ -289,7 +324,7 @@ public void testUserInfoAndPathDoesNotChangeHost() { } @Test - public void testUserInfoAndReplacePathDoesNotChangeHost() { + public void testUserInfoSeparatorAndReplacePathDoesNotChangeHost() { UriBuilder ub = UriBuilder.fromPath("http://foo.com"); URI uri = ub.build(); assertEquals("http://foo.com", uri.toString()); @@ -306,7 +341,7 @@ public void testUserInfoAndReplacePathDoesNotChangeHost() { } @Test - public void testUserInfoAndReplaceMatrixDoesNotChangeHost() { + public void testUserInfoSeparatorAndReplaceMatrixDoesNotChangeHost() { UriBuilder ub = UriBuilder.fromPath("http://foo.com"); URI uri = ub.build(); assertEquals("http://foo.com", uri.toString()); @@ -323,7 +358,7 @@ public void testUserInfoAndReplaceMatrixDoesNotChangeHost() { } @Test - public void testUserInfoAndMatrixDoesNotChangeHost() { + public void testUserInfoSeparatorAndMatrixDoesNotChangeHost() { UriBuilder ub = UriBuilder.fromPath("http://foo.com"); URI uri = ub.build(); assertEquals("http://foo.com", uri.toString()); @@ -340,7 +375,7 @@ public void testUserInfoAndMatrixDoesNotChangeHost() { } @Test - public void testUserInfoAndTemplatedPathDoesNotChangeHost() { + public void testUserInfoSeparatorAndTemplatedPathDoesNotChangeHost() { UriBuilder ub = UriBuilder.fromPath("http://foo.com"); URI uri = ub.build(); assertEquals("http://foo.com", uri.toString()); From 2d03cc180bbbfbe0fb7df96b3b35bac409390ff3 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Wed, 21 Oct 2026 20:39:59 -0400 Subject: [PATCH 9/9] Add more test cases --- .../cxf/jaxrs/impl/UriBuilderImplTest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java index de19395ae4b..da70d70a2ca 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriBuilderImplTest.java @@ -374,6 +374,23 @@ public void testUserInfoSeparatorAndMatrixDoesNotChangeHost() { assertThat(template, equalTo("http://foo.com/;@evil.com")); } + @Test + public void testUserInfoSeparatorAndMatrixParamDoesNotChangeHost() { + UriBuilder ub = UriBuilder.fromPath("http://foo.com"); + URI uri = ub.build(); + assertEquals("http://foo.com", uri.toString()); + uri = ub.matrixParam("param", "@evil.com").build(); + assertThat(uri.toString(), equalTo("http://foo.com/;param=@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + uri = ub.buildFromEncoded(); + assertThat(uri.toString(), equalTo("http://foo.com/;param=@evil.com")); + assertThat(uri.getHost(), equalTo("foo.com")); + + String template = ub.toTemplate(); + assertThat(template, equalTo("http://foo.com/;param=@evil.com")); + } + @Test public void testUserInfoSeparatorAndTemplatedPathDoesNotChangeHost() { UriBuilder ub = UriBuilder.fromPath("http://foo.com");