diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 62dc7ab3b15..24796e989fa 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -312,18 +312,42 @@ public class Request implements HttpServletRequest maxFormContentSize = _context.getContextHandler().getMaxFormContentSize(); maxFormKeys = _context.getContextHandler().getMaxFormKeys(); } - else + + if (maxFormContentSize < 0) { - Number size = (Number)_connection.getConnector().getServer() - .getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); - maxFormContentSize = size == null?200000:size.intValue(); - Number keys = (Number)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys"); - maxFormKeys = keys == null?1000:keys.intValue(); + Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); + if (obj == null) + maxFormContentSize = 200000; + else if (obj instanceof Number) + { + Number size = (Number)obj; + maxFormContentSize = size.intValue(); + } + else if (obj instanceof String) + { + maxFormContentSize = Integer.valueOf((String)obj); + } + } + + if (maxFormKeys < 0) + { + Object obj = _connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys"); + if (obj == null) + maxFormKeys = 1000; + else if (obj instanceof Number) + { + Number keys = (Number)obj; + maxFormKeys = keys.intValue(); + } + else if (obj instanceof String) + { + maxFormKeys = Integer.valueOf((String)obj); + } } if (content_length > maxFormContentSize && maxFormContentSize > 0) { - throw new IllegalStateException("Form too large" + content_length + ">" + maxFormContentSize); + throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize); } InputStream in = getInputStream(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 10b9d1f1864..551169088d8 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -32,11 +32,12 @@ import java.util.Enumeration; import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.EnumSet; +import java.util.concurrent.CopyOnWriteArrayList; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; @@ -139,8 +140,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. private EventListener[] _eventListeners; private Logger _logger; private boolean _allowNullPathInfo; - private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue(); - private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",200000).intValue(); + private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",-1).intValue(); + private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",-1).intValue(); private boolean _compactPath = false; private boolean _aliases = false; @@ -150,6 +151,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. private Object _requestAttributeListeners; private Map _managedAttributes; private String[] _protectedTargets; + private final CopyOnWriteArrayList _aliasChecks = new CopyOnWriteArrayList(); private boolean _shutdown = false; private boolean _available = true; @@ -168,6 +170,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. _attributes = new AttributesMap(); _contextAttributes = new AttributesMap(); _initParams = new HashMap(); + addAliasCheck(new ApproveNonExistentDirectoryAliases()); } /* ------------------------------------------------------------ */ @@ -181,6 +184,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. _attributes = new AttributesMap(); _contextAttributes = new AttributesMap(); _initParams = new HashMap(); + addAliasCheck(new ApproveNonExistentDirectoryAliases()); } /* ------------------------------------------------------------ */ @@ -1565,14 +1569,23 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. path = URIUtil.canonicalPath(path); Resource resource = _baseResource.addPath(path); + // Is the resource aliased? if (!_aliases && resource.getAlias() != null) { - if (resource.exists()) - LOG.warn("Aliased resource: " + resource + "~=" + resource.getAlias()); - else if (path.endsWith("/") && resource.getAlias().toString().endsWith(path)) - return resource; - else if (LOG.isDebugEnabled()) + if (LOG.isDebugEnabled()) LOG.debug("Aliased resource: " + resource + "~=" + resource.getAlias()); + + // alias checks + for (Iterator i=_aliasChecks.iterator();i.hasNext();) + { + AliasCheck check = i.next(); + if (check.check(path,resource)) + { + if (LOG.isDebugEnabled()) + LOG.debug("Aliased resource: " + resource + " approved by " + check); + return resource; + } + } return null; } @@ -1653,6 +1666,25 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. return host; } + + /* ------------------------------------------------------------ */ + /** + * Add an AliasCheck instance to possibly permit aliased resources + * @param check The alias checker + */ + public void addAliasCheck(AliasCheck check) + { + _aliasChecks.add(check); + } + + /* ------------------------------------------------------------ */ + /** + * @return Mutable list of Alias checks + */ + public List getAliasChecks() + { + return _aliasChecks; + } /* ------------------------------------------------------------ */ /** @@ -2431,4 +2463,71 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } } + + + /* ------------------------------------------------------------ */ + /** Interface to check aliases + */ + public interface AliasCheck + { + /* ------------------------------------------------------------ */ + /** Check an alias + * @param path The path the aliased resource was created for + * @param resource The aliased resourced + * @return True if the resource is OK to be served. + */ + boolean check(String path, Resource resource); + } + + + /* ------------------------------------------------------------ */ + /** Approve Aliases with same suffix. + * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be + * approved because both the resource and alias end with ".html". + */ + public static class ApproveSameSuffixAliases implements AliasCheck + { + public boolean check(String path, Resource resource) + { + int dot = path.lastIndexOf('.'); + if (dot<0) + return false; + String suffix=path.substring(dot); + return resource.getAlias().toString().endsWith(suffix); + } + } + + + /* ------------------------------------------------------------ */ + /** Approve Aliases with a path prefix. + * Eg. a symbolic link from /dirA/foobar.html to /dirB/foobar.html would be + * approved because both the resource and alias end with "/foobar.html". + */ + public static class ApprovePathPrefixAliases implements AliasCheck + { + public boolean check(String path, Resource resource) + { + int slash = path.lastIndexOf('/'); + if (slash<0) + return false; + String suffix=path.substring(slash); + return resource.getAlias().toString().endsWith(suffix); + } + } + /* ------------------------------------------------------------ */ + /** Approve Aliases of a non existent directory. + * If a directory "/foobar/" does not exist, then the resource is + * aliased to "/foobar". Accept such aliases. + */ + public static class ApproveNonExistentDirectoryAliases implements AliasCheck + { + public boolean check(String path, Resource resource) + { + int slash = path.lastIndexOf('/'); + if (slash<0) + return false; + String suffix=path.substring(slash); + return resource.getAlias().toString().endsWith(suffix); + } + } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerAliasTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerAliasTest.java new file mode 100644 index 00000000000..1818f405e72 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerAliasTest.java @@ -0,0 +1,201 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.File; +import java.io.FilePermission; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.attribute.FileAttribute; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import junit.framework.Assert; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.resource.FileResource; +import org.eclipse.jetty.util.resource.Resource; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @version $Revision$ + */ +public class ContextHandlerAliasTest +{ + private Server _server; + private ContextHandler _ctx; + private File _tmp; + private File _dir; + + + @Before + public void before() throws Exception + { + _server=new Server(); + _ctx = new ContextHandler(); + _server.setHandler(_ctx); + + + _tmp = new File( System.getProperty( "basedir", "." ) + "/target/tmp/aliastests" ).getCanonicalFile(); + if (_tmp.exists()) + IO.delete(_tmp); + assertTrue(_tmp.mkdirs()); + + File root = new File(_tmp,getClass().getName()); + assertTrue(root.mkdir()); + + File webInf = new File(root,"WEB-INF"); + assertTrue(webInf.mkdir()); + + assertTrue(new File(webInf,"jsp").mkdir()); + assertTrue(new File(webInf,"web.xml").createNewFile()); + assertTrue(new File(root,"index.html").createNewFile()); + + _dir=root; + _ctx.setBaseResource(Resource.newResource(_dir)); + _server.start(); + } + + @After + public void after() throws Exception + { + _server.stop(); + if (_tmp!=null && _tmp.exists()) + IO.delete(_tmp); + } + + @Test + public void testGetResources() throws Exception + { + Resource r =_ctx.getResource("/index.html"); + Assert.assertTrue(r.exists()); + } + + @Test + public void testJvmNullBugAlias() throws Exception + { + // JVM Files ignores null characters at end of name + String normal="/index.html"; + String withnull="/index.html\u0000"; + + _ctx.setAliases(true); + Assert.assertTrue(_ctx.getResource(normal).exists()); + Assert.assertTrue(_ctx.getResource(withnull).exists()); + _ctx.setAliases(false); + Assert.assertTrue(_ctx.getResource(normal).exists()); + Assert.assertNull(_ctx.getResource(withnull)); + } + + @Test + public void testSymLinkToContext() throws Exception + { + File symlink = new File(_tmp,"symlink"); + try + { + Files.createSymbolicLink(symlink.toPath(),_dir.toPath()); + + _server.stop(); + _ctx.setBaseResource(FileResource.newResource(symlink)); + _ctx.setAliases(false); + _server.start(); + + Resource r =_ctx.getResource("/index.html"); + Assert.assertTrue(r.exists()); + } + finally + { + symlink.delete(); + } + } + + @Test + public void testSymLinkToContent() throws Exception + { + File symlink = new File(_dir,"link.html"); + try + { + Files.createSymbolicLink(symlink.toPath(),new File(_dir,"index.html").toPath()); + + _ctx.setAliases(true); + Assert.assertTrue(_ctx.getResource("/index.html").exists()); + Assert.assertTrue(_ctx.getResource("/link.html").exists()); + + _ctx.setAliases(false); + Assert.assertTrue(_ctx.getResource("/index.html").exists()); + Assert.assertNull(_ctx.getResource("/link.html")); + + } + finally + { + symlink.delete(); + } + } + + @Test + public void testSymLinkToContentWithSuffixCheck() throws Exception + { + File symlink = new File(_dir,"link.html"); + try + { + Files.createSymbolicLink(symlink.toPath(),new File(_dir,"index.html").toPath()); + + _ctx.setAliases(false); + _ctx.addAliasCheck(new ContextHandler.ApproveSameSuffixAliases()); + Assert.assertTrue(_ctx.getResource("/index.html").exists()); + Assert.assertTrue(_ctx.getResource("/link.html").exists()); + } + finally + { + symlink.delete(); + } + } + + @Test + public void testSymLinkToContentWithPathPrefixCheck() throws Exception + { + File symlink = new File(_dir,"dirlink"); + try + { + Files.createSymbolicLink(symlink.toPath(),new File(_dir,".").toPath()); + + _ctx.setAliases(false); + _ctx.addAliasCheck(new ContextHandler.ApprovePathPrefixAliases()); + Assert.assertTrue(_ctx.getResource("/index.html").exists()); + Assert.assertTrue(_ctx.getResource("/dirlink/index.html").exists()); + } + finally + { + symlink.delete(); + } + } +} diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java index 7d87278e553..086e7a4f358 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java @@ -205,7 +205,7 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory if (!aliases && !FileResource.getCheckAliases()) throw new IllegalStateException("Alias checking disabled"); if (aliases) - _servletContext.log("Aliases are enabled"); + _servletContext.log("Aliases are enabled! Security constraints may be bypassed!!!"); _useFileMappedBuffer=getInitBoolean("useFileMappedBuffer",_useFileMappedBuffer); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index d9c525b1f29..247150d9e0b 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -189,20 +189,6 @@ public abstract class Resource implements ResourceFactory } } - // Make sure that any special characters stripped really are ignorable. - String nurl=url.toString(); - if (nurl.length()>0 && nurl.charAt(nurl.length()-1)!=resource.charAt(resource.length()-1)) - { - if ((nurl.charAt(nurl.length()-1)!='/' || - nurl.charAt(nurl.length()-2)!=resource.charAt(resource.length()-1)) - && - (resource.charAt(resource.length()-1)!='/' || - resource.charAt(resource.length()-2)!=nurl.charAt(nurl.length()-1) - )) - { - return new BadResource(url,"Trailing special characters stripped by URL in "+resource); - } - } return newResource(url); } diff --git a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java index af27b996a56..cf1385975a6 100644 --- a/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java +++ b/jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppContextTest.java @@ -107,13 +107,8 @@ public class WebAppContextTest server.setHandler(context); server.start(); - // When ServletContext ctx = context.getServletContext(); - - // Then - // This passes: assertNotNull(ctx.getRealPath("/doesnotexist")); - // This fails: assertNotNull(ctx.getRealPath("/doesnotexist/")); } diff --git a/test-jetty-webapp/src/main/config/contexts/test.xml b/test-jetty-webapp/src/main/config/contexts/test.xml index 32e5b40584f..00372852e43 100644 --- a/test-jetty-webapp/src/main/config/contexts/test.xml +++ b/test-jetty-webapp/src/main/config/contexts/test.xml @@ -13,7 +13,6 @@ detected. - @@ -30,6 +29,19 @@ detected. /etc/webdefault.xml /contexts/test.d/override-web.xml + + + + + + + + + + + + +