Merge remote-tracking branch 'origin/jetty-8' into jetty-9

Conflicts:
	VERSION.txt
	example-async-rest/async-rest-jar/pom.xml
	example-async-rest/async-rest-webapp/pom.xml
	example-async-rest/pom.xml
	example-jetty-embedded/pom.xml
	jetty-aggregate/jetty-all-server/pom.xml
	jetty-aggregate/jetty-all/pom.xml
	jetty-aggregate/jetty-client/pom.xml
	jetty-aggregate/jetty-server/pom.xml
	jetty-aggregate/jetty-servlet/pom.xml
	jetty-aggregate/jetty-webapp/pom.xml
	jetty-aggregate/jetty-websocket/pom.xml
	jetty-aggregate/pom.xml
	jetty-ajp/src/main/java/org/eclipse/jetty/ajp/Ajp13Generator.java
	jetty-annotations/pom.xml
	jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AbstractDiscoverableAnnotationHandler.java
	jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationConfiguration.java
	jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java
	jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletContainerInitializerListener.java
	jetty-annotations/src/main/java/org/eclipse/jetty/annotations/ServletSecurityAnnotationHandler.java
	jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java
	jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotationHandler.java
	jetty-client/pom.xml
	jetty-continuation/pom.xml
	jetty-deploy/pom.xml
	jetty-distribution/pom.xml
	jetty-http-spi/pom.xml
	jetty-http/pom.xml
	jetty-http/src/main/java/org/eclipse/jetty/http/Generator.java
	jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java
	jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java
	jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java
	jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java
	jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java
	jetty-io/pom.xml
	jetty-io/src/main/java/org/eclipse/jetty/io/AbstractBuffer.java
	jetty-io/src/main/java/org/eclipse/jetty/io/Buffer.java
	jetty-io/src/main/java/org/eclipse/jetty/io/BufferUtil.java
	jetty-jaspi/pom.xml
	jetty-jmx/pom.xml
	jetty-jndi/pom.xml
	jetty-jndi/src/main/java/org/eclipse/jetty/jndi/ContextFactory.java
	jetty-jndi/src/test/java/org/eclipse/jetty/jndi/java/TestJNDI.java
	jetty-jsp/pom.xml
	jetty-monitor/pom.xml
	jetty-nested/pom.xml
	jetty-nosql/pom.xml
	jetty-osgi/jetty-osgi-boot-jsp/pom.xml
	jetty-osgi/jetty-osgi-boot-warurl/pom.xml
	jetty-osgi/jetty-osgi-boot/pom.xml
	jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiDeployer.java
	jetty-osgi/jetty-osgi-httpservice/pom.xml
	jetty-osgi/pom.xml
	jetty-osgi/test-jetty-osgi-context/pom.xml
	jetty-osgi/test-jetty-osgi-webapp/pom.xml
	jetty-osgi/test-jetty-osgi/pom.xml
	jetty-overlay-deployer/pom.xml
	jetty-plus/pom.xml
	jetty-policy/pom.xml
	jetty-rewrite/pom.xml
	jetty-runner/pom.xml
	jetty-security/pom.xml
	jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java
	jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
	jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java
	jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java
	jetty-server/pom.xml
	jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java
	jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java
	jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
	jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java
	jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
	jetty-server/src/main/java/org/eclipse/jetty/server/Response.java
	jetty-server/src/main/java/org/eclipse/jetty/server/bio/SocketConnector.java
	jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java
	jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java
	jetty-server/src/test/java/org/eclipse/jetty/server/HttpURITest.java
	jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java
	jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java
	jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslSocketServerTest.java
	jetty-server/src/test/resources/jetty-logging.properties
	jetty-servlet/pom.xml
	jetty-servlet/src/main/java/org/eclipse/jetty/servlet/DefaultServlet.java
	jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletContextHandler.java
	jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java
	jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java
	jetty-servlets/pom.xml
	jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java
	jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java
	jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java
	jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java
	jetty-spdy/pom.xml
	jetty-spdy/spdy-client/pom.xml
	jetty-spdy/spdy-core/pom.xml
	jetty-spdy/spdy-example-webapp/pom.xml
	jetty-spdy/spdy-http-server/pom.xml
	jetty-spdy/spdy-jetty-http/src/main/java/org/eclipse/jetty/spdy/http/ServerHTTPSPDYAsyncConnection.java
	jetty-start/pom.xml
	jetty-start/src/test/java/org/eclipse/jetty/start/CommandLineBuilderTest.java
	jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
	jetty-util-ajax/pom.xml
	jetty-util/pom.xml
	jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java
	jetty-util/src/main/java/org/eclipse/jetty/util/log/StdErrLog.java
	jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java
	jetty-webapp/pom.xml
	jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java
	jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java
	jetty-websocket/pom.xml
	jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketServletRFCTest.java
	jetty-websocket/src/test/java/org/eclipse/jetty/websocket/helper/MessageSender.java
	jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/UnitGenerator.java
	jetty-xml/pom.xml
	pom.xml
	test-continuation/pom.xml
	test-jetty-nested/pom.xml
	test-jetty-servlet/pom.xml
	test-jetty-webapp/pom.xml
	test-jetty-webapp/src/main/webapp/WEB-INF/web.xml
	tests/pom.xml
	tests/test-integration/pom.xml
	tests/test-loginservice/pom.xml
	tests/test-sessions/pom.xml
	tests/test-sessions/test-hash-sessions/pom.xml
	tests/test-sessions/test-jdbc-sessions/pom.xml
	tests/test-sessions/test-mongodb-sessions/pom.xml
	tests/test-sessions/test-sessions-common/pom.xml
	tests/test-webapps/pom.xml
	tests/test-webapps/test-webapp-rfc2616/pom.xml
This commit is contained in:
Greg Wilkins 2012-10-12 21:19:21 +11:00
commit fac580c374
81 changed files with 3443 additions and 671 deletions

View File

@ -22,6 +22,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler; import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.AnnotationParser.Value;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.DiscoveredAnnotation; import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
@ -33,13 +35,32 @@ import org.eclipse.jetty.webapp.WebAppContext;
public abstract class AbstractDiscoverableAnnotationHandler implements DiscoverableAnnotationHandler public abstract class AbstractDiscoverableAnnotationHandler implements DiscoverableAnnotationHandler
{ {
protected WebAppContext _context; protected WebAppContext _context;
protected List<DiscoveredAnnotation> _annotations = new ArrayList<DiscoveredAnnotation>(); protected List<DiscoveredAnnotation> _annotations;
protected Resource _resource;
public AbstractDiscoverableAnnotationHandler(WebAppContext context) public AbstractDiscoverableAnnotationHandler(WebAppContext context)
{
this(context, null);
}
public AbstractDiscoverableAnnotationHandler(WebAppContext context, List<DiscoveredAnnotation> list)
{ {
_context = context; _context = context;
if (list == null)
_annotations = new ArrayList<DiscoveredAnnotation>();
else
_annotations = list;
} }
public Resource getResource()
{
return _resource;
}
public void setResource(Resource resource)
{
_resource = resource;
}
public List<DiscoveredAnnotation> getAnnotationList () public List<DiscoveredAnnotation> getAnnotationList ()
{ {
@ -51,7 +72,6 @@ public abstract class AbstractDiscoverableAnnotationHandler implements Discovera
_annotations.clear(); _annotations.clear();
} }
public void addAnnotation (DiscoveredAnnotation a) public void addAnnotation (DiscoveredAnnotation a)
{ {
_annotations.add(a); _annotations.add(a);

View File

@ -28,6 +28,7 @@ import javax.servlet.annotation.HandlesTypes;
import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler; import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
import org.eclipse.jetty.plus.annotation.ContainerInitializer; import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
@ -50,10 +51,20 @@ public class AnnotationConfiguration extends AbstractConfiguration
public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers"; public static final String CONTAINER_INITIALIZERS = "org.eclipse.jetty.containerInitializers";
protected List<DiscoverableAnnotationHandler> _discoverableAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
protected ClassInheritanceHandler _classInheritanceHandler;
protected List<ContainerInitializerAnnotationHandler> _containerInitializerAnnotationHandlers = new ArrayList<ContainerInitializerAnnotationHandler>();
public void preConfigure(final WebAppContext context) throws Exception public void preConfigure(final WebAppContext context) throws Exception
{ {
} }
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override @Override
public void configure(WebAppContext context) throws Exception public void configure(WebAppContext context) throws Exception
{ {
@ -68,10 +79,9 @@ public class AnnotationConfiguration extends AbstractConfiguration
//If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations //If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered()) if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
{ {
parser = createAnnotationParser(); _discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
parser.registerAnnotationHandler("javax.servlet.annotation.WebServlet", new WebServletAnnotationHandler(context)); _discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
parser.registerAnnotationHandler("javax.servlet.annotation.WebFilter", new WebFilterAnnotationHandler(context)); _discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
parser.registerAnnotationHandler("javax.servlet.annotation.WebListener", new WebListenerAnnotationHandler(context));
} }
} }
else else
@ -81,11 +91,11 @@ public class AnnotationConfiguration extends AbstractConfiguration
//Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the //Regardless of metadata, if there are any ServletContainerInitializers with @HandlesTypes, then we need to scan all the
//classes so we can call their onStartup() methods correctly //classes so we can call their onStartup() methods correctly
List<ServletContainerInitializer> nonExcludedInitializers = getNonExcludedInitializers(context); createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
parser = registerServletContainerInitializerAnnotationHandlers(context, parser, nonExcludedInitializers);
if (parser != null) if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
{ {
parser = createAnnotationParser();
if (LOG.isDebugEnabled()) LOG.debug("Scanning all classses for annotations: webxmlVersion="+context.getServletContext().getEffectiveMajorVersion()+" configurationDiscovered="+context.isConfigurationDiscovered()); if (LOG.isDebugEnabled()) LOG.debug("Scanning all classses for annotations: webxmlVersion="+context.getServletContext().getEffectiveMajorVersion()+" configurationDiscovered="+context.isConfigurationDiscovered());
parseContainerPath(context, parser); parseContainerPath(context, parser);
//email from Rajiv Mordani jsrs 315 7 April 2010 //email from Rajiv Mordani jsrs 315 7 April 2010
@ -95,11 +105,39 @@ public class AnnotationConfiguration extends AbstractConfiguration
// WEB-INF/classes + order of the elements. // WEB-INF/classes + order of the elements.
parseWebInfClasses(context, parser); parseWebInfClasses(context, parser);
parseWebInfLib (context, parser); parseWebInfLib (context, parser);
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
context.getMetaData().addDiscoveredAnnotations(((AbstractDiscoverableAnnotationHandler)h).getAnnotationList());
} }
} }
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#postConfigure(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
public void postConfigure(WebAppContext context) throws Exception
{
MultiMap map = (MultiMap)context.getAttribute(CLASS_INHERITANCE_MAP);
if (map != null)
map.clear();
context.removeAttribute(CLASS_INHERITANCE_MAP);
List<ContainerInitializer> initializers = (List<ContainerInitializer>)context.getAttribute(CONTAINER_INITIALIZERS);
if (initializers != null)
initializers.clear();
if (_discoverableAnnotationHandlers != null)
_discoverableAnnotationHandlers.clear();
_classInheritanceHandler = null;
if (_containerInitializerAnnotationHandlers != null)
_containerInitializerAnnotationHandlers.clear();
super.postConfigure(context);
}
/** /**
* @return a new AnnotationParser. This method can be overridden to use a different impleemntation of * @return a new AnnotationParser. This method can be overridden to use a different impleemntation of
* the AnnotationParser. Note that this is considered internal API. * the AnnotationParser. Note that this is considered internal API.
@ -109,6 +147,9 @@ public class AnnotationConfiguration extends AbstractConfiguration
return new AnnotationParser(); return new AnnotationParser();
} }
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
*/
@Override @Override
public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
{ {
@ -116,32 +157,23 @@ public class AnnotationConfiguration extends AbstractConfiguration
} }
/**
public AnnotationParser registerServletContainerInitializerAnnotationHandlers (WebAppContext context, AnnotationParser parser, List<ServletContainerInitializer> scis) * @param context
* @param scis
* @throws Exception
*/
public void createServletContainerInitializerAnnotationHandlers (WebAppContext context, List<ServletContainerInitializer> scis)
throws Exception throws Exception
{ {
//TODO verify my interpretation of the spec. That is, that metadata-complete has nothing
//to do with finding the ServletContainerInitializers, classes designated to be of interest to them,
//or even calling them on startup.
//Get all ServletContainerInitializers, and check them for HandlesTypes annotations.
//For each class in the HandlesTypes value, if it IS an annotation, register a handler
//that will record the classes that have that annotation.
//If it is NOT an annotation, then we will interrogate the type hierarchy discovered during
//parsing later on to find the applicable classes.
if (scis == null || scis.isEmpty()) if (scis == null || scis.isEmpty())
return parser; // nothing to do return; // nothing to do
ServletContainerInitializerListener listener = new ServletContainerInitializerListener();
listener.setWebAppContext(context);
context.addEventListener(listener);
//may need to add a listener List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
ArrayList<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
context.setAttribute(CONTAINER_INITIALIZERS, initializers); context.setAttribute(CONTAINER_INITIALIZERS, initializers);
for (ServletContainerInitializer service : scis) for (ServletContainerInitializer service : scis)
@ -158,17 +190,14 @@ public class AnnotationConfiguration extends AbstractConfiguration
{ {
initializer.setInterestedTypes(classes); initializer.setInterestedTypes(classes);
//We need to create a parser if we haven't already
if (parser == null)
parser = createAnnotationParser();
//If we haven't already done so, we need to register a handler that will //If we haven't already done so, we need to register a handler that will
//process the whole class hierarchy //process the whole class hierarchy to satisfy the ServletContainerInitializer
if (context.getAttribute(CLASS_INHERITANCE_MAP) == null) if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
{ {
ClassInheritanceHandler classHandler = new ClassInheritanceHandler(); MultiMap map = new MultiMap();
context.setAttribute(CLASS_INHERITANCE_MAP, classHandler.getMap()); context.setAttribute(CLASS_INHERITANCE_MAP, map);
parser.registerClassHandler(classHandler); _classInheritanceHandler = new ClassInheritanceHandler(map);
} }
for (Class c: classes) for (Class c: classes)
@ -179,7 +208,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
{ {
if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName()); if (LOG.isDebugEnabled()) LOG.debug("Registering annotation handler for "+c.getName());
parser.registerAnnotationHandler(c.getName(), new ContainerInitializerAnnotationHandler(initializer, c)); _containerInitializerAnnotationHandlers.add(new ContainerInitializerAnnotationHandler(initializer, c));
} }
} }
} }
@ -190,15 +219,16 @@ public class AnnotationConfiguration extends AbstractConfiguration
if (LOG.isDebugEnabled()) LOG.debug("No annotation on initializer "+service.getClass()); if (LOG.isDebugEnabled()) LOG.debug("No annotation on initializer "+service.getClass());
} }
//return the parser in case we lazily created it
return parser;
//add a listener which will call the servletcontainerinitializers when appropriate
ServletContainerInitializerListener listener = new ServletContainerInitializerListener();
listener.setWebAppContext(context);
context.addEventListener(listener);
} }
/** /**
* Check to see if the ServletContainerIntializer loaded via the ServiceLoader came * Check to see if the ServletContainerIntializer loaded via the ServiceLoader came
* from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85. * from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
@ -241,7 +271,12 @@ public class AnnotationConfiguration extends AbstractConfiguration
public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context) /**
* @param context
* @return
* @throws Exception
*/
public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
throws Exception throws Exception
{ {
List<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>(); List<ServletContainerInitializer> nonExcludedInitializers = new ArrayList<ServletContainerInitializer>();
@ -263,14 +298,29 @@ public class AnnotationConfiguration extends AbstractConfiguration
/**
* Scan jars on container path.
*
* @param context
* @param parser
* @throws Exception
*/
public void parseContainerPath (final WebAppContext context, final AnnotationParser parser) public void parseContainerPath (final WebAppContext context, final AnnotationParser parser)
throws Exception throws Exception
{ {
//if no pattern for the container path is defined, then by default scan NOTHING //if no pattern for the container path is defined, then by default scan NOTHING
LOG.debug("Scanning container jars"); LOG.debug("Scanning container jars");
//clear any previously discovered annotations //always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
clearAnnotationList(parser.getAnnotationHandlers()); parser.clearHandlers();
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
}
parser.registerHandlers(_discoverableAnnotationHandlers);
parser.registerHandler(_classInheritanceHandler);
parser.registerHandlers(_containerInitializerAnnotationHandlers);
//Convert from Resource to URI //Convert from Resource to URI
ArrayList<URI> containerUris = new ArrayList<URI>(); ArrayList<URI> containerUris = new ArrayList<URI>();
@ -297,16 +347,19 @@ public class AnnotationConfiguration extends AbstractConfiguration
return true; return true;
return false; return false;
} }
}); });
//gather together all annotations discovered
List<DiscoveredAnnotation> annotations = new ArrayList<DiscoveredAnnotation>();
gatherAnnotations(annotations, parser.getAnnotationHandlers());
context.getMetaData().addDiscoveredAnnotations(annotations);
} }
/**
* Scan jars in WEB-INF/lib
*
* @param context
* @param parser
* @throws Exception
*/
public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser) public void parseWebInfLib (final WebAppContext context, final AnnotationParser parser)
throws Exception throws Exception
{ {
@ -325,17 +378,35 @@ public class AnnotationConfiguration extends AbstractConfiguration
for (Resource r : jars) for (Resource r : jars)
{ {
//clear any previously discovered annotations from handlers //for each jar, we decide which set of annotations we need to parse for
clearAnnotationList(parser.getAnnotationHandlers()); parser.clearHandlers();
URI uri = r.getURI(); URI uri = r.getURI();
FragmentDescriptor f = getFragmentFromJar(r, frags); FragmentDescriptor f = getFragmentFromJar(r, frags);
//if a jar has no web-fragment.xml we scan it (because it is not exluded by the ordering) //if its from a fragment jar that is metadata complete, we should skip scanning for @webservlet etc
// but yet we still need to do the scanning for the classes on behalf of the servletcontainerinitializers
//if a jar has no web-fragment.xml we scan it (because it is not excluded by the ordering)
//or if it has a fragment we scan it if it is not metadata complete //or if it has a fragment we scan it if it is not metadata complete
if (f == null || !isMetaDataComplete(f)) if (f == null || !isMetaDataComplete(f) || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
{ {
//register the classinheritance handler if there is one
parser.registerHandler(_classInheritanceHandler);
//register the handlers for the @HandlesTypes values that are themselves annotations if there are any
parser.registerHandlers(_containerInitializerAnnotationHandlers);
//only register the discoverable annotation handlers if this fragment is not metadata complete, or has no fragment descriptor
if (f == null || !isMetaDataComplete(f))
{
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
((AbstractDiscoverableAnnotationHandler)h).setResource(r);
}
parser.registerHandlers(_discoverableAnnotationHandlers);
}
parser.parse(uri, parser.parse(uri,
new ClassNameResolver() new ClassNameResolver()
{ {
@ -354,13 +425,17 @@ public class AnnotationConfiguration extends AbstractConfiguration
return true; return true;
} }
}); });
List<DiscoveredAnnotation> annotations = new ArrayList<DiscoveredAnnotation>();
gatherAnnotations(annotations, parser.getAnnotationHandlers());
context.getMetaData().addDiscoveredAnnotations(r, annotations);
} }
} }
} }
/**
* Scan classes in WEB-INF/classes
*
* @param context
* @param parser
* @throws Exception
*/
public void parseWebInfClasses (final WebAppContext context, final AnnotationParser parser) public void parseWebInfClasses (final WebAppContext context, final AnnotationParser parser)
throws Exception throws Exception
{ {
@ -370,7 +445,16 @@ public class AnnotationConfiguration extends AbstractConfiguration
Resource classesDir = context.getWebInf().addPath("classes/"); Resource classesDir = context.getWebInf().addPath("classes/");
if (classesDir.exists()) if (classesDir.exists())
{ {
clearAnnotationList(parser.getAnnotationHandlers()); parser.clearHandlers();
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
}
parser.registerHandlers(_discoverableAnnotationHandlers);
parser.registerHandler(_classInheritanceHandler);
parser.registerHandlers(_containerInitializerAnnotationHandlers);
parser.parse(classesDir, parser.parse(classesDir,
new ClassNameResolver() new ClassNameResolver()
{ {
@ -389,17 +473,20 @@ public class AnnotationConfiguration extends AbstractConfiguration
return true; return true;
} }
}); });
//TODO - where to set the annotations discovered from WEB-INF/classes?
List<DiscoveredAnnotation> annotations = new ArrayList<DiscoveredAnnotation>();
gatherAnnotations(annotations, parser.getAnnotationHandlers());
context.getMetaData().addDiscoveredAnnotations (annotations);
} }
} }
} }
/**
* Get the web-fragment.xml from a jar
*
* @param jar
* @param frags
* @return
* @throws Exception
*/
public FragmentDescriptor getFragmentFromJar (Resource jar, List<FragmentDescriptor> frags) public FragmentDescriptor getFragmentFromJar (Resource jar, List<FragmentDescriptor> frags)
throws Exception throws Exception
{ {
@ -422,24 +509,5 @@ public class AnnotationConfiguration extends AbstractConfiguration
return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True); return (d!=null && d.getMetaDataComplete() == MetaDataComplete.True);
} }
protected void clearAnnotationList (List<DiscoverableAnnotationHandler> handlers)
{
if (handlers == null)
return;
for (DiscoverableAnnotationHandler h:handlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
((AbstractDiscoverableAnnotationHandler)h).resetList();
}
}
protected void gatherAnnotations (List<DiscoveredAnnotation> annotations, List<DiscoverableAnnotationHandler> handlers)
{
for (DiscoverableAnnotationHandler h:handlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
annotations.addAll(((AbstractDiscoverableAnnotationHandler)h).getAnnotationList());
}
}
} }

View File

