This is an automated email from the ASF dual-hosted git repository. reta pushed a commit to branch 3.3.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 567022973f8db62466ae230850cf04b8605a5418 Author: Andriy Redko <[email protected]> AuthorDate: Wed Jul 7 21:52:21 2021 -0400 CXF-8557: Incorrect Proxy Path Segmenting when @Path Annotation RegexExpression Contains '/' (#822) * CXF-8557: Incorrect Proxy Path Segmenting when @Path Annotation Regex Expression Contains '/' * Added more sophisticated handling of the incorrect template definitions and fallback to simple split by path segment separator (cherry picked from commit c0cae301ccfcb75b5ad011fd2192b5385539d9dd) (cherry picked from commit 8f7b5e9421428b8cfc8d6b898d85dbb288358e94) --- .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java | 73 ++++++++++++--- .../apache/cxf/jaxrs/impl/UriBuilderImplTest.java | 100 +++++++++++++++++++++ 2 files changed, 161 insertions(+), 12 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java index 5923836..38fab87 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java @@ -181,20 +181,69 @@ public final class JAXRSUtils { return getPathSegments(thePath, decode, true); } + /** + * Parses path segments taking into account the URI templates and template regexes. Per RFC-3986, + * "A path consists of a sequence of path segments separated by a slash ("/") character.", however + * it is possible to include slash ("/") inside template regex, for example "/my/path/{a:b/c}", see + * please {@link URITemplate}. In this case, the whole template definition is extracted as a path + * segment, without breaking it. + * @param thePath path + * @param decode should the path segments be decoded or not + * @param ignoreLastSlash should the last slash be ignored or not + * @return + */ public static List<PathSegment> getPathSegments(String thePath, boolean decode, boolean ignoreLastSlash) { - List<PathSegment> theList = - Arrays.asList(thePath.split("/")).stream() - .filter(StringUtils.notEmpty()) - .map(p -> new PathSegmentImpl(p, decode)) - .collect(Collectors.toList()); - - int len = thePath.length(); - if (len > 0 && thePath.charAt(len - 1) == '/') { - String value = ignoreLastSlash ? "" : "/"; - theList.add(new PathSegmentImpl(value, false)); - } - return theList; + + final List<PathSegment> segments = new ArrayList<>(); + int templateDepth = 0; + int start = 0; + for (int i = 0; i < thePath.length(); ++i) { + if (thePath.charAt(i) == '/') { + // The '/' is in template (possibly, with arbitrary regex) definition + if (templateDepth != 0) { + continue; + } else if (start != i) { + final String segment = thePath.substring(start, i); + segments.add(new PathSegmentImpl(segment, decode)); + } + + // advance the positions, empty path segments + start = i + 1; + } else if (thePath.charAt(i) == '{') { + ++templateDepth; + } else if (thePath.charAt(i) == '}') { + --templateDepth; // could go negative, since the template could be unbalanced + } + } + + // the URI has unbalanced curly braces, backtrack to the last seen position of the path + // segment separator and just split segments as-is from there + if (templateDepth != 0) { + segments.addAll( + Arrays + .stream(thePath.substring(start).split("/")) + .filter(StringUtils.notEmpty()) + .map(p -> new PathSegmentImpl(p, decode)) + .collect(Collectors.toList())); + + int len = thePath.length(); + if (len > 0 && thePath.charAt(len - 1) == '/') { + String value = ignoreLastSlash ? "" : "/"; + segments.add(new PathSegmentImpl(value, false)); + } + } else { + // the last symbol is slash + if (start == thePath.length() && start > 0 && thePath.charAt(start - 1) == '/') { + String value = ignoreLastSlash ? "" : "/"; + segments.add(new PathSegmentImpl(value, false)); + } else if (!thePath.isEmpty()) { + final String segment = thePath.substring(start); + segments.add(new PathSegmentImpl(segment, decode)); + } + } + + return segments; } private static String[] getUserMediaTypes(Object provider, boolean consumes) { 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 bef65a4..b7d7c3a 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 @@ -1851,4 +1851,104 @@ public class UriBuilderImplTest { .toTemplate(); assertEquals("my/path?p=%25250%25", template); } + + @Test + public void pathParamFromTemplateWithOrRegex() { + final URI uri = UriBuilder + .fromUri("my/path") + .path("{p:my|his}") + .build("my"); + assertEquals("my/path/my", uri.toString()); + } + + @Test + public void pathParamFromTemplateWithRegex() { + final URI uri = UriBuilder + .fromUri("my/path") + .path("{p:his/him}") + .buildFromEncoded("his/him"); + assertEquals("my/path/his/him", uri.toString()); + } + + @Test + public void pathParamFromNestedTemplateWithRegex() { + // The nested templates are not supported and are not detected + final URI uri = UriBuilder + .fromUri("my/path") + .path("{{p:his/him}}") + .build(); + assertEquals("my/path/%7B%7Bp:his/him%7D%7D", uri.toString()); + } + + @Test + public void pathParamFromBadTemplateNested() { + final URI uri = UriBuilder + .fromUri("my/path") + .path("{p{d}}") + .build("my"); + assertEquals("my/path/%7Bp%7Bd%7D%7D", uri.toString()); + } + + @Test + public void pathParamFromBadTemplateUnopened() { + final URI uri = UriBuilder + .fromUri("my/path") + .path("p{d}/}") + .build("my"); + assertEquals("my/path/pmy/%7D", uri.toString()); + } + + @Test + public void pathParamFromBadTemplateUnclosed() { + final URI uri = UriBuilder + .fromUri("my/path") + .path("{p/{d}") + .build("my"); + assertEquals("my/path/%7Bp/my", uri.toString()); + } + + @Test + public void pathParamFromEmpty() { + final URI uri = UriBuilder + .fromUri("/") + .path("/") + .build(); + assertEquals("/", uri.toString()); + } + + @Test + public void pathParamFromEmptyWithSpaces() { + final URI uri = UriBuilder + .fromUri("/") + .path(" / ") + .build(); + assertEquals("/%20%20%20/%20%20%20", uri.toString()); + } + + @Test + public void pathParamFromBadTemplateUnopenedAndEnclosedSlash() { + final URI uri = UriBuilder + .fromUri("my/path") + .path("p{d:my/day}/}") + .buildFromEncoded("my/day"); + assertEquals("my/path/pmy/day/%7D", uri.toString()); + } + + @Test + public void pathParamFromBadTemplateUnclosedAndEnclosedSlash() { + final URI uri = UriBuilder + .fromUri("my/path") + .path("{p/{d:my/day}") + .build(); + assertEquals("my/path/%7Bp/%7Bd:my/day%7D", uri.toString()); + } + + @Test + public void pathParamFromBadTemplate() { + final URI uri = UriBuilder + .fromUri("/") + .path("{") + .build(); + assertEquals("/%7B", uri.toString()); + } } \ No newline at end of file
