refined PathResource alias handling

This commit is contained in:
Greg Wilkins 2014-08-02 12:58:24 +10:00
parent 1873b306b3
commit aaa2e5c6c1
9 changed files with 124 additions and 57 deletions

View File

@ -57,7 +57,7 @@ public class QuickStartConfiguration extends WebInfConfiguration
Resource webApp = context.newResource(war);
// Accept aliases for WAR files
if (webApp.getAlias() != null)
if (webApp.isAlias())
{
LOG.debug(webApp + " anti-aliased to " + webApp.getAlias());
webApp = context.newResource(webApp.getAlias());

View File

@ -26,6 +26,7 @@ import java.nio.file.Path;
import org.eclipse.jetty.server.handler.ContextHandler.AliasCheck;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.util.resource.Resource;
@ -41,56 +42,52 @@ public class AllowSymLinkAliasChecker implements AliasCheck
private static final Logger LOG = Log.getLogger(AllowSymLinkAliasChecker.class);
@Override
public boolean check(String path, Resource resource)
public boolean check(String uri, Resource resource)
{
// Only support PathResource alias checking
if (!(resource instanceof PathResource))
return false;
PathResource pathResource = (PathResource)resource;
try
{
File file =resource.getFile();
if (file==null)
return false;
Path path = pathResource.getPath();
// If the file exists
if (file.exists())
// is the file itself a symlink?
if (Files.isSymbolicLink(path) && Files.isSameFile(path,pathResource.getAliasPath()))
{
// we can use the real path method to check the symlinks resolve to the alias
URI real = file.toPath().toRealPath().toUri();
if (real.equals(resource.getAlias()))
if (LOG.isDebugEnabled())
LOG.debug("Allow symlink {} --> {}",resource,pathResource.getAliasPath());
return true;
}
// No, so let's check each element ourselves
Path d = path.getRoot();
for (Path e:path)
{
d=d.resolve(e);
while (Files.exists(d) && Files.isSymbolicLink(d))
{
if (LOG.isDebugEnabled())
LOG.debug("Allow symlink {} --> {}",resource,real);
return true;
Path link=Files.readSymbolicLink(d);
if (!link.isAbsolute())
link=d.resolve(link);
d=link;
}
}
else
if (pathResource.getAliasPath().equals(d))
{
// file does not exists, so we have to walk the path and links ourselves.
Path p = file.toPath().toAbsolutePath();
File d = p.getRoot().toFile();
for (Path e:p)
{
d=new File(d,e.toString());
while (d.exists() && Files.isSymbolicLink(d.toPath()))
{
Path link=Files.readSymbolicLink(d.toPath());
if (!link.isAbsolute())
link=link.resolve(d.toPath());
d=link.toFile().getAbsoluteFile().getCanonicalFile();
}
}
if (resource.getAlias().equals(d.toURI()))
{
if (LOG.isDebugEnabled())
LOG.debug("Allow symlink {} --> {}",resource,d);
return true;
}
if (LOG.isDebugEnabled())
LOG.debug("Allow path symlink {} --> {}",resource,d);
return true;
}
}
catch(Exception e)
{
e.printStackTrace();
LOG.ignore(e);
}
return false;
}

View File

@ -1668,7 +1668,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
public boolean checkAlias(String path, Resource resource)
{
// Is the resource aliased?
if (resource.getAlias() != null)
if (resource.isAlias())
{
if (LOG.isDebugEnabled())
LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias());

View File

@ -315,7 +315,7 @@ public class ResourceHandler extends HandlerWrapper
{
path=URIUtil.canonicalPath(path);
Resource r = base.addPath(path);
if (r!=null && r.getAlias()!=null && !_context.checkAlias(path, r))
if (r!=null && r.isAlias() && !_context.checkAlias(path, r))
return null;
return r;
}

View File

@ -33,6 +33,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.resource.Resource;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
@ -352,8 +353,8 @@ public class ContextHandlerGetResourceTest
@Test
public void testSymlinkKnown() throws Exception
{
if (!OS.IS_UNIX)
return;
Assume.assumeTrue(OS.IS_UNIX);
try
{
allowSymlinks.set(true);

View File

@ -50,36 +50,41 @@ import org.eclipse.jetty.util.log.Logger;
public class PathResource extends Resource
{
private static final Logger LOG = Log.getLogger(PathResource.class);
private final static LinkOption NO_FOLLOW_OPTIONS[] = new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
private final static LinkOption NO_FOLLOW_LINKS[] = new LinkOption[] { LinkOption.NOFOLLOW_LINKS };
private final static LinkOption FOLLOW_LINKS[] = new LinkOption[] {};
private final Path path;
private final URI alias;
private final Path alias;
private final URI uri;
private static final URI toAliasUri(final Path path)
private static final Path checkAliasPath(final Path path)
{
Path abs = path;
if (!abs.isAbsolute())
{
abs = path.toAbsolutePath();
}
URI providedUri = abs.toUri();
try
{
URI realUri = abs.toRealPath().toUri();
if (!providedUri.equals(realUri))
if (Files.isSymbolicLink(path))
return Files.readSymbolicLink(path);
if (Files.exists(path))
{
return realUri;
Path real = abs.toRealPath(FOLLOW_LINKS);
if (!abs.equals(real))
return real;
}
}
catch (NoSuchFileException e)
{
// Ignore
}
catch (IOException e)
catch (Exception e)
{
// TODO: reevaluate severity level
LOG.warn("bad alias ({}) for {}", e.getClass().getName(), e.getMessage());
return abs;
}
return null;
}
@ -93,7 +98,7 @@ public class PathResource extends Resource
{
this.path = path.toAbsolutePath();
this.uri = this.path.toUri();
this.alias = toAliasUri(path);
this.alias = checkAliasPath(path);
}
public PathResource(URI uri) throws IOException
@ -129,7 +134,7 @@ public class PathResource extends Resource
this.path = path.toAbsolutePath();
this.uri = path.toUri();
this.alias = toAliasUri(path);
this.alias = checkAliasPath(path);
}
public PathResource(URL url) throws IOException, URISyntaxException
@ -211,7 +216,7 @@ public class PathResource extends Resource
@Override
public boolean exists()
{
return Files.exists(path,NO_FOLLOW_OPTIONS);
return Files.exists(path,NO_FOLLOW_LINKS);
}
@Override
@ -220,6 +225,11 @@ public class PathResource extends Resource
return path.toFile();
}
public Path getPath() throws IOException
{
return path;
}
@Override
public InputStream getInputStream() throws IOException
{
@ -276,7 +286,7 @@ public class PathResource extends Resource
@Override
public boolean isDirectory()
{
return Files.isDirectory(path,NO_FOLLOW_OPTIONS);
return Files.isDirectory(path,NO_FOLLOW_LINKS);
}
@Override
@ -284,7 +294,7 @@ public class PathResource extends Resource
{
try
{
FileTime ft = Files.getLastModifiedTime(path,NO_FOLLOW_OPTIONS);
FileTime ft = Files.getLastModifiedTime(path,NO_FOLLOW_LINKS);
return ft.toMillis();
}
catch (IOException e)
@ -309,10 +319,21 @@ public class PathResource extends Resource
}
@Override
public URI getAlias()
public boolean isAlias()
{
return this.alias!=null;
}
public Path getAliasPath()
{
return this.alias;
}
@Override
public URI getAlias()
{
return this.alias==null?null:this.alias.toUri();
}
@Override
public String[] list()
@ -354,7 +375,7 @@ public class PathResource extends Resource
try
{
Path result = Files.move(path,destRes.path);
return Files.exists(result,NO_FOLLOW_OPTIONS);
return Files.exists(result,NO_FOLLOW_LINKS);
}
catch (IOException e)
{

View File

@ -475,6 +475,12 @@ public abstract class Resource implements ResourceFactory, Closeable
{
_associate=o;
}
/* ------------------------------------------------------------ */
public boolean isAlias()
{
return getAlias()!=null;
}
/* ------------------------------------------------------------ */
/**

View File

@ -134,7 +134,7 @@ public class FileSystemResourceTest
public boolean matches(Object item)
{
final Resource res = (Resource)item;
return res.getAlias() == null;
return !res.isAlias();
}
@Override
@ -497,6 +497,48 @@ public class FileSystemResourceTest
assertThat("file.alias", newResource(resBar.getFile()), isAliasFor(resFoo));
}
}
@Test
public void testNonExistantSymlink() throws Exception
{
File dir = testdir.getDir();
Path foo = new File(dir, "foo").toPath();
Path bar = new File(dir, "bar").toPath();
try
{
Files.createSymbolicLink(bar,foo);
}
catch (UnsupportedOperationException | FileSystemException e)
{
// if unable to create symlink, no point testing the rest
// this is the path that Microsoft Windows takes.
assumeNoException(e);
}
try (Resource base = newResource(testdir.getDir()))
{
// FileResource does not pass this test!
assumeFalse(base instanceof FileResource);
Resource resFoo = base.addPath("foo");
Resource resBar = base.addPath("bar");
assertThat("resFoo.uri", resFoo.getURI(), is(foo.toUri()));
// Access to the same resource, but via a symlink means that they are not equivalent
assertThat("foo.equals(bar)", resFoo.equals(resBar), is(false));
assertThat("resource.alias", resFoo, hasNoAlias());
assertThat("resource.uri.alias", newResource(resFoo.getURI()), hasNoAlias());
assertThat("resource.file.alias", newResource(resFoo.getFile()), hasNoAlias());
assertThat("alias", resBar, isAliasFor(resFoo));
assertThat("uri.alias", newResource(resBar.getURI()), isAliasFor(resFoo));
assertThat("file.alias", newResource(resBar.getFile()), isAliasFor(resFoo));
}
}
@Test

View File

@ -402,7 +402,7 @@ public class WebInfConfiguration extends AbstractConfiguration
throw new IllegalStateException("No resourceBase or war set for context");
// Accept aliases for WAR files
if (web_app.getAlias() != null)
if (web_app.isAlias())
{
LOG.debug(web_app + " anti-aliased to " + web_app.getAlias());
web_app = context.newResource(web_app.getAlias());