@ -52,11 +52,8 @@ public class AnnotationParser
{ {
private static final Logger LOG = Log.getLogger(AnnotationParser.class); private static final Logger LOG = Log.getLogger(AnnotationParser.class);
protected List<String> _parsedClassNames = new ArrayList<String>(); protected List<String> _parsedClassNames = new ArrayList<String>();
protected Map<String, List<DiscoverableAnnotationHandler>> _annotationHandlers = new HashMap<String, List<DiscoverableAnnotationHandler>>(); protected List<Handler> _handlers = new ArrayList<Handler>();
protected List<ClassHandler> _classHandlers = new ArrayList<ClassHandler>();
protected List<MethodHandler> _methodHandlers = new ArrayList<MethodHandler>();
protected List<FieldHandler> _fieldHandlers = new ArrayList<FieldHandler>();
public static String normalize (String name) public static String normalize (String name)
{ {
@ -167,37 +164,122 @@ public class AnnotationParser
public interface DiscoverableAnnotationHandler /**
* Handler
*
* Signature for all handlers that respond to parsing class files.
*/
public interface Handler
{ {
}
/**
* DiscoverableAnnotationHandler
*
* Processes an annotation when it is discovered on a class.
*/
public interface DiscoverableAnnotationHandler extends Handler
{
/**
* Process an annotation that was discovered on a class
* @param className
* @param version
* @param access
* @param signature
* @param superName
* @param interfaces
* @param annotation
* @param values
*/
public void handleClass (String className, int version, int access, public void handleClass (String className, int version, int access,
String signature, String superName, String[] interfaces, String signature, String superName, String[] interfaces,
String annotation, List<Value>values); String annotation, List<Value>values);
/**
* Process an annotation that was discovered on a method
* @param className
* @param methodName
* @param access
* @param desc
* @param signature
* @param exceptions
* @param annotation
* @param values
*/
public void handleMethod (String className, String methodName, int access, public void handleMethod (String className, String methodName, int access,
String desc, String signature,String[] exceptions, String desc, String signature,String[] exceptions,
String annotation, List<Value>values); String annotation, List<Value>values);
/**
* Process an annotation that was discovered on a field
* @param className
* @param fieldName
* @param access
* @param fieldType
* @param signature
* @param value
* @param annotation
* @param values
*/
public void handleField (String className, String fieldName, int access, public void handleField (String className, String fieldName, int access,
String fieldType, String signature, Object value, String fieldType, String signature, Object value,
String annotation, List<Value>values); String annotation, List<Value>values);
/**
* Get the name of the annotation processed by this handler. Can be null
*
* @return
*/
public String getAnnotationName();
} }
public interface ClassHandler
/**
* ClassHandler
*
* Responds to finding a Class
*/
public interface ClassHandler extends Handler
{ {
public void handle (String className, int version, int access, String signature, String superName, String[] interfaces); public void handle (String className, int version, int access, String signature, String superName, String[] interfaces);
} }
public interface MethodHandler
/**
* MethodHandler
*
* Responds to finding a Method
*/
public interface MethodHandler extends Handler
{ {
public void handle (String className, String methodName, int access, String desc, String signature,String[] exceptions); public void handle (String className, String methodName, int access, String desc, String signature,String[] exceptions);
} }
public interface FieldHandler
/**
* FieldHandler
*
* Responds to finding a Field
*/
public interface FieldHandler extends Handler
{ {
public void handle (String className, String fieldName, int access, String fieldType, String signature, Object value); public void handle (String className, String fieldName, int access, String fieldType, String signature, Object value);
} }
/**
* MyAnnotationVisitor
*
* ASM Visitor for Annotations
*/
public class MyAnnotationVisitor implements AnnotationVisitor public class MyAnnotationVisitor implements AnnotationVisitor
{ {
List<Value> _annotationValues; List<Value> _annotationValues;
@ -307,9 +389,12 @@ public class AnnotationParser
normalizedInterfaces[i++] = normalize(s); normalizedInterfaces[i++] = normalize(s);
} }
for (ClassHandler h : AnnotationParser.this._classHandlers) for (Handler h : AnnotationParser.this._handlers)
{ {
h.handle(_className, _version, _access, _signature, normalize(_superName), normalizedInterfaces); if (h instanceof ClassHandler)
{
((ClassHandler)h).handle(_className, _version, _access, _signature, normalize(_superName), normalizedInterfaces);
}
} }
} }
@ -322,12 +407,13 @@ public class AnnotationParser
super.visitEnd(); super.visitEnd();
//call all AnnotationHandlers with classname, annotation name + values //call all AnnotationHandlers with classname, annotation name + values
List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName); for (Handler h : AnnotationParser.this._handlers)
if (handlers != null)
{ {
for (DiscoverableAnnotationHandler h:handlers) if (h instanceof DiscoverableAnnotationHandler)
{ {
h.handleClass(_className, _version, _access, _signature, _superName, _interfaces, _annotationName, _annotationValues); DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
dah.handleClass(_className, _version, _access, _signature, _superName, _interfaces, _annotationName, _annotationValues);
} }
} }
} }
@ -353,12 +439,13 @@ public class AnnotationParser
{ {
super.visitEnd(); super.visitEnd();
//call all AnnotationHandlers with classname, method, annotation name + values //call all AnnotationHandlers with classname, method, annotation name + values
List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName); for (Handler h : AnnotationParser.this._handlers)
if (handlers != null)
{ {
for (DiscoverableAnnotationHandler h:handlers) if (h instanceof DiscoverableAnnotationHandler)
{ {
h.handleMethod(_className, name, access, methodDesc, signature, exceptions, _annotationName, _annotationValues); DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
dah.handleMethod(_className, name, access, methodDesc, signature, exceptions, _annotationName, _annotationValues);
} }
} }
} }
@ -385,12 +472,13 @@ public class AnnotationParser
public void visitEnd() public void visitEnd()
{ {
super.visitEnd(); super.visitEnd();
List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName); for (Handler h : AnnotationParser.this._handlers)
if (handlers != null)
{ {
for (DiscoverableAnnotationHandler h:handlers) if (h instanceof DiscoverableAnnotationHandler)
{ {
h.handleField(_className, fieldName, access, fieldType, signature, value, _annotationName, _annotationValues); DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
if (_annotationName.equalsIgnoreCase(dah.getAnnotationName()))
dah.handleField(_className, fieldName, access, fieldType, signature, value, _annotationName, _annotationValues);
} }
} }
} }
@ -406,46 +494,130 @@ public class AnnotationParser
* Register a handler that will be called back when the named annotation is * Register a handler that will be called back when the named annotation is
* encountered on a class. * encountered on a class.
* *
* @deprecated see registerHandler(Handler)
* @param annotationName * @param annotationName
* @param handler * @param handler
*/ */
public void registerAnnotationHandler (String annotationName, DiscoverableAnnotationHandler handler) public void registerAnnotationHandler (String annotationName, DiscoverableAnnotationHandler handler)
{ {
List<DiscoverableAnnotationHandler> handlers = _annotationHandlers.get(annotationName); _handlers.add(handler);
if (handlers == null)
{
handlers = new ArrayList<DiscoverableAnnotationHandler>();
_annotationHandlers.put(annotationName, handlers);
}
handlers.add(handler);
} }
/**
* @deprecated
* @param annotationName
* @return
*/
public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName) public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName)
{ {
List<DiscoverableAnnotationHandler> handlers = _annotationHandlers.get(annotationName); List<DiscoverableAnnotationHandler> handlers = new ArrayList<DiscoverableAnnotationHandler>();
if (handlers == null) for (Handler h:_handlers)
return Collections.emptyList(); {
return new ArrayList<DiscoverableAnnotationHandler>(); if (h instanceof DiscoverableAnnotationHandler)
{
DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
if (annotationName.equals(dah.getAnnotationName()))
handlers.add(dah);
}
}
return handlers;
} }
/**
* @deprecated
* @return
*/
public List<DiscoverableAnnotationHandler> getAnnotationHandlers() public List<DiscoverableAnnotationHandler> getAnnotationHandlers()
{ {
List<DiscoverableAnnotationHandler> allHandlers = new ArrayList<DiscoverableAnnotationHandler>(); List<DiscoverableAnnotationHandler> allAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
for (List<DiscoverableAnnotationHandler> list:_annotationHandlers.values()) for (Handler h:_handlers)
allHandlers.addAll(list); {
return allHandlers; if (h instanceof DiscoverableAnnotationHandler)
allAnnotationHandlers.add((DiscoverableAnnotationHandler)h);
}
return allAnnotationHandlers;
} }
/**
* @deprecated see registerHandler(Handler)
* @param handler
*/
public void registerClassHandler (ClassHandler handler) public void registerClassHandler (ClassHandler handler)
{ {
_classHandlers.add(handler); _handlers.add(handler);
} }
/**
* Add a particular handler
*
* @param h
*/
public void registerHandler(Handler h)
{
if (h == null)
return;
_handlers.add(h);
}
/**
* Add a list of handlers
*
* @param handlers
*/
public void registerHandlers(List<? extends Handler> handlers)
{
if (handlers == null)
return;
_handlers.addAll(handlers);
}
/**
* Remove a particular handler
*
* @param h
* @return
*/
public boolean deregisterHandler(Handler h)
{
return _handlers.remove(h);
}
/**
* Remove all registered handlers
*/
public void clearHandlers()
{
_handlers.clear();
}
/**
* True if the class has already been processed, false otherwise
* @param className
* @return
*/
public boolean isParsed (String className) public boolean isParsed (String className)
{ {
return _parsedClassNames.contains(className); return _parsedClassNames.contains(className);
} }
/**
* Parse a given class
*
* @param className
* @param resolver
* @throws Exception
*/
public void parse (String className, ClassNameResolver resolver) public void parse (String className, ClassNameResolver resolver)
throws Exception throws Exception
{ {
@ -467,6 +639,16 @@ public class AnnotationParser
} }
} }
/**
* Parse the given class, optionally walking its inheritance hierarchy
*
* @param clazz
* @param resolver
* @param visitSuperClasses
* @throws Exception
*/
public void parse (Class clazz, ClassNameResolver resolver, boolean visitSuperClasses) public void parse (Class clazz, ClassNameResolver resolver, boolean visitSuperClasses)
throws Exception throws Exception
{ {
@ -493,6 +675,15 @@ public class AnnotationParser
} }
} }
/**
* Parse the given classes
*
* @param classNames
* @param resolver
* @throws Exception
*/
public void parse (String[] classNames, ClassNameResolver resolver) public void parse (String[] classNames, ClassNameResolver resolver)
throws Exception throws Exception
{ {
@ -502,6 +693,14 @@ public class AnnotationParser
parse(Arrays.asList(classNames), resolver); parse(Arrays.asList(classNames), resolver);
} }
/**
* Parse the given classes
*
* @param classNames
* @param resolver
* @throws Exception
*/
public void parse (List<String> classNames, ClassNameResolver resolver) public void parse (List<String> classNames, ClassNameResolver resolver)
throws Exception throws Exception
{ {
@ -520,6 +719,14 @@ public class AnnotationParser
} }
} }
/**
* Parse all classes in a directory
*
* @param dir
* @param resolver
* @throws Exception
*/
public void parse (Resource dir, ClassNameResolver resolver) public void parse (Resource dir, ClassNameResolver resolver)
throws Exception throws Exception
{ {
@ -555,8 +762,9 @@ public class AnnotationParser
/** /**
* Find annotations on classes in the supplied classloader. * Parse classes in the supplied classloader.
* Only class files in jar files will be scanned. * Only class files in jar files will be scanned.
*
* @param loader * @param loader
* @param visitParents * @param visitParents
* @param nullInclusive * @param nullInclusive
@ -605,7 +813,8 @@ public class AnnotationParser
/** /**
* Find annotations in classes in the supplied url of jar files. * Parse classes in the supplied url of jar files.
*
* @param uris * @param uris
* @param resolver * @param resolver
* @throws Exception * @throws Exception
@ -647,6 +856,12 @@ public class AnnotationParser
scanner.scan(null, uris, true); scanner.scan(null, uris, true);
} }
/**
* Parse a particular resource
* @param uri
* @param resolver
* @throws Exception
*/
public void parse (URI uri, final ClassNameResolver resolver) public void parse (URI uri, final ClassNameResolver resolver)
throws Exception throws Exception
{ {
@ -656,6 +871,14 @@ public class AnnotationParser
parse(uris, resolver); parse(uris, resolver);
} }
/**
* Use ASM on a class
*
* @param is
* @throws IOException
*/
protected void scanClass (InputStream is) protected void scanClass (InputStream is)
throws IOException throws IOException
{ {

View File

@ -35,10 +35,16 @@ public class ClassInheritanceHandler implements ClassHandler
private static final Logger LOG = Log.getLogger(ClassInheritanceHandler.class); private static final Logger LOG = Log.getLogger(ClassInheritanceHandler.class);
MultiMap _inheritanceMap = new MultiMap(); MultiMap _inheritanceMap;
public ClassInheritanceHandler() public ClassInheritanceHandler()
{ {
_inheritanceMap = new MultiMap();
}
public ClassInheritanceHandler(MultiMap map)
{
_inheritanceMap = map;
} }
public void handle(String className, int version, int access, String signature, String superName, String[] interfaces) public void handle(String className, int version, int access, String signature, String superName, String[] interfaces)

View File

@ -65,4 +65,15 @@ public class ContainerInitializerAnnotationHandler implements DiscoverableAnnota
_initializer.addAnnotatedTypeName(className); _initializer.addAnnotatedTypeName(className);
} }
@Override
public String getAnnotationName()
{
return _annotation.getName();
}
public ContainerInitializer getContainerInitializer()
{
return _initializer;
}
} }

View File

@ -26,6 +26,8 @@ import javax.servlet.ServletContextListener;
import org.eclipse.jetty.plus.annotation.ContainerInitializer; import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
/** /**
@ -35,7 +37,8 @@ import org.eclipse.jetty.webapp.WebAppContext;
*/ */
public class ServletContainerInitializerListener implements ServletContextListener public class ServletContainerInitializerListener implements ServletContextListener
{ {
WebAppContext _context = null; private static final Logger LOG = Log.getLogger(ServletContainerInitializerListener.class);
protected WebAppContext _context = null;
public void setWebAppContext (WebAppContext context) public void setWebAppContext (WebAppContext context)
@ -103,14 +106,10 @@ public class ServletContainerInitializerListener implements ServletContextListen
} }
catch (Exception e) catch (Exception e)
{ {
//OK, how do I throw an exception such that it really stops the startup sequence? LOG.warn(e);
e.printStackTrace(); throw new RuntimeException(e);
} }
} }
//Email from Jan Luehe 18 August: after all ServletContainerInitializers have been
//called, need to check to see if there are any ServletRegistrations remaining
//that are "preliminary" and fail the deployment if so. Implemented in ServletHolder.doStart().
} }
} }
@ -136,7 +135,6 @@ public class ServletContainerInitializerListener implements ServletContextListen
*/ */
public void contextDestroyed(ServletContextEvent sce) public void contextDestroyed(ServletContextEvent sce)
{ {
// TODO Auto-generated method stub
} }

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.annotations;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.ServletSecurityElement;
import javax.servlet.annotation.HttpConstraint; import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.HttpMethodConstraint; import javax.servlet.annotation.HttpMethodConstraint;
import javax.servlet.annotation.ServletSecurity; import javax.servlet.annotation.ServletSecurity;
@ -29,11 +30,13 @@ import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler; import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
import org.eclipse.jetty.security.ConstraintAware; import org.eclipse.jetty.security.ConstraintAware;
import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping; import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.webapp.Origin;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
/** /**
@ -80,7 +83,7 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
if (servletSecurity == null) if (servletSecurity == null)
return; return;
//If there are already constraints defined (ie from web.xml or programmatically(?)) that match any //If there are already constraints defined (ie from web.xml) that match any
//of the url patterns defined for this servlet, then skip the security annotation. //of the url patterns defined for this servlet, then skip the security annotation.
List<ServletMapping> servletMappings = getServletMappings(clazz.getCanonicalName()); List<ServletMapping> servletMappings = getServletMappings(clazz.getCanonicalName());
@ -95,19 +98,15 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
//Make a fresh list //Make a fresh list
constraintMappings = new ArrayList<ConstraintMapping>(); constraintMappings = new ArrayList<ConstraintMapping>();
//Get the values that form the constraints that will apply unless there are HttpMethodConstraints to augment them ServletSecurityElement securityElement = new ServletSecurityElement(servletSecurity);
HttpConstraint defaults = servletSecurity.value(); for (ServletMapping sm : servletMappings)
{
//Make a Constraint for the <auth-constraint> and <user-data-constraint> specified by the HttpConstraint for (String url : sm.getPathSpecs())
Constraint defaultConstraint = makeConstraint (clazz, {
defaults.rolesAllowed(), _context.getMetaData().setOrigin("constraint.url."+url, Origin.Annotation);
defaults.value(), constraintMappings.addAll(ConstraintSecurityHandler.createConstraintsWithMappingsForPath(clazz.getName(), url, securityElement));
defaults.transportGuarantee()); }
}
constraintMappings.addAll(makeMethodMappings(clazz,
defaultConstraint,
servletMappings,
servletSecurity.httpMethodConstraints()));
//set up the security constraints produced by the annotation //set up the security constraints produced by the annotation
ConstraintAware securityHandler = (ConstraintAware)_context.getSecurityHandler(); ConstraintAware securityHandler = (ConstraintAware)_context.getSecurityHandler();
@ -129,108 +128,13 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
*/ */
protected Constraint makeConstraint (Class servlet, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport) protected Constraint makeConstraint (Class servlet, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
{ {
Constraint constraint = new Constraint(); return ConstraintSecurityHandler.createConstraint(servlet.getName(), rolesAllowed, permitOrDeny, transport);
if (rolesAllowed == null || rolesAllowed.length==0)
{
if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
{
//Equivalent to <auth-constraint> with no roles
constraint.setName(servlet.getName()+"-Deny");
constraint.setAuthenticate(true);
}
else
{
//Equivalent to no <auth-constraint>
constraint.setAuthenticate(false);
constraint.setName(servlet.getName()+"-Permit");
}
}
else
{
//Equivalent to <auth-constraint> with list of <security-role-name>s
constraint.setAuthenticate(true);
constraint.setRoles(rolesAllowed);
constraint.setName(servlet.getName()+"-RolesAllowed");
}
//Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
return constraint;
}
/**
* Make a ConstraintMapping which captures the <http-method> or <http-method-omission> elements for a particular url pattern,
* and relates it to a Constraint object (<auth-constraint> and <user-data-constraint>).
* @param constraint
* @param url
* @param method
* @param omissions
* @return
*/
protected ConstraintMapping makeConstraintMapping (Constraint constraint, String url, String method, String[] omissions)
{
ConstraintMapping mapping = new ConstraintMapping();
mapping.setConstraint(constraint);
mapping.setPathSpec(url);
if (method != null)
mapping.setMethod(method);
if (omissions != null)
mapping.setMethodOmissions(omissions);
return mapping;
}
/**
* Make the Jetty Constraints and ConstraintMapping objects that correspond to the HttpMethodConstraint
* annotations for each url pattern for the servlet.
* @param servlet
* @param defaultConstraint
* @param servletMappings
* @param annotations
* @return
*/
protected List<ConstraintMapping> makeMethodMappings (Class servlet, Constraint defaultConstraint, List<ServletMapping> servletMappings, HttpMethodConstraint[] annotations)
{
List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
//for each url-pattern existing for the servlet make a ConstraintMapping for the HttpConstraint, and ConstraintMappings for
//each HttpMethodConstraint
for (ServletMapping sm : servletMappings)
{
for (String url : sm.getPathSpecs())
{
//Make a ConstraintMapping that matches the defaultConstraint
ConstraintMapping defaultMapping = makeConstraintMapping(defaultConstraint, url, null, null);
//If there are HttpMethodConstraint annotations, make a Constraint and a ConstraintMapping for it
if (annotations != null && annotations.length>0)
{
List<String> omissions = new ArrayList<String>();
//for each HttpMethodConstraint annotation, make a new Constraint and ConstraintMappings for this url
for (int i=0; i < annotations.length;i++)
{
//Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements
Constraint methodConstraint = makeConstraint(servlet,
annotations[i].rolesAllowed(),
annotations[i].emptyRoleSemantic(),
annotations[i].transportGuarantee());
//Make ConstraintMapping that captures the <http-method> elements
ConstraintMapping methodConstraintMapping = makeConstraintMapping (methodConstraint,
url,annotations[i].value(),
null);
mappings.add(methodConstraintMapping);
omissions.add(annotations[i].value());
}
defaultMapping.setMethodOmissions(omissions.toArray(new String[0]));
}
//add the constraint mapping containing the http-method-omissions, if there are any
mappings.add(defaultMapping);
}
}
return mappings;
} }
@ -282,6 +186,7 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
{ {
for (int j=0; j < pathSpecs.length; j++) for (int j=0; j < pathSpecs.length; j++)
{ {
//TODO decide if we need to check the origin
if (pathSpecs[j].equals(constraintMappings.get(i).getPathSpec())) if (pathSpecs[j].equals(constraintMappings.get(i).getPathSpec()))
{ {
exists = true; exists = true;

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.Holder; import org.eclipse.jetty.servlet.Holder;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.DiscoveredAnnotation; import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.MetaData; import org.eclipse.jetty.webapp.MetaData;
import org.eclipse.jetty.webapp.Origin; import org.eclipse.jetty.webapp.Origin;
@ -52,6 +53,11 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
{ {
super(context, className); super(context, className);
} }
public WebFilterAnnotation(WebAppContext context, String className, Resource resource)
{
super(context, className, resource);
}
/** /**
* @see org.eclipse.jetty.annotations.ClassAnnotation#apply() * @see org.eclipse.jetty.annotations.ClassAnnotation#apply()

View File

@ -23,6 +23,7 @@ import java.util.List;
import org.eclipse.jetty.annotations.AnnotationParser.Value; import org.eclipse.jetty.annotations.AnnotationParser.Value;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
/** /**
@ -38,24 +39,38 @@ public class WebFilterAnnotationHandler extends AbstractDiscoverableAnnotationHa
{ {
super(context); super(context);
} }
public WebFilterAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
{
super(context, list);
}
@Override
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation, public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
List<Value> values) List<Value> values)
{ {
WebFilterAnnotation wfAnnotation = new WebFilterAnnotation(_context, className); WebFilterAnnotation wfAnnotation = new WebFilterAnnotation(_context, className, _resource);
addAnnotation(wfAnnotation); addAnnotation(wfAnnotation);
} }
@Override
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
List<Value> values) List<Value> values)
{ {
LOG.warn ("@WebFilter not applicable for fields: "+className+"."+fieldName); LOG.warn ("@WebFilter not applicable for fields: "+className+"."+fieldName);
} }
@Override
public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation, public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
List<Value> values) List<Value> values)
{ {
LOG.warn ("@WebFilter not applicable for methods: "+className+"."+methodName+" "+signature); LOG.warn ("@WebFilter not applicable for methods: "+className+"."+methodName+" "+signature);
} }
@Override
public String getAnnotationName()
{
return "javax.servlet.annotation.WebFilter";
}
} }

View File

@ -27,6 +27,7 @@ import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.DiscoveredAnnotation; import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.MetaData; import org.eclipse.jetty.webapp.MetaData;
import org.eclipse.jetty.webapp.Origin; import org.eclipse.jetty.webapp.Origin;
@ -49,6 +50,11 @@ public class WebListenerAnnotation extends DiscoveredAnnotation
{ {
super(context, className); super(context, className);
} }
public WebListenerAnnotation(WebAppContext context, String className, Resource resource)
{
super(context, className, resource);
}
/** /**
* @see org.eclipse.jetty.annotations.ClassAnnotation#apply() * @see org.eclipse.jetty.annotations.ClassAnnotation#apply()

View File

@ -23,6 +23,7 @@ import java.util.List;
import org.eclipse.jetty.annotations.AnnotationParser.Value; import org.eclipse.jetty.annotations.AnnotationParser.Value;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotationHandler public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotationHandler
@ -34,13 +35,18 @@ public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotation
super(context); super(context);
} }
public WebListenerAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
{
super(context, list);
}
/** /**
* @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List) * @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
*/ */
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation, public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
List<Value> values) List<Value> values)
{ {
WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, className); WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, className, _resource);
addAnnotation(wlAnnotation); addAnnotation(wlAnnotation);
} }
@ -56,4 +62,10 @@ public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotation
LOG.warn ("@WebListener is not applicable to methods: "+className+"."+methodName+" "+signature); LOG.warn ("@WebListener is not applicable to methods: "+className+"."+methodName+" "+signature);
} }
@Override
public String getAnnotationName()
{
return "javax.servlet.annotation.WebListener";
}
} }

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.LazyList;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.DiscoveredAnnotation; import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.MetaData; import org.eclipse.jetty.webapp.MetaData;
import org.eclipse.jetty.webapp.Origin; import org.eclipse.jetty.webapp.Origin;
@ -47,7 +48,13 @@ public class WebServletAnnotation extends DiscoveredAnnotation
{ {
super(context, className); super(context, className);
} }
public WebServletAnnotation (WebAppContext context, String className, Resource resource)
{
super(context, className, resource);
}
/** /**
* @see org.eclipse.jetty.annotations.ClassAnnotation#apply() * @see org.eclipse.jetty.annotations.ClassAnnotation#apply()
*/ */

View File

@ -23,6 +23,7 @@ import java.util.List;
import org.eclipse.jetty.annotations.AnnotationParser.Value; import org.eclipse.jetty.annotations.AnnotationParser.Value;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
/** /**
@ -40,6 +41,11 @@ public class WebServletAnnotationHandler extends AbstractDiscoverableAnnotationH
super(context); super(context);
} }
public WebServletAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
{
super(context, list);
}
/** /**
* Handle discovering a WebServlet annotation. * Handle discovering a WebServlet annotation.
@ -47,25 +53,35 @@ public class WebServletAnnotationHandler extends AbstractDiscoverableAnnotationH
* *
* @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List) * @see org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler#handleClass(java.lang.String, int, int, java.lang.String, java.lang.String, java.lang.String[], java.lang.String, java.util.List)
*/ */
@Override
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotationName, public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotationName,
List<Value> values) List<Value> values)
{ {
if (!"javax.servlet.annotation.WebServlet".equals(annotationName)) if (!"javax.servlet.annotation.WebServlet".equals(annotationName))
return; return;
WebServletAnnotation annotation = new WebServletAnnotation (_context, className); WebServletAnnotation annotation = new WebServletAnnotation (_context, className, _resource);
addAnnotation(annotation); addAnnotation(annotation);
} }
@Override
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation, public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
List<Value> values) List<Value> values)
{ {
LOG.warn ("@WebServlet annotation not supported for fields"); LOG.warn ("@WebServlet annotation not supported for fields");
} }
@Override
public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation, public void handleMethod(String className, String methodName, int access, String params, String signature, String[] exceptions, String annotation,
List<Value> values) List<Value> values)
{ {
LOG.warn ("@WebServlet annotation not supported for methods"); LOG.warn ("@WebServlet annotation not supported for methods");
} }
@Override
public String getAnnotationName()
{
return "javax.servlet.annotation.WebServlet";
}
} }

View File

@ -66,6 +66,12 @@ public class TestAnnotationInheritance
{ {
annotatedMethods.add(className+"."+methodName); annotatedMethods.add(className+"."+methodName);
} }
@Override
public String getAnnotationName()
{
return "org.eclipse.jetty.annotations.Sample";
}
} }
@After @After
@ -85,7 +91,7 @@ public class TestAnnotationInheritance
SampleHandler handler = new SampleHandler(); SampleHandler handler = new SampleHandler();
AnnotationParser parser = new AnnotationParser(); AnnotationParser parser = new AnnotationParser();
parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Sample", handler); parser.registerHandler(handler);
parser.parse(classNames, new ClassNameResolver () parser.parse(classNames, new ClassNameResolver ()
{ {
public boolean isExcluded(String name) public boolean isExcluded(String name)

View File

@ -41,6 +41,9 @@ public class TestAnnotationParser
{ {
private List<String> methods = Arrays.asList("a", "b", "c", "d", "l"); private List<String> methods = Arrays.asList("a", "b", "c", "d", "l");
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation, public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
List<Value> values) List<Value> values)
{ {
@ -81,9 +84,15 @@ public class TestAnnotationParser
assertTrue(methods.contains(methodName)); assertTrue(methods.contains(methodName));
assertEquals("org.eclipse.jetty.annotations.Sample", annotation); assertEquals("org.eclipse.jetty.annotations.Sample", annotation);
} }
@Override
public String getAnnotationName()
{
return "org.eclipse.jetty.annotations.Sample";
}
} }
parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Sample", new SampleAnnotationHandler()); parser.registerHandler(new SampleAnnotationHandler());
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
parser.parse(classNames, new ClassNameResolver () parser.parse(classNames, new ClassNameResolver ()
@ -140,9 +149,17 @@ public class TestAnnotationParser
System.err.println(anv.toString()); System.err.println(anv.toString());
} }
} }
@Override
public String getAnnotationName()
{
return "org.eclipse.jetty.annotations.Multi";
}
} }
parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Multi", new MultiAnnotationHandler()); parser.registerHandler(new MultiAnnotationHandler());
parser.parse(classNames, null); parser.parse(classNames, null);
} }
} }

View File

@ -172,6 +172,9 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
{ {
} }
/**
* @throws IOException
*/
protected void commitRequest() throws IOException protected void commitRequest() throws IOException
{ {
synchronized (this) synchronized (this)
@ -223,6 +226,7 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
requestHeaders.putLongField(HttpHeader.CONTENT_LENGTH, requestContent.length()); requestHeaders.putLongField(HttpHeader.CONTENT_LENGTH, requestContent.length());
_generator.completeHeader(requestHeaders,false); _generator.completeHeader(requestHeaders,false);
_generator.addContent(new View(requestContent),true); _generator.addContent(new View(requestContent),true);
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
} }
else else
{ {
@ -230,24 +234,14 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
if (requestContentStream != null) if (requestContentStream != null)
{ {
_generator.completeHeader(requestHeaders, false); _generator.completeHeader(requestHeaders, false);
int available = requestContentStream.available();
if (available > 0)
{
// TODO deal with any known content length
// TODO reuse this buffer!
byte[] buf = new byte[available];
int length = requestContentStream.read(buf);
_generator.addContent(new ByteArrayBuffer(buf, 0, length), false);
}
} }
else else
{ {
requestHeaders.remove(HttpHeader.CONTENT_LENGTH); requestHeaders.remove(HttpHeader.CONTENT_LENGTH);
_generator.completeHeader(requestHeaders, true); _generator.completeHeader(requestHeaders, true);
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
} }
} }
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
} }
} }

View File

