diff --git a/VERSION.txt b/VERSION.txt index a1354afdaba..932b7a30b2e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -38,6 +38,37 @@ jetty-8.1.12.v20130726 - 26 July 2013 + 413684 Trailing slash shows JSP source + 413812 Make RateTracker serializable +jetty-7.6.12.v20130726 - 26 July 2013 + + 396706 CGI support parameters + + 397193 MongoSessionManager refresh updates last access time + + 407342 ReloadedSessionMissingClassTest uses class compiled with jdk7 + + 408529 Etags set in 304 response + + 408600 set correct jetty.url in all pom files + + 408642 setContentType from addHeader + + 408662 In pax-web servlet services requests even if init() has not finished + running + + 408909 GzipFilter setting of headers when reset and/or not compressed + + 409028 Jetty HttpClient does not work with proxy CONNECT method. + + 409133 Empty causes StackOverflowError + + 409556 FileInputStream not closed in DirectNIOBuffer + + 410630 MongoSessionManager conflicting session update op + + 410750 NoSQLSessions: implement session context data persistence across + server restarts + + 411135 HttpClient may send proxied https requests to the proxy instead of + the target server. + + 411216 RequestLogHandler handles async completion + + 411458 MultiPartFilter getParameterMap doesn't preserve multivalued + parameters 411459 MultiPartFilter.Wrapper getParameter should use charset + encoding of part + + 411755 MultiPartInputStreamParser fails on base64 encoded content + + 411909 GzipFilter flushbuffer() results in erroneous finish() call + + 412712 HttpClient does not send the terminal chunk after partial writes. + + 412750 HttpClient close expired connections fix + + 413371 Default JSON.Converters for List and Set. + + 413372 JSON Enum uses name rather than toString() + + 413684 Trailing slash shows JSP source + + 413812 Make RateTracker serializable + jetty-8.1.11.v20130520 - 20 May 2013 + 402844 STOP.PORT & STOP.KEY behaviour has changed + 403281 jetty.sh waits for started or failure before returning diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java index d71aa2da4c6..2b8aedb460e 100644 --- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java +++ b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/DumpServlet.java @@ -43,6 +43,11 @@ public class DumpServlet extends HttpServlet response.getWriter().println("servletPath=" + request.getServletPath()); response.getWriter().println("pathInfo=" + request.getPathInfo()); response.getWriter().println("session=" + request.getSession(true).getId()); + + String r=request.getParameter("resource"); + if (r!=null) + response.getWriter().println("resource("+r+")=" + getServletContext().getResource(r)); + response.getWriter().println(""); } } \ No newline at end of file diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index 0d81261baf9..f97ca2e059a 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -76,6 +76,11 @@ junit test + + org.eclipse.jetty.toolchain + jetty-test-helper + test + org.eclipse.jetty jetty-jndi diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java index 84e94ddb41a..9f5808620c9 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java @@ -25,12 +25,9 @@ import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; @@ -733,10 +730,10 @@ public class AnnotationParser public void parse (Resource dir, ClassNameResolver resolver) throws Exception { - if (!dir.isDirectory() || !dir.exists()) + if (!dir.isDirectory() || !dir.exists() || dir.getName().startsWith(".")) return; - + String[] files=dir.list(); for (int f=0;files!=null && f + *
  • the name ends with .class
  • + *
  • it isn't a dot file or in a hidden directory
  • + *
  • the name of the class at least begins with a valid identifier for a class name
  • + * + * @param path + * @return + */ + private boolean isValidClassFileName (String name) + { + //no name cannot be valid + if (name == null || name.length()==0) + return false; + + //skip anything that is not a class file + if (!name.toLowerCase(Locale.ENGLISH).endsWith(".class")) + { + if (LOG.isDebugEnabled()) LOG.debug("Not a class: {}",name); + return false; + } + + //skip any classfiles that are not a valid java identifier + int c0 = 0; + int ldir = name.lastIndexOf('/', name.length()-6); + c0 = (ldir > -1 ? ldir+1 : c0); + if (!Character.isJavaIdentifierStart(name.charAt(c0))) + { + if (LOG.isDebugEnabled()) LOG.debug("Not a java identifier: {}"+name); + return false; + } + + return true; + } + + + + /** + * Check that the given path does not contain hidden directories + * + * @param path + * @return + */ + private boolean isValidClassFilePath (String path) + { + //no path is not valid + if (path == null || path.length()==0) + return false; + + + //skip any classfiles that are in a hidden directory + if (path.startsWith(".") || path.contains("/.")) + { + if (LOG.isDebugEnabled()) LOG.debug("Contains hidden dirs: {}"+path); + return false; + } + + return true; + } } diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java index 567a0f30c0b..22cc1ecaea9 100644 --- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationParser.java @@ -18,11 +18,25 @@ package org.eclipse.jetty.annotations; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler; import org.eclipse.jetty.annotations.AnnotationParser.Value; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.TestingDir; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.resource.Resource; +import org.junit.Rule; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -31,6 +45,56 @@ import static org.junit.Assert.fail; public class TestAnnotationParser { + + @Rule + public TestingDir testdir = new TestingDir(); + + + + public static class TrackingAnnotationHandler implements DiscoverableAnnotationHandler + { + + private final String annotationName; + public final Set foundClasses; + + public TrackingAnnotationHandler(String annotationName) + { + this.annotationName = annotationName; + this.foundClasses = new HashSet(); + } + + + public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation, + List values) + { + foundClasses.add(className); + } + + + public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation, + List values) + { + /* ignore */ + } + + + public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, + List values) + { + /* ignore */ + } + + + @Override + public String getAnnotationName() + { + return this.annotationName; + } + } + + + + @Test public void testSampleAnnotation() throws Exception { @@ -51,35 +115,20 @@ public class TestAnnotationParser } public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, - List values) + List values) { - assertEquals ("m", fieldName); - assertEquals (org.objectweb.asm.Type.OBJECT, org.objectweb.asm.Type.getType(fieldType).getSort()); - assertEquals (1, values.size()); - Value anv1 = values.get(0); - assertEquals ("value", anv1.getName()); - assertEquals (7, anv1.getValue()); + assertEquals ("m", fieldName); + assertEquals (org.objectweb.asm.Type.OBJECT, org.objectweb.asm.Type.getType(fieldType).getSort()); + assertEquals (1, values.size()); + Value anv1 = values.get(0); + assertEquals ("value", anv1.getName()); + assertEquals (7, anv1.getValue()); } public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation, List values) { - System.err.println("Sample annotated method : classname="+className+" methodName="+methodName+" access="+access+" desc="+desc+" signature="+signature); - - org.objectweb.asm.Type retType = org.objectweb.asm.Type.getReturnType(desc); - System.err.println("REturn type = "+retType); - org.objectweb.asm.Type[] params = org.objectweb.asm.Type.getArgumentTypes(desc); - if (params == null) - System.err.println("No params"); - else - System.err.println(params.length+" params"); - - if (exceptions == null) - System.err.println("No exceptions"); - else - System.err.println(exceptions.length+" exceptions"); - assertEquals("org.eclipse.jetty.annotations.ClassA", className); assertTrue(methods.contains(methodName)); assertEquals("org.eclipse.jetty.annotations.Sample", annotation); @@ -109,8 +158,7 @@ public class TestAnnotationParser }); long end = System.currentTimeMillis(); - - System.err.println("Time to parse class: "+((end-start))); + //System.err.println("Time to parse class: "+((end-start))); } @Test @@ -125,11 +173,6 @@ public class TestAnnotationParser List values) { assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className)); - - for (Value anv: values) - { - System.err.println(anv.toString()); - } } public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, @@ -144,10 +187,6 @@ public class TestAnnotationParser { assertTrue("org.eclipse.jetty.annotations.ClassB".equals(className)); assertTrue("a".equals(methodName)); - for (Value anv: values) - { - System.err.println(anv.toString()); - } } @Override @@ -162,4 +201,67 @@ public class TestAnnotationParser parser.registerHandler(new MultiAnnotationHandler()); parser.parse(classNames, null); } + + + @Test + public void testHiddenFilesInJar () throws Exception + { + File badClassesJar = MavenTestingUtils.getTestResourceFile("bad-classes.jar"); + AnnotationParser parser = new AnnotationParser(); + parser.parse(badClassesJar.toURI(), null); + //only the valid classes inside bad-classes.jar should be parsed. If any invalid classes are parsed and exception would be thrown here + } + + @Test + public void testBasedirExclusion() throws Exception + { + // Build up basedir, which itself has a path segment that violates java package and classnaming. + // The basedir should have no effect on annotation scanning. + // Intentionally using a base director name that starts with a "." + // This mimics what you see in jenkins, hudson, hadoop, solr, camel, and selenium for their + // installed and/or managed webapps + File basedir = testdir.getFile(".base/workspace/classes"); + FS.ensureEmpty(basedir); + + // Copy in class that is known to have annotations. + copyClass(ClassA.class,basedir); + + // Setup Tracker + TrackingAnnotationHandler tracker = new TrackingAnnotationHandler(Sample.class.getName()); + + // Setup annotation scanning + AnnotationParser parser = new AnnotationParser(); + parser.registerHandler(tracker); + + // Parse + parser.parse(Resource.newResource(basedir),null); + + // Validate + assertTrue(tracker.foundClasses.contains(ClassA.class.getName())); + } + + private void copyClass(Class clazz, File basedir) throws IOException + { + String classname = clazz.getName().replace('.',File.separatorChar) + ".class"; + URL url = this.getClass().getResource('/'+classname); + assertTrue(url != null); + + String classpath = classname.substring(0,classname.lastIndexOf(File.separatorChar)); + FS.ensureDirExists(new File(basedir,classpath)); + + InputStream in = null; + OutputStream out = null; + try + { + in = url.openStream(); + out = new FileOutputStream(new File(basedir,classname)); + IO.copy(in,out); + } + finally + { + IO.close(out); + IO.close(in); + } + } + } diff --git a/jetty-annotations/src/test/resources/bad-classes.jar b/jetty-annotations/src/test/resources/bad-classes.jar new file mode 100644 index 00000000000..5538c18b552 Binary files /dev/null and b/jetty-annotations/src/test/resources/bad-classes.jar differ diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java index fa07f7a8f13..1e33328aca4 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/providers/ContextProvider.java @@ -27,6 +27,7 @@ import org.eclipse.jetty.deploy.ConfigurationManager; import org.eclipse.jetty.deploy.util.FileID; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.xml.XmlConfiguration; /** Context directory App Provider. @@ -37,6 +38,8 @@ import org.eclipse.jetty.xml.XmlConfiguration; public class ContextProvider extends ScanningAppProvider { private ConfigurationManager _configurationManager; + private boolean _parentLoaderPriority = false; + private String _defaultsDescriptor; public ContextProvider() { @@ -79,7 +82,22 @@ public class ContextProvider extends ScanningAppProvider if (resource.exists() && FileID.isXmlFile(file)) { - XmlConfiguration xmlc = new XmlConfiguration(resource.getURL()); + XmlConfiguration xmlc = new XmlConfiguration(resource.getURL()) + { + @Override + public void initializeDefaults(Object context) + { + super.initializeDefaults(context); + + if (context instanceof WebAppContext) + { + WebAppContext webapp = (WebAppContext)context; + webapp.setParentLoaderPriority(_parentLoaderPriority); + if (_defaultsDescriptor!=null) + webapp.setDefaultsDescriptor(_defaultsDescriptor); + } + } + }; xmlc.getIdMap().put("Server",getDeploymentManager().getServer()); if (getConfigurationManager() != null) @@ -89,5 +107,44 @@ public class ContextProvider extends ScanningAppProvider throw new IllegalStateException("App resouce does not exist "+resource); } + + /* ------------------------------------------------------------ */ + /** Get the parentLoaderPriority. + * @return the parentLoaderPriority + */ + public boolean isParentLoaderPriority() + { + return _parentLoaderPriority; + } + + /* ------------------------------------------------------------ */ + /** Set the parentLoaderPriority. + *

    If the context created is a WebAppContext, then set the + * default value for {@link WebAppContext#setParentLoaderPriority(boolean)}. + * @param parentLoaderPriority the parentLoaderPriority to set + */ + public void setParentLoaderPriority(boolean parentLoaderPriority) + { + _parentLoaderPriority = parentLoaderPriority; + } + /* ------------------------------------------------------------ */ + /** Get the defaultsDescriptor. + * @return the defaultsDescriptor + */ + public String getDefaultsDescriptor() + { + return _defaultsDescriptor; + } + + /* ------------------------------------------------------------ */ + /** Set the defaultsDescriptor. + *

    If the context created is a WebAppContext, then set the + * default value for {@link WebAppContext#setDefaultsDescriptor(String)} + * @param defaultsDescriptor the defaultsDescriptor to set + */ + public void setDefaultsDescriptor(String defaultsDescriptor) + { + _defaultsDescriptor = defaultsDescriptor; + } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 1cdea65178f..be51abbb47b 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -414,6 +414,9 @@ public class PathMap extends HashMap implements Externalizable public static boolean match(String pathSpec, String path, boolean noDefault) throws IllegalArgumentException { + if (pathSpec.length()==0) + return "/".equals(path); + char c = pathSpec.charAt(0); if (c=='/') { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java index 20dd0c804df..7402275a321 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/PathMapTest.java @@ -133,6 +133,8 @@ public class PathMapTest extends TestCase assertTrue("!match *.foo", !PathMap.match("*.foo", "anything.bar")); assertEquals("match / with ''", "10", p.getMatch("/").getValue()); + + assertTrue("match \"\"", PathMap.match("", "/")); } /** diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java index c0784d91dc4..58861e021ea 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/NoSqlSession.java @@ -63,16 +63,57 @@ public class NoSqlSession extends AbstractSession { synchronized (this) { - if (_dirty==null) - _dirty=new HashSet(); - _dirty.add(name); Object old = super.doPutOrRemove(name,value); + if (_manager.getSavePeriod()==-2) + { save(true); + } return old; } } + + + @Override + public void setAttribute(String name, Object value) + { + if ( updateAttribute(name,value) ) + { + if (_dirty==null) + { + _dirty=new HashSet(); + } + + _dirty.add(name); + } + } + + /* + * a boolean version of the setAttribute method that lets us manage the _dirty set + */ + protected boolean updateAttribute (String name, Object value) + { + Object old=null; + synchronized (this) + { + checkValid(); + old=doPutOrRemove(name,value); + } + + if (value==null || !value.equals(old)) + { + if (old!=null) + unbindValue(name,old); + if (value!=null) + bindValue(name,value); + + _manager.doSessionAttributeListeners(this,name,old,value); + return true; + } + return false; + } + /* ------------------------------------------------------------ */ @Override protected void checkValid() throws IllegalStateException diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java index 5ecf5256bd9..ab5ee46aa7d 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/jaas/spi/PropertyFileLoginModule.java @@ -20,11 +20,10 @@ package org.eclipse.jetty.plus.jaas.spi; import java.security.Principal; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import javax.security.auth.Subject; import javax.security.auth.callback.CallbackHandler; @@ -46,7 +45,7 @@ public class PropertyFileLoginModule extends AbstractLoginModule private static final Logger LOG = Log.getLogger(PropertyFileLoginModule.class); - private static Map _propertyUserStores = new HashMap(); + private static ConcurrentHashMap _propertyUserStores = new ConcurrentHashMap(); private int _refreshInterval = 0; private String _filename = DEFAULT_FILENAME; @@ -69,31 +68,35 @@ public class PropertyFileLoginModule extends AbstractLoginModule private void setupPropertyUserStore(Map options) { + parseConfig(options); + if (_propertyUserStores.get(_filename) == null) { - parseConfig(options); + PropertyUserStore propertyUserStore = new PropertyUserStore(); + propertyUserStore.setConfig(_filename); + propertyUserStore.setRefreshInterval(_refreshInterval); - PropertyUserStore _propertyUserStore = new PropertyUserStore(); - _propertyUserStore.setConfig(_filename); - _propertyUserStore.setRefreshInterval(_refreshInterval); - LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval); - - try + PropertyUserStore prev = _propertyUserStores.putIfAbsent(_filename, propertyUserStore); + if (prev == null) { - _propertyUserStore.start(); - } - catch (Exception e) - { - LOG.warn("Exception while starting propertyUserStore: ",e); - } + LOG.debug("setupPropertyUserStore: Starting new PropertyUserStore. PropertiesFile: " + _filename + " refreshInterval: " + _refreshInterval); - _propertyUserStores.put(_filename,_propertyUserStore); + try + { + propertyUserStore.start(); + } + catch (Exception e) + { + LOG.warn("Exception while starting propertyUserStore: ",e); + } + } } } private void parseConfig(Map options) { - _filename = (String)options.get("file") != null?(String)options.get("file"):DEFAULT_FILENAME; + _filename = (String)options.get("file"); + _filename = (_filename == null? DEFAULT_FILENAME : _filename); String refreshIntervalString = (String)options.get("refreshInterval"); _refreshInterval = refreshIntervalString == null?_refreshInterval:Integer.parseInt(refreshIntervalString); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index 2ab4e6c9f09..33db3ac1899 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -23,11 +23,13 @@ import java.io.PrintWriter; import java.util.Collection; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.Enumeration; import java.util.Locale; import javax.servlet.RequestDispatcher; import javax.servlet.ServletOutputStream; +import javax.servlet.SessionTrackingMode; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; @@ -213,7 +215,7 @@ public class Response implements HttpServletResponse return null; // should not encode if cookies in evidence - if (request.isRequestedSessionIdFromCookie()) + if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) { int prefix=url.indexOf(sessionURLPrefix); if (prefix!=-1) 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 8a84167fe9b..414ac95474c 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 @@ -1579,7 +1579,7 @@ 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) { @@ -2474,7 +2474,6 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. } } } - } @@ -2498,8 +2497,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. * Eg. a symbolic link from /foobar.html to /somewhere/wibble.html would be * approved because both the resource and alias end with ".html". */ + @Deprecated public static class ApproveSameSuffixAliases implements AliasCheck { + { + LOG.warn("ApproveSameSuffixAlias is not safe for production"); + } + public boolean check(String path, Resource resource) { int dot = path.lastIndexOf('.'); @@ -2516,8 +2520,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. * 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". */ + @Deprecated public static class ApprovePathPrefixAliases implements AliasCheck { + { + LOG.warn("ApprovePathPrefixAliases is not safe for production"); + } + public boolean check(String path, Resource resource) { int slash = path.lastIndexOf('/'); @@ -2527,6 +2536,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. return resource.toString().endsWith(suffix); } } + /* ------------------------------------------------------------ */ /** Approve Aliases of a non existent directory. * If a directory "/foobar/" does not exist, then the resource is @@ -2536,11 +2546,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { public boolean check(String path, Resource resource) { - int slash = path.lastIndexOf('/'); - if (slash<0 || resource.exists()) + if (resource.exists()) return false; - String suffix=path.substring(slash); - return resource.getAlias().toString().endsWith(suffix); + + String a=resource.getAlias().toString(); + String r=resource.getURL().toString(); + + if (a.length()>r.length()) + return a.startsWith(r) && a.length()==r.length()+1 && a.endsWith("/"); + else + return r.startsWith(a) && r.length()==a.length()+1 && r.endsWith("/"); } } + } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java index d115f542be2..2fea87b8cee 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/RequestLogHandler.java @@ -69,25 +69,28 @@ public class RequestLogHandler extends HandlerWrapper } finally { - if (continuation.isAsync()) + if (_requestLog != null && baseRequest.getDispatcherType().equals(DispatcherType.REQUEST)) { - if (continuation.isInitial()) - continuation.addContinuationListener(new ContinuationListener() - { - - public void onTimeout(Continuation continuation) + if (continuation.isAsync()) + { + if (continuation.isInitial()) + continuation.addContinuationListener(new ContinuationListener() { - - } - - public void onComplete(Continuation continuation) - { - _requestLog.log(baseRequest, (Response)response); - } - }); + + public void onTimeout(Continuation continuation) + { + + } + + public void onComplete(Continuation continuation) + { + _requestLog.log(baseRequest, (Response)response); + } + }); + } + else + _requestLog.log(baseRequest, (Response)response); } - else - _requestLog.log(baseRequest, (Response)response); } } diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java index d79fa4809e4..9b99186eb98 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java @@ -757,7 +757,39 @@ public class MultipartFilterTest assertTrue(response.getContent().contains("aaaa,bbbbb")); } - + @Test + public void testBufferOverflowNoCRLF () throws Exception + { + String boundary="XyXyXy"; + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + tester.addServlet(BoundaryServlet.class,"/testb"); + tester.setAttribute("fileName", "abc"); + tester.setAttribute("desc", "123"); + tester.setAttribute("title", "ttt"); + request.setMethod("POST"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/testb"); + request.setHeader("Content-Type","multipart/form-data; boundary="+boundary); + + String content = "--XyXyXy"; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write(content.getBytes()); + + for (int i=0; i< 8500; i++) //create content that will overrun default buffer size of BufferedInputStream + { + baos.write('a'); + } + request.setContent(baos.toString()); + + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getContent().contains("Buffer size exceeded")); + assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus()); + } + /* * see the testParameterMap test * diff --git a/jetty-spdy/spdy-core/pom.xml b/jetty-spdy/spdy-core/pom.xml index 218da99801d..935c4846da6 100644 --- a/jetty-spdy/spdy-core/pom.xml +++ b/jetty-spdy/spdy-core/pom.xml @@ -6,9 +6,9 @@ 8.1.13-SNAPSHOT - 4.0.0 - spdy-core - Jetty :: SPDY :: Core + 4.0.0 + spdy-core + Jetty :: SPDY :: Core http://www.eclipse.org/jetty @@ -16,21 +16,21 @@ jetty-util ${project.version} - - junit - junit - test - - - org.hamcrest - hamcrest-all - test - - - org.mockito - mockito-core - test - + + junit + junit + test + + + org.hamcrest + hamcrest-library + test + + + org.mockito + mockito-core + test + org.slf4j slf4j-log4j12 diff --git a/jetty-spdy/spdy-jetty/pom.xml b/jetty-spdy/spdy-jetty/pom.xml index 57e63f81c2e..bc3e4a53920 100644 --- a/jetty-spdy/spdy-jetty/pom.xml +++ b/jetty-spdy/spdy-jetty/pom.xml @@ -64,13 +64,13 @@ junit junit - test + test - org.hamcrest - hamcrest-all - test - + org.hamcrest + hamcrest-library + test + org.slf4j slf4j-log4j12 diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java index bd021f0c696..03fa55c2b01 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java @@ -486,7 +486,16 @@ public class MultiPartInputStream byte[] byteBoundary=(boundary+"--").getBytes(StringUtil.__ISO_8859_1); // Get first boundary - String line=((ReadLineInputStream)_in).readLine(); + String line = null; + try + { + line=((ReadLineInputStream)_in).readLine(); + } + catch (IOException e) + { + LOG.warn("Badly formatted multipart request"); + throw e; + } if (line == null) throw new IOException("Missing content for multipart request"); @@ -723,7 +732,6 @@ public class MultiPartInputStream } finally { - part.close(); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java index bb4c9fbc877..1aeec5fbb60 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java @@ -49,6 +49,10 @@ public class ReadLineInputStream extends BufferedInputStream while (true) { int b=super.read(); + + if (markpos < 0) + throw new IOException("Buffer size exceeded: no line terminator"); + if (b==-1) { int m=markpos; diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java index add7e67b0c4..9e0faec884e 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java @@ -44,7 +44,6 @@ import org.eclipse.jetty.util.log.Logger; * This class can check for aliasing in the filesystem (eg case * insensitivity). By default this is turned on, or it can be controlled * by calling the static method @see FileResource#setCheckAliases(boolean) - * * */ public class FileResource extends URLResource @@ -167,15 +166,16 @@ public class FileResource extends URLResource r=(URLResource)Resource.newResource(url); } + // Check for encoding aliases + // The encoded path should be a suffix of the resource (give or take a directory / ) String encoded=URIUtil.encodePath(path); int expected=r.toString().length()-encoded.length(); int index = r._urlString.lastIndexOf(encoded, expected); - if (expected!=index && ((expected-1)!=index || path.endsWith("/") || !r.isDirectory())) { - if (!(r instanceof BadResource)) + if (r instanceof FileResource) { - ((FileResource)r)._alias=new URL(url); + ((FileResource)r)._alias=((FileResource)r)._file.getCanonicalFile().toURI().toURL(); ((FileResource)r)._aliasChecked=true; } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java index ecda413dcc4..dceddd9a4e9 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java @@ -521,7 +521,35 @@ public class MultiPartInputStreamTest extends TestCase assertThat(baos.toString("UTF-8"), is("Other")); } - + + public void testBufferOverflowNoCRLF () throws Exception + { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + baos.write("--AaB03x".getBytes()); + for (int i=0; i< 8500; i++) //create content that will overrun default buffer size of BufferedInputStream + { + baos.write('a'); + } + + MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); + MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(baos.toByteArray()), + _contentType, + config, + _tmpDir); + mpis.setDeleteOnExit(true); + try + { + mpis.getParts(); + fail ("Multipart buffer overrun"); + } + catch (IOException e) + { + assertTrue(e.getMessage().startsWith("Buffer size exceeded")); + } + + } + + public void testCharsetEncoding () throws Exception { String contentType = "multipart/form-data; boundary=TheBoundary; charset=ISO-8859-1"; diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 820bb13c4df..9e758997988 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -329,7 +329,9 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor if (jsp_file != null) { holder.setForcedPath(jsp_file); - holder.setClassName(jspServletClass); //only use our default instance + ServletHolder jsp=context.getServletHandler().getServlet("jsp"); + if (jsp!=null) + holder.setClassName(jsp.getClassName()); } // handle load-on-startup @@ -673,16 +675,43 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor // // this is additive across web-fragments Iterator iter = node.iterator("tracking-mode"); - Set modes = new HashSet(); - modes.addAll(context.getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes()); - while (iter.hasNext()) - { - XmlParser.Node mNode = (XmlParser.Node) iter.next(); - String trackMode = mNode.toString(false, true); - modes.add(SessionTrackingMode.valueOf(trackMode)); + if (iter.hasNext()) + { + Set modes = null; + Origin o = context.getMetaData().getOrigin("session.tracking-mode"); + switch (o) + { + case NotSet://not previously set, starting fresh + case WebDefaults://previously set in web defaults, allow this descriptor to start fresh + { + + modes = new HashSet(); + context.getMetaData().setOrigin("session.tracking-mode", descriptor); + break; + } + case WebXml: + case WebFragment: + case WebOverride: + { + //if setting from an override descriptor, start afresh, otherwise add-in tracking-modes + if (descriptor instanceof OverrideDescriptor) + modes = new HashSet(); + else + modes = new HashSet(context.getSessionHandler().getSessionManager().getEffectiveSessionTrackingModes()); + context.getMetaData().setOrigin("session.tracking-mode", descriptor); + break; + } + } + + while (iter.hasNext()) + { + XmlParser.Node mNode = (XmlParser.Node) iter.next(); + String trackMode = mNode.toString(false, true); + modes.add(SessionTrackingMode.valueOf(trackMode)); + } + context.getSessionHandler().getSessionManager().setSessionTrackingModes(modes); } - context.getSessionHandler().getSessionManager().setSessionTrackingModes(modes); - + //Servlet Spec 3.0 // diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java index f2bd3803496..13613484795 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java @@ -87,7 +87,10 @@ public class WebInfConfiguration extends AbstractConfiguration context.getMetaData().addContainerJar(Resource.newResource(uri)); } }; - ClassLoader loader = context.getClassLoader(); + ClassLoader loader = null; + if (context.getClassLoader() != null) + loader = context.getClassLoader().getParent(); + while (loader != null && (loader instanceof URLClassLoader)) { URL[] urls = ((URLClassLoader)loader).getURLs(); diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java index dedaa735aa3..6c72bfc17f4 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketGeneratorRFC6455.java @@ -19,14 +19,16 @@ package org.eclipse.jetty.websocket; import java.io.IOException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; -/* ------------------------------------------------------------ */ -/** WebSocketGenerator. +/** + * WebSocketGenerator. * This class generates websocket packets. * It is fully synchronized because it is likely that async * threads will call the addMessage methods while other @@ -34,214 +36,263 @@ import org.eclipse.jetty.io.EofException; */ public class WebSocketGeneratorRFC6455 implements WebSocketGenerator { - final private WebSocketBuffers _buffers; - final private EndPoint _endp; + private final Lock _lock = new ReentrantLock(); + private final WebSocketBuffers _buffers; + private final EndPoint _endp; + private final byte[] _mask = new byte[4]; + private final MaskGen _maskGen; private Buffer _buffer; - private final byte[] _mask=new byte[4]; private int _m; private boolean _opsent; - private final MaskGen _maskGen; private boolean _closed; public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp) { - _buffers=buffers; - _endp=endp; - _maskGen=null; + this(buffers, endp, null); } public WebSocketGeneratorRFC6455(WebSocketBuffers buffers, EndPoint endp, MaskGen maskGen) { - _buffers=buffers; - _endp=endp; - _maskGen=maskGen; + _buffers = buffers; + _endp = endp; + _maskGen = maskGen; } - public synchronized Buffer getBuffer() + public Buffer getBuffer() { - return _buffer; - } - - public synchronized void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException - { - // System.err.printf("<< %s %s %s\n",TypeUtil.toHexString(flags),TypeUtil.toHexString(opcode),length); - - if (_closed) - throw new EofException("Closed"); - if (opcode==WebSocketConnectionRFC6455.OP_CLOSE) - _closed=true; - - boolean mask=_maskGen!=null; - - if (_buffer==null) - _buffer=mask?_buffers.getBuffer():_buffers.getDirectBuffer(); - - boolean last=WebSocketConnectionRFC6455.isLastFrame(flags); - - int space=mask?14:10; - - do + _lock.lock(); + try { - opcode = _opsent?WebSocketConnectionRFC6455.OP_CONTINUATION:opcode; - opcode=(byte)(((0xf&flags)<<4)+(0xf&opcode)); - _opsent=true; + return _buffer; + } + finally + { + _lock.unlock(); + } + } - int payload=length; - if (payload+space>_buffer.capacity()) - { - // We must fragement, so clear FIN bit - opcode=(byte)(opcode&0x7F); // Clear the FIN bit - payload=_buffer.capacity()-space; - } - else if (last) - opcode= (byte)(opcode|0x80); // Set the FIN bit + public void addFrame(byte flags, byte opcode, byte[] content, int offset, int length) throws IOException + { + _lock.lock(); + try + { + if (_closed) + throw new EofException("Closed"); + if (opcode == WebSocketConnectionRFC6455.OP_CLOSE) + _closed = true; - // ensure there is space for header - if (_buffer.space() <= space) + boolean mask = _maskGen != null; + + if (_buffer == null) + _buffer = mask ? _buffers.getBuffer() : _buffers.getDirectBuffer(); + + boolean last = WebSocketConnectionRFC6455.isLastFrame(flags); + + int space = mask ? 14 : 10; + + do { - flushBuffer(); + opcode = _opsent ? WebSocketConnectionRFC6455.OP_CONTINUATION : opcode; + opcode = (byte)(((0xf & flags) << 4) + (0xf & opcode)); + _opsent = true; + + int payload = length; + if (payload + space > _buffer.capacity()) + { + // We must fragement, so clear FIN bit + opcode = (byte)(opcode & 0x7F); // Clear the FIN bit + payload = _buffer.capacity() - space; + } + else if (last) + opcode = (byte)(opcode | 0x80); // Set the FIN bit + + // ensure there is space for header if (_buffer.space() <= space) - flush(); - } + { + flushBuffer(); + if (_buffer.space() <= space) + flush(); + } - // write the opcode and length - if (payload>0xffff) - { - _buffer.put(new byte[]{ - opcode, - mask?(byte)0xff:(byte)0x7f, - (byte)0, - (byte)0, - (byte)0, - (byte)0, - (byte)((payload>>24)&0xff), - (byte)((payload>>16)&0xff), - (byte)((payload>>8)&0xff), - (byte)(payload&0xff)}); - } - else if (payload >=0x7e) - { - _buffer.put(new byte[]{ - opcode, - mask?(byte)0xfe:(byte)0x7e, - (byte)(payload>>8), - (byte)(payload&0xff)}); - } - else - { - _buffer.put(new byte[]{ - opcode, - (byte)(mask?(0x80|payload):payload)}); - } - - // write mask - if (mask) - { - _maskGen.genMask(_mask); - _m=0; - _buffer.put(_mask); - } - - // write payload - int remaining = payload; - while (remaining > 0) - { - _buffer.compact(); - int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); + // write the opcode and length + if (payload > 0xffff) + { + _buffer.put(new byte[]{ + opcode, + mask ? (byte)0xff : (byte)0x7f, + (byte)0, + (byte)0, + (byte)0, + (byte)0, + (byte)((payload >> 24) & 0xff), + (byte)((payload >> 16) & 0xff), + (byte)((payload >> 8) & 0xff), + (byte)(payload & 0xff)}); + } + else if (payload >= 0x7e) + { + _buffer.put(new byte[]{ + opcode, + mask ? (byte)0xfe : (byte)0x7e, + (byte)(payload >> 8), + (byte)(payload & 0xff)}); + } + else + { + _buffer.put(new byte[]{ + opcode, + (byte)(mask ? (0x80 | payload) : payload)}); + } + // write mask if (mask) { - for (int i=0;i 0) + // write payload + int remaining = payload; + while (remaining > 0) { - // Gently flush the data, issuing a non-blocking write - flushBuffer(); - } - else - { - // Forcibly flush the data, issuing a blocking write - flush(); - if (remaining == 0) + _buffer.compact(); + int chunk = remaining < _buffer.space() ? remaining : _buffer.space(); + + if (mask) + { + for (int i = 0; i < chunk; i++) + _buffer.put((byte)(content[offset + (payload - remaining) + i] ^ _mask[+_m++ % 4])); + } + else + _buffer.put(content, offset + (payload - remaining), chunk); + + remaining -= chunk; + if (_buffer.space() > 0) { // Gently flush the data, issuing a non-blocking write flushBuffer(); } + else + { + // Forcibly flush the data, issuing a blocking write + flush(); + if (remaining == 0) + { + // Gently flush the data, issuing a non-blocking write + flushBuffer(); + } + } } + offset += payload; + length -= payload; } - offset+=payload; - length-=payload; - } - while (length>0); - _opsent=!last; + while (length > 0); + _opsent = !last; - if (_buffer!=null && _buffer.length()==0) - { - _buffers.returnBuffer(_buffer); - _buffer=null; - } - } - - public synchronized int flushBuffer() throws IOException - { - if (!_endp.isOpen()) - throw new EofException(); - - if (_buffer!=null) - { - int flushed=_buffer.hasContent()?_endp.flush(_buffer):0; - if (_closed&&_buffer.length()==0) - _endp.shutdownOutput(); - return flushed; - } - - return 0; - } - - public synchronized int flush() throws IOException - { - if (_buffer==null) - return 0; - int result = flushBuffer(); - - if (!_endp.isBlocking()) - { - long now = System.currentTimeMillis(); - long end=now+_endp.getMaxIdleTime(); - while (_buffer.length()>0) + if (_buffer != null && _buffer.length() == 0) { - boolean ready = _endp.blockWritable(end-now); - if (!ready) - { - now = System.currentTimeMillis(); - if (now 0) + { + boolean ready = _endp.blockWritable(end - now); + if (!ready) + { + now = System.currentTimeMillis(); + if (now < end) + continue; + throw new IOException("Write timeout"); + } + + result += flushBuffer(); + } + } + _buffer.compact(); + return result; + } + finally + { + _lock.unlock(); + } + } + + public boolean isBufferEmpty() + { + _lock.lock(); + try + { + return _buffer == null || _buffer.length() == 0; + } + finally + { + _lock.unlock(); + } + } + + public void returnBuffer() + { + _lock.lock(); + try + { + if (_buffer != null && _buffer.length() == 0) + { + _buffers.returnBuffer(_buffer); + _buffer = null; + } + } + finally + { + _lock.unlock(); } } diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/ConfigurationProcessor.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/ConfigurationProcessor.java index b179da4a2e2..c6f91510304 100644 --- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/ConfigurationProcessor.java +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/ConfigurationProcessor.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.xml; import java.net.URL; -import java.util.Map; /** * A ConfigurationProcessor for non XmlConfiguration format files. @@ -32,7 +31,7 @@ import java.util.Map; */ public interface ConfigurationProcessor { - public void init(URL url, XmlParser.Node config, Map idMap, Map properties); + public void init(URL url, XmlParser.Node root, XmlConfiguration configuration); public Object configure( Object obj) throws Exception; public Object configure() throws Exception; diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java index 42c234cd45d..a415f8ca400 100644 --- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java +++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java @@ -227,7 +227,7 @@ public class XmlConfiguration { throw new IllegalArgumentException("Unknown XML tag:"+config.getTag()); } - _processor.init(_url,config,_idMap, _propertyMap); + _processor.init(_url,config,this); } @@ -295,52 +295,64 @@ public class XmlConfiguration { return _processor.configure(); } + + /* ------------------------------------------------------------ */ + /** Initialize a new Object defaults. + *

    This method must be called by any {@link ConfigurationProcessor} when it + * creates a new instance of an object before configuring it, so that a derived + * XmlConfiguration class may inject default values. + * @param object + */ + public void initializeDefaults(Object object) + { + } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ private static class JettyXmlConfiguration implements ConfigurationProcessor { - XmlParser.Node _config; - Map _idMap; - Map _propertyMap; + XmlParser.Node _root; + XmlConfiguration _configuration; - public void init(URL url, XmlParser.Node config, Map idMap, Map properties) + public void init(URL url, XmlParser.Node root, XmlConfiguration configuration) { - _config=config; - _idMap=idMap; - _propertyMap=properties; + _root=root; + _configuration=configuration; } /* ------------------------------------------------------------ */ public Object configure(Object obj) throws Exception { // Check the class of the object - Class oClass = nodeClass(_config); + Class oClass = nodeClass(_root); if (oClass != null && !oClass.isInstance(obj)) { String loaders = (oClass.getClassLoader()==obj.getClass().getClassLoader())?"":"Object Class and type Class are from different loaders."; throw new IllegalArgumentException("Object of class '"+obj.getClass().getCanonicalName()+"' is not of type '" + oClass.getCanonicalName()+"'. "+loaders); } - configure(obj,_config,0); + configure(obj,_root,0); return obj; } /* ------------------------------------------------------------ */ public Object configure() throws Exception { - Class oClass = nodeClass(_config); + Class oClass = nodeClass(_root); - String id = _config.getAttribute("id"); - Object obj = id == null?null:_idMap.get(id); + String id = _root.getAttribute("id"); + Object obj = id == null?null:_configuration.getIdMap().get(id); if (obj == null && oClass != null) + { obj = oClass.newInstance(); + _configuration.initializeDefaults(obj); + } if (oClass != null && !oClass.isInstance(obj)) throw new ClassCastException(oClass.toString()); - configure(obj,_config,0); + configure(obj,_root,0); return obj; } @@ -368,7 +380,7 @@ public class XmlConfiguration { String id = cfg.getAttribute("id"); if (id != null) - _idMap.put(id,obj); + _configuration.getIdMap().put(id,obj); for (; i < cfg.size(); i++) { @@ -558,6 +570,7 @@ public class XmlConfiguration } Constructor cons = sClass.getConstructor(vClass); arg[0] = cons.newInstance(arg); + _configuration.initializeDefaults(arg[0]); set.invoke(obj,arg); return; } @@ -675,7 +688,7 @@ public class XmlConfiguration } } if (id != null) - _idMap.put(id,obj); + _configuration.getIdMap().put(id,obj); return obj; } @@ -730,7 +743,7 @@ public class XmlConfiguration { Object n= TypeUtil.call(oClass,method,obj,arg); if (id != null) - _idMap.put(id,n); + _configuration.getIdMap().put(id,n); configure(n,node,argi); return n; } @@ -792,6 +805,7 @@ public class XmlConfiguration try { n = constructors[c].newInstance(arg); + _configuration.initializeDefaults(n); called = true; } catch (IllegalAccessException e) @@ -809,7 +823,7 @@ public class XmlConfiguration if (called) { if (id != null) - _idMap.put(id,n); + _configuration.getIdMap().put(id,n); configure(n,node,argi); return n; } @@ -827,7 +841,7 @@ public class XmlConfiguration private Object refObj(Object obj, XmlParser.Node node) throws Exception { String id = node.getAttribute("id"); - obj = _idMap.get(id); + obj = _configuration.getIdMap().get(id); if (obj == null) throw new IllegalStateException("No object for id=" + id); configure(obj,node,0); @@ -870,12 +884,12 @@ public class XmlConfiguration Object v = value(obj,item); al = LazyList.add(al,(v == null && aClass.isPrimitive())?0:v); if (nid != null) - _idMap.put(nid,v); + _configuration.getIdMap().put(nid,v); } Object array = LazyList.toArray(al,aClass); if (id != null) - _idMap.put(id,array); + _configuration.getIdMap().put(id,array); return array; } @@ -889,7 +903,7 @@ public class XmlConfiguration Map map = new HashMap(); if (id != null) - _idMap.put(id,map); + _configuration.getIdMap().put(id,map); for (Object o : node) { @@ -925,9 +939,9 @@ public class XmlConfiguration map.put(k,v); if (kid != null) - _idMap.put(kid,k); + _configuration.getIdMap().put(kid,k); if (vid != null) - _idMap.put(vid,v); + _configuration.getIdMap().put(vid,v); } return map; @@ -947,12 +961,13 @@ public class XmlConfiguration String name = node.getAttribute("name"); String defaultValue = node.getAttribute("default"); Object prop; - if (_propertyMap != null && _propertyMap.containsKey(name)) - prop = _propertyMap.get(name); + Map property_map=_configuration.getProperties(); + if (property_map != null && property_map.containsKey(name)) + prop = property_map.get(name); else prop = defaultValue; if (id != null) - _idMap.put(id,prop); + _configuration.getIdMap().put(id,prop); if (prop != null) configure(prop,node,0); return prop; @@ -975,7 +990,7 @@ public class XmlConfiguration String ref = node.getAttribute("ref"); if (ref != null) { - value = _idMap.get(ref); + value = _configuration.getIdMap().get(ref); } else { diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java index 6b33d7dbae0..cfa96b16a4a 100644 --- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java +++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/TestConfiguration.java @@ -34,6 +34,7 @@ public class TestConfiguration extends HashMap public static int VALUE=77; public TestConfiguration nested; + public String testString="default"; public Object testObject; public int testInt; public URL url; @@ -65,6 +66,25 @@ public class TestConfiguration extends HashMap propValue=value; } + public TestConfiguration getNested() + { + return nested; + } + + public void setNested(TestConfiguration nested) + { + this.nested = nested; + } + + public String getTestString() + { + return testString; + } + + public void setTestString(String testString) + { + this.testString = testString; + } public void call() { @@ -73,7 +93,6 @@ public class TestConfiguration extends HashMap public TestConfiguration call(Boolean b) { - nested=new TestConfiguration(); nested.put("Arg",b); return nested; } diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java index 819be68cb76..e01b3f56d5c 100644 --- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java +++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java @@ -21,11 +21,12 @@ package org.eclipse.jetty.xml; import java.net.URL; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; import org.junit.Test; -import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertEquals; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; @@ -127,10 +128,28 @@ public class XmlConfigurationTest properties.put("whatever", "xxx"); URL url = XmlConfigurationTest.class.getClassLoader().getResource(_configure); - XmlConfiguration configuration = new XmlConfiguration(url); + final AtomicInteger count = new AtomicInteger(0); + XmlConfiguration configuration = new XmlConfiguration(url) + { + @Override + public void initializeDefaults(Object object) + { + if (object instanceof TestConfiguration) + { + count.incrementAndGet(); + ((TestConfiguration)object).setNested(null); + ((TestConfiguration)object).setTestString("NEW DEFAULT"); + } + } + }; configuration.getProperties().putAll(properties); TestConfiguration tc = (TestConfiguration)configuration.configure(); + assertEquals(3,count.get()); + assertEquals("NEW DEFAULT",tc.getTestString()); + assertEquals("nested",tc.getNested().getTestString()); + assertEquals("NEW DEFAULT",tc.getNested().getNested().getTestString()); + assertEquals("Set String","SetValue",tc.testObject); assertEquals("Set Type",2,tc.testInt); diff --git a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml index 32cda2bcff4..c1bb61ec539 100644 --- a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml +++ b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml @@ -83,6 +83,16 @@ 2.3 + + + nested + + + + + + + diff --git a/pom.xml b/pom.xml index fdea9aa440b..630ed953c9d 100644 --- a/pom.xml +++ b/pom.xml @@ -510,7 +510,7 @@ org.eclipse.jetty.toolchain jetty-test-helper - 2.0 + 2.5 org.slf4j @@ -530,17 +530,22 @@ junit junit - 4.8.1 + 4.11 - org.hamcrest - hamcrest-all - 1.1 + org.hamcrest + hamcrest-core + 1.3 + + + org.hamcrest + hamcrest-library + 1.3 org.mockito mockito-core - 1.8.5 + 1.9.5 diff --git a/test-jetty-webapp/src/main/config/contexts/test.xml b/test-jetty-webapp/src/main/config/contexts/test.xml index 00372852e43..12d5b18913e 100644 --- a/test-jetty-webapp/src/main/config/contexts/test.xml +++ b/test-jetty-webapp/src/main/config/contexts/test.xml @@ -28,19 +28,6 @@ detected. false /etc/webdefault.xml /contexts/test.d/override-web.xml - - - - - - - - - - - - -