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:
- *
- * - /api/search.{format}
- * - /api/search/users/{userid}.{format}
- * - /api/{version}/search/users/{userid}
- *
- *
- * 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