Skip to content

Commit df953d2

Browse files
authored
[Fix #991] Changing URLPatternMatcher implementation (#996)
1 parent e634311 commit df953d2

File tree

2 files changed

+64
-89
lines changed

2 files changed

+64
-89
lines changed
Lines changed: 47 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,57 @@
11
/*
22
Original Copyright Headers
3-
This file has been modified, but copied from
4-
https://github.com/RestExpress/RestExpress/blob/master/core/src/main/java/org/restexpress/url/UrlPattern.java
3+
This file include a substantially simplified version of
4+
https://github.com/wilkincheung/URI-Template-Pattern-Matcher/blob/master/src/main/java/com/prodigi/service/UriTemplateValidator.java
55
*/
66
/**
7-
* Copyright 2010, Strategic Gains, Inc.
8-
* <p>
9-
* Licensed under the Apache License, Version 2.0 (the "License");
10-
* you may not use this file except in compliance with the License.
11-
* You may obtain a copy of the License at
12-
* <p>
13-
* http://www.apache.org/licenses/LICENSE-2.0
14-
* <p>
15-
* Unless required by applicable law or agreed to in writing, software
16-
* distributed under the License is distributed on an "AS IS" BASIS,
17-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18-
* See the License for the specific language governing permissions and
19-
* limitations under the License.
20-
*/
21-
package io.quarkiverse.openapi.generator.providers;
7+
The MIT License (MIT)
8+
Copyright (c) 2015 Wilkin Cheung
229
23-
import java.util.regex.Pattern;
10+
Permission is hereby granted, free of charge, to any person obtaining a copy
11+
of this software and associated documentation files (the "Software"), to deal
12+
in the Software without restriction, including without limitation the rights
13+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14+
copies of the Software, and to permit persons to whom the Software is
15+
furnished to do so, subject to the following conditions:
2416
25-
/**
26-
* PathPatternMatcher leverages Regex Pattern to represent a parameterized URL. Parameters within the URL are
27-
* denoted by curly braces '{}' with the parameter name contained within (e.g. '{userid}').
28-
* <p>
29-
* <p/>
30-
* Parameter names must be formed of word characters (e.g. A-Z, a-z, 0-9, '_').
31-
* <p/>
32-
* An optional format parameter following a dot ('.') may be added to the end. While it could be named any valid parameter name,
33-
* RestExpress offers special handling (e.g. within the Request, etc.) if it's named 'format'.
34-
* <p/>
35-
* Note that the format specifier allows only word characters and percent-encoded characters.
36-
* <p>
37-
* <p/>
38-
* URL Pattern examples:
39-
* <ul>
40-
* <li>/api/search.{format}</li>
41-
* <li>/api/search/users/{userid}.{format}</li>
42-
* <li>/api/{version}/search/users/{userid}</li>
43-
* </ul>
44-
* <p>
45-
* RestExpress parses URI paths which is described in the URI Generic Syntax IETF RFC 3986 specification,
46-
* section 3.3 (http://tools.ietf.org/html/rfc3986#section-3.3). RestExpress parses paths into segments
47-
* separated by slashes ("/"), the segments of which are composed of unreserved, percent encoded,
48-
* sub-delimiters, colon (":") or ampersand ("@"), each of which are defined below (from the spec):
49-
* <p/>
50-
* pct-encoded = "%" HEXDIG HEXDIG
51-
* <p/>
52-
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"<br/>
53-
* reserved = gen-delims / sub-delims<br/>
54-
* gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"</br>
55-
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" *
56-
* <p/>
57-
* In other words, RestExpress accepts path segments containing: [A-Z] [a-z] [0-9] % - . _ ~ ! $ & ' ( ) * + , ; = : @
58-
* <p/>
59-
* RestExpress also accepts square brackets ('[' and ']'), but this is deprecated and not recommended.
60-
*
61-
* @author toddf
62-
* @see <a href="Uniform Resource Identifier (URI): Generic Syntax">http://www.ietf.org/rfc/rfc3986.txt</a>
63-
* @since Apr 28, 2010
64-
*/
65-
public class UrlPatternMatcher {
66-
// Finds parameters in the URL pattern string.
67-
private static final String URL_PARAM_REGEX = "\\{(\\S*?)\\}";
17+
The above copyright notice and this permission notice shall be included in all
18+
copies or substantial portions of the Software.
6819
69-
// Replaces parameter names in the URL pattern string to match parameters in URLs.
70-
private static final String URL_PARAM_MATCH_REGEX = "\\([%\\\\w-.\\\\~!\\$&'\\\\(\\\\)\\\\*\\\\+,;=:\\\\[\\\\]@]+?\\)";
20+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26+
SOFTWARE.
27+
*/
28+
package io.quarkiverse.openapi.generator.providers;
7129

72-
// Finds the 'format' portion of the URL pattern string.
73-
private static final String URL_FORMAT_REGEX = "(?:\\.\\{format\\})$";
30+
import java.util.regex.Matcher;
31+
import java.util.regex.Pattern;
7432

75-
// 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
76-
// when a URL pattern contains a format parameter.
77-
private static final String URL_FORMAT_MATCH_REGEX = "(?:\\\\.\\([\\\\w%]+?\\))?";
33+
public class UrlPatternMatcher {
7834

79-
// Finds the query string portion within a URL. Appended to the end of the built-up regex string.
35+
// For each pattern {keyName} replaces it with (.*)
36+
private static final Pattern LEVEL_ONE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
37+
// Replaces each {keyName} with (.*)
38+
private static final String REPLACES_WITH = "(.*)";
8039
private static final String URL_QUERY_STRING_REGEX = "(?:\\?.*?)?$";
8140

82-
/**
83-
* The URL pattern describing the URL layout and any parameters.
84-
*/
85-
private final String urlPattern;
86-
87-
/**
88-
* A compiled regex created from the urlPattern, above.
89-
*/
90-
private Pattern compiledUrl;
41+
private final Pattern pattern;
9142

92-
/**
93-
* @param pattern
94-
*/
95-
public UrlPatternMatcher(String pattern) {
96-
this.urlPattern = pattern;
97-
String parsedPattern = this.urlPattern.replaceFirst(URL_FORMAT_REGEX, URL_FORMAT_MATCH_REGEX);
98-
parsedPattern = parsedPattern.replaceAll(URL_PARAM_REGEX, URL_PARAM_MATCH_REGEX);
99-
this.compiledUrl = Pattern.compile(parsedPattern + URL_QUERY_STRING_REGEX);
43+
public UrlPatternMatcher(String uriTemplate) {
44+
StringBuilder patternBuilder = new StringBuilder();
45+
Matcher m = LEVEL_ONE_PATTERN.matcher(uriTemplate);
46+
int end = 0;
47+
while (m.find()) {
48+
// In each loop, find next pattern in URI that is "{keyName}"
49+
// If found,append the substring to patternBuilder.
50+
patternBuilder.append(Pattern.quote(uriTemplate.substring(end, m.start()))).append(REPLACES_WITH);
51+
end = m.end();
52+
}
53+
patternBuilder.append(Pattern.quote(uriTemplate.substring(end, uriTemplate.length())));
54+
this.pattern = Pattern.compile(patternBuilder + URL_QUERY_STRING_REGEX);
10055
}
10156

10257
/**
@@ -107,6 +62,10 @@ public UrlPatternMatcher(String pattern) {
10762
* @return true if the given URL matches the underlying pattern. Otherwise false.
10863
*/
10964
public boolean matches(String url) {
110-
return compiledUrl.matcher(url).matches();
65+
return pattern.matcher(url).matches();
66+
}
67+
68+
public String toString() {
69+
return pattern.toString();
11170
}
112-
}
71+
}

client/runtime/src/test/java/io/quarkiverse/openapi/generator/providers/UrlPatternMatcherTest.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ void verifyPathsMatch(final String pathPattern, final String requestPath) {
1616
Assertions.assertTrue(pattern.matches(requestPath));
1717
}
1818

19+
@ParameterizedTest
20+
@MethodSource("providePathsThatNotMatch")
21+
void verifyPathsNotMatch(final String pathPattern, final String requestPath) {
22+
UrlPatternMatcher pattern = new UrlPatternMatcher(pathPattern);
23+
Assertions.assertFalse(pattern.matches(requestPath));
24+
}
25+
1926
private static Stream<Arguments> providePathsThatMatch() {
2027
return Stream.of(
2128
Arguments.of("/pets/{id}", "/pets/1"),
@@ -32,7 +39,16 @@ private static Stream<Arguments> providePathsThatMatch() {
3239
Arguments.of("/{id}/{foo}/{id2}", "/1/2/3?q=1&q2=2"),
3340
Arguments.of("/{id}/{foo}/{id2}", "/1/2/3"),
3441
Arguments.of("/v2/pets/{id}", "/v2/pets/1"),
35-
Arguments.of("/pets/{pet-id}/types/{type-id}", "/pets/1/types/2"));
42+
Arguments.of("/pets/{pet-id}/types/{type-id}", "/pets/1/types/2"),
43+
Arguments.of("/repos/{ref}", "/repos/prefixed/cool.sw"),
44+
Arguments.of("/repos/{ref}/pepe", "/repos/prefixed/cool.sw/pepe"),
45+
Arguments.of("pepe/pepa/pepu", "pepe/pepa/pepu"));
46+
}
47+
48+
private static Stream<Arguments> providePathsThatNotMatch() {
49+
return Stream.of(
50+
Arguments.of("/pets/{id}", "/pes/1"),
51+
Arguments.of("/{id}/pepe", "/1/2/pep"));
3652
}
3753

3854
}

0 commit comments

Comments
 (0)