@ -114,6 +114,8 @@ public class AsyncHttpConnection extends AbstractHttpConnection implements Async
ByteBuffer chunk=_requestContentChunk; ByteBuffer chunk=_requestContentChunk;
_requestContentChunk=exchange.getRequestContentChunk(null); _requestContentChunk=exchange.getRequestContentChunk(null);
_generator.addContent(chunk,_requestContentChunk==null); _generator.addContent(chunk,_requestContentChunk==null);
if (_requestContentChunk==null)
exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
} }
} }
} }
@ -208,12 +210,12 @@ public class AsyncHttpConnection extends AbstractHttpConnection implements Async
{ {
Connection switched=exchange.onSwitchProtocol(_endp); Connection switched=exchange.onSwitchProtocol(_endp);
if (switched!=null) if (switched!=null)
connection=switched;
{ {
// switched protocol! // switched protocol!
_pipeline = null;
if (_pipeline!=null) if (_pipeline!=null)
{
_destination.send(_pipeline); _destination.send(_pipeline);
}
_pipeline = null; _pipeline = null;
connection=switched; connection=switched;

View File

@ -122,6 +122,8 @@ public class BlockingHttpConnection extends AbstractHttpConnection
ByteBuffer chunk=_requestContentChunk; ByteBuffer chunk=_requestContentChunk;
_requestContentChunk=exchange.getRequestContentChunk(null); _requestContentChunk=exchange.getRequestContentChunk(null);
_generator.addContent(chunk,_requestContentChunk==null); _generator.addContent(chunk,_requestContentChunk==null);
if (_requestContentChunk==null)
exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
} }
} }
} }

View File

@ -792,12 +792,16 @@ public class HttpFields implements Iterable<HttpFields.Field>
QuotedStringTokenizer.quoteIfNeeded(buf, name, delim); QuotedStringTokenizer.quoteIfNeeded(buf, name, delim);
buf.append('='); buf.append('=');
String start=buf.toString(); String start=buf.toString();
boolean hasDomain = false;
boolean hasPath = false;
if (value != null && value.length() > 0) if (value != null && value.length() > 0)
QuotedStringTokenizer.quoteIfNeeded(buf, value, delim); QuotedStringTokenizer.quoteIfNeeded(buf, value, delim);
if (path != null && path.length() > 0) if (path != null && path.length() > 0)
{ {
hasPath = true;
buf.append(";Path="); buf.append(";Path=");
if (path.trim().startsWith("\"")) if (path.trim().startsWith("\""))
buf.append(path); buf.append(path);
@ -806,6 +810,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
} }
if (domain != null && domain.length() > 0) if (domain != null && domain.length() > 0)
{ {
hasDomain = true;
buf.append(";Domain="); buf.append(";Domain=");
QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(),delim); QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(),delim);
} }
@ -841,14 +846,20 @@ public class HttpFields implements Iterable<HttpFields.Field>
Field last=null; Field last=null;
while (field!=null) while (field!=null)
{ {
if (field._value!=null && field._value.toString().startsWith(start)) String val = (field._value == null ? null : field._value.toString());
if (val!=null && val.startsWith(start))
{ {
_fields.remove(field); //existing cookie has same name, does it also match domain and path?
if (last==null) if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) &&
_names.put(HttpHeader.SET_COOKIE.toString(),field._next); ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path))))
else {
last._next=field._next; _fields.remove(field);
break; if (last==null)
_names.put(HttpHeader.SET_COOKIE.toString(),field._next);
else
last._next=field._next;
break;
}
} }
last=field; last=field;
field=field._next; field=field._next;

View File

@ -100,7 +100,15 @@ public class HttpURI
public HttpURI(String raw) public HttpURI(String raw)
{ {
_rawString=raw; _rawString=raw;
byte[] b = raw.getBytes(); byte[] b;
try
{
b = raw.getBytes(StringUtil.__UTF8);
}
catch (UnsupportedEncodingException e)
{
throw new RuntimeException(e.getMessage());
}
parse(b,0,b.length); parse(b,0,b.length);
_charset = URIUtil.__CHARSET; _charset = URIUtil.__CHARSET;
} }
@ -693,20 +701,22 @@ public class HttpURI
{ {
if (_query==_fragment) if (_query==_fragment)
return; return;
UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters); if (_charset==StringUtil.__UTF8_CHARSET)
UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
else
UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,_charset.toString()),parameters,_charset.toString());
} }
public void decodeQueryTo(MultiMap parameters, String encoding) public void decodeQueryTo(MultiMap parameters, String encoding) throws UnsupportedEncodingException
throws UnsupportedEncodingException {
{
if (_query==_fragment) if (_query==_fragment)
return; return;
if (encoding==null || StringUtil.isUTF8(encoding)) if (encoding==null || StringUtil.isUTF8(encoding))
UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters); UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
else else
UrlEncoded.decodeTo(new String(_raw,_query+1,_fragment-_query-1,_charset),parameters,encoding); UrlEncoded.decodeTo(StringUtil.toString(_raw,_query+1,_fragment-_query-1,encoding),parameters,encoding);
} }
public void clear() public void clear()
{ {

View File

@ -277,7 +277,8 @@ public class HttpFieldsTest
assertEquals("minimal=value",fields.getStringField("Set-Cookie")); assertEquals("minimal=value",fields.getStringField("Set-Cookie"));
fields.clear(); fields.clear();
fields.addSetCookie("everything","wrong","wrong","wrong",0,"to be replaced",true,true,0); //test cookies with same name, domain and path, only 1 allowed
fields.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0);
fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0); fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0);
assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie")); assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie"));
Enumeration<String> e =fields.getValues("Set-Cookie"); Enumeration<String> e =fields.getValues("Set-Cookie");
@ -285,7 +286,61 @@ public class HttpFieldsTest
assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); assertEquals("everything=value;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements()); assertFalse(e.hasMoreElements());
assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires")); assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires"));
assertFalse(e.hasMoreElements());
//test cookies with same name, different domain
fields.clear();
fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
fields.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies with same name, same path, one with domain, one without
fields.clear();
fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0);
fields.addSetCookie("everything","value","","path",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies with same name, different path
fields.clear();
fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
fields.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies with same name, same domain, one with path, one without
fields.clear();
fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0);
fields.addSetCookie("everything","value","domain1","",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=other;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement());
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
//test cookies same name only, no path, no domain
fields.clear();
fields.addSetCookie("everything","other","","",0,"blah",true,true,0);
fields.addSetCookie("everything","value","","",0,"comment",true,true,0);
e =fields.getValues("Set-Cookie");
assertTrue(e.hasMoreElements());
assertEquals("everything=value;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement());
assertFalse(e.hasMoreElements());
fields.clear(); fields.clear();
fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,2); fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,2);

View File

@ -37,6 +37,7 @@ import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.ServerAuthException;
import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.security.UserAuthentication;
import org.eclipse.jetty.security.authentication.DeferredAuthentication; import org.eclipse.jetty.security.authentication.DeferredAuthentication;
import org.eclipse.jetty.security.authentication.LoginAuthenticator;
import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.Authentication;
import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.server.Authentication.User;
@ -44,7 +45,7 @@ import org.eclipse.jetty.server.Authentication.User;
/** /**
* @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $ * @version $Rev: 4793 $ $Date: 2009-03-19 00:00:01 +0100 (Thu, 19 Mar 2009) $
*/ */
public class JaspiAuthenticator implements Authenticator public class JaspiAuthenticator extends LoginAuthenticator
{ {
private final ServerAuthConfig _authConfig; private final ServerAuthConfig _authConfig;
@ -58,7 +59,7 @@ public class JaspiAuthenticator implements Authenticator
private final IdentityService _identityService; private final IdentityService _identityService;
private final DeferredAuthentication _deferred;
public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, Subject serviceSubject, public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, Subject serviceSubject,
boolean allowLazyAuthentication, IdentityService identityService) boolean allowLazyAuthentication, IdentityService identityService)
@ -72,11 +73,11 @@ public class JaspiAuthenticator implements Authenticator
this._serviceSubject = serviceSubject; this._serviceSubject = serviceSubject;
this._allowLazyAuthentication = allowLazyAuthentication; this._allowLazyAuthentication = allowLazyAuthentication;
this._identityService = identityService; this._identityService = identityService;
this._deferred = new DeferredAuthentication(this);
} }
public void setConfiguration(AuthConfiguration configuration) public void setConfiguration(AuthConfiguration configuration)
{ {
super.setConfiguration(configuration);
} }
public String getAuthMethod() public String getAuthMethod()
@ -93,7 +94,7 @@ public class JaspiAuthenticator implements Authenticator
//if its not mandatory to authenticate, and the authenticator returned UNAUTHENTICATED, we treat it as authentication deferred //if its not mandatory to authenticate, and the authenticator returned UNAUTHENTICATED, we treat it as authentication deferred
if (_allowLazyAuthentication && !info.isAuthMandatory() && a == Authentication.UNAUTHENTICATED) if (_allowLazyAuthentication && !info.isAuthMandatory() && a == Authentication.UNAUTHENTICATED)
a =_deferred; a = new DeferredAuthentication(this);
return a; return a;
} }

View File

@ -97,25 +97,15 @@ public class ContextFactory implements ObjectFactory
return ctx; return ctx;
} }
// Next, see if we are in a webapp context, if we are, use
// the classloader of the webapp to find the right jndi comp context
ClassLoader loader = null; ClassLoader loader = null;
if (ContextHandler.getCurrentContext() != null)
loader = Thread.currentThread().getContextClassLoader();
if (__log.isDebugEnabled() && loader != null) __log.debug("Using thread context classloader");
if (loader == null && ContextHandler.getCurrentContext() != null)
{ {
loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader(); loader = ContextHandler.getCurrentContext().getContextHandler().getClassLoader();
} if (__log.isDebugEnabled() && loader != null) __log.debug("Using classloader of current org.eclipse.jetty.server.handler.ContextHandler");
if (loader != null)
{
if (__log.isDebugEnabled()) __log.debug("Using classloader of current org.eclipse.jetty.server.handler.ContextHandler");
}
else
{
//Not already in a webapp context, in that case, we try the
//curren't thread's classloader instead
loader = Thread.currentThread().getContextClassLoader();
if (__log.isDebugEnabled()) __log.debug("Using thread context classloader");
} }
//Get the context matching the classloader //Get the context matching the classloader
@ -124,47 +114,24 @@ public class ContextFactory implements ObjectFactory
//The map does not contain an entry for this classloader //The map does not contain an entry for this classloader
if (ctx == null) if (ctx == null)
{ {
//Check if a parent classloader has created the context //Didn't find a context to match, make one
ctx = getParentClassLoaderContext(loader); Reference ref = (Reference)obj;
StringRefAddr parserAddr = (StringRefAddr)ref.get("parser");
String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent());
NameParser parser = (NameParser)(parserClassName==null?null:loader.loadClass(parserClassName).newInstance());
//Didn't find a context to match any of the ancestors ctx = new NamingContext (env,
//of the classloader, so make a context name.get(0),
if (ctx == null) (NamingContext)nameCtx,
{ parser);
Reference ref = (Reference)obj; if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader);
StringRefAddr parserAddr = (StringRefAddr)ref.get("parser"); __contextMap.put (loader, ctx);
String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent());
NameParser parser = (NameParser)(parserClassName==null?null:loader.loadClass(parserClassName).newInstance());
ctx = new NamingContext (env,
name.get(0),
(NamingContext)nameCtx,
parser);
if(__log.isDebugEnabled())__log.debug("No entry for classloader: "+loader);
__contextMap.put (loader, ctx);
}
} }
return ctx; return ctx;
} }
/**
* Keep trying ancestors of the given classloader to find one to which
* the context is bound.
* @param loader
* @return the context from the parent class loader
*/
public Context getParentClassLoaderContext (ClassLoader loader)
{
Context ctx = null;
ClassLoader cl = loader;
for (cl = cl.getParent(); (cl != null) && (ctx == null); cl = cl.getParent())
{
ctx = (Context)__contextMap.get(cl);
}
return ctx;
}
/** /**

View File

@ -35,6 +35,7 @@ import javax.naming.Reference;
import javax.naming.StringRefAddr; import javax.naming.StringRefAddr;
import javax.naming.spi.ObjectFactory; import javax.naming.spi.ObjectFactory;
import org.eclipse.jetty.jndi.ContextFactory;
import org.eclipse.jetty.jndi.NamingContext; import org.eclipse.jetty.jndi.NamingContext;
import org.eclipse.jetty.jndi.local.localContextRoot; import org.eclipse.jetty.jndi.local.localContextRoot;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
@ -44,7 +45,8 @@ import org.junit.Test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
/** /**
* *
*/ */
@ -71,14 +73,16 @@ public class TestJNDI
@Test @Test
public void testIt() throws Exception public void testIt() throws Exception
{ {
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader currentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader);
try try
{ {
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader currentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader);
//Uncomment to aid with debug
/* /*
javaRootURLContext.getRoot().addListener(new NamingContext.Listener() javaRootURLContext.getRoot().addListener(new NamingContext.Listener()
{ {
@ -116,7 +120,19 @@ public class TestJNDI
InitialContext initCtxA = new InitialContext(); InitialContext initCtxA = new InitialContext();
initCtxA.bind ("blah", "123"); initCtxA.bind ("blah", "123");
assertEquals ("123", initCtxA.lookup("blah")); assertEquals ("123", initCtxA.lookup("blah"));
initCtxA.destroySubcontext("blah");
try
{
initCtxA.lookup("blah");
fail("context blah was not destroyed");
}
catch (NameNotFoundException e)
{
//expected
}
InitialContext initCtx = new InitialContext(); InitialContext initCtx = new InitialContext();
Context sub0 = (Context)initCtx.lookup("java:"); Context sub0 = (Context)initCtx.lookup("java:");
@ -216,6 +232,7 @@ public class TestJNDI
try try
{ {
initCtx.lookup("java:comp/env/rubbish"); initCtx.lookup("java:comp/env/rubbish");
fail("env should not exist for this classloader");
} }
catch (NameNotFoundException e) catch (NameNotFoundException e)
{ {
@ -284,18 +301,78 @@ public class TestJNDI
{ {
//expected failure to modify immutable context //expected failure to modify immutable context
} }
System.err.println("java:"+javaRootURLContext.getRoot().dump());
System.err.println("local:"+localContextRoot.getRoot().dump());
//test what happens when you close an initial context that was used //test what happens when you close an initial context that was used
initCtx.close(); initCtx.close();
} }
finally finally
{ {
//make some effort to clean up
InitialContext ic = new InitialContext(); InitialContext ic = new InitialContext();
Context java = (Context)ic.lookup("java:");
java.destroySubcontext("zero");
java.destroySubcontext("fee");
currentThread.setContextClassLoader(childLoader1);
Context comp = (Context)ic.lookup("java:comp"); Context comp = (Context)ic.lookup("java:comp");
comp.destroySubcontext("env"); comp.destroySubcontext("env");
comp.unbind("crud");
comp.unbind("crud2");
}
}
@Test
public void testParent()
throws Exception
{
//set up some classloaders
Thread currentThread = Thread.currentThread();
ClassLoader parentLoader = currentThread.getContextClassLoader();
ClassLoader childLoader1 = new URLClassLoader(new URL[0], parentLoader);
try
{
//Test creating a comp for the parent loader does not leak to child
InitialContext initCtx = new InitialContext();
Context comp = (Context)initCtx.lookup("java:comp");
assertNotNull(comp);
Context env = (Context)comp.createSubcontext("env");
assertNotNull(env);
env.bind("foo", "aaabbbcccddd");
assertEquals("aaabbbcccddd", (String)initCtx.lookup("java:comp/env/foo"));
//Change to child loader
currentThread.setContextClassLoader(childLoader1);
comp = (Context)initCtx.lookup("java:comp");
Context childEnv = (Context)comp.createSubcontext("env");
assertNotSame(env, childEnv);
childEnv.bind("foo", "eeefffggghhh");
assertEquals("eeefffggghhh", (String)initCtx.lookup("java:comp/env/foo"));
//Change back to parent
currentThread.setContextClassLoader(parentLoader);
assertEquals("aaabbbcccddd", (String)initCtx.lookup("java:comp/env/foo"));
}
finally
{
//make some effort to clean up
InitialContext ic = new InitialContext();
currentThread.setContextClassLoader(parentLoader);
Context comp = (Context)ic.lookup("java:comp");
comp.destroySubcontext("env");
currentThread.setContextClassLoader(childLoader1);
comp = (Context)ic.lookup("java:comp");
comp.destroySubcontext("env");
} }
} }
} }

View File

@ -35,6 +35,8 @@ import org.eclipse.jetty.osgi.boot.JettyBootstrapActivator;
import org.eclipse.jetty.osgi.boot.OSGiAppProvider; import org.eclipse.jetty.osgi.boot.OSGiAppProvider;
import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper;
import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer; import org.eclipse.jetty.osgi.boot.utils.WebappRegistrationCustomizer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.osgi.framework.Bundle; import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil; import org.osgi.framework.FrameworkUtil;
import org.xml.sax.EntityResolver; import org.xml.sax.EntityResolver;
@ -52,6 +54,7 @@ import org.xml.sax.SAXException;
*/ */
public class WebappRegistrationCustomizerImpl implements WebappRegistrationCustomizer public class WebappRegistrationCustomizerImpl implements WebappRegistrationCustomizer
{ {
private static final Logger LOG = Log.getLogger(WebappRegistrationCustomizerImpl.class);
/** /**
* Default name of a class that belongs to the jstl bundle. From that class * Default name of a class that belongs to the jstl bundle. From that class
@ -112,8 +115,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto
} }
catch (Exception e) catch (Exception e)
{ {
System.err.println("Unable to set the JspFactory: jsp support incomplete."); LOG.warn("Unable to set the JspFactory: jsp support incomplete.",e);
e.printStackTrace();
} }
} }
@ -138,17 +140,23 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto
public URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper locatorHelper) throws Exception public URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper locatorHelper) throws Exception
{ {
HashSet<Class<?>> classesToAddToTheTldBundles = new HashSet<Class<?>>(); ArrayList<URL> urls = new ArrayList<URL>();
HashSet<Class<?>> classesToAddToTheTldBundles = new HashSet<Class<?>>();
// Look for the jstl bundle // Look for the jstl bundle
// We assume the jstl's tlds are defined there. // We assume the jstl's tlds are defined there.
// We assume that the jstl bundle is imported by this bundle // We assume that the jstl bundle is imported by this bundle
// So we can look for this class using this bundle's classloader: // So we can look for this class using this bundle's classloader:
Class<?> jstlClass = WebappRegistrationCustomizerImpl.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS); try
{
Class<?> jstlClass = WebappRegistrationCustomizerImpl.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
classesToAddToTheTldBundles.add(jstlClass); classesToAddToTheTldBundles.add(jstlClass);
}
ArrayList<URL> urls = new ArrayList<URL>(); catch (ClassNotFoundException e)
{
LOG.info("jstl not on classpath", e);
}
for (Class<?> cl : classesToAddToTheTldBundles) for (Class<?> cl : classesToAddToTheTldBundles)
{ {
Bundle tldBundle = FrameworkUtil.getBundle(cl); Bundle tldBundle = FrameworkUtil.getBundle(cl);
@ -208,7 +216,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace(); LOG.warn(e);
} }
} }

View File

@ -21,6 +21,8 @@ package org.eclipse.jetty.osgi.annotations;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.ClassNameResolver; import org.eclipse.jetty.annotations.ClassNameResolver;
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants; import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
@ -151,20 +153,25 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
protected void parseBundle(WebAppContext context, AnnotationParser parser, protected void parseBundle(WebAppContext context, AnnotationParser parser,
Bundle webbundle, Bundle bundle) throws Exception Bundle webbundle, Bundle bundle) throws Exception
{ {
Resource bundleRes = parser.getResource(bundle); Resource bundleRes = parser.getResource(bundle);
parser.clearHandlers();
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
{
if (webbundle == bundle)
((AbstractDiscoverableAnnotationHandler)h).setResource(null);
else
((AbstractDiscoverableAnnotationHandler)h).setResource(bundleRes);
}
}
parser.registerHandlers(_discoverableAnnotationHandlers);
parser.registerHandler(_classInheritanceHandler);
parser.registerHandlers(_containerInitializerAnnotationHandlers);
parser.parse(bundle,createClassNameResolver(context)); parser.parse(bundle,createClassNameResolver(context));
List<DiscoveredAnnotation> annotations = new ArrayList<DiscoveredAnnotation>();
gatherAnnotations(annotations, parser.getAnnotationHandlers());
if (webbundle == bundle)
{
//just like the super with its question about annotations in WEB-INF/classes:
//"TODO - where to set the annotations discovered from WEB-INF/classes?"
context.getMetaData().addDiscoveredAnnotations(annotations);
}
else
{
context.getMetaData().addDiscoveredAnnotations(bundleRes, annotations);
}
} }
/** /**

View File

@ -48,4 +48,18 @@ public class TldLocatableURLClassloader extends URLClassLoader
{ {
return _jarsWithTldsInside; return _jarsWithTldsInside;
} }
public String toString()
{
StringBuilder builder = new StringBuilder();
if (_jarsWithTldsInside != null)
{
for (URL u:_jarsWithTldsInside)
builder.append(" "+u.toString());
return builder.toString();
}
else
return super.toString();
}
} }

View File

@ -19,15 +19,24 @@
package org.eclipse.jetty.security; package org.eclipse.jetty.security;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.HttpConstraintElement;
import javax.servlet.HttpMethodConstraintElement;
import javax.servlet.ServletSecurityElement;
import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.http.PathMap;
import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpChannel;
@ -36,24 +45,230 @@ import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.StringMap; import org.eclipse.jetty.util.StringMap;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Constraint;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Handler to enforce SecurityConstraints. This implementation is servlet spec * Handler to enforce SecurityConstraints. This implementation is servlet spec
* 2.4 compliant and pre-computes the constraint combinations for runtime * 3.0 compliant and pre-computes the constraint combinations for runtime
* efficiency. * efficiency.
* *
*/ */
public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
{ {
private static final String OMISSION_SUFFIX = ".omission";
private static final String ALL_METHODS = "*"; private static final String ALL_METHODS = "*";
private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>(); private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
private final Set<String> _roles = new CopyOnWriteArraySet<>(); private final Set<String> _roles = new CopyOnWriteArraySet<>();
private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>(); private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
private boolean _strict = true; private boolean _strict = true;
/* ------------------------------------------------------------ */
/**
* @return
*/
public static Constraint createConstraint()
{
return new Constraint();
}
/* ------------------------------------------------------------ */
/**
* @param constraint
* @return
*/
public static Constraint createConstraint(Constraint constraint)
{
try
{
return (Constraint)constraint.clone();
}
catch (CloneNotSupportedException e)
{
throw new IllegalStateException (e);
}
}
/* ------------------------------------------------------------ */
/**
* Create a security constraint
*
* @param name
* @param authenticate
* @param roles
* @param dataConstraint
* @return
*/
public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint)
{
Constraint constraint = createConstraint();
if (name != null)
constraint.setName(name);
constraint.setAuthenticate(authenticate);
constraint.setRoles(roles);
constraint.setDataConstraint(dataConstraint);
return constraint;
}
/* ------------------------------------------------------------ */
/**
* @param name
* @param element
* @return
*/
public static Constraint createConstraint (String name, HttpConstraintElement element)
{
return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee());
}
/* ------------------------------------------------------------ */
/**
* @param name
* @param rolesAllowed
* @param permitOrDeny
* @param transport
* @return
*/
public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport)
{
Constraint constraint = createConstraint();
if (rolesAllowed == null || rolesAllowed.length==0)
{
if (permitOrDeny.equals(EmptyRoleSemantic.DENY))
{
//Equivalent to <auth-constraint> with no roles
constraint.setName(name+"-Deny");
constraint.setAuthenticate(true);
}
else
{
//Equivalent to no <auth-constraint>
constraint.setName(name+"-Permit");
constraint.setAuthenticate(false);
}
}
else
{
//Equivalent to <auth-constraint> with list of <security-role-name>s
constraint.setAuthenticate(true);
constraint.setRoles(rolesAllowed);
constraint.setName(name+"-RolesAllowed");
}
//Equivalent to //<user-data-constraint><transport-guarantee>CONFIDENTIAL</transport-guarantee></user-data-constraint>
constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE));
return constraint;
}
/* ------------------------------------------------------------ */
/**
* @param pathSpec
* @param constraintMappings
* @return
*/
public static List<ConstraintMapping> getConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
{
if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
return Collections.emptyList();
List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
for (ConstraintMapping mapping:constraintMappings)
{
if (pathSpec.equals(mapping.getPathSpec()))
{
mappings.add(mapping);
}
}
return mappings;
}
/* ------------------------------------------------------------ */
/** Take out of the constraint mappings those that match the
* given path.
*
* @param pathSpec
* @param constraintMappings a new list minus the matching constraints
* @return
*/
public static List<ConstraintMapping> removeConstraintMappingsForPath(String pathSpec, List<ConstraintMapping> constraintMappings)
{
if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0)
return Collections.emptyList();
List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
for (ConstraintMapping mapping:constraintMappings)
{
//Remove the matching mappings by only copying in non-matching mappings
if (!pathSpec.equals(mapping.getPathSpec()))
{
mappings.add(mapping);
}
}
return mappings;
}
/* ------------------------------------------------------------ */
/** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement
*
* @param name
* @param pathSpec
* @param securityElement
* @return
*/
public static List<ConstraintMapping> createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement)
{
List<ConstraintMapping> mappings = new ArrayList<ConstraintMapping>();
//Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints)
Constraint constraint = ConstraintSecurityHandler.createConstraint(name, securityElement);
//Create a mapping for the pathSpec for the default case
ConstraintMapping defaultMapping = new ConstraintMapping();
defaultMapping.setPathSpec(pathSpec);
defaultMapping.setConstraint(constraint);
mappings.add(defaultMapping);
//See Spec 13.4.1.2 p127
List<String> methodOmissions = new ArrayList<String>();
//make constraint mappings for this url for each of the HttpMethodConstraintElements
Collection<HttpMethodConstraintElement> methodConstraints = securityElement.getHttpMethodConstraints();
if (methodConstraints != null)
{
for (HttpMethodConstraintElement methodConstraint:methodConstraints)
{
//Make a Constraint that captures the <auth-constraint> and <user-data-constraint> elements supplied for the HttpMethodConstraintElement
Constraint mconstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraint);
ConstraintMapping mapping = new ConstraintMapping();
mapping.setConstraint(mconstraint);
mapping.setPathSpec(pathSpec);
if (methodConstraint.getMethodName() != null)
{
mapping.setMethod(methodConstraint.getMethodName());
//See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
methodOmissions.add(methodConstraint.getMethodName());
}
mappings.add(mapping);
}
}
//See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint
if (methodOmissions.size() > 0)
defaultMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()]));
return mappings;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Get the strict mode. /** Get the strict mode.
* @return true if the security handler is running in strict mode. * @return true if the security handler is running in strict mode.
@ -140,8 +355,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
@Override @Override
public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles) public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
{ {
if (isStarted())
throw new IllegalStateException("Started");
_constraintMappings.clear(); _constraintMappings.clear();
_constraintMappings.addAll(constraintMappings); _constraintMappings.addAll(constraintMappings);
@ -160,6 +373,14 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
} }
} }
setRoles(roles); setRoles(roles);
if (isStarted())
{
for (ConstraintMapping mapping : _constraintMappings)
{
processConstraintMapping(mapping);
}
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -172,9 +393,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
*/ */
public void setRoles(Set<String> roles) public void setRoles(Set<String> roles)
{ {
if (isStarted())
throw new IllegalStateException("Started");
_roles.clear(); _roles.clear();
_roles.addAll(roles); _roles.addAll(roles);
} }
@ -238,14 +456,24 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
} }
super.doStart(); super.doStart();
} }
/* ------------------------------------------------------------ */
@Override @Override
protected void doStop() throws Exception protected void doStop() throws Exception
{ {
super.doStop(); super.doStop();
_constraintMap.clear(); _constraintMap.clear();
} }
/* ------------------------------------------------------------ */
/**
* Create and combine the constraint with the existing processed
* constraints.
*
* @param mapping
*/
protected void processConstraintMapping(ConstraintMapping mapping) protected void processConstraintMapping(ConstraintMapping mapping)
{ {
Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec()); Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
@ -258,6 +486,12 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden()) if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
return; return;
if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
{
processConstraintMappingWithMethodOmissions(mapping, mappings);
return;
}
String httpMethod = mapping.getMethod(); String httpMethod = mapping.getMethod();
if (httpMethod==null) if (httpMethod==null)
httpMethod=ALL_METHODS; httpMethod=ALL_METHODS;
@ -274,10 +508,10 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
if (roleInfo.isForbidden()) if (roleInfo.isForbidden())
return; return;
Constraint constraint = mapping.getConstraint(); //add in info from the constraint
boolean forbidden = constraint.isForbidden(); configureRoleInfo(roleInfo, mapping);
roleInfo.setForbidden(forbidden);
if (forbidden) if (roleInfo.isForbidden())
{ {
if (httpMethod.equals(ALL_METHODS)) if (httpMethod.equals(ALL_METHODS))
{ {
@ -287,41 +521,12 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
} }
else else
{ {
UserDataConstraint userDataConstraint = UserDataConstraint.get(constraint.getDataConstraint()); //combine with any entry that covers all methods
roleInfo.setUserDataConstraint(userDataConstraint); if (httpMethod == null)
boolean checked = constraint.getAuthenticate();
roleInfo.setChecked(checked);
if (roleInfo.isChecked())
{
if (constraint.isAnyRole())
{
if (_strict)
{
// * means "all defined roles"
for (String role : _roles)
roleInfo.addRole(role);
}
else
// * means any role
roleInfo.setAnyRole(true);
}
else
{
String[] newRoles = constraint.getRoles();
for (String role : newRoles)
{
if (_strict &&!_roles.contains(role))
throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
roleInfo.addRole(role);
}
}
}
if (httpMethod.equals(ALL_METHODS))
{ {
for (Map.Entry<String, RoleInfo> entry : mappings.entrySet()) for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
{ {
if (!entry.getKey().equals(ALL_METHODS)) if (entry.getKey() != null)
{ {
RoleInfo specific = entry.getValue(); RoleInfo specific = entry.getValue();
specific.combine(roleInfo); specific.combine(roleInfo);
@ -331,17 +536,145 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
} }
} }
/* ------------------------------------------------------------ */
/** Constraints that name method omissions are dealt with differently.
* We create an entry in the mappings with key "method.omission". This entry
* is only ever combined with other omissions for the same method to produce a
* consolidated RoleInfo. Then, when we wish to find the relevant constraints for
* a given Request (in prepareConstraintInfo()), we consult 3 types of entries in
* the mappings: an entry that names the method of the Request specifically, an
* entry that names constraints that apply to all methods, entries of the form
* method.omission, where the method of the Request is not named in the omission.
* @param mapping
* @param mappings
*/
protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map<String, RoleInfo> mappings)
{
String[] omissions = mapping.getMethodOmissions();
for (String omission:omissions)
{
//for each method omission, see if there is already a RoleInfo for it in mappings
RoleInfo ri = mappings.get(omission+OMISSION_SUFFIX);
if (ri == null)
{
//if not, make one
ri = new RoleInfo();
mappings.put(omission+OMISSION_SUFFIX, ri);
}
//initialize RoleInfo or combine from ConstraintMapping
configureRoleInfo(ri, mapping);
}
}
/* ------------------------------------------------------------ */
/**
* Initialize or update the RoleInfo from the constraint
* @param ri
* @param mapping
*/
protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping)
{
Constraint constraint = mapping.getConstraint();
boolean forbidden = constraint.isForbidden();
ri.setForbidden(forbidden);
//set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint
//which we need in order to do combining of omissions in prepareConstraintInfo
UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint());
ri.setUserDataConstraint(userDataConstraint);
//if forbidden, no point setting up roles
if (!ri.isForbidden())
{
//add in the roles
boolean checked = mapping.getConstraint().getAuthenticate();
ri.setChecked(checked);
if (ri.isChecked())
{
if (mapping.getConstraint().isAnyRole())
{
if (_strict)
{
// * means "all defined roles"
for (String role : _roles)
ri.addRole(role);
}
else
// * means any role
ri.setAnyRole(true);
}
else
{
String[] newRoles = mapping.getConstraint().getRoles();
for (String role : newRoles)
{
if (_strict &&!_roles.contains(role))
throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles);
ri.addRole(role);
}
}
}
}
}
/* ------------------------------------------------------------ */
/**
* Find constraints that apply to the given path.
* In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping
* represents a merged set of user data constraints, roles etc -:
* <ol>
* <li>A mapping of an exact method name </li>
* <li>A mapping will null key that matches every method name</li>
* <li>Mappings with keys of the form "method.omission" that indicates it will match every method name EXCEPT that given</li>
* </ol>
*
* @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request)
*/
@Override @Override
protected RoleInfo prepareConstraintInfo(String pathInContext, Request request) protected RoleInfo prepareConstraintInfo(String pathInContext, Request request)
{ {
Map<String, RoleInfo> mappings = _constraintMap.match(pathInContext); Map<String, RoleInfo> mappings = (Map<String, RoleInfo>)_constraintMap.match(pathInContext);
if (mappings != null) if (mappings != null)
{ {
String httpMethod = request.getMethod(); String httpMethod = request.getMethod();
RoleInfo roleInfo = mappings.get(httpMethod); RoleInfo roleInfo = mappings.get(httpMethod);
if (roleInfo == null) if (roleInfo == null)
roleInfo = mappings.get(ALL_METHODS); {
//No specific http-method names matched
List<RoleInfo> applicableConstraints = new ArrayList<RoleInfo>();
//Get info for constraint that matches all methods if it exists
RoleInfo all = mappings.get(ALL_METHODS);
if (all != null)
applicableConstraints.add(all);
//Get info for constraints that name method omissions where target method name is not omitted
//(ie matches because target method is not omitted, hence considered covered by the constraint)
for (Entry<String, RoleInfo> entry: mappings.entrySet())
{
if (entry.getKey() != null && entry.getKey().contains(OMISSION_SUFFIX) && !(httpMethod+OMISSION_SUFFIX).equals(entry.getKey()))
applicableConstraints.add(entry.getValue());
}
if (applicableConstraints.size() == 1)
roleInfo = applicableConstraints.get(0);
else
{
roleInfo = new RoleInfo();
roleInfo.setUserDataConstraint(UserDataConstraint.None);
for (RoleInfo r:applicableConstraints)
roleInfo.combine(r);
}
}
return roleInfo; return roleInfo;
} }
@ -396,7 +729,12 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
{ {
return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked(); return constraintInfo != null && ((RoleInfo)constraintInfo).isChecked();
} }
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity)
*/
@Override @Override
protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity) protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
throws IOException throws IOException
@ -435,4 +773,5 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
Collections.singleton(_roles), Collections.singleton(_roles),
_constraintMap.entrySet()); _constraintMap.entrySet());
} }
} }

