From e17eec52eb9b6c430924aa9eb01bfe7e7b429ec2 Mon Sep 17 00:00:00 2001 From: Francisco Javier Tirado Sarti <65240126+fjtirado@users.noreply.github.com> Date: Tue, 18 Feb 2025 15:04:39 +0100 Subject: [PATCH] [Fix #991] Changing URLPatternMatcher implementation (#992) --- .../providers/UrlPatternMatcher.java | 135 ++++++------------ .../providers/UrlPatternMatcherTest.java | 18 ++- 2 files changed, 64 insertions(+), 89 deletions(-) diff --git a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcher.java b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcher.java index 819a85a65..a80451c25 100644 --- a/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcher.java +++ b/client/runtime/src/main/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcher.java @@ -1,102 +1,57 @@ /* Original Copyright Headers - This file has been modified, but copied from - https://github.com/RestExpress/RestExpress/blob/master/core/src/main/java/org/restexpress/url/UrlPattern.java + This file include a substantially simplified version of + https://github.com/wilkincheung/URI-Template-Pattern-Matcher/blob/master/src/main/java/com/prodigi/service/UriTemplateValidator.java */ /** - * Copyright 2010, Strategic Gains, Inc. - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.quarkiverse.openapi.generator.providers; +The MIT License (MIT) +Copyright (c) 2015 Wilkin Cheung -import java.util.regex.Pattern; +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -/** - * PathPatternMatcher leverages Regex Pattern to represent a parameterized URL. Parameters within the URL are - * denoted by curly braces '{}' with the parameter name contained within (e.g. '{userid}'). - *

- *

- * Parameter names must be formed of word characters (e.g. A-Z, a-z, 0-9, '_'). - *

- * An optional format parameter following a dot ('.') may be added to the end. While it could be named any valid parameter name, - * RestExpress offers special handling (e.g. within the Request, etc.) if it's named 'format'. - *

- * Note that the format specifier allows only word characters and percent-encoded characters. - *

- *

- * URL Pattern examples: - *

- *

- * RestExpress parses URI paths which is described in the URI Generic Syntax IETF RFC 3986 specification, - * section 3.3 (http://tools.ietf.org/html/rfc3986#section-3.3). RestExpress parses paths into segments - * separated by slashes ("/"), the segments of which are composed of unreserved, percent encoded, - * sub-delimiters, colon (":") or ampersand ("@"), each of which are defined below (from the spec): - *

- * pct-encoded = "%" HEXDIG HEXDIG - *

- * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
- * reserved = gen-delims / sub-delims
- * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
- * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" * - *

- * In other words, RestExpress accepts path segments containing: [A-Z] [a-z] [0-9] % - . _ ~ ! $ & ' ( ) * + , ; = : @ - *

- * RestExpress also accepts square brackets ('[' and ']'), but this is deprecated and not recommended. - * - * @author toddf - * @see http://www.ietf.org/rfc/rfc3986.txt - * @since Apr 28, 2010 - */ -public class UrlPatternMatcher { - // Finds parameters in the URL pattern string. - private static final String URL_PARAM_REGEX = "\\{(\\S*?)\\}"; +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. - // Replaces parameter names in the URL pattern string to match parameters in URLs. - private static final String URL_PARAM_MATCH_REGEX = "\\([%\\\\w-.\\\\~!\\$&'\\\\(\\\\)\\\\*\\\\+,;=:\\\\[\\\\]@]+?\\)"; +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package io.quarkiverse.openapi.generator.providers; - // Finds the 'format' portion of the URL pattern string. - private static final String URL_FORMAT_REGEX = "(?:\\.\\{format\\})$"; +import java.util.regex.Matcher; +import java.util.regex.Pattern; - // Replaces the format parameter name in the URL pattern string to match the format specifier in URLs. Appended to the end of the regex string - // when a URL pattern contains a format parameter. - private static final String URL_FORMAT_MATCH_REGEX = "(?:\\\\.\\([\\\\w%]+?\\))?"; +public class UrlPatternMatcher { - // Finds the query string portion within a URL. Appended to the end of the built-up regex string. + // For each pattern {keyName} replaces it with (.*) + private static final Pattern LEVEL_ONE_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); + // Replaces each {keyName} with (.*) + private static final String REPLACES_WITH = "(.*)"; private static final String URL_QUERY_STRING_REGEX = "(?:\\?.*?)?$"; - /** - * The URL pattern describing the URL layout and any parameters. - */ - private final String urlPattern; - - /** - * A compiled regex created from the urlPattern, above. - */ - private Pattern compiledUrl; + private final Pattern pattern; - /** - * @param pattern - */ - public UrlPatternMatcher(String pattern) { - this.urlPattern = pattern; - String parsedPattern = this.urlPattern.replaceFirst(URL_FORMAT_REGEX, URL_FORMAT_MATCH_REGEX); - parsedPattern = parsedPattern.replaceAll(URL_PARAM_REGEX, URL_PARAM_MATCH_REGEX); - this.compiledUrl = Pattern.compile(parsedPattern + URL_QUERY_STRING_REGEX); + public UrlPatternMatcher(String uriTemplate) { + StringBuilder patternBuilder = new StringBuilder(); + Matcher m = LEVEL_ONE_PATTERN.matcher(uriTemplate); + int end = 0; + while (m.find()) { + // In each loop, find next pattern in URI that is "{keyName}" + // If found,append the substring to patternBuilder. + patternBuilder.append(Pattern.quote(uriTemplate.substring(end, m.start()))).append(REPLACES_WITH); + end = m.end(); + } + patternBuilder.append(Pattern.quote(uriTemplate.substring(end, uriTemplate.length()))); + this.pattern = Pattern.compile(patternBuilder + URL_QUERY_STRING_REGEX); } /** @@ -107,6 +62,10 @@ public UrlPatternMatcher(String pattern) { * @return true if the given URL matches the underlying pattern. Otherwise false. */ public boolean matches(String url) { - return compiledUrl.matcher(url).matches(); + return pattern.matcher(url).matches(); + } + + public String toString() { + return pattern.toString(); } -} \ No newline at end of file +} diff --git a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcherTest.java b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcherTest.java index 9610f7bfd..4c7a7d9f8 100644 --- a/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcherTest.java +++ b/client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcherTest.java @@ -16,6 +16,13 @@ void verifyPathsMatch(final String pathPattern, final String requestPath) { Assertions.assertTrue(pattern.matches(requestPath)); } + @ParameterizedTest + @MethodSource("providePathsThatNotMatch") + void verifyPathsNotMatch(final String pathPattern, final String requestPath) { + UrlPatternMatcher pattern = new UrlPatternMatcher(pathPattern); + Assertions.assertFalse(pattern.matches(requestPath)); + } + private static Stream providePathsThatMatch() { return Stream.of( Arguments.of("/pets/{id}", "/pets/1"), @@ -32,7 +39,16 @@ private static Stream providePathsThatMatch() { Arguments.of("/{id}/{foo}/{id2}", "/1/2/3?q=1&q2=2"), Arguments.of("/{id}/{foo}/{id2}", "/1/2/3"), Arguments.of("/v2/pets/{id}", "/v2/pets/1"), - Arguments.of("/pets/{pet-id}/types/{type-id}", "/pets/1/types/2")); + Arguments.of("/pets/{pet-id}/types/{type-id}", "/pets/1/types/2"), + Arguments.of("/repos/{ref}", "/repos/prefixed/cool.sw"), + Arguments.of("/repos/{ref}/pepe", "/repos/prefixed/cool.sw/pepe"), + Arguments.of("pepe/pepa/pepu", "pepe/pepa/pepu")); + } + + private static Stream providePathsThatNotMatch() { + return Stream.of( + Arguments.of("/pets/{id}", "/pes/1"), + Arguments.of("/{id}/pepe", "/1/2/pep")); } } \ No newline at end of file