Skip to content

Commit 508aea8

Browse files
committed
Rework implementation of PathPattern.extractPathWithinPattern
This commit changes the implementation of the PathPattern extractPathWithinPattern method that used an old AntPathMatcher derivative to a new version that integrates more closely with PathContainer. It also introduces consistency in a couple of areas. The javadoc is updated to specify this but basically: - the response from the extra method will have all leading and trailing separators removed. - the response will have multiple adjacent separators within the reponse reduced to just one. (For example response would be aaa/bb/cc and not aaa///bbb//cc) If your response would start or finish with multiple separators, they are all removed. Issue: SPR-16120
1 parent b1b5353 commit 508aea8

File tree

2 files changed

+58
-71
lines changed

2 files changed

+58
-71
lines changed

spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java

Lines changed: 50 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -246,99 +246,79 @@ else if (!hasLength(pathContainer)) {
246246
* <li>'{@code /docs/cvs/*.html}' and '{@code /docs/cvs/commit.html} -> '{@code commit.html}'</li>
247247
* <li>'{@code /docs/**}' and '{@code /docs/cvs/commit} -> '{@code cvs/commit}'</li>
248248
* </ul>
249-
* <p><b>Note:</b> Assumes that {@link #matches} returns {@code true} for
249+
* <p><b>Notes:</b>
250+
* <ul>
251+
* <li>Assumes that {@link #matches} returns {@code true} for
250252
* the same path but does <strong>not</strong> enforce this.
253+
* <li>Duplicate occurrences of separators within the returned result are removed
254+
* <li>Leading and trailing separators are removed from the returned result
255+
* </ul>
251256
* @param path a path that matches this pattern
252257
* @return the subset of the path that is matched by pattern or "" if none
253258
* of it is matched by pattern elements
254259
*/
255260
public PathContainer extractPathWithinPattern(PathContainer path) {
261+
List<Element> pathElements = path.elements();
262+
int pathElementsCount = pathElements.size();
256263

257-
// TODO: implement extractPathWithinPattern for PathContainer
258-
259-
// TODO: align behavior with matchStartOfPath with regards to this:
260-
// As per the contract on {@link PathMatcher}, this method will trim leading/trailing
261-
// separators. It will also remove duplicate separators in the returned path.
262-
263-
String result = extractPathWithinPattern(path.value());
264-
return PathContainer.parsePath(result);
265-
}
266-
267-
private String extractPathWithinPattern(String path) {
268-
// assert this.matches(path)
264+
int startIndex = 0;
265+
// Find first path element that is not a separator or a literal (i.e. the first pattern based element)
269266
PathElement elem = head;
270-
int separatorCount = 0;
271-
boolean matchTheRest = false;
272-
273-
// Find first path element that is pattern based
274267
while (elem != null) {
275-
if (elem instanceof SeparatorPathElement || elem instanceof CaptureTheRestPathElement ||
276-
elem instanceof WildcardTheRestPathElement) {
277-
separatorCount++;
278-
if (elem instanceof WildcardTheRestPathElement || elem instanceof CaptureTheRestPathElement) {
279-
matchTheRest = true;
280-
}
281-
}
282268
if (elem.getWildcardCount() != 0 || elem.getCaptureCount() != 0) {
283269
break;
284270
}
285271
elem = elem.next;
272+
startIndex++;
286273
}
287-
288274
if (elem == null) {
289-
return ""; // there is no pattern mapped section
290-
}
291-
292-
// Now separatorCount indicates how many sections of the path to skip
293-
char[] pathChars = path.toCharArray();
294-
int len = pathChars.length;
295-
int pos = 0;
296-
while (separatorCount > 0 && pos < len) {
297-
if (path.charAt(pos++) == separator) {
298-
separatorCount--;
299-
}
300-
}
301-
int end = len;
302-
// Trim trailing separators
303-
if (!matchTheRest) {
304-
while (end > 0 && path.charAt(end - 1) == separator) {
305-
end--;
275+
// There is no pattern piece
276+
return PathContainer.parsePath("");
277+
}
278+
279+
// Skip leading separators that would be in the result
280+
while (startIndex < pathElementsCount && (pathElements.get(startIndex) instanceof Separator)) {
281+
startIndex++;
282+
}
283+
284+
int endIndex = pathElements.size();
285+
// Skip trailing separators that would be in the result
286+
while (endIndex > 0 && (pathElements.get(endIndex - 1) instanceof Separator)) {
287+
endIndex--;
288+
}
289+
290+
boolean multipleAdjacentSeparators = false;
291+
for (int i = startIndex; i < (endIndex - 1); i++) {
292+
if ((pathElements.get(i) instanceof Separator) && (pathElements.get(i+1) instanceof Separator)) {
293+
multipleAdjacentSeparators=true;
294+
break;
306295
}
307296
}
308-
309-
// Check if multiple separators embedded in the resulting path, if so trim them out.
310-
// Example: aaa////bbb//ccc/d -> aaa/bbb/ccc/d
311-
// The stringWithDuplicateSeparatorsRemoved is only computed if necessary
312-
int c = pos;
313-
StringBuilder stringWithDuplicateSeparatorsRemoved = null;
314-
while (c < end) {
315-
char ch = path.charAt(c);
316-
if (ch == separator) {
317-
if ((c + 1) < end && path.charAt(c + 1) == separator) {
318-
// multiple separators
319-
if (stringWithDuplicateSeparatorsRemoved == null) {
320-
// first time seen, need to capture all data up to this point
321-
stringWithDuplicateSeparatorsRemoved = new StringBuilder();
322-
stringWithDuplicateSeparatorsRemoved.append(path.substring(pos, c));
323-
}
324-
do {
325-
c++;
326-
} while ((c + 1) < end && path.charAt(c + 1) == separator);
297+
298+
PathContainer resultPath = null;
299+
if (multipleAdjacentSeparators) {
300+
// Need to rebuild the path without the duplicate adjacent separators
301+
StringBuilder buf = new StringBuilder();
302+
int i = startIndex;
303+
while (i < endIndex) {
304+
Element e = pathElements.get(i++);
305+
buf.append(e.value());
306+
if (e instanceof Separator) {
307+
while (i < endIndex && (pathElements.get(i) instanceof Separator)) {
308+
i++;
309+
}
327310
}
328311
}
329-
if (stringWithDuplicateSeparatorsRemoved != null) {
330-
stringWithDuplicateSeparatorsRemoved.append(ch);
331-
}
332-
c++;
312+
resultPath = PathContainer.parsePath(buf.toString());
313+
} else if (startIndex >= endIndex) {
314+
resultPath = PathContainer.parsePath("");
333315
}
334-
335-
if (stringWithDuplicateSeparatorsRemoved != null) {
336-
return stringWithDuplicateSeparatorsRemoved.toString();
316+
else {
317+
resultPath = path.subPath(startIndex, endIndex);
337318
}
338-
return (pos == len ? "" : path.substring(pos, end));
319+
return resultPath;
339320
}
340321

341-
342322
/**
343323
* Compare this pattern with a supplied pattern: return -1,0,+1 if this pattern
344324
* is more specific, the same or less specific than the supplied pattern.

spring-web/src/test/java/org/springframework/web/util/pattern/PathPatternTests.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ public void caseSensitivity() {
664664

665665
@Test
666666
public void extractPathWithinPattern_spr15259() {
667-
checkExtractPathWithinPattern("/**","//","/");
667+
checkExtractPathWithinPattern("/**","//","");
668668
checkExtractPathWithinPattern("/**","/","");
669669
checkExtractPathWithinPattern("/**","","");
670670
checkExtractPathWithinPattern("/**","/foobar","foobar");
@@ -682,6 +682,13 @@ public void extractPathWithinPattern() throws Exception {
682682
checkExtractPathWithinPattern("/*.html", "/commit.html", "commit.html");
683683
checkExtractPathWithinPattern("/docs/*/*/*/*", "/docs/cvs/other/commit.html", "cvs/other/commit.html");
684684
checkExtractPathWithinPattern("/d?cs/**", "/docs/cvs/commit", "docs/cvs/commit");
685+
checkExtractPathWithinPattern("/*/**", "/docs/cvs/commit///", "docs/cvs/commit");
686+
checkExtractPathWithinPattern("/*/**", "/docs/cvs/commit/", "docs/cvs/commit");
687+
checkExtractPathWithinPattern("/aaa/bbb/**", "/aaa///","");
688+
checkExtractPathWithinPattern("/aaa/bbb/**", "/aaa//","");
689+
checkExtractPathWithinPattern("/aaa/bbb/**", "/aaa/","");
690+
checkExtractPathWithinPattern("/docs/**", "/docs/cvs/commit///", "cvs/commit");
691+
checkExtractPathWithinPattern("/docs/**", "/docs/cvs/commit/", "cvs/commit");
685692
checkExtractPathWithinPattern("/docs/c?s/*.html", "/docs/cvs/commit.html", "cvs/commit.html");
686693
checkExtractPathWithinPattern("/d?cs/*/*.html", "/docs/cvs/commit.html", "docs/cvs/commit.html");
687694
checkExtractPathWithinPattern("/a/b/c*d*/*.html", "/a/b/cod/foo.html", "cod/foo.html");

0 commit comments

Comments
 (0)