View File

@ -119,18 +119,25 @@ public class SessionAuthentication implements Authentication.User, Serializable,
@Override @Override
public void sessionWillPassivate(HttpSessionEvent se) public void sessionWillPassivate(HttpSessionEvent se)
{ {
} }
@Override @Override
public void sessionDidActivate(HttpSessionEvent se) public void sessionDidActivate(HttpSessionEvent se)
{ {
if (_session==null) if (_session==null)
{
_session=se.getSession(); _session=se.getSession();
}
} }
@Override @Override
public void valueBound(HttpSessionBindingEvent event) public void valueBound(HttpSessionBindingEvent event)
{ {
if (_session==null)
{
_session=event.getSession();
}
} }
@Override @Override

View File

@ -79,6 +79,8 @@ public class ConstraintTest
_loginService.putUser("user",new Password("password")); _loginService.putUser("user",new Password("password"));
_loginService.putUser("user2",new Password("password"), new String[] {"user"}); _loginService.putUser("user2",new Password("password"), new String[] {"user"});
_loginService.putUser("admin",new Password("password"), new String[] {"user","administrator"}); _loginService.putUser("admin",new Password("password"), new String[] {"user","administrator"});
_loginService.putUser("user3", new Password("password"), new String[] {"foo"});
_context.setContextPath("/ctx"); _context.setContextPath("/ctx");
_server.setHandler(_context); _server.setHandler(_context);
@ -190,17 +192,59 @@ public class ConstraintTest
@Test @Test
public void testBasic() throws Exception public void testBasic() throws Exception
{ {
List<ConstraintMapping> list = new ArrayList<ConstraintMapping>(_security.getConstraintMappings());
Constraint constraint6 = new Constraint();
constraint6.setAuthenticate(true);
constraint6.setName("omit POST and GET");
constraint6.setRoles(new String[]{"user"});
ConstraintMapping mapping6 = new ConstraintMapping();
mapping6.setPathSpec("/omit/*");
mapping6.setConstraint(constraint6);
mapping6.setMethodOmissions(new String[]{"GET", "HEAD"}); //requests for every method except GET and HEAD must be in role "user"
list.add(mapping6);
Constraint constraint7 = new Constraint();
constraint7.setAuthenticate(true);
constraint7.setName("non-omitted GET");
constraint7.setRoles(new String[]{"administrator"});
ConstraintMapping mapping7 = new ConstraintMapping();
mapping7.setPathSpec("/omit/*");
mapping7.setConstraint(constraint7);
mapping7.setMethod("GET"); //requests for GET must be in role "admin"
list.add(mapping7);
Constraint constraint8 = new Constraint();
constraint8.setAuthenticate(true);
constraint8.setName("non specific");
constraint8.setRoles(new String[]{"foo"});
ConstraintMapping mapping8 = new ConstraintMapping();
mapping8.setPathSpec("/omit/*");
mapping8.setConstraint(constraint8);//requests for all methods must be in role "foo"
list.add(mapping8);
Set<String> knownRoles=new HashSet<String>();
knownRoles.add("user");
knownRoles.add("administrator");
knownRoles.add("foo");
_security.setConstraintMappings(list, knownRoles);
_security.setAuthenticator(new BasicAuthenticator()); _security.setAuthenticator(new BasicAuthenticator());
_security.setStrict(false); _security.setStrict(false);
_server.start(); _server.start();
String response; String response;
/*
response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n"); response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK")); assertThat(response,startsWith("HTTP/1.1 200 OK"));
*/
response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n"); response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden")); assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
/*
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized")); assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\"")); assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
@ -215,8 +259,8 @@ public class ConstraintTest
"Authorization: Basic " + B64Code.encode("user:password") + "\r\n" + "Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
"\r\n"); "\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK")); assertThat(response,startsWith("HTTP/1.1 200 OK"));
*/
/*
// test admin // test admin
response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n"); response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized")); assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
@ -242,7 +286,33 @@ public class ConstraintTest
response = _connector.getResponses("GET /ctx/admin/relax/info HTTP/1.0\r\n\r\n"); response = _connector.getResponses("GET /ctx/admin/relax/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK")); assertThat(response,startsWith("HTTP/1.1 200 OK"));
//check GET is in role administrator
response = _connector.getResponses("GET /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("admin:password") + "\r\n" +
"\r\n");
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
//check POST is in role user
response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
"\r\n");
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
//check POST can be in role foo too
response = _connector.getResponses("POST /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user3:password") + "\r\n" +
"\r\n");
assertTrue(response.startsWith("HTTP/1.1 200 OK"));
//check HEAD cannot be in role user
response = _connector.getResponses("HEAD /ctx/omit/x HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("user2:password") + "\r\n" +
"\r\n");
assertTrue(response.startsWith("HTTP/1.1 200 OK"));*/
} }
@Test @Test
public void testFormDispatch() throws Exception public void testFormDispatch() throws Exception
@ -853,7 +923,7 @@ public class ConstraintTest
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException
{ {
baseRequest.setHandled(true); baseRequest.setHandled(true);
if (request.getAuthType()==null || "user".equals(request.getRemoteUser()) || request.isUserInRole("user")) if (request.getAuthType()==null || "user".equals(request.getRemoteUser()) || request.isUserInRole("user") || request.isUserInRole("foo"))
{ {
response.setStatus(200); response.setStatus(200);
response.setContentType("text/plain; charset=UTF-8"); response.setContentType("text/plain; charset=UTF-8");

View File

@ -0,0 +1,315 @@
//
// ========================================================================
// 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.security;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
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.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Password;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
* @version $Revision: 1441 $ $Date: 2010-04-02 12:28:17 +0200 (Fri, 02 Apr 2010) $
*/
public class SpecExampleConstraintTest
{
private static final String TEST_REALM = "TestRealm";
private static Server _server;
private static LocalConnector _connector;
private static SessionHandler _session;
private ConstraintSecurityHandler _security;
@BeforeClass
public static void startServer()
{
_server = new Server();
_connector = new LocalConnector(_server);
_server.setConnectors(new Connector[]{_connector});
ContextHandler _context = new ContextHandler();
_session = new SessionHandler();
HashLoginService _loginService = new HashLoginService(TEST_REALM);
_loginService.putUser("fred",new Password("password"));
_loginService.putUser("harry",new Password("password"), new String[] {"HOMEOWNER"});
_loginService.putUser("chris",new Password("password"), new String[] {"CONTRACTOR"});
_loginService.putUser("steven", new Password("password"), new String[] {"SALESCLERK"});
_context.setContextPath("/ctx");
_server.setHandler(_context);
_context.setHandler(_session);
_server.addBean(_loginService);
}
@Before
public void setupSecurity()
{
_security = new ConstraintSecurityHandler();
_session.setHandler(_security);
RequestHandler _handler = new RequestHandler();
_security.setHandler(_handler);
/*
<security-constraint>
<web-resource-collection>
<web-resource-name>precluded methods</web-resource-name>
<url-pattern>/*</url-pattern>
<url-pattern>/acme/wholesale/*</url-pattern>
<url-pattern>/acme/retail/*</url-pattern>
<http-method-exception>GET</http-method-exception>
<http-method-exception>POST</http-method-exception>
</web-resource-collection>
<auth-constraint/>
</security-constraint>
*/
Constraint constraint0 = new Constraint();
constraint0.setAuthenticate(true);
constraint0.setName("precluded methods");
ConstraintMapping mapping0 = new ConstraintMapping();
mapping0.setPathSpec("/*");
mapping0.setConstraint(constraint0);
mapping0.setMethodOmissions(new String[]{"GET", "POST"});
ConstraintMapping mapping1 = new ConstraintMapping();
mapping1.setPathSpec("/acme/wholesale/*");
mapping1.setConstraint(constraint0);
mapping1.setMethodOmissions(new String[]{"GET", "POST"});
ConstraintMapping mapping2 = new ConstraintMapping();
mapping2.setPathSpec("/acme/retail/*");
mapping2.setConstraint(constraint0);
mapping2.setMethodOmissions(new String[]{"GET", "POST"});
/*
<security-constraint>
<web-resource-collection>
<web-resource-name>wholesale</web-resource-name>
<url-pattern>/acme/wholesale/*</url-pattern>
<http-method>GET</http-method>
<http-method>PUT</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>SALESCLERK</role-name>
</auth-constraint>
</security-constraint>
*/
Constraint constraint1 = new Constraint();
constraint1.setAuthenticate(true);
constraint1.setName("wholesale");
constraint1.setRoles(new String[]{"SALESCLERK"});
ConstraintMapping mapping3 = new ConstraintMapping();
mapping3.setPathSpec("/acme/wholesale/*");
mapping3.setConstraint(constraint1);
mapping3.setMethod("GET");
ConstraintMapping mapping4 = new ConstraintMapping();
mapping4.setPathSpec("/acme/wholesale/*");
mapping4.setConstraint(constraint1);
mapping4.setMethod("PUT");
/*
<security-constraint>
<web-resource-collection>
<web-resource-name>wholesale 2</web-resource-name>
<url-pattern>/acme/wholesale/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>CONTRACTOR</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
*/
Constraint constraint2 = new Constraint();
constraint2.setAuthenticate(true);
constraint2.setName("wholesale 2");
constraint2.setRoles(new String[]{"CONTRACTOR"});
constraint2.setDataConstraint(Constraint.DC_CONFIDENTIAL);
ConstraintMapping mapping5 = new ConstraintMapping();
mapping5.setPathSpec("/acme/wholesale/*");
mapping5.setMethod("GET");
mapping5.setConstraint(constraint2);
ConstraintMapping mapping6 = new ConstraintMapping();
mapping6.setPathSpec("/acme/wholesale/*");
mapping6.setMethod("POST");
mapping6.setConstraint(constraint2);
/*
<security-constraint>
<web-resource-collection>
<web-resource-name>retail</web-resource-name>
<url-pattern>/acme/retail/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>CONTRACTOR</role-name>
<role-name>HOMEOWNER</role-name>
</auth-constraint>
</security-constraint>
*/
Constraint constraint4 = new Constraint();
constraint4.setName("retail");
constraint4.setAuthenticate(true);
constraint4.setRoles(new String[]{"CONTRACTOR", "HOMEOWNER"});
ConstraintMapping mapping7 = new ConstraintMapping();
mapping7.setPathSpec("/acme/retail/*");
mapping7.setMethod("GET");
mapping7.setConstraint(constraint4);
ConstraintMapping mapping8 = new ConstraintMapping();
mapping8.setPathSpec("/acme/retail/*");
mapping8.setMethod("POST");
mapping8.setConstraint(constraint4);
Set<String> knownRoles=new HashSet<String>();
knownRoles.add("CONTRACTOR");
knownRoles.add("HOMEOWNER");
knownRoles.add("SALESCLERK");
_security.setConstraintMappings(Arrays.asList(new ConstraintMapping[]
{
mapping0, mapping1, mapping2, mapping3, mapping4, mapping5, mapping6, mapping7, mapping8
}), knownRoles);
}
@After
public void stopServer() throws Exception
{
if (_server.isRunning())
{
_server.stop();
_server.join();
}
}
@Test
public void testBasic() throws Exception
{
_security.setAuthenticator(new BasicAuthenticator());
_security.setStrict(false);
_server.start();
String response;
/*
/star all methods except GET/POST forbidden
/acme/wholesale/star all methods except GET/POST forbidden
/acme/retail/star all methods except GET/POST forbidden
/acme/wholesale/star GET must be in role CONTRACTOR or SALESCLERK
/acme/wholesale/star POST must be in role CONTRACTOR and confidential transport
/acme/retail/star GET must be in role CONTRACTOR or HOMEOWNER
/acme/retail/star POST must be in role CONTRACTOR or HOMEOWNER
*/
//a user in role HOMEOWNER is forbidden HEAD request
response = _connector.getResponses("HEAD /ctx/index.html HTTP/1.0\r\n\r\n");
assertTrue(response.startsWith("HTTP/1.1 403 Forbidden"));
response = _connector.getResponses("HEAD /ctx/index.html HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("harry:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
response = _connector.getResponses("HEAD /ctx/acme/wholesale/index.html HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("harry:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
response = _connector.getResponses("HEAD /ctx/acme/retail/index.html HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("harry:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
//a user in role CONTRACTOR can do a GET
response = _connector.getResponses("GET /ctx/acme/wholesale/index.html HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("chris:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
//a user in role CONTRACTOR can only do a post if confidential
response = _connector.getResponses("POST /ctx/acme/wholesale/index.html HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("chris:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 403 !"));
//a user in role HOMEOWNER can do a GET
response = _connector.getResponses("GET /ctx/acme/retail/index.html HTTP/1.0\r\n" +
"Authorization: Basic " + B64Code.encode("harry:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
}
private class RequestHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException
{
baseRequest.setHandled(true);
response.setStatus(200);
response.setContentType("text/plain; charset=UTF-8");
response.getWriter().println("URI="+request.getRequestURI());
String user = request.getRemoteUser();
response.getWriter().println("user="+user);
if (request.getParameter("test_parameter")!=null)
response.getWriter().println(request.getParameter("test_parameter"));
}
}
}

View File

@ -73,9 +73,9 @@ public abstract class HttpInput<T> extends ServletInputStream
@Override @Override
public int read() throws IOException public int read() throws IOException
{ {
byte[] oneByte = new byte[1]; byte[] bytes = new byte[1];
int len = read(oneByte, 0, 1); int read = read(bytes, 0, 1);
return len < 0 ? len : 0xFF & oneByte[0]; return read < 0 ? -1 : 0xff & bytes[0];
} }
@Override @Override
@ -127,7 +127,6 @@ public abstract class HttpInput<T> extends ServletInputStream
} }
return get(item, b, off, len); return get(item, b, off, len);
} }
protected abstract int remaining(T item); protected abstract int remaining(T item);
protected abstract int get(T item, byte[] buffer, int offset, int length); protected abstract int get(T item, byte[] buffer, int offset, int length);

View File

@ -173,6 +173,7 @@ public class HttpOutput extends ServletOutputStream
_channel.write(_aggregate, false); _channel.write(_aggregate, false);
} }
@Override @Override
public void write(int b) throws IOException public void write(int b) throws IOException
{ {
@ -181,7 +182,7 @@ public class HttpOutput extends ServletOutputStream
if (_aggregate == null) if (_aggregate == null)
_aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false); _aggregate = _channel.getByteBufferPool().acquire(getBufferSize(), false);
BufferUtil.append(_aggregate, (byte)b); BufferUtil.append(_aggregate, (byte)b);
_written++; _written++;

View File

@ -49,6 +49,8 @@ import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest; import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent; import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletResponse; import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -71,6 +73,7 @@ import org.eclipse.jetty.server.handler.ContextHandler.Context;
import org.eclipse.jetty.server.session.AbstractSessionManager; import org.eclipse.jetty.server.session.AbstractSessionManager;
import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.AttributesMap; import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.MultiPartInputStream; import org.eclipse.jetty.util.MultiPartInputStream;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
@ -114,6 +117,8 @@ import org.eclipse.jetty.util.log.Logger;
public class Request implements HttpServletRequest public class Request implements HttpServletRequest
{ {
public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig"; public static final String __MULTIPART_CONFIG_ELEMENT = "org.eclipse.multipartConfig";
public static final String __MULTIPART_INPUT_STREAM = "org.eclipse.multiPartInputStream";
public static final String __MULTIPART_CONTEXT = "org.eclipse.multiPartContext";
private static final Logger LOG = Log.getLogger(Request.class); private static final Logger LOG = Log.getLogger(Request.class);
private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault()); private static final Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
@ -123,6 +128,42 @@ public class Request implements HttpServletRequest
private final HttpFields _fields=new HttpFields(); private final HttpFields _fields=new HttpFields();
private final List<ServletRequestAttributeListener> _requestAttributeListeners=new ArrayList<>(); private final List<ServletRequestAttributeListener> _requestAttributeListeners=new ArrayList<>();
private final HttpInput<?> _input; private final HttpInput<?> _input;
public static class MultiPartCleanerListener implements ServletRequestListener
{
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
//Clean up any tmp files created by MultiPartInputStream
MultiPartInputStream mpis = (MultiPartInputStream)sre.getServletRequest().getAttribute(__MULTIPART_INPUT_STREAM);
if (mpis != null)
{
ContextHandler.Context context = (ContextHandler.Context)sre.getServletRequest().getAttribute(__MULTIPART_CONTEXT);
//Only do the cleanup if we are exiting from the context in which a servlet parsed the multipart files
if (context == sre.getServletContext())
{
try
{
mpis.deleteParts();
}
catch (MultiException e)
{
sre.getServletContext().log("Errors deleting multipart tmp files", e);
}
}
}
}
@Override
public void requestInitialized(ServletRequestEvent sre)
{
//nothing to do, multipart config set up by ServletHolder.handle()
}
}
private boolean _secure; private boolean _secure;
private boolean _asyncSupported = true; private boolean _asyncSupported = true;
@ -1763,6 +1804,7 @@ public class Request implements HttpServletRequest
public void setQueryString(String queryString) public void setQueryString(String queryString)
{ {
_queryString = queryString; _queryString = queryString;
_queryEncoding = null; //assume utf-8
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -1950,9 +1992,16 @@ public class Request implements HttpServletRequest
if (_multiPartInputStream == null) if (_multiPartInputStream == null)
{ {
MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
if (config == null)
throw new IllegalStateException("No multipart config for servlet");
_multiPartInputStream = new MultiPartInputStream(getInputStream(), _multiPartInputStream = new MultiPartInputStream(getInputStream(),
getContentType(),(MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT), getContentType(),config,
(_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
setAttribute(__MULTIPART_CONTEXT, _context);
Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
for (Part p:parts) for (Part p:parts)
{ {
@ -1982,9 +2031,17 @@ public class Request implements HttpServletRequest
if (_multiPartInputStream == null) if (_multiPartInputStream == null)
{ {
MultipartConfigElement config = (MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT);
if (config == null)
throw new IllegalStateException("No multipart config for servlet");
_multiPartInputStream = new MultiPartInputStream(getInputStream(), _multiPartInputStream = new MultiPartInputStream(getInputStream(),
getContentType(),(MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT), getContentType(), config,
(_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null));
setAttribute(__MULTIPART_INPUT_STREAM, _multiPartInputStream);
setAttribute(__MULTIPART_CONTEXT, _context);
Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing Collection<Part> parts = _multiPartInputStream.getParts(); //causes parsing
for (Part p:parts) for (Part p:parts)
{ {
@ -2042,7 +2099,7 @@ public class Request implements HttpServletRequest
{ {
// extract parameters from dispatch query // extract parameters from dispatch query
MultiMap<String> parameters = new MultiMap<>(); MultiMap<String> parameters = new MultiMap<>();
UrlEncoded.decodeTo(query,parameters,getCharacterEncoding()); UrlEncoded.decodeTo(query,parameters, StringUtil.__UTF8); //have to assume UTF-8 because we can't know otherwise
boolean merge_old_query = false; boolean merge_old_query = false;
@ -2063,10 +2120,11 @@ public class Request implements HttpServletRequest
{ {
StringBuilder overridden_query_string = new StringBuilder(); StringBuilder overridden_query_string = new StringBuilder();
MultiMap<String> overridden_old_query = new MultiMap<>(); MultiMap<String> overridden_old_query = new MultiMap<>();
UrlEncoded.decodeTo(_queryString,overridden_old_query,getCharacterEncoding()); UrlEncoded.decodeTo(_queryString,overridden_old_query,getQueryEncoding());//decode using any queryencoding set for the request
MultiMap<String> overridden_new_query = new MultiMap<>(); MultiMap<String> overridden_new_query = new MultiMap<>();
UrlEncoded.decodeTo(query,overridden_new_query,getCharacterEncoding()); UrlEncoded.decodeTo(query,overridden_new_query,StringUtil.__UTF8); //have to assume utf8 as we cannot know otherwise
for(String name: overridden_old_query.keySet()) for(String name: overridden_old_query.keySet())
{ {

View File

@ -22,7 +22,9 @@ import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.channels.IllegalSelectorException; import java.nio.channels.IllegalSelectorException;
import java.util.Collection; import java.util.Collection;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.RequestDispatcher; import javax.servlet.RequestDispatcher;
@ -58,6 +60,15 @@ public class Response implements HttpServletResponse
{ {
private static final Logger LOG = Log.getLogger(Response.class); private static final Logger LOG = Log.getLogger(Response.class);
/* ------------------------------------------------------------ */
public static Response getResponse(HttpServletResponse response)
{
if (response instanceof Response)
return (Response)response;
return HttpChannel.getCurrentHttpChannel().getResponse();
}
public enum OutputType public enum OutputType
{ {
NONE, STREAM, WRITER NONE, STREAM, WRITER
@ -76,7 +87,7 @@ public class Response implements HttpServletResponse
*/ */
public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__"; public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
private final HttpChannel _channel; private final HttpChannel<?> _channel;
private final HttpOutput _out; private final HttpOutput _out;
private final HttpFields _fields = new HttpFields(); private final HttpFields _fields = new HttpFields();
private final AtomicInteger _include = new AtomicInteger(); private final AtomicInteger _include = new AtomicInteger();
@ -905,6 +916,22 @@ public class Response implements HttpServletResponse
} }
} }
public void reset(boolean preserveCookies)
{
if (!preserveCookies)
reset();
else
{
ArrayList<String> cookieValues = new ArrayList<String>(5);
Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString());
while (vals.hasMoreElements())
cookieValues.add(vals.nextElement());
reset();
for (String v:cookieValues)
_fields.add(HttpHeader.SET_COOKIE, v);
}
}
public void resetForForward() public void resetForForward()
{ {
resetBuffer(); resetBuffer();

View File

@ -443,7 +443,7 @@ public class Server extends HandlerWrapper implements Attributes
baseRequest.setRequestURI(null); baseRequest.setRequestURI(null);
baseRequest.setPathInfo(baseRequest.getRequestURI()); baseRequest.setPathInfo(baseRequest.getRequestURI());
if (uri.getQuery()!=null) if (uri.getQuery()!=null)
baseRequest.mergeQueryString(uri.getQuery()); baseRequest.mergeQueryString(uri.getQuery()); //we have to assume dispatch path and query are UTF8
} }
final String target=baseRequest.getPathInfo(); final String target=baseRequest.getPathInfo();

View File

@ -50,7 +50,7 @@ import org.eclipse.jetty.util.log.Logger;
<pre> <pre>
public static void attemptShutdown(int port, String shutdownCookie) { public static void attemptShutdown(int port, String shutdownCookie) {
try { try {
URL url = new URL("http://localhost:" + port + "/shutdown?cookie=" + shutdownCookie); URL url = new URL("http://localhost:" + port + "/shutdown?token=" + shutdownCookie);
HttpURLConnection connection = (HttpURLConnection)url.openConnection(); HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("POST"); connection.setRequestMethod("POST");
connection.getResponseCode(); connection.getResponseCode();

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.server.session;
import java.io.IOException; import java.io.IOException;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.EventListener; import java.util.EventListener;
import javax.servlet.DispatcherType; import javax.servlet.DispatcherType;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.SessionTrackingMode; import javax.servlet.SessionTrackingMode;
@ -39,7 +38,8 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** SessionHandler. /**
* SessionHandler.
*/ */
public class SessionHandler extends ScopedHandler public class SessionHandler extends ScopedHandler
{ {
@ -52,9 +52,8 @@ public class SessionHandler extends ScopedHandler
private SessionManager _sessionManager; private SessionManager _sessionManager;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Constructor. /**
* Construct a SessionHandler witha a HashSessionManager with a standard * Constructor. Construct a SessionHandler witha a HashSessionManager with a standard java.util.Random generator is created.
* java.util.Random generator is created.
*/ */
public SessionHandler() public SessionHandler()
{ {
@ -63,7 +62,8 @@ public class SessionHandler extends ScopedHandler
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @param manager The session manager * @param manager
* The session manager
*/ */
public SessionHandler(SessionManager manager) public SessionHandler(SessionManager manager)
{ {
@ -81,13 +81,14 @@ public class SessionHandler extends ScopedHandler
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @param sessionManager The sessionManager to set. * @param sessionManager
* The sessionManager to set.
*/ */
public void setSessionManager(SessionManager sessionManager) public void setSessionManager(SessionManager sessionManager)
{ {
if (isStarted()) if (isStarted())
throw new IllegalStateException(); throw new IllegalStateException();
if (sessionManager!=null) if (sessionManager != null)
sessionManager.setSessionHandler(this); sessionManager.setSessionHandler(this);
updateBean(_sessionManager,sessionManager); updateBean(_sessionManager,sessionManager);
_sessionManager=sessionManager; _sessionManager=sessionManager;
@ -104,6 +105,7 @@ public class SessionHandler extends ScopedHandler
setSessionManager(new HashSessionManager()); setSessionManager(new HashSessionManager());
super.doStart(); super.doStart();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* /*
* @see org.eclipse.thread.AbstractLifeCycle#doStop() * @see org.eclipse.thread.AbstractLifeCycle#doStop()
@ -121,12 +123,11 @@ public class SessionHandler extends ScopedHandler
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/ */
@Override @Override
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
throws IOException, ServletException
{ {
SessionManager old_session_manager=null; SessionManager old_session_manager = null;
HttpSession old_session=null; HttpSession old_session = null;
HttpSession access=null; HttpSession access = null;
try try
{ {
old_session_manager = baseRequest.getSessionManager(); old_session_manager = baseRequest.getSessionManager();
@ -141,54 +142,54 @@ public class SessionHandler extends ScopedHandler
} }
// access any existing session // access any existing session
HttpSession session=null; HttpSession session = null;
if (_sessionManager!=null) if (_sessionManager != null)
{ {
session=baseRequest.getSession(false); session = baseRequest.getSession(false);
if (session!=null) if (session != null)
{ {
if(session!=old_session) if (session != old_session)
{ {
access=session; access = session;
HttpCookie cookie = _sessionManager.access(session,request.isSecure()); HttpCookie cookie = _sessionManager.access(session,request.isSecure());
if (cookie!=null ) // Handle changed ID or max-age refresh if (cookie != null) // Handle changed ID or max-age refresh
baseRequest.getResponse().addCookie(cookie); baseRequest.getResponse().addCookie(cookie);
} }
} }
else else
{ {
session=baseRequest.recoverNewSession(_sessionManager); session = baseRequest.recoverNewSession(_sessionManager);
if (session!=null) if (session != null)
baseRequest.setSession(session); baseRequest.setSession(session);
} }
} }
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
LOG.debug("sessionManager="+_sessionManager); LOG.debug("sessionManager=" + _sessionManager);
LOG.debug("session="+session); LOG.debug("session=" + session);
} }
// start manual inline of nextScope(target,baseRequest,request,response); // start manual inline of nextScope(target,baseRequest,request,response);
if (_nextScope!=null) if (_nextScope != null)
_nextScope.doScope(target,baseRequest,request, response); _nextScope.doScope(target,baseRequest,request,response);
else if (_outerScope!=null) else if (_outerScope != null)
_outerScope.doHandle(target,baseRequest,request, response); _outerScope.doHandle(target,baseRequest,request,response);
else else
doHandle(target,baseRequest,request, response); doHandle(target,baseRequest,request,response);
// end manual inline (pathentic attempt to reduce stack depth) // end manual inline (pathentic attempt to reduce stack depth)
} }
finally finally
{ {
if (access!=null) if (access != null)
_sessionManager.complete(access); _sessionManager.complete(access);
HttpSession session = baseRequest.getSession(false); HttpSession session = baseRequest.getSession(false);
if (session!=null && old_session==null && session!=access) if (session != null && old_session == null && session != access)
_sessionManager.complete(session); _sessionManager.complete(session);
if (old_session_manager!=null && old_session_manager != _sessionManager) if (old_session_manager != null && old_session_manager != _sessionManager)
{ {
baseRequest.setSessionManager(old_session_manager); baseRequest.setSessionManager(old_session_manager);
baseRequest.setSession(old_session); baseRequest.setSession(old_session);
@ -201,91 +202,102 @@ public class SessionHandler extends ScopedHandler
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) * @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/ */
@Override @Override
public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
throws IOException, ServletException
{ {
// start manual inline of nextHandle(target,baseRequest,request,response); // start manual inline of nextHandle(target,baseRequest,request,response);
if (never()) if (never())
nextHandle(target,baseRequest,request,response); nextHandle(target,baseRequest,request,response);
else if (_nextScope!=null && _nextScope==_handler) else if (_nextScope != null && _nextScope == _handler)
_nextScope.doHandle(target,baseRequest,request, response); _nextScope.doHandle(target,baseRequest,request,response);
else if (_handler!=null) else if (_handler != null)
_handler.handle(target,baseRequest, request, response); _handler.handle(target,baseRequest,request,response);
// end manual inline // end manual inline
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Look for a requested session ID in cookies and URI parameters /**
* Look for a requested session ID in cookies and URI parameters
*
* @param baseRequest * @param baseRequest
* @param request * @param request
*/ */
protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request) protected void checkRequestedSessionId(Request baseRequest, HttpServletRequest request)
{ {
String requested_session_id=request.getRequestedSessionId(); String requested_session_id = request.getRequestedSessionId();
SessionManager sessionManager = getSessionManager(); SessionManager sessionManager = getSessionManager();
if (requested_session_id!=null && sessionManager!=null) if (requested_session_id != null && sessionManager != null)
{ {
HttpSession session=sessionManager.getHttpSession(requested_session_id); HttpSession session = sessionManager.getHttpSession(requested_session_id);
if (session!=null && sessionManager.isValid(session)) if (session != null && sessionManager.isValid(session))
baseRequest.setSession(session); baseRequest.setSession(session);
return; return;
} }
else if (!DispatcherType.REQUEST.equals(baseRequest.getDispatcherType())) else if (!DispatcherType.REQUEST.equals(baseRequest.getDispatcherType()))
return; return;
boolean requested_session_id_from_cookie=false; boolean requested_session_id_from_cookie = false;
HttpSession session=null; HttpSession session = null;
// Look for session id cookie // Look for session id cookie
if (_sessionManager.isUsingCookies()) if (_sessionManager.isUsingCookies())
{ {
Cookie[] cookies=request.getCookies(); Cookie[] cookies = request.getCookies();
if (cookies!=null && cookies.length>0) if (cookies != null && cookies.length > 0)
{ {
final String sessionCookie=sessionManager.getSessionCookieConfig().getName(); final String sessionCookie=sessionManager.getSessionCookieConfig().getName();
for (int i=0;i<cookies.length;i++) for (int i = 0; i < cookies.length; i++)
{ {
if (sessionCookie.equalsIgnoreCase(cookies[i].getName())) if (sessionCookie.equalsIgnoreCase(cookies[i].getName()))
{ {
requested_session_id=cookies[i].getValue(); requested_session_id = cookies[i].getValue();
requested_session_id_from_cookie = true; requested_session_id_from_cookie = true;
if(LOG.isDebugEnabled())
LOG.debug("Got Session ID {} from cookie",requested_session_id);
session=sessionManager.getHttpSession(requested_session_id); LOG.debug("Got Session ID {} from cookie",requested_session_id);
if (session!=null && sessionManager.isValid(session))
break; if (requested_session_id != null)
{
session = sessionManager.getHttpSession(requested_session_id);
if (session != null && sessionManager.isValid(session))
{
break;
}
}
else
{
LOG.warn("null session id from cookie");
}
} }
} }
} }
} }
if (requested_session_id==null || session==null) if (requested_session_id == null || session == null)
{ {
String uri = request.getRequestURI(); String uri = request.getRequestURI();
String prefix=sessionManager.getSessionIdPathParameterNamePrefix(); String prefix = sessionManager.getSessionIdPathParameterNamePrefix();
if (prefix!=null) if (prefix != null)
{ {
int s = uri.indexOf(prefix); int s = uri.indexOf(prefix);
if (s>=0) if (s >= 0)
{ {
s+=prefix.length(); s += prefix.length();
int i=s; int i = s;
while (i<uri.length()) while (i < uri.length())
{ {
char c=uri.charAt(i); char c = uri.charAt(i);
if (c==';'||c=='#'||c=='?'||c=='/') if (c == ';' || c == '#' || c == '?' || c == '/')
break; break;
i++; i++;
} }
requested_session_id = uri.substring(s,i); requested_session_id = uri.substring(s,i);
requested_session_id_from_cookie = false; requested_session_id_from_cookie = false;
session=sessionManager.getHttpSession(requested_session_id); session = sessionManager.getHttpSession(requested_session_id);
if(LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Got Session ID {} from URL",requested_session_id); LOG.debug("Got Session ID {} from URL",requested_session_id);
} }
} }
@ -293,7 +305,7 @@ public class SessionHandler extends ScopedHandler
baseRequest.setRequestedSessionId(requested_session_id); baseRequest.setRequestedSessionId(requested_session_id);
baseRequest.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie); baseRequest.setRequestedSessionIdFromCookie(requested_session_id!=null && requested_session_id_from_cookie);
if (session!=null && sessionManager.isValid(session)) if (session != null && sessionManager.isValid(session))
baseRequest.setSession(session); baseRequest.setSession(session);
} }
@ -303,14 +315,14 @@ public class SessionHandler extends ScopedHandler
*/ */
public void addEventListener(EventListener listener) public void addEventListener(EventListener listener)
{ {
if(_sessionManager!=null) if (_sessionManager != null)
_sessionManager.addEventListener(listener); _sessionManager.addEventListener(listener);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public void clearEventListeners() public void clearEventListeners()
{ {
if(_sessionManager!=null) if (_sessionManager != null)
_sessionManager.clearEventListeners(); _sessionManager.clearEventListeners();
} }
} }

View File

@ -36,13 +36,15 @@ import java.net.URL;
import java.util.Arrays; import java.util.Arrays;
import java.util.Random; import java.util.Random;
import java.util.concurrent.Exchanger; import java.util.concurrent.Exchanger;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
@ -53,6 +55,8 @@ import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.matchers.JUnitMatchers; import org.junit.matchers.JUnitMatchers;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertTrue;
/** /**
* *
*/ */
@ -174,9 +178,83 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
} }
} }
@Test
public void testExceptionThrownInHandler() throws Exception
{
configureServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
throw new ServletException("handler exception");
}
});
StringBuffer request = new StringBuffer("GET / HTTP/1.0\r\n");
request.append("Host: localhost\r\n\r\n");
Socket client = newSocket(HOST, _connector.getLocalPort());
OutputStream os = client.getOutputStream();
os.write(request.toString().getBytes());
os.flush();
String response = readResponse(client);
assertThat("response code is 500", response.contains("500"), is(true));
}
@Test
public void testInterruptedRequest() throws Exception
{
final AtomicBoolean fourBytesRead = new AtomicBoolean(false);
final AtomicBoolean earlyEOFException = new AtomicBoolean(false);
configureServer(new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
int contentLength = request.getContentLength();
ServletInputStream inputStream = request.getInputStream();
for (int i = 0; i < contentLength; i++)
{
try
{
inputStream.read();
}
catch (EofException e)
{
earlyEOFException.set(true);
throw e;
}
if (i == 3)
fourBytesRead.set(true);
}
}
});
StringBuffer request = new StringBuffer("GET / HTTP/1.0\n");
request.append("Host: localhost\n");
request.append("Content-length: 6\n\n");
request.append("foo");
Socket client = newSocket(HOST, _connector.getLocalPort());
OutputStream os = client.getOutputStream();
os.write(request.toString().getBytes());
os.flush();
client.shutdownOutput();
String response = readResponse(client);
client.close();
assertThat("response contains 500", response, Matchers.containsString(" 500 "));
assertThat("The 4th byte (-1) has not been passed to the handler", fourBytesRead.get(), is(false));
assertThat("EofException has been caught", earlyEOFException.get(), is(true));
}
/* /*
* Feed a full header method * Feed a full header method
*/ */
@Test @Test
public void testFullHeader() throws Exception public void testFullHeader() throws Exception
{ {
@ -672,6 +750,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
// Handler that sends big blocks of data in each of 10 writes, and then sends the time it took for each big block. // Handler that sends big blocks of data in each of 10 writes, and then sends the time it took for each big block.
protected static class BigBlockHandler extends AbstractHandler protected static class BigBlockHandler extends AbstractHandler
{ {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{ {
byte[] buf = new byte[128 * 1024]; byte[] buf = new byte[128 * 1024];
@ -1262,6 +1341,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
new Thread() new Thread()
{ {
@Override
public void run() public void run()
{ {
try try
@ -1295,6 +1375,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
public class NoopHandler extends AbstractHandler public class NoopHandler extends AbstractHandler
{ {
@Override
public void handle(String target, Request baseRequest, public void handle(String target, Request baseRequest,
HttpServletRequest request, HttpServletResponse response) throws IOException, HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException ServletException

View File

@ -18,14 +18,18 @@
package org.eclipse.jetty.server; package org.eclipse.jetty.server;
import static org.junit.Assert.assertNotNull;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@ -205,6 +209,105 @@ public class HttpURITest
} }
} }
@Test
public void testNoPercentEncodingOfQueryUsingNonUTF8() throws Exception
{
byte[] utf8_bytes = "/%D0%A1%D1%82%D1%80%D0%BE%D0%BD%D0%B3-%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80/%D0%BA%D0%B0%D1%82%D0%B0%D0%BB%D0%BE%D0%B3?".getBytes("UTF-8");
byte[] cp1251_bytes = TypeUtil.fromHexString("e2fbe1f0e0edee3dd2e5ecefe5f0e0f2f3f0e0");
String expectedCP1251String = new String(cp1251_bytes, "cp1251");
String expectedCP1251Key = new String(cp1251_bytes, 0, 7, "cp1251");
String expectedCP1251Value = new String(cp1251_bytes, 8, cp1251_bytes.length-8, "cp1251");
//paste both byte arrays together to form the uri
byte[] allbytes = new byte[utf8_bytes.length+cp1251_bytes.length];
int i=0;
for (;i<utf8_bytes.length;i++) {
allbytes[i] = utf8_bytes[i];
}
for (int j=0; j< cp1251_bytes.length;j++)
allbytes[i+j] = cp1251_bytes[j];
//Test using a HttpUri that expects a particular charset encoding. See URIUtil.__CHARSET
HttpURI uri = new HttpURI(Charset.forName("cp1251"));
uri.parse(allbytes, 0, allbytes.length);
assertEquals(expectedCP1251String, uri.getQuery("cp1251"));
//Test params decoded correctly
MultiMap params = new MultiMap();
uri.decodeQueryTo(params);
String val = params.getString(expectedCP1251Key);
assertNotNull(val);
assertEquals(expectedCP1251Value, val);
//Test using HttpURI where you pass in the charset encoding.
HttpURI httpuri = new HttpURI();
httpuri.parse(allbytes,0,allbytes.length);
assertNotNull(httpuri.getQuery("UTF-8")); //still get back a query string, just incorrectly encoded
assertEquals(expectedCP1251String, httpuri.getQuery("cp1251"));
//Test params decoded correctly
params.clear();
httpuri.decodeQueryTo(params, "cp1251");
val = params.getString(expectedCP1251Key);
assertNotNull(val);
assertEquals(expectedCP1251Value, val);
//test able to set the query encoding and call getQueryString multiple times
Request request = new Request(null,null);
request.setUri(httpuri);
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
assertNotNull (request.getQueryString()); //will be incorrect encoding but not null
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "cp1251");
assertEquals(expectedCP1251String, request.getQueryString());
}
@Test
public void testPercentEncodingOfQueryStringUsingNonUTF8() throws UnsupportedEncodingException
{
byte[] utf8_bytes = "/%D0%A1%D1%82%D1%80%D0%BE%D0%BD%D0%B3-%D1%84%D0%B8%D0%BB%D1%8C%D1%82%D1%80/%D0%BA%D0%B0%D1%82%D0%B0%D0%BB%D0%BE%D0%B3?".getBytes("UTF-8");
byte[] cp1251_bytes = "%e2%fb%e1%f0%e0%ed%ee=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0".getBytes("cp1251");
byte[] key_bytes = TypeUtil.fromHexString("e2fbe1f0e0edee");
byte[] val_bytes = TypeUtil.fromHexString("d2e5ecefe5f0e0f2f3f0e0");
String expectedCP1251String = new String(cp1251_bytes, "cp1251");
String expectedCP1251Key = new String(key_bytes, "cp1251");
String expectedCP1251Value = new String(val_bytes, "cp1251");
byte[] allbytes = new byte[utf8_bytes.length+cp1251_bytes.length];
//stick both arrays together to form uri
int i=0;
for (;i<utf8_bytes.length;i++) {
allbytes[i] = utf8_bytes[i];
}
for (int j=0; j< cp1251_bytes.length;j++)
allbytes[i+j] = cp1251_bytes[j];
HttpURI httpuri = new HttpURI();
httpuri.parse(allbytes,0,allbytes.length);
assertNotNull(httpuri.getQuery("UTF-8")); //will be incorrectly encoded, but no errors
assertEquals(expectedCP1251String, httpuri.getQuery("cp1251"));
//test params decoded correctly
MultiMap params = new MultiMap();
httpuri.decodeQueryTo(params, "cp1251");
String val = params.getString(expectedCP1251Key);
assertNotNull(val);
assertEquals(expectedCP1251Value, val);
//test able to set the query encoding and call getQueryString multiple times
Request request = new Request(null,null);
request.setUri(httpuri);
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "ISO-8859-1");
assertNotNull (request.getQueryString()); //will be incorrect encoding but not null
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "cp1251");
assertEquals(expectedCP1251String, request.getQueryString());
}
@Test @Test
public void testUnicodeErrors() throws UnsupportedEncodingException public void testUnicodeErrors() throws UnsupportedEncodingException

View File

@ -38,7 +38,9 @@ import java.util.Enumeration;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -48,6 +50,7 @@ import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStream;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
@ -133,7 +136,7 @@ public class RequestTest
} }
@Test @Test
public void testMultiPart() throws Exception public void testMultiPartNoConfig() throws Exception
{ {
_handler._checker = new RequestTester() _handler._checker = new RequestTester()
{ {
@ -143,14 +146,16 @@ public class RequestTest
try try
{ {
Part foo = request.getPart("stuff"); Part foo = request.getPart("stuff");
assertNotNull(foo); return false;
String value = request.getParameter("stuff"); }
byte[] expected = "000000000000000000000000000000000000000000000000000".getBytes("ISO-8859-1"); catch (IllegalStateException e)
return value.equals(new String(expected, "ISO-8859-1")); {
//expected exception because no multipart config is set up
assertTrue(e.getMessage().startsWith("No multipart config"));
return true;
} }
catch (Exception e) catch (Exception e)
{ {
e.printStackTrace();
return false; return false;
} }
} }
@ -178,6 +183,66 @@ public class RequestTest
String responses=_connector.getResponses(request); String responses=_connector.getResponses(request);
assertTrue(responses.startsWith("HTTP/1.1 200")); assertTrue(responses.startsWith("HTTP/1.1 200"));
} }
@Test
public void testMultiPart() throws Exception
{
final File tmpDir = new File (System.getProperty("java.io.tmpdir"));
final File testTmpDir = new File (tmpDir, "reqtest");
testTmpDir.deleteOnExit();
assertTrue(testTmpDir.mkdirs());
assertTrue(testTmpDir.list().length == 0);
ContextHandler contextHandler = new ContextHandler();
contextHandler.setContextPath("/foo");
contextHandler.setResourceBase(".");
contextHandler.setHandler(new MultiPartRequestHandler(testTmpDir));
contextHandler.addEventListener(new Request.MultiPartCleanerListener()
{
@Override
public void requestDestroyed(ServletRequestEvent sre)
{
MultiPartInputStream m = (MultiPartInputStream)sre.getServletRequest().getAttribute(Request.__MULTIPART_INPUT_STREAM);
ContextHandler.Context c = (ContextHandler.Context)sre.getServletRequest().getAttribute(Request.__MULTIPART_CONTEXT);
assertNotNull (m);
assertNotNull (c);
assertTrue(c == sre.getServletContext());
assertTrue(!m.getParsedParts().isEmpty());
assertTrue(testTmpDir.list().length == 2);
super.requestDestroyed(sre);
String[] files = testTmpDir.list();
assertTrue(files.length == 0);
}
});
_server.stop();
_server.setHandler(contextHandler);
_server.start();
String multipart = "--AaB03x\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r\n"+
"Joe Blow\r\n"+
"--AaB03x\r\n"+
"content-disposition: form-data; name=\"stuff\"; filename=\"foo.upload\"\r\n"+
"Content-Type: text/plain;charset=ISO-8859-1\r\n"+
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"--AaB03x--\r\n";
String request="GET /foo/x.html HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+
"Content-Length: "+multipart.getBytes().length+"\r\n"+
"\r\n"+
multipart;
String responses=_connector.getResponses(request);
System.err.println(responses);
assertTrue(responses.startsWith("HTTP/1.1 200"));
}
@Test @Test
public void testBadUtf8ParamExtraction() throws Exception public void testBadUtf8ParamExtraction() throws Exception
@ -945,4 +1010,43 @@ public class RequestTest
response.sendError(500); response.sendError(500);
} }
} }
private class MultiPartRequestHandler extends AbstractHandler
{
File tmpDir;
public MultiPartRequestHandler(File tmpDir)
{
this.tmpDir = tmpDir;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
((Request)request).setHandled(true);
try
{
MultipartConfigElement mpce = new MultipartConfigElement(tmpDir.getAbsolutePath(),-1, -1, 2);
request.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, mpce);
Part foo = request.getPart("stuff");
assertNotNull(foo);
assertTrue(foo.getSize() > 0);
response.setStatus(200);
}
catch (IllegalStateException e)
{
//expected exception because no multipart config is set up
assertTrue(e.getMessage().startsWith("No multipart config"));
response.setStatus(200);
}
catch (Exception e)
{
response.sendError(500);
}
}
}
} }

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.server;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -29,6 +30,9 @@ import java.io.LineNumberReader;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.Socket; import java.net.Socket;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
@ -564,6 +568,42 @@ public class ResponseTest
assertEquals("name=value;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set); assertEquals("name=value;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment", set);
} }
@Test
public void testCookiesWithReset() throws Exception
{
Response response = newResponse();
Cookie cookie=new Cookie("name","value");
cookie.setDomain("domain");
cookie.setPath("/path");
cookie.setSecure(true);
cookie.setComment("comment__HTTP_ONLY__");
response.addCookie(cookie);
Cookie cookie2=new Cookie("name2", "value2");
cookie2.setDomain("domain");
cookie2.setPath("/path");
response.addCookie(cookie2);
//keep the cookies
response.reset(true);
Enumeration<String> set = response.getHttpFields().getValues("Set-Cookie");
assertNotNull(set);
ArrayList<String> list = Collections.list(set);
assertEquals(2, list.size());
assertTrue(list.contains("name=value;Path=/path;Domain=domain;Secure;HttpOnly;Comment=comment"));
assertTrue(list.contains("name2=value2;Path=/path;Domain=domain"));
//get rid of the cookies
response.reset();
set = response.getHttpFields().getValues("Set-Cookie");
assertFalse(set.hasMoreElements());
}
private Response newResponse() private Response newResponse()
{ {

View File

@ -18,15 +18,12 @@
package org.eclipse.jetty.server.ssl; package org.eclipse.jetty.server.ssl;
import static org.junit.Assert.assertEquals;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.security.KeyStore; import java.security.KeyStore;
import java.util.Arrays; import java.util.Arrays;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
@ -39,6 +36,8 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/** /**
* HttpServer Tester. * HttpServer Tester.
@ -189,6 +188,12 @@ public class SelectChannelServerSslTest extends HttpServerTestBase
} }
} }
@Override
@Test
@Ignore("Override and ignore this test as SSLSocket.shutdownOutput() is not supported, " +
"but shutdownOutput() is needed by the test.")
public void testInterruptedRequest(){}
@Override @Override
@Ignore @Ignore
public void testAvailable() throws Exception public void testAvailable() throws Exception

View File

@ -1,3 +0,0 @@
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.LEVEL=DEBUG
org.eclipse.jetty.server.Server.LEVEL=WARN

View File

@ -25,6 +25,7 @@ import java.io.OutputStream;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -667,6 +668,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString()); String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
if (ifms!=null) if (ifms!=null)
{ {
//Get jetty's Response impl
Response r = Response.getResponse(response);
if (content!=null) if (content!=null)
{ {
String mdlm=content.getLastModified(); String mdlm=content.getLastModified();
@ -674,9 +678,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{ {
if (ifms.equals(mdlm)) if (ifms.equals(mdlm))
{ {
response.reset(); r.reset(true);
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.flushBuffer(); r.flushBuffer();
return false; return false;
} }
} }
@ -686,10 +690,10 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (ifmsl!=-1) if (ifmsl!=-1)
{ {
if (resource.lastModified()/1000 <= ifmsl/1000) if (resource.lastModified()/1000 <= ifmsl/1000)
{ {
response.reset(); r.reset(true);
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.flushBuffer(); r.flushBuffer();
return false; return false;
} }
} }
@ -942,8 +946,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
pos=start; pos=start;
} }
System.err.println("PART "+ibr);
IO.copy(in,multi,size); IO.copy(in,multi,size);
pos+=size; pos+=size;
} }
@ -1022,6 +1024,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
if (_cacheControl!=null) if (_cacheControl!=null)
response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl); response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/* /*

View File

@ -47,6 +47,7 @@ import javax.servlet.descriptor.JspPropertyGroupDescriptor;
import javax.servlet.descriptor.TaglibDescriptor; import javax.servlet.descriptor.TaglibDescriptor;
import org.eclipse.jetty.security.ConstraintAware; import org.eclipse.jetty.security.ConstraintAware;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.Dispatcher; import org.eclipse.jetty.server.Dispatcher;
@ -59,6 +60,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.security.Constraint;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -393,10 +395,21 @@ public class ServletContextHandler extends ContextHandler
* @param registration ServletRegistration.Dynamic instance that setServletSecurity was called on * @param registration ServletRegistration.Dynamic instance that setServletSecurity was called on
* @param servletSecurityElement new security info * @param servletSecurityElement new security info
* @return the set of exact URL mappings currently associated with the registration that are also present in the web.xml * @return the set of exact URL mappings currently associated with the registration that are also present in the web.xml
* security constratins and thus will be unaffected by this call. * security constraints and thus will be unaffected by this call.
*/ */
public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement) public Set<String> setServletSecurity(ServletRegistration.Dynamic registration, ServletSecurityElement servletSecurityElement)
{ {
//Default implementation is to just accept them all. If using a webapp, then this behaviour is overridden in WebAppContext.setServletSecurity
Collection<String> pathSpecs = registration.getMappings();
if (pathSpecs != null)
{
for (String pathSpec:pathSpecs)
{
List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
for (ConstraintMapping m:mappings)
((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
}
}
return Collections.emptySet(); return Collections.emptySet();
} }

View File

@ -509,6 +509,8 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
initJspServlet(); initJspServlet();
} }
initMultiPart();
_servlet.init(_config); _servlet.init(_config);
} }
catch (UnavailableException e) catch (UnavailableException e)
@ -565,6 +567,25 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
} }
} }
/* ------------------------------------------------------------ */
/**
* Register a ServletRequestListener that will ensure tmp multipart
* files are deleted when the request goes out of scope.
*
* @throws Exception
*/
protected void initMultiPart () throws Exception
{
//if this servlet can handle multipart requests, ensure tmp files will be
//cleaned up correctly
if (((Registration)getRegistration()).getMultipartConfig() != null)
{
//Register a listener to delete tmp files that are created as a result of this
//servlet calling Request.getPart() or Request.getParts()
ContextHandler ch = ((ContextHandler.Context)getServletHandler().getServletContext()).getContextHandler();
ch.addEventListener(new Request.MultiPartCleanerListener());
}
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.servlet; package org.eclipse.jetty.servlet;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
@ -49,6 +50,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -111,6 +113,22 @@ public class DispatcherTest
assertEquals(expected, responses); assertEquals(expected, responses);
} }
@Test
public void testForwardNonUTF8() throws Exception
{
_contextHandler.addServlet(ForwardNonUTF8Servlet.class, "/ForwardServlet/*");
_contextHandler.addServlet(AssertNonUTF8ForwardServlet.class, "/AssertForwardServlet/*");
String expected=
"HTTP/1.1 200 OK\r\n"+
"Content-Type: text/html\r\n"+
"Content-Length: 0\r\n"+
"\r\n";
String responses = _connector.getResponses("GET /context/ForwardServlet?do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1 HTTP/1.0\n\n");
assertEquals(expected, responses);
}
@Test @Test
public void testForwardWithParam() throws Exception public void testForwardWithParam() throws Exception
{ {
@ -299,6 +317,7 @@ public class DispatcherTest
dispatcher = getServletContext().getRequestDispatcher("/IncludeServlet/includepath?do=assertforwardinclude"); dispatcher = getServletContext().getRequestDispatcher("/IncludeServlet/includepath?do=assertforwardinclude");
else if(request.getParameter("do").equals("assertincludeforward")) else if(request.getParameter("do").equals("assertincludeforward"))
dispatcher = getServletContext().getRequestDispatcher("/AssertIncludeForwardServlet/assertpath?do=end"); dispatcher = getServletContext().getRequestDispatcher("/AssertIncludeForwardServlet/assertpath?do=end");
else if(request.getParameter("do").equals("assertforward")) else if(request.getParameter("do").equals("assertforward"))
dispatcher = getServletContext().getRequestDispatcher("/AssertForwardServlet?do=end&do=the"); dispatcher = getServletContext().getRequestDispatcher("/AssertForwardServlet?do=end&do=the");
else if(request.getParameter("do").equals("ctx.echo")) else if(request.getParameter("do").equals("ctx.echo"))
@ -309,6 +328,18 @@ public class DispatcherTest
} }
} }
public static class ForwardNonUTF8Servlet extends HttpServlet implements Servlet
{
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
RequestDispatcher dispatcher = null;
request.setAttribute("org.eclipse.jetty.server.Request.queryEncoding", "cp1251");
dispatcher = getServletContext().getRequestDispatcher("/AssertForwardServlet?do=end&else=%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BE%3D%D0%A2%D0%B5%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D1%83%D1%80%D0%B0");
dispatcher.forward(request, response);
}
}
/* /*
* Forward filter works with roger, echo and reverse echo servlets to test various * Forward filter works with roger, echo and reverse echo servlets to test various
* forwarding bits using filters. * forwarding bits using filters.
@ -527,6 +558,45 @@ public class DispatcherTest
response.setStatus(HttpServletResponse.SC_OK); response.setStatus(HttpServletResponse.SC_OK);
} }
} }
public static class AssertNonUTF8ForwardServlet extends HttpServlet implements Servlet
{
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
byte[] cp1251_bytes = TypeUtil.fromHexString("d2e5ecefe5f0e0f2f3f0e0");
String expectedCP1251String = new String(cp1251_bytes, "cp1251");
assertEquals( "/context/ForwardServlet", request.getAttribute(Dispatcher.FORWARD_REQUEST_URI));
assertEquals( "/context", request.getAttribute(Dispatcher.FORWARD_CONTEXT_PATH) );
assertEquals( "/ForwardServlet", request.getAttribute(Dispatcher.FORWARD_SERVLET_PATH));
assertEquals( null, request.getAttribute(Dispatcher.FORWARD_PATH_INFO));
assertEquals( "do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1", request.getAttribute(Dispatcher.FORWARD_QUERY_STRING) );
List<String> expectedAttributeNames = Arrays.asList(Dispatcher.FORWARD_REQUEST_URI, Dispatcher.FORWARD_CONTEXT_PATH,
Dispatcher.FORWARD_SERVLET_PATH, Dispatcher.FORWARD_QUERY_STRING);
List<String> requestAttributeNames = Collections.list(request.getAttributeNames());
assertTrue(requestAttributeNames.containsAll(expectedAttributeNames));
assertEquals(null, request.getPathInfo());
assertEquals(null, request.getPathTranslated());
assertTrue(request.getQueryString().startsWith("do=end&else=%D0%B2%D1%8B%D0%B1%D1%80%D0%B0%D0%BD%D0%BE%3D%D0%A2%D0%B5%D0%BC%D0%BF%D0%B5%D1%80%D0%B0%D1%82%D1%83%D1%80%D0%B0&test=1&foreign="));
String[] vals = request.getParameterValues("foreign");
assertTrue(vals!=null);
assertEquals(1, vals.length);
assertEquals(expectedCP1251String, vals[0]);
assertEquals("/context/AssertForwardServlet", request.getRequestURI());
assertEquals("/context", request.getContextPath());
assertEquals("/AssertForwardServlet", request.getServletPath());
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
}
}
public static class AssertIncludeServlet extends HttpServlet implements Servlet public static class AssertIncludeServlet extends HttpServlet implements Servlet
{ {

View File

@ -89,7 +89,6 @@ public class ResponseHeadersTest
} }
int port = connector.getLocalPort(); int port = connector.getLocalPort();
serverUri = new URI(String.format("http://%s:%d/",host,port)); serverUri = new URI(String.format("http://%s:%d/",host,port));
System.out.printf("Server URI: %s%n",serverUri);
} }
@AfterClass @AfterClass
@ -130,10 +129,8 @@ public class ResponseHeadersTest
// Read response // Read response
String respHeader = readResponseHeader(in); String respHeader = readResponseHeader(in);
System.out.println("RESPONSE: " + respHeader);
// Now test for properly formatted HTTP Response Headers. // Now test for properly formatted HTTP Response Headers.
Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 101 Switching Protocols")); Assert.assertThat("Response Code",respHeader,startsWith("HTTP/1.1 101 Switching Protocols"));
Assert.assertThat("Response Header Upgrade",respHeader,containsString("Upgrade: WebSocket\r\n")); Assert.assertThat("Response Header Upgrade",respHeader,containsString("Upgrade: WebSocket\r\n"));
Assert.assertThat("Response Header Connection",respHeader,containsString("Connection: Upgrade\r\n")); Assert.assertThat("Response Header Connection",respHeader,containsString("Connection: Upgrade\r\n"));

View File

@ -105,6 +105,7 @@ public class GzipFilter extends UserAgentFilter
private static final Logger LOG = Log.getLogger(GzipFilter.class); private static final Logger LOG = Log.getLogger(GzipFilter.class);
public final static String GZIP="gzip"; public final static String GZIP="gzip";
public final static String DEFLATE="deflate"; public final static String DEFLATE="deflate";
protected Set<String> _mimeTypes; protected Set<String> _mimeTypes;
protected int _bufferSize=8192; protected int _bufferSize=8192;
@ -115,6 +116,11 @@ public class GzipFilter extends UserAgentFilter
protected Set<Pattern> _excludedAgentPatterns; protected Set<Pattern> _excludedAgentPatterns;
protected Set<String> _excludedPaths; protected Set<String> _excludedPaths;
protected Set<Pattern> _excludedPathPatterns; protected Set<Pattern> _excludedPathPatterns;
private static final int STATE_SEPARATOR = 0;
private static final int STATE_Q = 1;
private static final int STATE_QVALUE = 2;
private static final int STATE_DEFAULT = 3;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -262,15 +268,89 @@ public class GzipFilter extends UserAgentFilter
{ {
// TODO, this could be a little more robust. // TODO, this could be a little more robust.
// prefer gzip over deflate // prefer gzip over deflate
String compression = null;
if (encodingHeader!=null) if (encodingHeader!=null)
{ {
if (encodingHeader.toLowerCase().contains(GZIP))
return GZIP; String[] encodings = getEncodings(encodingHeader);
else if (encodingHeader.toLowerCase().contains(DEFLATE)) if (encodings != null)
return DEFLATE; {
for (int i=0; i< encodings.length; i++)
{
if (encodings[i].toLowerCase().contains(GZIP))
{
if (isEncodingAcceptable(encodings[i]))
{
compression = GZIP;
break; //prefer Gzip over deflate
}
}
if (encodings[i].toLowerCase().contains(DEFLATE))
{
if (isEncodingAcceptable(encodings[i]))
{
compression = DEFLATE; //Keep checking in case gzip is acceptable
}
}
}
}
} }
return null; return compression;
} }
private String[] getEncodings (String encodingHeader)
{
if (encodingHeader == null)
return null;
return encodingHeader.split(",");
}
private boolean isEncodingAcceptable(String encoding)
{
int state = STATE_DEFAULT;
int qvalueIdx = -1;
for (int i=0;i<encoding.length();i++)
{
char c = encoding.charAt(i);
switch (state)
{
case STATE_DEFAULT:
{
if (';' == c)
state = STATE_SEPARATOR;
break;
}
case STATE_SEPARATOR:
{
if ('q' == c || 'Q' == c)
state = STATE_Q;
break;
}
case STATE_Q:
{
if ('=' == c)
state = STATE_QVALUE;
break;
}
case STATE_QVALUE:
{
if (qvalueIdx < 0 && '0' == c || '1' == c)
qvalueIdx = i;
break;
}
}
}
if (qvalueIdx < 0)
return true;
if ("0".equals(encoding.substring(qvalueIdx).trim()))
return false;
return true;
}
protected CompressedResponseWrapper createWrappedResponse(HttpServletRequest request, HttpServletResponse response, final String compressionType) protected CompressedResponseWrapper createWrappedResponse(HttpServletRequest request, HttpServletResponse response, final String compressionType)
{ {

View File

@ -77,7 +77,7 @@ import org.eclipse.jetty.util.StringUtil;
public class MultiPartFilter implements Filter public class MultiPartFilter implements Filter
{ {
public final static String CONTENT_TYPE_SUFFIX=".org.eclipse.jetty.servlet.contentType"; public final static String CONTENT_TYPE_SUFFIX=".org.eclipse.jetty.servlet.contentType";
private final static String FILES ="org.eclipse.jetty.servlet.MultiPartFilter.files"; private final static String MULTIPART = "org.eclipse.jetty.servlet.MultiPartFile.multiPartInputStream";
private File tempdir; private File tempdir;
private boolean _deleteFiles; private boolean _deleteFiles;
private ServletContext _context; private ServletContext _context;
@ -141,7 +141,8 @@ public class MultiPartFilter implements Filter
MultipartConfigElement config = new MultipartConfigElement(tempdir.getCanonicalPath(), _maxFileSize, _maxRequestSize, _fileOutputBuffer); MultipartConfigElement config = new MultipartConfigElement(tempdir.getCanonicalPath(), _maxFileSize, _maxRequestSize, _fileOutputBuffer);
MultiPartInputStream mpis = new MultiPartInputStream(in, content_type, config, tempdir); MultiPartInputStream mpis = new MultiPartInputStream(in, content_type, config, tempdir);
mpis.setDeleteOnExit(_deleteFiles);
request.setAttribute(MULTIPART, mpis);
try try
{ {
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
@ -161,18 +162,6 @@ public class MultiPartFilter implements Filter
if (mp.getContentType() != null) if (mp.getContentType() != null)
params.add(mp.getName()+CONTENT_TYPE_SUFFIX, mp.getContentType()); params.add(mp.getName()+CONTENT_TYPE_SUFFIX, mp.getContentType());
} }
if (_deleteFiles)
{
mp.getFile().deleteOnExit();
ArrayList<File> files = (ArrayList<File>)request.getAttribute(FILES);
if (files==null)
{
files=new ArrayList<>();
request.setAttribute(FILES,files);
}
files.add(mp.getFile());
}
} }
else else
{ {
@ -193,24 +182,27 @@ public class MultiPartFilter implements Filter
deleteFiles(request); deleteFiles(request);
} }
} }
/* ------------------------------------------------------------ */
private void deleteFiles(ServletRequest request) private void deleteFiles(ServletRequest request)
{ {
ArrayList<File> files = (ArrayList<File>)request.getAttribute(FILES); if (!_deleteFiles)
if (files!=null) return;
MultiPartInputStream mpis = (MultiPartInputStream)request.getAttribute(MULTIPART);
if (mpis != null)
{ {
for (File file : files) try
{ {
try mpis.deleteParts();
{ }
file.delete(); catch (Exception e)
} {
catch (Exception e) _context.log("Error deleting multipart tmp files", e);
{
_context.log("failed to delete " + file, e);
}
} }
} }
request.removeAttribute(MULTIPART);
} }
/* ------------------------------------------------------------------------------- */ /* ------------------------------------------------------------------------------- */

View File

@ -65,6 +65,10 @@ public class GzipFilterDefaultNoRecompressTest
{ "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP }, { "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP },
{ "jetty_logo.xcf", "image/xcf", GzipFilter.GZIP }, { "jetty_logo.xcf", "image/xcf", GzipFilter.GZIP },
{ "jetty_logo.jp2", "image/jpeg2000", GzipFilter.GZIP }, { "jetty_logo.jp2", "image/jpeg2000", GzipFilter.GZIP },
//qvalue disables compression
{ "test_quotes.txt", "text/plain", GzipFilter.GZIP+";q=0"},
{ "test_quotes.txt", "text/plain", GzipFilter.GZIP+"; q = 0 "},
// Same tests again for deflate // Same tests again for deflate
// Some already compressed files // Some already compressed files

View File

@ -122,6 +122,52 @@ public class GzipFilterDefaultTest
} }
} }
@Test
public void testIsGzipCompressedTinyWithQ() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType+";q=0.5");
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
holder.setInitParameter("mimeTypes","text/plain");
try
{
tester.start();
tester.assertIsResponseGzipCompressed("file.txt");
}
finally
{
tester.stop();
}
}
@Test
public void testIsGzipCompressedTinyWithBadQ() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType+";q=");
// Test content that is smaller than the buffer.
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
holder.setInitParameter("mimeTypes","text/plain");
try
{
tester.start();
tester.assertIsResponseGzipCompressed("file.txt");
}
finally
{
tester.stop();
}
}
@Test @Test
public void testIsGzipCompressedLarge() throws Exception public void testIsGzipCompressedLarge() throws Exception
{ {
@ -145,6 +191,28 @@ public class GzipFilterDefaultTest
} }
} }
@Test
public void testIsNotGzipCompressedWithQ() throws Exception
{
GzipTester tester = new GzipTester(testingdir, compressionType+"; q = 0");
int filesize = CompressedResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
tester.prepareServerFile("file.txt",filesize);
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
holder.setInitParameter("mimeTypes","text/plain");
try
{
tester.start();
tester.assertIsResponseNotGzipCompressed("file.txt", filesize, HttpStatus.OK_200);
}
finally
{
tester.stop();
}
}
@Test @Test
public void testIsNotGzipCompressed() throws Exception public void testIsNotGzipCompressed() throws Exception
{ {

View File

@ -113,7 +113,7 @@ public class MultipartFilterTest
response = HttpTester.parseResponse(tester.getResponses(request.generate())); response = HttpTester.parseResponse(tester.getResponses(request.generate()));
assertEquals(HttpServletResponse.SC_OK,response.getStatus()); assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,response.getStatus());
} }

View File

@ -98,7 +98,11 @@ public class GzipTester
// Assert the response headers // Assert the response headers
// Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK)); // Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
Assert.assertThat("Response.header[Content-Length]",response.get("Content-Length"),notNullValue()); Assert.assertThat("Response.header[Content-Length]",response.get("Content-Length"),notNullValue());
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType)); int qindex = compressionType.indexOf(";");
if (qindex < 0)
Assert.assertThat("Response.header[Content-Encoding]",response.get("Content-Encoding"),containsString(compressionType));
else
Assert.assertThat("Response.header[Content-Encoding]", response.get("Content-Encoding"),containsString(compressionType.substring(0,qindex)));
// Assert that the decompressed contents are what we expect. // Assert that the decompressed contents are what we expect.
File serverFile = testdir.getFile(serverFilename); File serverFile = testdir.getFile(serverFilename);
@ -111,11 +115,11 @@ public class GzipTester
try try
{ {
bais = new ByteArrayInputStream(response.getContentBytes()); bais = new ByteArrayInputStream(response.getContentBytes());
if (compressionType.equals(GzipFilter.GZIP)) if (compressionType.startsWith(GzipFilter.GZIP))
{ {
in = new GZIPInputStream(bais); in = new GZIPInputStream(bais);
} }
else if (compressionType.equals(GzipFilter.DEFLATE)) else if (compressionType.startsWith(GzipFilter.DEFLATE))
{ {
in = new InflaterInputStream(bais, new Inflater(true)); in = new InflaterInputStream(bais, new Inflater(true));
} }
@ -247,6 +251,7 @@ public class GzipTester
Assert.assertEquals("Expected response equals actual response",expectedResponse,actual); Assert.assertEquals("Expected response equals actual response",expectedResponse,actual);
} }
} }
/** /**
* Asserts that the request results in a properly structured GzipFilter response, where the content is * Asserts that the request results in a properly structured GzipFilter response, where the content is

View File

@ -41,7 +41,8 @@ public class CommandLineBuilder
*/ */
public void addArg(String arg) public void addArg(String arg)
{ {
args.add(quote(arg)); if (arg != null)
args.add(quote(arg));
} }
/** /**
@ -84,7 +85,8 @@ public class CommandLineBuilder
*/ */
public void addRawArg(String arg) public void addRawArg(String arg)
{ {
args.add(arg); if (arg != null)
args.add(arg);
} }
public List<String> getArgs() public List<String> getArgs()
@ -106,7 +108,7 @@ public class CommandLineBuilder
return arg; return arg;
} }
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
// buf.append('"'); // buf.append('"');
boolean escaped = false; boolean escaped = false;
for (char c : arg.toCharArray()) for (char c : arg.toCharArray())
{ {
@ -117,7 +119,7 @@ public class CommandLineBuilder
escaped = (c == '\\'); escaped = (c == '\\');
buf.append(c); buf.append(c);
} }
// buf.append('"'); // buf.append('"');
return buf.toString(); return buf.toString();
} }
@ -133,7 +135,7 @@ public class CommandLineBuilder
{ {
buf.append(' '); buf.append(' ');
} }
buf.append(arg); buf.append(quote(arg));
delim = true; delim = true;
} }

