diff options
2 files changed, 264 insertions, 93 deletions
diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java index 8494386324..0f893b42f1 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java @@ -19,9 +19,19 @@ package org.eclipse.jetty.quickstart; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URL; +import java.nio.file.FileSystems; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; @@ -30,7 +40,8 @@ import org.eclipse.jetty.util.resource.Resource; /** * Normalize Attribute to String. - * <p>Replaces and expands: + * <p> + * Replaces and expands: * <ul> * <li>${WAR}</li> * <li>${jetty.base}</li> @@ -42,116 +53,237 @@ import org.eclipse.jetty.util.resource.Resource; public class AttributeNormalizer { private static final Logger LOG = Log.getLogger(AttributeNormalizer.class); - private final Path _warPath; - private final Path _jettyBasePath; - private final Path _jettyHomePath; - private final Path _userHomePath; - private final Path _userDirPath; - - + private static final Pattern __propertyPattern = Pattern.compile("(?<=[^$]|^)\\$\\{([^}]*)\\}"); + + private static class PathAttribute + { + public final Path path; + public final String key; + + public PathAttribute(String key, Path path) throws IOException + { + this.key = key; + this.path = toCanonicalPath(path); + // TODO: Don't allow non-directory paths? (but what if the path doesn't exist?) + } + + public PathAttribute(String key, String systemPropertyKey) throws IOException + { + this(key, toCanonicalPath(System.getProperty(systemPropertyKey))); + } + + private static Path toCanonicalPath(String path) throws IOException + { + if (path == null) + { + return null; + } + return toCanonicalPath(FileSystems.getDefault().getPath(path)); + } + + private static Path toCanonicalPath(Path path) throws IOException + { + if (Files.exists(path)) + { + return path.toRealPath(); + } + return path.toAbsolutePath(); + } + } + + private static class PathAttributeComparator implements Comparator<PathAttribute> + { + @Override + public int compare(PathAttribute o1, PathAttribute o2) + { + if( (o1.path == null) && (o2.path != null) ) + { + return -1; + } + + if( (o1.path != null) && (o2.path == null) ) + { + return 1; + } + + return o2.path.getNameCount() - o1.path.getNameCount(); + } + } + + private List<PathAttribute> attributes = new ArrayList<>(); + public AttributeNormalizer(Resource baseResource) { try { - _warPath=baseResource==null?null:baseResource.getFile().toPath(); - _jettyBasePath=systemPath("jetty.base"); - _jettyHomePath=systemPath("jetty.home"); - _userHomePath=systemPath("user.home"); - _userDirPath=systemPath("user.dir"); + // Track path attributes for expansion + attributes.add(new PathAttribute("WAR", baseResource == null ? null : baseResource.getFile().toPath())); + attributes.add(new PathAttribute("jetty.base", "jetty.base")); + attributes.add(new PathAttribute("jetty.home", "jetty.home")); + attributes.add(new PathAttribute("user.home", "user.home")); + attributes.add(new PathAttribute("user.dir", "user.dir")); + + Collections.sort(attributes, new PathAttributeComparator()); } - catch(Exception e) + catch (Exception e) { throw new IllegalArgumentException(e); } } - - private static Path systemPath(String property) throws Exception - { - String p=System.getProperty(property); - if (p!=null) - return new File(p).getAbsoluteFile().getCanonicalFile().toPath(); - return null; - } - + public String normalize(Object o) { try { // Find a URI - URI uri=null; + URI uri = null; if (o instanceof URI) - uri=(URI)o; + uri = (URI)o; else if (o instanceof URL) uri = ((URL)o).toURI(); else if (o instanceof File) uri = ((File)o).toURI(); else { - String s=o.toString(); - uri=new URI(s); - if (uri.getScheme()==null) + String s = o.toString(); + uri = new URI(s); + if (uri.getScheme() == null) return s; } - + if ("jar".equalsIgnoreCase(uri.getScheme())) { String raw = uri.getRawSchemeSpecificPart(); - int bang=raw.indexOf("!/"); - String normal=normalize(raw.substring(0,bang)); - String suffix=raw.substring(bang); - return "jar:"+normal+suffix; + int bang = raw.indexOf("!/"); + String normal = normalize(raw.substring(0,bang)); + String suffix = raw.substring(bang); + return "jar:" + normal + suffix; } else if ("file".equalsIgnoreCase(uri.getScheme())) { - return "file:"+normalizePath(new File(uri).toPath()); + return "file:" + normalizePath(new File(uri).toPath()); } - + } - catch(Exception e) + catch (Exception e) { LOG.warn(e); } return String.valueOf(o); } - + public String normalizePath(Path path) { - if (_warPath!=null && path.startsWith(_warPath)) - return URIUtil.addPaths("${WAR}",_warPath.relativize(path).toString()); - if (_jettyBasePath!=null && path.startsWith(_jettyBasePath)) - return URIUtil.addPaths("${jetty.base}",_jettyBasePath.relativize(path).toString()); - if (_jettyHomePath!=null && path.startsWith(_jettyHomePath)) - return URIUtil.addPaths("${jetty.home}",_jettyHomePath.relativize(path).toString()); - if (_userHomePath!=null && path.startsWith(_userHomePath)) - return URIUtil.addPaths("${user.home}",_userHomePath.relativize(path).toString()); - if (_userDirPath!=null && path.startsWith(_userDirPath)) - return URIUtil.addPaths("${user.dir}",_userDirPath.relativize(path).toString()); - + for (PathAttribute attr : attributes) + { + if (attr.path == null) + continue; + + try + { + if (path.startsWith(attr.path) || path.equals(attr.path) || Files.isSameFile(path,attr.path)) + { + return URIUtil.addPaths("${" + attr.key + "}",attr.path.relativize(path).toString()); + } + } + catch (IOException ignore) + { + LOG.ignore(ignore); + } + } + return path.toString(); } - - - public String expand(String s) + + public String expand(String str) + { + return expand(str,new Stack<String>()); + } + + public String expand(String str, Stack<String> seenStack) + { + if (str == null) + { + return str; + } + + if (str.indexOf("${") < 0) + { + // Contains no potential expressions. + return str; + } + + Matcher mat = __propertyPattern.matcher(str); + StringBuilder expanded = new StringBuilder(); + int offset = 0; + String property; + String value; + + while (mat.find(offset)) + { + property = mat.group(1); + + // Loop detection + if (seenStack.contains(property)) + { + StringBuilder err = new StringBuilder(); + err.append("Property expansion loop detected: "); + int idx = seenStack.lastIndexOf(property); + for (int i = idx; i < seenStack.size(); i++) + { + err.append(seenStack.get(i)); + err.append(" -> "); + } + err.append(property); + throw new RuntimeException(err.toString()); + } + + seenStack.push(property); + + // find property name + expanded.append(str.subSequence(offset,mat.start())); + // get property value + value = getString(property); + if (value == null) + { + if(LOG.isDebugEnabled()) + LOG.debug("Unable to expand: {}",property); + expanded.append(mat.group()); + } + else + { + // recursively expand + value = expand(value,seenStack); + expanded.append(value); + } + // update offset + offset = mat.end(); + } + + // leftover + expanded.append(str.substring(offset)); + + // special case for "$$" + if (expanded.indexOf("$$") >= 0) + { + return expanded.toString().replaceAll("\\$\\$","\\$"); + } + + return expanded.toString(); + } + + private String getString(String property) { - int i=s.indexOf("${"); - if (i<0) - return s; - int e=s.indexOf('}',i+3); - String prop=s.substring(i+2,e); - switch(prop) + // Use known attributes first + for (PathAttribute attr : attributes) { - case "WAR": - return s.substring(0,i)+_warPath+expand(s.substring(e+1)); - case "jetty.base": - return s.substring(0,i)+_jettyBasePath+expand(s.substring(e+1)); - case "jetty.home": - return s.substring(0,i)+_jettyHomePath+expand(s.substring(e+1)); - case "user.home": - return s.substring(0,i)+_userHomePath+expand(s.substring(e+1)); - case "user.dir": - return s.substring(0,i)+_userDirPath+expand(s.substring(e+1)); - default: - return s; + if (attr.key.equalsIgnoreCase(property)) + { + return attr.path.toString(); + } } + + // Use system properties next + return System.getProperty(property); } } diff --git a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerTest.java b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerTest.java index 8121d63e8e..e0bec424b7 100644 --- a/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerTest.java +++ b/tests/test-quickstart/src/test/java/org/eclipse/jetty/quickstart/AttributeNormalizerTest.java @@ -18,12 +18,17 @@ package org.eclipse.jetty.quickstart; -import static org.junit.Assert.assertEquals; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -43,16 +48,50 @@ public class AttributeNormalizerTest public static List<String[]> data() { String[][] tests = { - { "WAR", "/opt/jetty-distro/demo.base/webapps/root" }, - { "jetty.home", "/opt/jetty-distro" }, - { "jetty.base", "/opt/jetty-distro/demo.base" }, - { "user.home", "/home/user" }, - { "user.dir", "/etc/init.d" }, + { "WAR", toSystemPath("/opt/jetty-distro/demo.base/webapps/root") }, + { "jetty.home", toSystemPath("/opt/jetty-distro") }, + { "jetty.base", toSystemPath("/opt/jetty-distro/demo.base") }, + { "user.home", toSystemPath("/home/user") }, + { "user.dir", toSystemPath("/etc/init.d") }, }; return Arrays.asList(tests); } + /** + * As the declared paths in this testcase might be actual paths on the system + * running these tests, the expected paths should be cleaned up to represent + * the actual system paths. + * <p> + * Eg: on fedora /etc/init.d is a symlink to /etc/rc.d/init.d + */ + private static String toSystemPath(String rawpath) + { + Path path = FileSystems.getDefault().getPath(rawpath); + if (Files.exists(path)) + { + // It exists, resolve it to the real path + try + { + path = path.toRealPath(); + } + catch (IOException e) + { + // something prevented us from resolving to real path, fallback to + // absolute path resolution (not as accurate) + path = path.toAbsolutePath(); + e.printStackTrace(); + } + } + else + { + // File doesn't exist, resolve to absolute path + // We can't rely on File.toCanonicalPath() here + path = path.toAbsolutePath(); + } + return path.toString(); + } + private static String origJettyBase; private static String origJettyHome; private static String origUserHome; @@ -97,108 +136,108 @@ public class AttributeNormalizerTest @Test public void testEqual() { - assertEquals("file:${" + key + "}",normalizer.normalize("file:" + path)); + assertThat(normalizer.normalize("file:" + path), is("file:${" + key + "}")); } @Test public void testEqualsSlash() { - assertEquals("file:${" + key + "}",normalizer.normalize("file:" + path + "/")); + assertThat(normalizer.normalize("file:" + path + "/"), is("file:${" + key + "}")); } @Test public void testEqualsSlashFile() { - assertEquals("file:${" + key + "}/file",normalizer.normalize("file:" + path + "/file")); + assertThat(normalizer.normalize("file:" + path + "/file"), is("file:${" + key + "}/file")); } @Test public void testURIEquals() throws URISyntaxException { - assertEquals("file:${" + key + "}",normalizer.normalize(new URI("file:" + path))); + assertThat(normalizer.normalize(new URI("file:" + path)), is("file:${" + key + "}")); } @Test public void testURIEqualsSlash() throws URISyntaxException { - assertEquals("file:${" + key + "}",normalizer.normalize(new URI("file:" + path + "/"))); + assertThat(normalizer.normalize(new URI("file:" + path + "/")), is("file:${" + key + "}")); } @Test public void testURIEqualsSlashFile() throws URISyntaxException { - assertEquals("file:${" + key + "}/file",normalizer.normalize(new URI("file:" + path + "/file"))); + assertThat(normalizer.normalize(new URI("file:" + path + "/file")), is("file:${" + key + "}/file")); } @Test public void testURLEquals() throws MalformedURLException { - assertEquals("file:${" + key + "}",normalizer.normalize(new URL("file:" + path))); + assertThat(normalizer.normalize(new URL("file:" + path)), is("file:${" + key + "}")); } @Test public void testURLEqualsSlash() throws MalformedURLException { - assertEquals("file:${" + key + "}",normalizer.normalize(new URL("file:" + path + "/"))); + assertThat(normalizer.normalize(new URL("file:" + path + "/")), is("file:${" + key + "}")); } @Test public void testURLEqualsSlashFile() throws MalformedURLException { - assertEquals("file:${" + key + "}/file",normalizer.normalize(new URL("file:" + path + "/file"))); + assertThat(normalizer.normalize(new URL("file:" + path + "/file")), is("file:${" + key + "}/file")); } @Test public void testJarFileEquals_BangFile() { - assertEquals("jar:file:${" + key + "}!/file",normalizer.normalize("jar:file:" + path + "!/file")); + assertThat(normalizer.normalize("jar:file:" + path + "!/file"), is("jar:file:${" + key + "}!/file")); } @Test public void testJarFileEquals_SlashBangFile() { - assertEquals("jar:file:${" + key + "}!/file",normalizer.normalize("jar:file:" + path + "/!/file")); + assertThat(normalizer.normalize("jar:file:" + path + "/!/file"), is("jar:file:${" + key + "}!/file")); } @Test public void testJarFileEquals_FileBangFile() { - assertEquals("jar:file:${" + key + "}/file!/file",normalizer.normalize("jar:file:" + path + "/file!/file")); + assertThat(normalizer.normalize("jar:file:" + path + "/file!/file"), is("jar:file:${" + key + "}/file!/file")); } @Test public void testJarFileEquals_URIBangFile() throws URISyntaxException { - assertEquals("jar:file:${" + key + "}!/file",normalizer.normalize(new URI("jar:file:" + path + "!/file"))); + assertThat(normalizer.normalize(new URI("jar:file:" + path + "!/file")), is("jar:file:${" + key + "}!/file")); } @Test public void testJarFileEquals_URISlashBangFile() throws URISyntaxException { - assertEquals("jar:file:${" + key + "}!/file",normalizer.normalize(new URI("jar:file:" + path + "/!/file"))); + assertThat(normalizer.normalize(new URI("jar:file:" + path + "/!/file")), is("jar:file:${" + key + "}!/file")); } @Test public void testJarFileEquals_URIFileBangFile() throws URISyntaxException { - assertEquals("jar:file:${" + key + "}/file!/file",normalizer.normalize(new URI("jar:file:" + path + "/file!/file"))); + assertThat(normalizer.normalize(new URI("jar:file:" + path + "/file!/file")), is("jar:file:${" + key + "}/file!/file")); } @Test public void testJarFileEquals_URLBangFile() throws MalformedURLException { - assertEquals("jar:file:${" + key + "}!/file",normalizer.normalize(new URL("jar:file:" + path + "!/file"))); + assertThat(normalizer.normalize(new URL("jar:file:" + path + "!/file")), is("jar:file:${" + key + "}!/file")); } @Test public void testJarFileEquals_URLSlashBangFile() throws MalformedURLException { - assertEquals("jar:file:${" + key + "}!/file",normalizer.normalize(new URL("jar:file:" + path + "/!/file"))); + assertThat(normalizer.normalize(new URL("jar:file:" + path + "/!/file")), is("jar:file:${" + key + "}!/file")); } @Test public void testJarFileEquals_URLFileBangFile() throws MalformedURLException { - assertEquals("jar:file:${" + key + "}/file!/file",normalizer.normalize(new URL("jar:file:" + path + "/file!/file"))); + assertThat(normalizer.normalize(new URL("jar:file:" + path + "/file!/file")), is("jar:file:${" + key + "}/file!/file")); } } |