diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 267e9568d31..7f912103ea7 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -20,11 +20,6 @@ package org.eclipse.jetty.util; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.ListIterator; -import java.util.regex.Pattern; import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; import org.eclipse.jetty.util.log.Log; @@ -54,7 +49,6 @@ public class URIUtil public static final String HTTP_COLON="http:"; public static final String HTTPS="https"; public static final String HTTPS_COLON="https:"; - private static final Pattern __PATH_SPLIT = Pattern.compile("(?<=\\/)"); // Use UTF-8 as per http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars public static final Charset __CHARSET=StandardCharsets.UTF_8 ; @@ -509,40 +503,130 @@ public class URIUtil */ public static String canonicalPath(String path) { - if (path == null || path.isEmpty() || !path.contains(".")) + if (path==null || path.length()==0) return path; - if(path.startsWith("/..")) - return null; + int end=path.length(); + int start = path.lastIndexOf('/', end); - List directories = new LinkedList<>(); - Collections.addAll(directories, __PATH_SPLIT.split(path)); - - for(ListIterator iterator = directories.listIterator(); iterator.hasNext();) + search: + while (end>0) { - switch (iterator.next()) { - case "./": - case ".": - if (iterator.hasNext() && directories.get(iterator.nextIndex()).equals("/")) { - break; - } - iterator.remove(); - break; - case "../": - case "..": - if(iterator.previousIndex() == 0) { - return null; - } - iterator.remove(); - iterator.previous(); - iterator.remove(); - break; + switch(end-start) + { + case 2: // possible single dot + if (path.charAt(start+1)!='.') + break; + break search; + case 3: // possible double dot + if (path.charAt(start+1)!='.' || path.charAt(start+2)!='.') + break; + break search; } + + end=start; + start=path.lastIndexOf('/',end-1); } - if (directories.isEmpty() && path.startsWith("/")) - return null; - return String.join("", directories); + // If we have checked the entire string + if (start>=end) + return path; + + StringBuilder buf = new StringBuilder(path); + int delStart=-1; + int delEnd=-1; + int skip=0; + + while (end>0) + { + switch(end-start) + { + case 2: // possible single dot + if (buf.charAt(start+1)!='.') + { + if (skip>0 && --skip==0) + { + delStart=start>=0?start:0; + if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + break; + } + + if(start<0 && buf.length()>2 && buf.charAt(1)=='/' && buf.charAt(2)=='/') + break; + + if(delEnd<0) + delEnd=end; + delStart=start; + if (delStart<0 || delStart==0&&buf.charAt(delStart)=='/') + { + delStart++; + if (delEnd=0 && buf.charAt(start)!='/') + start--; + continue; + + case 3: // possible double dot + if (buf.charAt(start+1)!='.' || buf.charAt(start+2)!='.') + { + if (skip>0 && --skip==0) + { delStart=start>=0?start:0; + if(delStart>0 && delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + break; + } + + delStart=start; + if (delEnd<0) + delEnd=end; + + skip++; + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + continue; + + default: + if (skip>0 && --skip==0) + { + delStart=start>=0?start:0; + if(delEnd==buf.length() && buf.charAt(delEnd-1)=='.') + delStart++; + } + } + + // Do the delete + if (skip<=0 && delStart>=0 && delEnd>=delStart) + { + buf.delete(delStart,delEnd); + delStart=delEnd=-1; + if (skip>0) + delEnd=end; + } + + end=start--; + while (start>=0 && buf.charAt(start)!='/') + start--; + } + + // Too many .. + if (skip>0) + return null; + + // Do the delete + if (delEnd>=0) + buf.delete(delStart,delEnd); + + return buf.toString(); } /* ------------------------------------------------------------ */