View File

@ -30,6 +30,7 @@ import java.io.FilenameFilter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -167,9 +168,17 @@ public class Main
{ {
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1")); int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
String key = Config.getProperty("STOP.KEY",null); String key = Config.getProperty("STOP.KEY",null);
stop(port,key); stop(port,key, false);
return null; return null;
} }
if ("--stop-wait".equals(arg))
{
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
String key = Config.getProperty("STOP.KEY",null);
stop(port,key, true);
return null;
}
if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg)) if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
{ {
@ -268,7 +277,7 @@ public class Main
{ {
String opts[] = assign[1].split(","); String opts[] = assign[1].split(",");
for (String opt : opts) for (String opt : opts)
_config.addActiveOption(opt); _config.addActiveOption(opt.trim());
} }
else else
{ {
@ -982,6 +991,12 @@ public class Main
* Stop a running jetty instance. * Stop a running jetty instance.
*/ */
public void stop(int port, String key) public void stop(int port, String key)
{
stop (port,key,false);
}
public void stop (int port, String key, boolean wait)
{ {
int _port = port; int _port = port;
String _key = key; String _key = key;
@ -1005,6 +1020,14 @@ public class Main
OutputStream out = s.getOutputStream(); OutputStream out = s.getOutputStream();
out.write((_key + "\r\nstop\r\n").getBytes()); out.write((_key + "\r\nstop\r\n").getBytes());
out.flush(); out.flush();
if (wait)
{
LineNumberReader lin = new LineNumberReader(new InputStreamReader(s.getInputStream()));
String response=lin.readLine();
if ("Stopped".equals(response))
System.err.println("Stopped");
}
} }
finally finally
{ {

View File

@ -17,6 +17,7 @@
// //
package org.eclipse.jetty.start; package org.eclipse.jetty.start;
import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.LineNumberReader; import java.io.LineNumberReader;
import java.net.InetAddress; import java.net.InetAddress;
@ -113,21 +114,23 @@ public class Monitor extends Thread
Config.debug("command=" + cmd); Config.debug("command=" + cmd);
if ("stop".equals(cmd)) if ("stop".equals(cmd))
{ {
try {socket.close();}catch(Exception e){e.printStackTrace();}
try {_socket.close();}catch(Exception e){e.printStackTrace();}
if (_process!=null) if (_process!=null)
{ {
//if we have a child process, wait for it to finish before we stop //if we have a child process, wait for it to finish before we stop
try try
{ {
_process.destroy(); _process.destroy();
_process.waitFor(); _process.waitFor();
} }
catch (InterruptedException e) catch (InterruptedException e)
{ {
System.err.println("Interrupted waiting for child to terminate"); System.err.println("Interrupted waiting for child to terminate");
} }
} }
socket.getOutputStream().write("Stopped\r\n".getBytes());
try {socket.close();}catch(Exception e){e.printStackTrace();}
try {_socket.close();}catch(Exception e){e.printStackTrace();}
System.exit(0); System.exit(0);
} }
else if ("status".equals(cmd)) else if ("status".equals(cmd))

View File

@ -24,7 +24,10 @@ Command Line Options:
contains -X or -D arguments, but creates an extra contains -X or -D arguments, but creates an extra
JVM instance. JVM instance.
--stop Stop the running Jetty instance. --stop Send a stop signal to the running Jetty instance.
--stop-wait Send a stop signal to the running Jetty instance, waiting for
confirmation that it is stopping.
--daemon Start in daemon mode with stderr and stdout --daemon Start in daemon mode with stderr and stdout
redirected to ${jetty.log}/start.log redirected to ${jetty.log}/start.log

View File

@ -18,21 +18,29 @@
package org.eclipse.jetty.start; package org.eclipse.jetty.start;
import static org.hamcrest.Matchers.is;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
public class CommandLineBuilderTest public class CommandLineBuilderTest
{ {
private CommandLineBuilder cmd = new CommandLineBuilder("java");
@Before
public void setUp()
{
cmd.addEqualsArg("-Djava.io.tmpdir","/home/java/temp dir/");
cmd.addArg("--version");
}
@Test @Test
public void testSimpleCommandline() public void testSimpleCommandline()
{ {
CommandLineBuilder cmd = new CommandLineBuilder("java"); Assert.assertThat(cmd.toString(),is("java -Djava.io.tmpdir=/home/java/temp\\ dir/ --version"));
cmd.addEqualsArg("-Djava.io.tmpdir","/home/java/temp dir/");
cmd.addArg("--version");
Assert.assertThat(cmd.toString(), is("java -Djava.io.tmpdir=/home/java/temp\\ dir/ --version"));
} }
@Test @Test
@ -53,6 +61,12 @@ public class CommandLineBuilderTest
assertQuoting("/opt/jetty 7 \"special\"/home","/opt/jetty\\ 7\\ \\\"special\\\"/home"); assertQuoting("/opt/jetty 7 \"special\"/home","/opt/jetty\\ 7\\ \\\"special\\\"/home");
} }
@Test
public void testToStringIsQuotedEvenIfArgsAreNotQuotedForProcessBuilder()
{
System.out.println(cmd.toString());
}
private void assertQuoting(String raw, String expected) private void assertQuoting(String raw, String expected)
{ {
String actual = CommandLineBuilder.quote(raw); String actual = CommandLineBuilder.quote(raw);

View File

@ -26,13 +26,17 @@ import java.util.List;
import java.util.Vector; import java.util.Vector;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
@ -106,14 +110,24 @@ public class MainTest
Classpath classpath = nastyWayToCreateAClasspathObject("/jetty/home with spaces/"); Classpath classpath = nastyWayToCreateAClasspathObject("/jetty/home with spaces/");
CommandLineBuilder cmd = main.buildCommandLine(classpath,xmls); CommandLineBuilder cmd = main.buildCommandLine(classpath,xmls);
Assert.assertThat("CommandLineBuilder shouldn't be null",cmd,notNullValue()); assertThat("CommandLineBuilder shouldn't be null",cmd,notNullValue());
String commandLine = cmd.toString();
Assert.assertThat("CommandLine shouldn't be null",commandLine,notNullValue());
Assert.assertThat("Classpath should be correctly quoted and match expected value",commandLine,
containsString("-cp /jetty/home with spaces/somejar.jar:/jetty/home with spaces/someotherjar.jar"));
Assert.assertThat("CommandLine should contain jvmArgs",commandLine,containsString("--exec -Xms1024m -Xmx1024m"));
Assert.assertThat("CommandLine should contain xmls",commandLine,containsString("jetty.xml jetty-jmx.xml jetty-logging.xml"));
List<String> commandArgs = cmd.getArgs();
assertThat("commandArgs should contain 11 elements",commandArgs.size(),equalTo(11));
assertThat("args does not contain -cp",commandArgs,hasItems("-cp"));
assertThat("Classpath should be correctly quoted and match expected value",commandArgs,
hasItems("/jetty/home with spaces/somejar.jar:/jetty/home with spaces/someotherjar.jar"));
assertThat("args does not contain --exec",commandArgs,hasItems("--exec"));
assertThat("CommandLine should contain jvmArgs",commandArgs,hasItems("-Xms1024m"));
assertThat("CommandLine should contain jvmArgs", commandArgs, hasItems("-Xmx1024m"));
assertThat("CommandLine should contain xmls",commandArgs,hasItems("jetty.xml"));
assertThat("CommandLine should contain xmls",commandArgs,hasItems("jetty-jmx.xml"));
assertThat("CommandLine should contain xmls", commandArgs, hasItems("jetty-logging.xml"));
String commandLine = cmd.toString();
assertThat("cmd.toString() should be properly escaped",commandLine,containsString("-cp /jetty/home\\ with\\ " +
"spaces/somejar.jar:/jetty/home\\ with\\ spaces/someotherjar.jar"));
assertThat("cmd.toString() doesn't contain xml config files",commandLine,containsString(" jetty.xml jetty-jmx.xml jetty-logging.xml"));
} }
private Classpath nastyWayToCreateAClasspathObject(String jettyHome) throws NoSuchFieldException, IllegalAccessException private Classpath nastyWayToCreateAClasspathObject(String jettyHome) throws NoSuchFieldException, IllegalAccessException

View File

@ -32,6 +32,7 @@ import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import javax.servlet.MultipartConfigElement; import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -53,7 +54,7 @@ public class MultiPartInputStream
protected MultiMap _parts; protected MultiMap _parts;
protected File _tmpDir; protected File _tmpDir;
protected File _contextTmpDir; protected File _contextTmpDir;
protected boolean _deleteOnExit;
@ -67,6 +68,7 @@ public class MultiPartInputStream
protected String _contentType; protected String _contentType;
protected MultiMap _headers; protected MultiMap _headers;
protected long _size = 0; protected long _size = 0;
protected boolean _temporary = true;
public MultiPart (String name, String filename) public MultiPart (String name, String filename)
throws IOException throws IOException
@ -135,6 +137,8 @@ public class MultiPartInputStream
throws IOException throws IOException
{ {
_file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir); _file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir);
if (_deleteOnExit)
_file.deleteOnExit();
FileOutputStream fos = new FileOutputStream(_file); FileOutputStream fos = new FileOutputStream(_file);
BufferedOutputStream bos = new BufferedOutputStream(fos); BufferedOutputStream bos = new BufferedOutputStream(fos);
@ -197,11 +201,12 @@ public class MultiPartInputStream
{ {
if (_file != null) if (_file != null)
{ {
//written to a file, whether temporary or not
return new BufferedInputStream (new FileInputStream(_file)); return new BufferedInputStream (new FileInputStream(_file));
} }
else else
{ {
//part content is in a ByteArrayOutputStream //part content is in memory
return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size()); return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
} }
} }
@ -226,7 +231,7 @@ public class MultiPartInputStream
*/ */
public long getSize() public long getSize()
{ {
return _size; return _size;
} }
/** /**
@ -236,8 +241,11 @@ public class MultiPartInputStream
{ {
if (_file == null) if (_file == null)
{ {
_temporary = false;
//part data is only in the ByteArrayOutputStream and never been written to disk //part data is only in the ByteArrayOutputStream and never been written to disk
_file = new File (_tmpDir, fileName); _file = new File (_tmpDir, fileName);
BufferedOutputStream bos = null; BufferedOutputStream bos = null;
try try
{ {
@ -255,6 +263,8 @@ public class MultiPartInputStream
else else
{ {
//the part data is already written to a temporary file, just rename it //the part data is already written to a temporary file, just rename it
_temporary = false;
File f = new File(_tmpDir, fileName); File f = new File(_tmpDir, fileName);
if (_file.renameTo(f)) if (_file.renameTo(f))
_file = f; _file = f;
@ -262,11 +272,24 @@ public class MultiPartInputStream
} }
/** /**
* Remove the file, whether or not Part.write() was called on it
* (ie no longer temporary)
* @see javax.servlet.http.Part#delete() * @see javax.servlet.http.Part#delete()
*/ */
public void delete() throws IOException public void delete() throws IOException
{ {
if (_file != null) if (_file != null && _file.exists())
_file.delete();
}
/**
* Only remove tmp files.
*
* @throws IOException
*/
public void cleanUp() throws IOException
{
if (_temporary && _file != null && _file.exists())
_file.delete(); _file.delete();
} }
@ -308,12 +331,65 @@ public class MultiPartInputStream
_contextTmpDir = contextTmpDir; _contextTmpDir = contextTmpDir;
if (_contextTmpDir == null) if (_contextTmpDir == null)
_contextTmpDir = new File (System.getProperty("java.io.tmpdir")); _contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
if (_config == null) if (_config == null)
_config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath()); _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath());
} }
/**
* Get the already parsed parts.
*
* @return
*/
public Collection<Part> getParsedParts()
{
if (_parts == null)
return Collections.emptyList();
Collection<Object> values = _parts.values();
List<Part> parts = new ArrayList<Part>();
for (Object o: values)
{
List<Part> asList = LazyList.getList(o, false);
parts.addAll(asList);
}
return parts;
}
/**
* Delete any tmp storage for parts, and clear out the parts list.
*
* @throws MultiException
*/
public void deleteParts ()
throws MultiException
{
Collection<Part> parts = getParsedParts();
MultiException err = new MultiException();
for (Part p:parts)
{
try
{
((MultiPartInputStream.MultiPart)p).cleanUp();
}
catch(Exception e)
{
err.add(e);
}
}
_parts.clear();
err.ifExceptionThrowMulti();
}
/**
* Parse, if necessary, the multipart data and return the list of Parts.
*
* @return
* @throws IOException
* @throws ServletException
*/
public Collection<Part> getParts() public Collection<Part> getParts()
throws IOException, ServletException throws IOException, ServletException
{ {
@ -329,6 +405,14 @@ public class MultiPartInputStream
} }
/**
* Get the named Part.
*
* @param name
* @return
* @throws IOException
* @throws ServletException
*/
public Part getPart(String name) public Part getPart(String name)
throws IOException, ServletException throws IOException, ServletException
{ {
@ -337,6 +421,12 @@ public class MultiPartInputStream
} }
/**
* Parse, if necessary, the multipart stream.
*
* @throws IOException
* @throws ServletException
*/
protected void parse () protected void parse ()
throws IOException, ServletException throws IOException, ServletException
{ {
@ -583,6 +673,19 @@ public class MultiPartInputStream
part.close(); part.close();
} }
} }
if (!lastPart)
throw new IOException("Incomplete parts");
}
public void setDeleteOnExit(boolean deleteOnExit)
{
_deleteOnExit = deleteOnExit;
}
public boolean isDeleteOnExit()
{
return _deleteOnExit;
} }

View File

@ -129,9 +129,6 @@ public class MultiPartOutputStream extends FilterOutputStream
{ {
out.write(b,off,len); out.write(b,off,len);
} }
} }

View File

@ -102,7 +102,7 @@ public class StringUtil
ByteBuffer slice = b.slice(); ByteBuffer slice = b.slice();
slice.position(position); slice.position(position);
slice.limit(position+length); slice.limit(position+length);
return BufferUtil.toString(slice); return BufferUtil.toString(slice,__UTF8_CHARSET);
} }

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.util;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
@ -54,6 +55,40 @@ public class MultiPartInputStreamTest
protected String _contentType = "multipart/form-data, boundary=AaB03x"; protected String _contentType = "multipart/form-data, boundary=AaB03x";
protected String _multi = createMultipartRequestString(FILENAME); protected String _multi = createMultipartRequestString(FILENAME);
protected String _dirname = System.getProperty("java.io.tmpdir")+File.separator+"myfiles-"+System.currentTimeMillis(); protected String _dirname = System.getProperty("java.io.tmpdir")+File.separator+"myfiles-"+System.currentTimeMillis();
protected File _tmpDir = new File(_dirname);
public MultiPartInputStreamTest ()
{
_tmpDir.deleteOnExit();
}
public void testBadMultiPartRequest()
throws Exception
{
String boundary = "X0Y0";
String str = "--" + boundary + "\r\n"+
"Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r\n"+
"Content-Type: application/octet-stream\r\n\r\n"+
"How now brown cow."+
"\r\n--" + boundary + "-\r\n\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(str.getBytes()),
"multipart/form-data, boundary="+boundary,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
try
{
mpis.getParts();
fail ("Multipart incomplete");
}
catch (IOException e)
{
assertTrue(e.getMessage().startsWith("Incomplete"));
}
}
@Test @Test
public void testNonMultiPartRequest() public void testNonMultiPartRequest()
@ -63,7 +98,8 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()), MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()),
"Content-type: text/plain", "Content-type: text/plain",
config, config,
new File(_dirname)); _tmpDir);
mpis.setDeleteOnExit(true);
assertTrue(mpis.getParts().isEmpty()); assertTrue(mpis.getParts().isEmpty());
} }
@ -75,7 +111,8 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()), MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType, _contentType,
config, config,
new File(_dirname)); _tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
assertFalse(parts.isEmpty()); assertFalse(parts.isEmpty());
} }
@ -88,11 +125,12 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()), MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType, _contentType,
config, config,
new File(_dirname)); _tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try try
{ {
mpis.getParts(); parts = mpis.getParts();
fail("Request should have exceeded maxRequestSize"); fail("Request should have exceeded maxRequestSize");
} }
catch (IllegalStateException e) catch (IllegalStateException e)
@ -109,11 +147,12 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()), MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType, _contentType,
config, config,
new File(_dirname)); _tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try try
{ {
mpis.getParts(); parts = mpis.getParts();
fail("stuff.txt should have been larger than maxFileSize"); fail("stuff.txt should have been larger than maxFileSize");
} }
catch (IllegalStateException e) catch (IllegalStateException e)
@ -123,6 +162,48 @@ public class MultiPartInputStreamTest
} }
@Test @Test
public void testPartFileNotDeleted () throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
MultiPart part = (MultiPart)mpis.getPart("stuff");
File stuff = ((MultiPartInputStream.MultiPart)part).getFile();
assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file
part.write("tptfd.txt");
File tptfd = new File (_dirname+File.separator+"tptfd.txt");
assertThat(tptfd.exists(), is(true));
assertThat(stuff.exists(), is(false)); //got renamed
part.cleanUp();
assertThat(tptfd.exists(), is(true)); //explicitly written file did not get removed after cleanup
tptfd.deleteOnExit(); //clean up test
}
public void testPartTmpFileDeletion () throws Exception
{
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString("tptfd").getBytes()),
_contentType,
config,
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
MultiPart part = (MultiPart)mpis.getPart("stuff");
File stuff = ((MultiPartInputStream.MultiPart)part).getFile();
assertThat(stuff,notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat (stuff.exists(), is(true));
part.cleanUp();
assertThat(stuff.exists(), is(false)); //tmp file was removed after cleanup
}
public void testMulti () public void testMulti ()
throws Exception throws Exception
{ {
@ -141,11 +222,11 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()), MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()),
_contentType, _contentType,
config, config,
new File(_dirname)); _tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
assertThat(parts.size(), is(2)); assertThat(parts.size(), is(2));
Part field1 = mpis.getPart("field1"); Part field1 = mpis.getPart("field1"); //field 1 too small to go into tmp file, should be in internal buffer
assertThat(field1,notNullValue()); assertThat(field1,notNullValue());
assertThat(field1.getName(),is("field1")); assertThat(field1.getName(),is("field1"));
InputStream is = field1.getInputStream(); InputStream is = field1.getInputStream();
@ -154,17 +235,18 @@ public class MultiPartInputStreamTest
assertEquals("Joe Blow", new String(os.toByteArray())); assertEquals("Joe Blow", new String(os.toByteArray()));
assertEquals(8, field1.getSize()); assertEquals(8, field1.getSize());
assertNotNull(((MultiPartInputStream.MultiPart)field1).getBytes()); //in internal buffer assertNotNull(((MultiPartInputStream.MultiPart)field1).getBytes());//in internal buffer
field1.write("field1.txt"); field1.write("field1.txt");
assertNull(((MultiPartInputStream.MultiPart)field1).getBytes()); //no longer in internal buffer assertNull(((MultiPartInputStream.MultiPart)field1).getBytes());//no longer in internal buffer
File f = new File (_dirname+File.separator+"field1.txt"); File f = new File (_dirname+File.separator+"field1.txt");
assertTrue(f.exists()); assertTrue(f.exists());
field1.write("another_field1.txt"); field1.write("another_field1.txt"); //write after having already written
File f2 = new File(_dirname+File.separator+"another_field1.txt"); File f2 = new File(_dirname+File.separator+"another_field1.txt");
assertTrue(f2.exists()); assertTrue(f2.exists());
assertFalse(f.exists()); //should have been renamed assertFalse(f.exists()); //should have been renamed
field1.delete(); //file should be deleted field1.delete(); //file should be deleted
assertFalse(f2.exists()); assertFalse(f.exists()); //original file was renamed
assertFalse(f2.exists()); //2nd written file was explicitly deleted
MultiPart stuff = (MultiPart)mpis.getPart("stuff"); MultiPart stuff = (MultiPart)mpis.getPart("stuff");
assertThat(stuff.getContentDispositionFilename(), is(filename)); assertThat(stuff.getContentDispositionFilename(), is(filename));
@ -174,14 +256,24 @@ public class MultiPartInputStreamTest
assertThat(stuff.getHeader("content-disposition"),is("form-data; name=\"stuff\"; filename=\"" + filename + "\"")); assertThat(stuff.getHeader("content-disposition"),is("form-data; name=\"stuff\"; filename=\"" + filename + "\""));
assertThat(stuff.getHeaderNames().size(),is(2)); assertThat(stuff.getHeaderNames().size(),is(2));
assertThat(stuff.getSize(),is(51L)); assertThat(stuff.getSize(),is(51L));
f = stuff.getFile(); File tmpfile = ((MultiPartInputStream.MultiPart)stuff).getFile();
assertThat(f,notNullValue()); // longer than 100 bytes, should already be a file assertThat(tmpfile,notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat(stuff.getBytes(),nullValue()); //not in internal buffer any more assertThat(((MultiPartInputStream.MultiPart)stuff).getBytes(),nullValue()); //not in an internal buffer
assertThat(f.exists(),is(true)); assertThat(tmpfile.exists(),is(true));
assertThat(f.getName(),is(not("stuff with space.txt"))); assertThat(tmpfile.getName(),is(not("stuff with space.txt")));
stuff.write(filename); stuff.write(filename);
f = new File(_dirname+File.separator+filename); f = new File(_dirname+File.separator+filename);
assertThat(f.exists(),is(true)); assertThat(f.exists(),is(true));
assertThat(tmpfile.exists(), is(false));
try
{
stuff.getInputStream();
}
catch (Exception e)
{
fail("Part.getInputStream() after file rename operation");
}
f.deleteOnExit(); //clean up after test
} }
@Test @Test
@ -197,15 +289,15 @@ public class MultiPartInputStreamTest
"content-disposition: form-data; name=\"stuff\"; filename=\"stuff2.txt\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"stuff2.txt\"\r\n"+
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n"+
"\r\n"+ "\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+ "110000000000000000000000000000000000000000000000000\r\n"+
"--AaB03x--\r\n"; "--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50); MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(sameNames.getBytes()), MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(sameNames.getBytes()),
_contentType, _contentType,
config, config,
new File(_dirname)); _tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts(); Collection<Part> parts = mpis.getParts();
assertEquals(2, parts.size()); assertEquals(2, parts.size());
for (Part p:parts) for (Part p:parts)
@ -219,6 +311,18 @@ public class MultiPartInputStreamTest
private String createMultipartRequestString(String filename) private String createMultipartRequestString(String filename)
{ {
int length = filename.length();
String name = filename;
if (length > 10)
name = filename.substring(0,10);
StringBuffer filler = new StringBuffer();
int i = name.length();
while (i < 51)
{
filler.append("0");
i++;
}
return "--AaB03x\r\n"+ return "--AaB03x\r\n"+
"content-disposition: form-data; name=\"field1\"\r\n"+ "content-disposition: form-data; name=\"field1\"\r\n"+
"\r\n"+ "\r\n"+
@ -226,8 +330,8 @@ public class MultiPartInputStreamTest
"--AaB03x\r\n"+ "--AaB03x\r\n"+
"content-disposition: form-data; name=\"stuff\"; filename=\"" + filename + "\"\r\n"+ "content-disposition: form-data; name=\"stuff\"; filename=\"" + filename + "\"\r\n"+
"Content-Type: text/plain\r\n"+ "Content-Type: text/plain\r\n"+
"\r\n"+ "\r\n"+name+
"000000000000000000000000000000000000000000000000000\r\n"+ filler.toString()+"\r\n" +
"--AaB03x--\r\n"; "--AaB03x--\r\n";
} }
} }

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.webapp;
import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
/** /**
* DiscoveredAnnotation * DiscoveredAnnotation
@ -36,16 +37,28 @@ public abstract class DiscoveredAnnotation
protected WebAppContext _context; protected WebAppContext _context;
protected String _className; protected String _className;
protected Class<?> _clazz; protected Class<?> _clazz;
protected Resource _resource; //resource it was discovered on, can be null (eg from WEB-INF/classes)
public abstract void apply(); public abstract void apply();
public DiscoveredAnnotation (WebAppContext context, String className) public DiscoveredAnnotation (WebAppContext context, String className)
{ {
_context = context; this(context,className, null);
_className = className;
} }
public DiscoveredAnnotation(WebAppContext context, String className, Resource resource)
{
_context = context;
_className = className;
_resource = resource;
}
public Resource getResource ()
{
return _resource;
}
public Class<?> getTargetClass() public Class<?> getTargetClass()
{ {
if (_clazz != null) if (_clazz != null)

View File

@ -200,7 +200,7 @@ public class MetaData
_metaDataComplete=true; _metaDataComplete=true;
break; break;
case False: case False:
_metaDataComplete=true; _metaDataComplete=false;
break; break;
case NotSet: case NotSet:
break; break;
@ -270,12 +270,42 @@ public class MetaData
*/ */
public void addDiscoveredAnnotations(List<DiscoveredAnnotation> annotations) public void addDiscoveredAnnotations(List<DiscoveredAnnotation> annotations)
{ {
_annotations.addAll(annotations); if (annotations == null)
return;
for (DiscoveredAnnotation a:annotations)
{
Resource r = a.getResource();
if (r == null || !_webInfJars.contains(r))
_annotations.add(a);
else
addDiscoveredAnnotation(a.getResource(), a);
}
} }
public void addDiscoveredAnnotation(Resource resource, DiscoveredAnnotation annotation)
{
List<DiscoveredAnnotation> list = _webFragmentAnnotations.get(resource);
if (list == null)
{
list = new ArrayList<DiscoveredAnnotation>();
_webFragmentAnnotations.put(resource, list);
}
list.add(annotation);
}
public void addDiscoveredAnnotations(Resource resource, List<DiscoveredAnnotation> annotations) public void addDiscoveredAnnotations(Resource resource, List<DiscoveredAnnotation> annotations)
{ {
_webFragmentAnnotations.put(resource, new ArrayList<DiscoveredAnnotation>(annotations)); List<DiscoveredAnnotation> list = _webFragmentAnnotations.get(resource);
if (list == null)
{
list = new ArrayList<DiscoveredAnnotation>();
_webFragmentAnnotations.put(resource, list);
}
list.addAll(annotations);
} }
public void addDescriptorProcessor(DescriptorProcessor p) public void addDescriptorProcessor(DescriptorProcessor p)
@ -507,6 +537,15 @@ public class MetaData
OriginInfo x = new OriginInfo (name, Origin.Annotation); OriginInfo x = new OriginInfo (name, Origin.Annotation);
_origins.put(name, x); _origins.put(name, x);
} }
public void setOrigin(String name, Origin origin)
{
if (name == null)
return;
OriginInfo x = new OriginInfo (name, origin);
_origins.put(name, x);
}
public boolean isMetaDataComplete() public boolean isMetaDataComplete()
{ {

View File

@ -18,4 +18,4 @@
package org.eclipse.jetty.webapp; package org.eclipse.jetty.webapp;
public enum Origin {NotSet, WebXml, WebDefaults, WebOverride, WebFragment, Annotation} public enum Origin {NotSet, WebXml, WebDefaults, WebOverride, WebFragment, Annotation, API}

View File

@ -617,7 +617,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{ {
//no servlet mappings //no servlet mappings
context.getMetaData().setOrigin(servlet_name+".servlet.mappings", descriptor); context.getMetaData().setOrigin(servlet_name+".servlet.mappings", descriptor);
ServletMapping mapping = addServletMapping(servlet_name, node, context); ServletMapping mapping = addServletMapping(servlet_name, node, context, descriptor);
mapping.setDefault(context.getMetaData().getOrigin(servlet_name+".servlet.mappings") == Origin.WebDefaults); mapping.setDefault(context.getMetaData().getOrigin(servlet_name+".servlet.mappings") == Origin.WebDefaults);
break; break;
} }
@ -629,14 +629,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
//otherwise just ignore it //otherwise just ignore it
if (!(descriptor instanceof FragmentDescriptor)) if (!(descriptor instanceof FragmentDescriptor))
{ {
addServletMapping(servlet_name, node, context); addServletMapping(servlet_name, node, context, descriptor);
} }
break; break;
} }
case WebFragment: case WebFragment:
{ {
//mappings previously set by another web-fragment, so merge in this web-fragment's mappings //mappings previously set by another web-fragment, so merge in this web-fragment's mappings
addServletMapping(servlet_name, node, context); addServletMapping(servlet_name, node, context, descriptor);
break; break;
} }
} }
@ -1161,7 +1161,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
* @param node * @param node
* @param context * @param context
*/ */
protected ServletMapping addServletMapping (String servletName, XmlParser.Node node, WebAppContext context) protected ServletMapping addServletMapping (String servletName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
{ {
ServletMapping mapping = new ServletMapping(); ServletMapping mapping = new ServletMapping();
mapping.setServletName(servletName); mapping.setServletName(servletName);
@ -1173,6 +1173,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
String p = iter.next().toString(false, true); String p = iter.next().toString(false, true);
p = normalizePattern(p); p = normalizePattern(p);
paths.add(p); paths.add(p);
context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, descriptor);
} }
mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()])); mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
context.getServletHandler().addServletMapping(mapping); context.getServletHandler().addServletMapping(mapping);
@ -1184,7 +1185,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
* @param node * @param node
* @param context * @param context
*/ */
protected void addFilterMapping (String filterName, XmlParser.Node node, WebAppContext context) protected void addFilterMapping (String filterName, XmlParser.Node node, WebAppContext context, Descriptor descriptor)
{ {
FilterMapping mapping = new FilterMapping(); FilterMapping mapping = new FilterMapping();
mapping.setFilterName(filterName); mapping.setFilterName(filterName);
@ -1196,6 +1197,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
String p = iter.next().toString(false, true); String p = iter.next().toString(false, true);
p = normalizePattern(p); p = normalizePattern(p);
paths.add(p); paths.add(p);
context.getMetaData().setOrigin(filterName+".filter.mapping."+p, descriptor);
} }
mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()])); mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
@ -1299,7 +1301,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
jpg.setIsXml(group.getString("is-xml", false, true)); jpg.setIsXml(group.getString("is-xml", false, true));
jpg.setDeferredSyntaxAllowedAsLiteral(group.getString("deferred-syntax-allowed-as-literal", false, true)); jpg.setDeferredSyntaxAllowedAsLiteral(group.getString("deferred-syntax-allowed-as-literal", false, true));
jpg.setTrimDirectiveWhitespaces(group.getString("trim-directive-whitespaces", false, true)); jpg.setTrimDirectiveWhitespaces(group.getString("trim-directive-whitespaces", false, true));
jpg.setDefaultContentType(group.getString("defaultContentType", false, true)); jpg.setDefaultContentType(group.getString("default-content-type", false, true));
jpg.setBuffer(group.getString("buffer", false, true)); jpg.setBuffer(group.getString("buffer", false, true));
jpg.setErrorOnUndeclaredNamespace(group.getString("error-on-undeclared-namespace", false, true)); jpg.setErrorOnUndeclaredNamespace(group.getString("error-on-undeclared-namespace", false, true));
@ -1352,6 +1354,8 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
//ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive //ServletSpec 3.0, p74 security-constraints, as minOccurs > 1, are additive
//across fragments //across fragments
//TODO: need to remember origin of the constraints
try try
{ {
XmlParser.Node auths = node.get("auth-constraint"); XmlParser.Node auths = node.get("auth-constraint");
@ -1400,29 +1404,50 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{ {
String url = iter2.next().toString(false, true); String url = iter2.next().toString(false, true);
url = normalizePattern(url); url = normalizePattern(url);
//remember origin so we can process ServletRegistration.Dynamic.setServletSecurityElement() correctly
context.getMetaData().setOrigin("constraint.url."+url, descriptor);
Iterator<XmlParser.Node> iter3 = collection.iterator("http-method"); Iterator<XmlParser.Node> iter3 = collection.iterator("http-method");
Iterator<XmlParser.Node> iter4 = collection.iterator("http-method-omission");
if (iter3.hasNext()) if (iter3.hasNext())
{ {
if (iter4.hasNext())
throw new IllegalStateException ("web-resource-collection cannot contain both http-method and http-method-omission");
//configure all the http-method elements for each url
while (iter3.hasNext()) while (iter3.hasNext())
{ {
String method = ((XmlParser.Node) iter3.next()).toString(false, true); String method = ((XmlParser.Node) iter3.next()).toString(false, true);
ConstraintMapping mapping = new ConstraintMapping(); ConstraintMapping mapping = new ConstraintMapping();
mapping.setMethod(method); mapping.setMethod(method);
mapping.setPathSpec(url); mapping.setPathSpec(url);
mapping.setConstraint(sc);
((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
}
}
else if (iter4.hasNext())
{
//configure all the http-method-omission elements for each url
while (iter4.hasNext())
{
String method = ((XmlParser.Node)iter4.next()).toString(false, true);
ConstraintMapping mapping = new ConstraintMapping();
mapping.setMethodOmissions(new String[]{method});
mapping.setPathSpec(url);
mapping.setConstraint(sc); mapping.setConstraint(sc);
((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping); ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
} }
} }
else else
{ {
//No http-methods or http-method-omissions specified, the constraint applies to all
ConstraintMapping mapping = new ConstraintMapping(); ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec(url); mapping.setPathSpec(url);
mapping.setConstraint(sc); mapping.setConstraint(sc);
((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping); ((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
} }
} }
} }
} }
catch (CloneNotSupportedException e) catch (CloneNotSupportedException e)
@ -1768,7 +1793,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{ {
//no filtermappings for this filter yet defined //no filtermappings for this filter yet defined
context.getMetaData().setOrigin(filter_name+".filter.mappings", descriptor); context.getMetaData().setOrigin(filter_name+".filter.mappings", descriptor);
addFilterMapping(filter_name, node, context); addFilterMapping(filter_name, node, context, descriptor);
break; break;
} }
case WebDefaults: case WebDefaults:
@ -1778,14 +1803,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
//filter mappings defined in a web xml file. If we're processing a fragment, we ignore filter mappings. //filter mappings defined in a web xml file. If we're processing a fragment, we ignore filter mappings.
if (!(descriptor instanceof FragmentDescriptor)) if (!(descriptor instanceof FragmentDescriptor))
{ {
addFilterMapping(filter_name, node, context); addFilterMapping(filter_name, node, context, descriptor);
} }
break; break;
} }
case WebFragment: case WebFragment:
{ {
//filter mappings first defined in a web-fragment, allow other fragments to add //filter mappings first defined in a web-fragment, allow other fragments to add
addFilterMapping(filter_name, node, context); addFilterMapping(filter_name, node, context, descriptor);
break; break;
} }
} }

View File

@ -110,7 +110,13 @@ public class TagLibConfiguration extends AbstractConfiguration
//Get the system classpath tlds and tell jasper about them, if jasper is on the classpath //Get the system classpath tlds and tell jasper about them, if jasper is on the classpath
try try
{ {
Class<?> clazz = getClass().getClassLoader().loadClass("org.apache.jasper.compiler.TldLocationsCache");
ClassLoader loader = _context.getClassLoader();
if (loader == null || loader.getParent() == null)
loader = getClass().getClassLoader();
else
loader = loader.getParent();
Class<?> clazz = loader.loadClass("org.apache.jasper.compiler.TldLocationsCache");
assert clazz!=null; assert clazz!=null;
Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES); Collection<Resource> tld_resources = (Collection<Resource>)_context.getAttribute(TLD_RESOURCES);

View File

@ -24,17 +24,28 @@ import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.security.PermissionCollection; import java.security.PermissionCollection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.EventListener; import java.util.EventListener;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.servlet.HttpMethodConstraintElement;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration.Dynamic;
import javax.servlet.ServletSecurityElement;
import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic;
import javax.servlet.annotation.ServletSecurity.TransportGuarantee;
import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingListener; import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionListener; import javax.servlet.http.HttpSessionListener;
import org.eclipse.jetty.security.ConstraintAware;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HandlerContainer; import org.eclipse.jetty.server.HandlerContainer;
@ -55,6 +66,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection; import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.security.Constraint;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Web Application Context Handler. /** Web Application Context Handler.
@ -1252,6 +1264,79 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
super.startContext(); super.startContext();
} }
/* ------------------------------------------------------------ */
@Override
public Set<String> setServletSecurity(Dynamic registration, ServletSecurityElement servletSecurityElement)
{
Set<String> unchangedURLMappings = new HashSet<String>();
//From javadoc for ServletSecurityElement:
/*
If a URL pattern of this ServletRegistration is an exact target of a security-constraint that
was established via the portable deployment descriptor, then this method does not change the
security-constraint for that pattern, and the pattern will be included in the return value.
If a URL pattern of this ServletRegistration is an exact target of a security constraint
that was established via the ServletSecurity annotation or a previous call to this method,
then this method replaces the security constraint for that pattern.
If a URL pattern of this ServletRegistration is neither the exact target of a security constraint
that was established via the ServletSecurity annotation or a previous call to this method,
nor the exact target of a security-constraint in the portable deployment descriptor, then
this method establishes the security constraint for that pattern from the argument ServletSecurityElement.
*/
Collection<String> pathMappings = registration.getMappings();
if (pathMappings != null)
{
Constraint constraint = ConstraintSecurityHandler.createConstraint(registration.getName(), servletSecurityElement);
for (String pathSpec:pathMappings)
{
Origin origin = getMetaData().getOrigin("constraint.url."+pathSpec);
switch (origin)
{
case NotSet:
{
//No mapping for this url already established
List<ConstraintMapping> mappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
for (ConstraintMapping m:mappings)
((ConstraintAware)getSecurityHandler()).addConstraintMapping(m);
getMetaData().setOrigin("constraint.url."+pathSpec, Origin.API);
break;
}
case WebXml:
case WebDefaults:
case WebOverride:
case WebFragment:
{
//a mapping for this url was created in a descriptor, which overrides everything
unchangedURLMappings.add(pathSpec);
break;
}
case Annotation:
case API:
{
//mapping established via an annotation or by previous call to this method,
//replace the security constraint for this pattern
List<ConstraintMapping> constraintMappings = ConstraintSecurityHandler.removeConstraintMappingsForPath(pathSpec, ((ConstraintAware)getSecurityHandler()).getConstraintMappings());
List<ConstraintMapping> freshMappings = ConstraintSecurityHandler.createConstraintsWithMappingsForPath(registration.getName(), pathSpec, servletSecurityElement);
constraintMappings.addAll(freshMappings);
((ConstraintSecurityHandler)getSecurityHandler()).setConstraintMappings(constraintMappings);
break;
}
}
}
}
return unchangedURLMappings;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public class Context extends ServletContextHandler.Context public class Context extends ServletContextHandler.Context
@ -1301,6 +1386,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
return servletContext; return servletContext;
} }
} }
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -31,4 +31,5 @@ public class UnitGenerator extends Generator
{ {
super(WebSocketPolicy.newServerPolicy(),new MappedByteBufferPool()); super(WebSocketPolicy.newServerPolicy(),new MappedByteBufferPool());
} }
} }

View File

@ -15,15 +15,6 @@
<param-value>a context value</param-value> <param-value>a context value</param-value>
</context-param> </context-param>
<!-- Add or override filter init parameter -->
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>com.acme.TestFilter</filter-class>
<init-param>
<param-name>remote</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<!-- Add or override servlet init parameter --> <!-- Add or override servlet init parameter -->
<servlet> <servlet>

View File

@ -0,0 +1,194 @@
//
// ========================================================================
// 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 com.acme;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.util.StringUtil;
/* ------------------------------------------------------------ */
/** Rego Servlet - tests being accessed from servlet 3.0 programmatic
* configuration.
*
*/
public class RegTest extends HttpServlet
{
/* ------------------------------------------------------------ */
@Override
public void init(ServletConfig config) throws ServletException
{
super.init(config);
}
/* ------------------------------------------------------------ */
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
doGet(request, response);
}
/* ------------------------------------------------------------ */
@Override
public void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException
{
request.setCharacterEncoding("UTF-8");
PrintWriter pout=null;
try
{
pout =response.getWriter();
}
catch(IllegalStateException e)
{
pout=new PrintWriter(new OutputStreamWriter(response.getOutputStream(),"UTF-8"));
}
try
{
pout.write("<html>\n<body>\n");
pout.write("<h1>Rego Servlet</h1>\n");
pout.write("<table width=\"95%\">");
pout.write("<tr>\n");
pout.write("<th align=\"right\">getMethod:&nbsp;</th>");
pout.write("<td>" + notag(request.getMethod())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getContentLength:&nbsp;</th>");
pout.write("<td>"+Integer.toString(request.getContentLength())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getContentType:&nbsp;</th>");
pout.write("<td>"+notag(request.getContentType())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRequestURI:&nbsp;</th>");
pout.write("<td>"+notag(request.getRequestURI())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRequestURL:&nbsp;</th>");
pout.write("<td>"+notag(request.getRequestURL().toString())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getContextPath:&nbsp;</th>");
pout.write("<td>"+request.getContextPath()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getServletPath:&nbsp;</th>");
pout.write("<td>"+notag(request.getServletPath())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getPathInfo:&nbsp;</th>");
pout.write("<td>"+notag(request.getPathInfo())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getPathTranslated:&nbsp;</th>");
pout.write("<td>"+notag(request.getPathTranslated())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getQueryString:&nbsp;</th>");
pout.write("<td>"+notag(request.getQueryString())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getProtocol:&nbsp;</th>");
pout.write("<td>"+request.getProtocol()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getScheme:&nbsp;</th>");
pout.write("<td>"+request.getScheme()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getServerName:&nbsp;</th>");
pout.write("<td>"+notag(request.getServerName())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getServerPort:&nbsp;</th>");
pout.write("<td>"+Integer.toString(request.getServerPort())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getLocalName:&nbsp;</th>");
pout.write("<td>"+request.getLocalName()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getLocalAddr:&nbsp;</th>");
pout.write("<td>"+request.getLocalAddr()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getLocalPort:&nbsp;</th>");
pout.write("<td>"+Integer.toString(request.getLocalPort())+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRemoteUser:&nbsp;</th>");
pout.write("<td>"+request.getRemoteUser()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getUserPrincipal:&nbsp;</th>");
pout.write("<td>"+request.getUserPrincipal()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRemoteAddr:&nbsp;</th>");
pout.write("<td>"+request.getRemoteAddr()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRemoteHost:&nbsp;</th>");
pout.write("<td>"+request.getRemoteHost()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRemotePort:&nbsp;</th>");
pout.write("<td>"+request.getRemotePort()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">getRequestedSessionId:&nbsp;</th>");
pout.write("<td>"+request.getRequestedSessionId()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">isSecure():&nbsp;</th>");
pout.write("<td>"+request.isSecure()+"</td>");
pout.write("</tr><tr>\n");
pout.write("<th align=\"right\">isUserInRole(admin):&nbsp;</th>");
pout.write("<td>"+request.isUserInRole("admin")+"</td>");
pout.write("</tr></table>");
}
catch (Exception e)
{
getServletContext().log("dump "+e);
}
pout.write("</body>\n</html>\n");
pout.close();
}
/* ------------------------------------------------------------ */
@Override
public String getServletInfo()
{
return "Rego Servlet";
}
/* ------------------------------------------------------------ */
@Override
public synchronized void destroy()
{
}
private String notag(String s)
{
if (s==null)
return "null";
s=StringUtil.replace(s,"&","&amp;");
s=StringUtil.replace(s,"<","&lt;");
s=StringUtil.replace(s,">","&gt;");
return s;
}
}

View File

@ -18,6 +18,7 @@
package com.acme; package com.acme;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContextAttributeEvent; import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener; import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextEvent;
@ -26,6 +27,12 @@ import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener; import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener; import javax.servlet.ServletRequestListener;
import javax.servlet.ServletRegistration;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletSecurityElement;
import javax.servlet.HttpConstraintElement;
import javax.servlet.HttpMethodConstraintElement;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSessionActivationListener; import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener; import javax.servlet.http.HttpSessionAttributeListener;
@ -33,6 +40,9 @@ import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener; import javax.servlet.http.HttpSessionListener;
import java.util.EnumSet;
import java.util.Set;
public class TestListener implements HttpSessionListener, HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener public class TestListener implements HttpSessionListener, HttpSessionAttributeListener, HttpSessionActivationListener, ServletContextListener, ServletContextAttributeListener, ServletRequestListener, ServletRequestAttributeListener
{ {
public void attributeAdded(HttpSessionBindingEvent se) public void attributeAdded(HttpSessionBindingEvent se)
@ -62,16 +72,30 @@ public class TestListener implements HttpSessionListener, HttpSessionAttributeL
public void contextInitialized(ServletContextEvent sce) public void contextInitialized(ServletContextEvent sce)
{ {
/* TODO for servlet 3.0 //configure programmatic security
* FilterRegistration registration=context.addFilter("TestFilter",TestFilter.class.getName()); ServletRegistration.Dynamic rego = sce.getServletContext().addServlet("RegoTest", RegTest.class.getName());
rego.addMapping("/rego/*");
HttpConstraintElement constraintElement = new HttpConstraintElement(ServletSecurity.EmptyRoleSemantic.PERMIT,
ServletSecurity.TransportGuarantee.NONE, new String[]{"admin"});
ServletSecurityElement securityElement = new ServletSecurityElement(constraintElement, null);
Set<String> unchanged = rego.setServletSecurity(securityElement);
System.err.println("Security constraints registered: "+unchanged.isEmpty());
//Test that a security constraint from web.xml can't be overridden programmatically
ServletRegistration.Dynamic rego2 = sce.getServletContext().addServlet("RegoTest2", RegTest.class.getName());
rego2.addMapping("/rego2/*");
securityElement = new ServletSecurityElement(constraintElement, null);
unchanged = rego2.setServletSecurity(securityElement);
System.err.println("Overridding web.xml constraints not possible:" +!unchanged.isEmpty());
/* For servlet 3.0 */
FilterRegistration.Dynamic registration = sce.getServletContext().addFilter("TestFilter",TestFilter.class.getName());
registration.setInitParameter("remote", "false");
registration.setAsyncSupported(true); registration.setAsyncSupported(true);
registration.addMappingForUrlPatterns( registration.addMappingForUrlPatterns(
EnumSet.of(DispatcherType.ERROR,DispatcherType.ASYNC,DispatcherType.FORWARD,DispatcherType.INCLUDE,DispatcherType.REQUEST), EnumSet.of(DispatcherType.ERROR,DispatcherType.ASYNC,DispatcherType.FORWARD,DispatcherType.INCLUDE,DispatcherType.REQUEST),
true, true,
new String[]{"/dump/*","/dispatch/*","*.dump"}); new String[]{"/*"});
*/
} }
public void contextDestroyed(ServletContextEvent sce) public void contextDestroyed(ServletContextEvent sce)

View File

@ -18,20 +18,6 @@
<listener-class>com.acme.TestListener</listener-class> <listener-class>com.acme.TestListener</listener-class>
</listener> </listener>
<filter>
<filter-name>TestFilter</filter-name>
<filter-class>com.acme.TestFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>remote</param-name>
<param-value>false</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>TestFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter> <filter>
<filter-name>QoSFilter</filter-name> <filter-name>QoSFilter</filter-name>
@ -120,7 +106,6 @@
</filter-mapping> </filter-mapping>
--> -->
<servlet> <servlet>
<servlet-name>Hello</servlet-name> <servlet-name>Hello</servlet-name>
<servlet-class>com.acme.HelloWorld</servlet-class> <servlet-class>com.acme.HelloWorld</servlet-class>
@ -276,6 +261,18 @@
<location>/error404.html</location> <location>/error404.html</location>
</error-page> </error-page>
<security-constraint>
<web-resource-collection>
<web-resource-name>Rego2</web-resource-name>
<url-pattern>/rego2/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>server-administrator</role-name>
</auth-constraint>
</security-constraint>
<security-constraint> <security-constraint>
<web-resource-collection> <web-resource-collection>
<web-resource-name>Auth2</web-resource-name> <web-resource-name>Auth2</web-resource-name>

View File

@ -18,6 +18,8 @@ This page contains several links to test the authentication constraints:
<li><a href="dump/auth/info">dump/auth/*</a> - Authenticated any user</li> <li><a href="dump/auth/info">dump/auth/*</a> - Authenticated any user</li>
<li><a href="dump/auth/admin/info">dump/auth/admin/*</a> - Authenticated admin role (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li> <li><a href="dump/auth/admin/info">dump/auth/admin/*</a> - Authenticated admin role (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
<li><a href="dump/auth/ssl/info">dump/auth/ssl/*</a> - Confidential</li> <li><a href="dump/auth/ssl/info">dump/auth/ssl/*</a> - Confidential</li>
<li><a href="rego/info">rego/info/*</a> - Authenticated admin role from programmatic security (<a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
<li><a href="rego2/info">rego2/info/*</a> - Authenticated servlet-administrator role from programmatic security (login as admin/admin, <a href="session/?Action=Invalidate">click</a> to invalidate session)</li>
</ul> </ul>
<p/> <p/>
<p> <p>

View File

@ -0,0 +1,30 @@
//
// ========================================================================
// 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.session;
public class ImmortalSessionTest extends AbstractImmortalSessionTest
{
@Override
public AbstractTestServer createServer(int port, int max, int scavenge)
{
return new HashTestServer(port,max,scavenge);
}
}

View File

@ -0,0 +1,30 @@
//
// ========================================================================
// 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.session;
public class SessionCookieTest extends AbstractSessionCookieTest
{
@Override
public AbstractTestServer createServer(int port, int max, int scavenge)
{
return new HashTestServer(port, max, scavenge);
}
}

View File

@ -0,0 +1,30 @@
//
// ========================================================================
// 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.session;
public class SessionValueSharedSaving extends AbstractSessionValueSavingTest
{
@Override
public AbstractTestServer createServer(int port, int max, int scavenge)
{
return new HashTestServer(port,max,scavenge);
}
}

View File

@ -0,0 +1,164 @@
//
// ========================================================================
// 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.session;
import java.io.IOException;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import junit.framework.Assert;
import org.eclipse.jetty.client.Address;
import org.eclipse.jetty.client.ContentExchange;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.http.HttpMethods;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* AbstractNewSessionTest
*/
public abstract class AbstractSessionCookieTest
{
public abstract AbstractTestServer createServer(int port, int max, int scavenge);
public void pause(int scavenge)
{
try
{
Thread.sleep(scavenge * 2500L);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
@Test
@Ignore("failing because an http cookie with null value is coming over as \"null\"")
public void testSessionCookie() throws Exception
{
String contextPath = "";
String servletMapping = "/server";
int scavengePeriod = 3;
AbstractTestServer server = createServer(0, 1, scavengePeriod);
ServletContextHandler context = server.addContext(contextPath);
context.addServlet(TestServlet.class, servletMapping);
server.start();
int port=server.getPort();
try
{
HttpClient client = new HttpClient();
client.setConnectorType(HttpClient.CONNECTOR_SOCKET);
client.start();
try
{
ContentExchange exchange = new ContentExchange(true);
exchange.setMethod(HttpMethods.GET);
exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=create");
client.send(exchange);
exchange.waitForDone();
assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
String sessionCookie = exchange.getResponseFields().getStringField("Set-Cookie");
assertTrue(sessionCookie != null);
// Mangle the cookie, replacing Path with $Path, etc.
//sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=", "$1\\$Path=");
// Let's wait for the scavenger to run, waiting 2.5 times the scavenger period
//pause(scavengePeriod);
exchange = new ContentExchange(true);
exchange.setMethod(HttpMethods.GET);
exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=check-cookie");
exchange.getRequestFields().add("Cookie", sessionCookie);
client.send(exchange);
exchange.waitForDone();
assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
exchange = new ContentExchange(true);
exchange.setMethod(HttpMethods.GET);
exchange.setURL("http://localhost:" + port + contextPath + servletMapping + "?action=null-cookie");
//exchange.getRequestFields().add("Cookie", "null");
HttpDestination dest = client.getDestination(new Address("localhost",port),false);
dest.addCookie(new HttpCookie("Cookie",null));
client.send(exchange);
exchange.waitForDone();
assertEquals(HttpServletResponse.SC_OK,exchange.getResponseStatus());
}
finally
{
client.stop();
}
}
finally
{
server.stop();
}
}
public static class TestServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
String action = request.getParameter("action");
if ("create".equals(action))
{
HttpSession session = request.getSession(true);
assertTrue(session.isNew());
}
else if ("check-cookie".equals(action))
{
HttpSession session = request.getSession(false);
assertTrue(session != null);
//request.getSession(true);
}
else if ("null-cookie".equals(action))
{
HttpSession session = request.getSession(false);
assertEquals(1, request.getCookies().length);
Assert.assertFalse("null".equals(request.getCookies()[0].getValue()));
assertTrue(session == null);
}
else
{
assertTrue(false);
}
}
}
}