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 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.WebAppContext;
@ -33,13 +35,32 @@ import org.eclipse.jetty.webapp.WebAppContext;
public abstract class AbstractDiscoverableAnnotationHandler implements DiscoverableAnnotationHandler
{
protected WebAppContext _context;
protected List<DiscoveredAnnotation> _annotations = new ArrayList<DiscoveredAnnotation>();
protected List<DiscoveredAnnotation> _annotations;
protected Resource _resource;
public AbstractDiscoverableAnnotationHandler(WebAppContext context)
{
_context = context;
this(context, null);
}
public AbstractDiscoverableAnnotationHandler(WebAppContext context, List<DiscoveredAnnotation> list)
{
_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 ()
{
@ -51,7 +72,6 @@ public abstract class AbstractDiscoverableAnnotationHandler implements Discovera
_annotations.clear();
}
public void addAnnotation (DiscoveredAnnotation 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.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
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";
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
{
}
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#configure(org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
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 (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
{
parser = createAnnotationParser();
parser.registerAnnotationHandler("javax.servlet.annotation.WebServlet", new WebServletAnnotationHandler(context));
parser.registerAnnotationHandler("javax.servlet.annotation.WebFilter", new WebFilterAnnotationHandler(context));
parser.registerAnnotationHandler("javax.servlet.annotation.WebListener", new WebListenerAnnotationHandler(context));
_discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
_discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
_discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
}
}
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
//classes so we can call their onStartup() methods correctly
List<ServletContainerInitializer> nonExcludedInitializers = getNonExcludedInitializers(context);
parser = registerServletContainerInitializerAnnotationHandlers(context, parser, nonExcludedInitializers);
createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
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());
parseContainerPath(context, parser);
//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.
parseWebInfClasses(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
* the AnnotationParser. Note that this is considered internal API.
@ -109,6 +147,9 @@ public class AnnotationConfiguration extends AbstractConfiguration
return new AnnotationParser();
}
/**
* @see org.eclipse.jetty.webapp.AbstractConfiguration#cloneConfigure(org.eclipse.jetty.webapp.WebAppContext, org.eclipse.jetty.webapp.WebAppContext)
*/
@Override
public void cloneConfigure(WebAppContext template, WebAppContext context) throws Exception
{
@ -117,31 +158,22 @@ 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
{
//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())
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
ArrayList<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
context.setAttribute(CONTAINER_INITIALIZERS, initializers);
for (ServletContainerInitializer service : scis)
@ -158,17 +190,14 @@ public class AnnotationConfiguration extends AbstractConfiguration
{
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
//process the whole class hierarchy
//process the whole class hierarchy to satisfy the ServletContainerInitializer
if (context.getAttribute(CLASS_INHERITANCE_MAP) == null)
{
ClassInheritanceHandler classHandler = new ClassInheritanceHandler();
context.setAttribute(CLASS_INHERITANCE_MAP, classHandler.getMap());
parser.registerClassHandler(classHandler);
MultiMap map = new MultiMap();
context.setAttribute(CLASS_INHERITANCE_MAP, map);
_classInheritanceHandler = new ClassInheritanceHandler(map);
}
for (Class c: classes)
@ -179,7 +208,7 @@ public class AnnotationConfiguration extends AbstractConfiguration
{
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());
}
//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
* from a jar that is excluded by the fragment ordering. See ServletSpec 3.0 p.85.
@ -241,6 +271,11 @@ public class AnnotationConfiguration extends AbstractConfiguration
/**
* @param context
* @return
* @throws Exception
*/
public List<ServletContainerInitializer> getNonExcludedInitializers (WebAppContext context)
throws Exception
{
@ -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)
throws Exception
{
//if no pattern for the container path is defined, then by default scan NOTHING
LOG.debug("Scanning container jars");
//clear any previously discovered annotations
clearAnnotationList(parser.getAnnotationHandlers());
//always parse for discoverable annotations as well as class hierarchy and servletcontainerinitializer related annotations
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
ArrayList<URI> containerUris = new ArrayList<URI>();
@ -299,14 +349,17 @@ public class AnnotationConfiguration extends AbstractConfiguration
}
});
//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)
throws Exception
{
@ -325,17 +378,35 @@ public class AnnotationConfiguration extends AbstractConfiguration
for (Resource r : jars)
{
//clear any previously discovered annotations from handlers
clearAnnotationList(parser.getAnnotationHandlers());
//for each jar, we decide which set of annotations we need to parse for
parser.clearHandlers();
URI uri = r.getURI();
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
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,
new ClassNameResolver()
{
@ -354,13 +425,17 @@ public class AnnotationConfiguration extends AbstractConfiguration
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)
throws Exception
{
@ -370,7 +445,16 @@ public class AnnotationConfiguration extends AbstractConfiguration
Resource classesDir = context.getWebInf().addPath("classes/");
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,
new ClassNameResolver()
{
@ -389,17 +473,20 @@ public class AnnotationConfiguration extends AbstractConfiguration
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)
throws Exception
{
@ -422,24 +509,5 @@ public class AnnotationConfiguration extends AbstractConfiguration
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

@ -53,10 +53,7 @@ public class AnnotationParser
private static final Logger LOG = Log.getLogger(AnnotationParser.class);
protected List<String> _parsedClassNames = new ArrayList<String>();
protected Map<String, List<DiscoverableAnnotationHandler>> _annotationHandlers = new HashMap<String, List<DiscoverableAnnotationHandler>>();
protected List<ClassHandler> _classHandlers = new ArrayList<ClassHandler>();
protected List<MethodHandler> _methodHandlers = new ArrayList<MethodHandler>();
protected List<FieldHandler> _fieldHandlers = new ArrayList<FieldHandler>();
protected List<Handler> _handlers = new ArrayList<Handler>();
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,
String signature, String superName, String[] interfaces,
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,
String desc, String signature,String[] exceptions,
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,
String fieldType, String signature, Object value,
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 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 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);
}
/**
* MyAnnotationVisitor
*
* ASM Visitor for Annotations
*/
public class MyAnnotationVisitor implements AnnotationVisitor
{
List<Value> _annotationValues;
@ -307,9 +389,12 @@ public class AnnotationParser
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();
//call all AnnotationHandlers with classname, annotation name + values
List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName);
if (handlers != null)
for (Handler h : AnnotationParser.this._handlers)
{
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();
//call all AnnotationHandlers with classname, method, annotation name + values
List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName);
if (handlers != null)
for (Handler h : AnnotationParser.this._handlers)
{
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()
{
super.visitEnd();
List<DiscoverableAnnotationHandler> handlers = AnnotationParser.this._annotationHandlers.get(_annotationName);
if (handlers != null)
for (Handler h : AnnotationParser.this._handlers)
{
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
* encountered on a class.
*
* @deprecated see registerHandler(Handler)
* @param annotationName
* @param handler
*/
public void registerAnnotationHandler (String annotationName, DiscoverableAnnotationHandler handler)
{
List<DiscoverableAnnotationHandler> handlers = _annotationHandlers.get(annotationName);
if (handlers == null)
{
handlers = new ArrayList<DiscoverableAnnotationHandler>();
_annotationHandlers.put(annotationName, handlers);
}
handlers.add(handler);
_handlers.add(handler);
}
/**
* @deprecated
* @param annotationName
* @return
*/
public List<DiscoverableAnnotationHandler> getAnnotationHandlers(String annotationName)
{
List<DiscoverableAnnotationHandler> handlers = _annotationHandlers.get(annotationName);
if (handlers == null)
return Collections.emptyList();
return new ArrayList<DiscoverableAnnotationHandler>();
List<DiscoverableAnnotationHandler> handlers = new ArrayList<DiscoverableAnnotationHandler>();
for (Handler h:_handlers)
{
if (h instanceof DiscoverableAnnotationHandler)
{
DiscoverableAnnotationHandler dah = (DiscoverableAnnotationHandler)h;
if (annotationName.equals(dah.getAnnotationName()))
handlers.add(dah);
}
}
return handlers;
}
/**
* @deprecated
* @return
*/
public List<DiscoverableAnnotationHandler> getAnnotationHandlers()
{
List<DiscoverableAnnotationHandler> allHandlers = new ArrayList<DiscoverableAnnotationHandler>();
for (List<DiscoverableAnnotationHandler> list:_annotationHandlers.values())
allHandlers.addAll(list);
return allHandlers;
List<DiscoverableAnnotationHandler> allAnnotationHandlers = new ArrayList<DiscoverableAnnotationHandler>();
for (Handler h:_handlers)
{
if (h instanceof DiscoverableAnnotationHandler)
allAnnotationHandlers.add((DiscoverableAnnotationHandler)h);
}
return allAnnotationHandlers;
}
/**
* @deprecated see registerHandler(Handler)
* @param 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)
{
return _parsedClassNames.contains(className);
}
/**
* Parse a given class
*
* @param className
* @param resolver
* @throws Exception
*/
public void parse (String className, ClassNameResolver resolver)
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)
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)
throws Exception
{
@ -502,6 +693,14 @@ public class AnnotationParser
parse(Arrays.asList(classNames), resolver);
}
/**
* Parse the given classes
*
* @param classNames
* @param resolver
* @throws Exception
*/
public void parse (List<String> classNames, ClassNameResolver resolver)
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)
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.
*
* @param loader
* @param visitParents
* @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 resolver
* @throws Exception
@ -647,6 +856,12 @@ public class AnnotationParser
scanner.scan(null, uris, true);
}
/**
* Parse a particular resource
* @param uri
* @param resolver
* @throws Exception
*/
public void parse (URI uri, final ClassNameResolver resolver)
throws Exception
{
@ -656,6 +871,14 @@ public class AnnotationParser
parse(uris, resolver);
}
/**
* Use ASM on a class
*
* @param is
* @throws IOException
*/
protected void scanClass (InputStream is)
throws IOException
{

View File

@ -35,10 +35,16 @@ public class ClassInheritanceHandler implements ClassHandler
private static final Logger LOG = Log.getLogger(ClassInheritanceHandler.class);
MultiMap _inheritanceMap = new MultiMap();
MultiMap _inheritanceMap;
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)

View File

@ -65,4 +65,15 @@ public class ContainerInitializerAnnotationHandler implements DiscoverableAnnota
_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.util.MultiMap;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.WebAppContext;
/**
@ -35,7 +37,8 @@ import org.eclipse.jetty.webapp.WebAppContext;
*/
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)
@ -103,14 +106,10 @@ public class ServletContainerInitializerListener implements ServletContextListen
}
catch (Exception e)
{
//OK, how do I throw an exception such that it really stops the startup sequence?
e.printStackTrace();
LOG.warn(e);
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)
{
// TODO Auto-generated method stub
}

View File

@ -20,6 +20,7 @@ package org.eclipse.jetty.annotations;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletSecurityElement;
import javax.servlet.annotation.HttpConstraint;
import javax.servlet.annotation.HttpMethodConstraint;
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.security.ConstraintAware;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.webapp.Origin;
import org.eclipse.jetty.webapp.WebAppContext;
/**
@ -80,7 +83,7 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
if (servletSecurity == null)
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.
List<ServletMapping> servletMappings = getServletMappings(clazz.getCanonicalName());
@ -95,19 +98,15 @@ public class ServletSecurityAnnotationHandler extends AbstractIntrospectableAnno
//Make a fresh list
constraintMappings = new ArrayList<ConstraintMapping>();
//Get the values that form the constraints that will apply unless there are HttpMethodConstraints to augment them
HttpConstraint defaults = servletSecurity.value();
//Make a Constraint for the <auth-constraint> and <user-data-constraint> specified by the HttpConstraint
Constraint defaultConstraint = makeConstraint (clazz,
defaults.rolesAllowed(),
defaults.value(),
defaults.transportGuarantee());
constraintMappings.addAll(makeMethodMappings(clazz,
defaultConstraint,
servletMappings,
servletSecurity.httpMethodConstraints()));
ServletSecurityElement securityElement = new ServletSecurityElement(servletSecurity);
for (ServletMapping sm : servletMappings)
{
for (String url : sm.getPathSpecs())
{
_context.getMetaData().setOrigin("constraint.url."+url, Origin.Annotation);
constraintMappings.addAll(ConstraintSecurityHandler.createConstraintsWithMappingsForPath(clazz.getName(), url, securityElement));
}
}
//set up the security constraints produced by the annotation
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)
{
Constraint constraint = new Constraint();
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;
}
return ConstraintSecurityHandler.createConstraint(servlet.getName(), rolesAllowed, permitOrDeny, transport);
/**
* 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++)
{
//TODO decide if we need to check the origin
if (pathSpecs[j].equals(constraintMappings.get(i).getPathSpec()))
{
exists = true;

View File

@ -30,6 +30,7 @@ import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.Holder;
import org.eclipse.jetty.util.log.Log;
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.MetaData;
import org.eclipse.jetty.webapp.Origin;
@ -53,6 +54,11 @@ public class WebFilterAnnotation extends DiscoveredAnnotation
super(context, className);
}
public WebFilterAnnotation(WebAppContext context, String className, Resource resource)
{
super(context, className, resource);
}
/**
* @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.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
/**
@ -39,23 +40,37 @@ public class WebFilterAnnotationHandler extends AbstractDiscoverableAnnotationHa
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,
List<Value> values)
{
WebFilterAnnotation wfAnnotation = new WebFilterAnnotation(_context, className);
WebFilterAnnotation wfAnnotation = new WebFilterAnnotation(_context, className, _resource);
addAnnotation(wfAnnotation);
}
@Override
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
List<Value> values)
{
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,
List<Value> values)
{
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.Logger;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.MetaData;
import org.eclipse.jetty.webapp.Origin;
@ -50,6 +51,11 @@ public class WebListenerAnnotation extends DiscoveredAnnotation
super(context, className);
}
public WebListenerAnnotation(WebAppContext context, String className, Resource resource)
{
super(context, className, resource);
}
/**
* @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.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotationHandler
@ -34,13 +35,18 @@ public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotation
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)
*/
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
List<Value> values)
{
WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, className);
WebListenerAnnotation wlAnnotation = new WebListenerAnnotation(_context, className, _resource);
addAnnotation(wlAnnotation);
}
@ -56,4 +62,10 @@ public class WebListenerAnnotationHandler extends AbstractDiscoverableAnnotation
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.log.Log;
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.MetaData;
import org.eclipse.jetty.webapp.Origin;
@ -48,6 +49,12 @@ public class WebServletAnnotation extends DiscoveredAnnotation
super(context, className);
}
public WebServletAnnotation (WebAppContext context, String className, Resource resource)
{
super(context, className, resource);
}
/**
* @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.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.DiscoveredAnnotation;
import org.eclipse.jetty.webapp.WebAppContext;
/**
@ -40,6 +41,11 @@ public class WebServletAnnotationHandler extends AbstractDiscoverableAnnotationH
super(context);
}
public WebServletAnnotationHandler (WebAppContext context, List<DiscoveredAnnotation> list)
{
super(context, list);
}
/**
* 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)
*/
@Override
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotationName,
List<Value> values)
{
if (!"javax.servlet.annotation.WebServlet".equals(annotationName))
return;
WebServletAnnotation annotation = new WebServletAnnotation (_context, className);
WebServletAnnotation annotation = new WebServletAnnotation (_context, className, _resource);
addAnnotation(annotation);
}
@Override
public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
List<Value> values)
{
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,
List<Value> values)
{
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);
}
@Override
public String getAnnotationName()
{
return "org.eclipse.jetty.annotations.Sample";
}
}
@After
@ -85,7 +91,7 @@ public class TestAnnotationInheritance
SampleHandler handler = new SampleHandler();
AnnotationParser parser = new AnnotationParser();
parser.registerAnnotationHandler("org.eclipse.jetty.annotations.Sample", handler);
parser.registerHandler(handler);
parser.parse(classNames, new ClassNameResolver ()
{
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");
public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
List<Value> values)
{
@ -81,9 +84,15 @@ public class TestAnnotationParser
assertTrue(methods.contains(methodName));
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();
parser.parse(classNames, new ClassNameResolver ()
@ -140,9 +149,17 @@ public class TestAnnotationParser
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);
}
}

View File

@ -172,6 +172,9 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
{
}
/**
* @throws IOException
*/
protected void commitRequest() throws IOException
{
synchronized (this)
@ -223,6 +226,7 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
requestHeaders.putLongField(HttpHeader.CONTENT_LENGTH, requestContent.length());
_generator.completeHeader(requestHeaders,false);
_generator.addContent(new View(requestContent),true);
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
}
else
{
@ -230,26 +234,16 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
if (requestContentStream != null)
{
_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
{
requestHeaders.remove(HttpHeader.CONTENT_LENGTH);
_generator.completeHeader(requestHeaders, true);
}
}
_exchange.setStatus(HttpExchange.STATUS_WAITING_FOR_RESPONSE);
}
}
}
}
protected void reset() throws IOException
{

View File

@ -114,6 +114,8 @@ public class AsyncHttpConnection extends AbstractHttpConnection implements Async
ByteBuffer chunk=_requestContentChunk;
_requestContentChunk=exchange.getRequestContentChunk(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);
if (switched!=null)
connection=switched;
{
// switched protocol!
_pipeline = null;
if (_pipeline!=null)
{
_destination.send(_pipeline);
}
_pipeline = null;
connection=switched;

View File

@ -122,6 +122,8 @@ public class BlockingHttpConnection extends AbstractHttpConnection
ByteBuffer chunk=_requestContentChunk;
_requestContentChunk=exchange.getRequestContentChunk(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);
buf.append('=');
String start=buf.toString();
boolean hasDomain = false;
boolean hasPath = false;
if (value != null && value.length() > 0)
QuotedStringTokenizer.quoteIfNeeded(buf, value, delim);
if (path != null && path.length() > 0)
{
hasPath = true;
buf.append(";Path=");
if (path.trim().startsWith("\""))
buf.append(path);
@ -806,6 +810,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
}
if (domain != null && domain.length() > 0)
{
hasDomain = true;
buf.append(";Domain=");
QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(),delim);
}
@ -841,7 +846,12 @@ public class HttpFields implements Iterable<HttpFields.Field>
Field last=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))
{
//existing cookie has same name, does it also match domain and path?
if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) &&
((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path))))
{
_fields.remove(field);
if (last==null)
@ -850,6 +860,7 @@ public class HttpFields implements Iterable<HttpFields.Field>
last._next=field._next;
break;
}
}
last=field;
field=field._next;
}

View File

@ -100,7 +100,15 @@ public class HttpURI
public HttpURI(String 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);
_charset = URIUtil.__CHARSET;
}
@ -693,11 +701,13 @@ public class HttpURI
{
if (_query==_fragment)
return;
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)
throws UnsupportedEncodingException
public void decodeQueryTo(MultiMap parameters, String encoding) throws UnsupportedEncodingException
{
if (_query==_fragment)
return;
@ -705,7 +715,7 @@ public class HttpURI
if (encoding==null || StringUtil.isUTF8(encoding))
UrlEncoded.decodeUtf8To(_raw,_query+1,_fragment-_query-1,parameters);
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()

View File

@ -277,7 +277,8 @@ public class HttpFieldsTest
assertEquals("minimal=value",fields.getStringField("Set-Cookie"));
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);
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");
@ -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());
assertFalse(e.hasMoreElements());
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.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.UserAuthentication;
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.UserIdentity;
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) $
*/
public class JaspiAuthenticator implements Authenticator
public class JaspiAuthenticator extends LoginAuthenticator
{
private final ServerAuthConfig _authConfig;
@ -58,7 +59,7 @@ public class JaspiAuthenticator implements Authenticator
private final IdentityService _identityService;
private final DeferredAuthentication _deferred;
public JaspiAuthenticator(ServerAuthConfig authConfig, Map authProperties, ServletCallbackHandler callbackHandler, Subject serviceSubject,
boolean allowLazyAuthentication, IdentityService identityService)
@ -72,11 +73,11 @@ public class JaspiAuthenticator implements Authenticator
this._serviceSubject = serviceSubject;
this._allowLazyAuthentication = allowLazyAuthentication;
this._identityService = identityService;
this._deferred = new DeferredAuthentication(this);
}
public void setConfiguration(AuthConfiguration configuration)
{
super.setConfiguration(configuration);
}
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 (_allowLazyAuthentication && !info.isAuthMandatory() && a == Authentication.UNAUTHENTICATED)
a =_deferred;
a = new DeferredAuthentication(this);
return a;
}

View File

@ -97,25 +97,15 @@ public class ContextFactory implements ObjectFactory
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;
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();
}
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");
if (__log.isDebugEnabled() && loader != null) __log.debug("Using classloader of current org.eclipse.jetty.server.handler.ContextHandler");
}
//Get the context matching the classloader
@ -124,13 +114,7 @@ public class ContextFactory implements ObjectFactory
//The map does not contain an entry for this classloader
if (ctx == null)
{
//Check if a parent classloader has created the context
ctx = getParentClassLoaderContext(loader);
//Didn't find a context to match any of the ancestors
//of the classloader, so make a context
if (ctx == null)
{
//Didn't find a context to match, make one
Reference ref = (Reference)obj;
StringRefAddr parserAddr = (StringRefAddr)ref.get("parser");
String parserClassName = (parserAddr==null?null:(String)parserAddr.getContent());
@ -140,31 +124,14 @@ public class ContextFactory implements ObjectFactory
name.get(0),
(NamingContext)nameCtx,
parser);
if(__log.isDebugEnabled())__log.debug("No entry for classloader: "+loader);
if(__log.isDebugEnabled())__log.debug("Made context "+name.get(0)+" for classloader: "+loader);
__contextMap.put (loader, 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.spi.ObjectFactory;
import org.eclipse.jetty.jndi.ContextFactory;
import org.eclipse.jetty.jndi.NamingContext;
import org.eclipse.jetty.jndi.local.localContextRoot;
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.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
/**
*
*/
@ -70,8 +72,6 @@ public class TestJNDI
@Test
public void testIt() throws Exception
{
try
{
//set up some classloaders
Thread currentThread = Thread.currentThread();
@ -79,6 +79,10 @@ public class TestJNDI
ClassLoader childLoader1 = new URLClassLoader(new URL[0], currentLoader);
ClassLoader childLoader2 = new URLClassLoader(new URL[0], currentLoader);
try
{
//Uncomment to aid with debug
/*
javaRootURLContext.getRoot().addListener(new NamingContext.Listener()
{
@ -117,6 +121,18 @@ public class TestJNDI
initCtxA.bind ("blah", "123");
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();
Context sub0 = (Context)initCtx.lookup("java:");
@ -216,6 +232,7 @@ public class TestJNDI
try
{
initCtx.lookup("java:comp/env/rubbish");
fail("env should not exist for this classloader");
}
catch (NameNotFoundException e)
{
@ -285,17 +302,77 @@ public class TestJNDI
//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
initCtx.close();
}
finally
{
//make some effort to clean up
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");
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.utils.BundleFileLocatorHelper;
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.FrameworkUtil;
import org.xml.sax.EntityResolver;
@ -52,6 +54,7 @@ import org.xml.sax.SAXException;
*/
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
@ -112,8 +115,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto
}
catch (Exception e)
{
System.err.println("Unable to set the JspFactory: jsp support incomplete.");
e.printStackTrace();
LOG.warn("Unable to set the JspFactory: jsp support incomplete.",e);
}
}
@ -138,17 +140,23 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto
public URL[] getJarsWithTlds(OSGiAppProvider provider, BundleFileLocatorHelper locatorHelper) throws Exception
{
ArrayList<URL> urls = new ArrayList<URL>();
HashSet<Class<?>> classesToAddToTheTldBundles = new HashSet<Class<?>>();
// Look for the jstl bundle
// We assume the jstl's tlds are defined there.
// We assume that the jstl bundle is imported by this bundle
// So we can look for this class using this bundle's classloader:
try
{
Class<?> jstlClass = WebappRegistrationCustomizerImpl.class.getClassLoader().loadClass(DEFAULT_JSTL_BUNDLE_CLASS);
classesToAddToTheTldBundles.add(jstlClass);
ArrayList<URL> urls = new ArrayList<URL>();
}
catch (ClassNotFoundException e)
{
LOG.info("jstl not on classpath", e);
}
for (Class<?> cl : classesToAddToTheTldBundles)
{
Bundle tldBundle = FrameworkUtil.getBundle(cl);
@ -208,7 +216,7 @@ public class WebappRegistrationCustomizerImpl implements WebappRegistrationCusto
}
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.List;
import org.eclipse.jetty.annotations.AbstractDiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.AnnotationParser.DiscoverableAnnotationHandler;
import org.eclipse.jetty.annotations.ClassNameResolver;
import org.eclipse.jetty.osgi.boot.OSGiWebappConstants;
import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker;
@ -151,21 +153,26 @@ public class AnnotationConfiguration extends org.eclipse.jetty.annotations.Annot
protected void parseBundle(WebAppContext context, AnnotationParser parser,
Bundle webbundle, Bundle bundle) throws Exception
{
Resource bundleRes = parser.getResource(bundle);
parser.parse(bundle,createClassNameResolver(context));
List<DiscoveredAnnotation> annotations = new ArrayList<DiscoveredAnnotation>();
gatherAnnotations(annotations, parser.getAnnotationHandlers());
parser.clearHandlers();
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
{
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);
}
((AbstractDiscoverableAnnotationHandler)h).setResource(null);
else
{
context.getMetaData().addDiscoveredAnnotations(bundleRes, annotations);
((AbstractDiscoverableAnnotationHandler)h).setResource(bundleRes);
}
}
parser.registerHandlers(_discoverableAnnotationHandlers);
parser.registerHandler(_classInheritanceHandler);
parser.registerHandlers(_containerInitializerAnnotationHandlers);
parser.parse(bundle,createClassNameResolver(context));
}
/**
* Returns the same classname resolver than for the webInfjar scanner

View File

@ -48,4 +48,18 @@ public class TldLocatableURLClassloader extends URLClassLoader
{
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;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
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.PathMap;
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.UserIdentity;
import org.eclipse.jetty.util.StringMap;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Constraint;
/* ------------------------------------------------------------ */
/**
* 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.
*
*/
public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware
{
private static final String OMISSION_SUFFIX = ".omission";
private static final String ALL_METHODS = "*";
private final List<ConstraintMapping> _constraintMappings= new CopyOnWriteArrayList<>();
private final Set<String> _roles = new CopyOnWriteArraySet<>();
private final PathMap<Map<String, RoleInfo>> _constraintMap = new PathMap<>();
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.
* @return true if the security handler is running in strict mode.
@ -140,8 +355,6 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
@Override
public void setConstraintMappings(List<ConstraintMapping> constraintMappings, Set<String> roles)
{
if (isStarted())
throw new IllegalStateException("Started");
_constraintMappings.clear();
_constraintMappings.addAll(constraintMappings);
@ -160,6 +373,14 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
}
}
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)
{
if (isStarted())
throw new IllegalStateException("Started");
_roles.clear();
_roles.addAll(roles);
}
@ -239,6 +457,8 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
super.doStart();
}
/* ------------------------------------------------------------ */
@Override
protected void doStop() throws Exception
{
@ -246,6 +466,14 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
_constraintMap.clear();
}
/* ------------------------------------------------------------ */
/**
* Create and combine the constraint with the existing processed
* constraints.
*
* @param mapping
*/
protected void processConstraintMapping(ConstraintMapping mapping)
{
Map<String, RoleInfo> mappings = _constraintMap.get(mapping.getPathSpec());
@ -258,6 +486,12 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden())
return;
if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0)
{
processConstraintMappingWithMethodOmissions(mapping, mappings);
return;
}
String httpMethod = mapping.getMethod();
if (httpMethod==null)
httpMethod=ALL_METHODS;
@ -274,10 +508,10 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
if (roleInfo.isForbidden())
return;
Constraint constraint = mapping.getConstraint();
boolean forbidden = constraint.isForbidden();
roleInfo.setForbidden(forbidden);
if (forbidden)
//add in info from the constraint
configureRoleInfo(roleInfo, mapping);
if (roleInfo.isForbidden())
{
if (httpMethod.equals(ALL_METHODS))
{
@ -287,41 +521,12 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
}
else
{
UserDataConstraint userDataConstraint = UserDataConstraint.get(constraint.getDataConstraint());
roleInfo.setUserDataConstraint(userDataConstraint);
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))
//combine with any entry that covers all methods
if (httpMethod == null)
{
for (Map.Entry<String, RoleInfo> entry : mappings.entrySet())
{
if (!entry.getKey().equals(ALL_METHODS))
if (entry.getKey() != null)
{
RoleInfo specific = entry.getValue();
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
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)
{
String httpMethod = request.getMethod();
RoleInfo roleInfo = mappings.get(httpMethod);
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;
}
@ -397,6 +730,11 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
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
protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity)
throws IOException
@ -435,4 +773,5 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr
Collections.singleton(_roles),
_constraintMap.entrySet());
}
}

View File

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

View File

@ -79,6 +79,8 @@ public class ConstraintTest
_loginService.putUser("user",new Password("password"));
_loginService.putUser("user2",new Password("password"), new String[] {"user"});
_loginService.putUser("admin",new Password("password"), new String[] {"user","administrator"});
_loginService.putUser("user3", new Password("password"), new String[] {"foo"});
_context.setContextPath("/ctx");
_server.setHandler(_context);
@ -190,17 +192,59 @@ public class ConstraintTest
@Test
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.setStrict(false);
_server.start();
String response;
/*
response = _connector.getResponses("GET /ctx/noauth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
*/
response = _connector.getResponses("GET /ctx/forbid/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 403 Forbidden"));
/*
response = _connector.getResponses("GET /ctx/auth/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
assertThat(response,containsString("WWW-Authenticate: basic realm=\"TestRealm\""));
@ -215,8 +259,8 @@ public class ConstraintTest
"Authorization: Basic " + B64Code.encode("user:password") + "\r\n" +
"\r\n");
assertThat(response,startsWith("HTTP/1.1 200 OK"));
*/
/*
// test admin
response = _connector.getResponses("GET /ctx/admin/info HTTP/1.0\r\n\r\n");
assertThat(response,startsWith("HTTP/1.1 401 Unauthorized"));
@ -242,8 +286,34 @@ public class ConstraintTest
response = _connector.getResponses("GET /ctx/admin/relax/info HTTP/1.0\r\n\r\n");
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
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
{
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.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
public int read() throws IOException
{
byte[] oneByte = new byte[1];
int len = read(oneByte, 0, 1);
return len < 0 ? len : 0xFF & oneByte[0];
byte[] bytes = new byte[1];
int read = read(bytes, 0, 1);
return read < 0 ? -1 : 0xff & bytes[0];
}
@Override
@ -127,7 +127,6 @@ public abstract class HttpInput<T> extends ServletInputStream
}
return get(item, b, off, len);
}
protected abstract int remaining(T item);
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);
}
@Override
public void write(int b) throws IOException
{

View File

@ -49,6 +49,8 @@ import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
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.util.Attributes;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.MultiPartInputStream;
import org.eclipse.jetty.util.StringUtil;
@ -114,6 +117,8 @@ import org.eclipse.jetty.util.log.Logger;
public class Request implements HttpServletRequest
{
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 Collection<Locale> __defaultLocale = Collections.singleton(Locale.getDefault());
@ -124,6 +129,42 @@ public class Request implements HttpServletRequest
private final List<ServletRequestAttributeListener> _requestAttributeListeners=new ArrayList<>();
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 _asyncSupported = true;
private boolean _newContext;
@ -1763,6 +1804,7 @@ public class Request implements HttpServletRequest
public void setQueryString(String queryString)
{
_queryString = queryString;
_queryEncoding = null; //assume utf-8
}
/* ------------------------------------------------------------ */
@ -1950,9 +1992,16 @@ public class Request implements HttpServletRequest
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(),
getContentType(),(MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT),
getContentType(),config,
(_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
for (Part p:parts)
{
@ -1982,9 +2031,17 @@ public class Request implements HttpServletRequest
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(),
getContentType(),(MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT),
getContentType(), config,
(_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
for (Part p:parts)
{
@ -2042,7 +2099,7 @@ public class Request implements HttpServletRequest
{
// extract parameters from dispatch query
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;
@ -2063,10 +2120,11 @@ public class Request implements HttpServletRequest
{
StringBuilder overridden_query_string = new StringBuilder();
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<>();
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())
{

View File

@ -22,7 +22,9 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.nio.channels.IllegalSelectorException;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.RequestDispatcher;
@ -58,6 +60,15 @@ public class Response implements HttpServletResponse
{
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
{
NONE, STREAM, WRITER
@ -76,7 +87,7 @@ public class Response implements HttpServletResponse
*/
public final static String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
private final HttpChannel _channel;
private final HttpChannel<?> _channel;
private final HttpOutput _out;
private final HttpFields _fields = new HttpFields();
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()
{
resetBuffer();

View File

@ -443,7 +443,7 @@ public class Server extends HandlerWrapper implements Attributes
baseRequest.setRequestURI(null);
baseRequest.setPathInfo(baseRequest.getRequestURI());
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();

View File

@ -50,7 +50,7 @@ import org.eclipse.jetty.util.log.Logger;
<pre>
public static void attemptShutdown(int port, String shutdownCookie) {
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();
connection.setRequestMethod("POST");
connection.getResponseCode();

View File

@ -21,7 +21,6 @@ package org.eclipse.jetty.server.session;
import java.io.IOException;
import java.util.EnumSet;
import java.util.EventListener;
import javax.servlet.DispatcherType;
import javax.servlet.ServletException;
import javax.servlet.SessionTrackingMode;
@ -39,7 +38,8 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */
/** SessionHandler.
/**
* SessionHandler.
*/
public class SessionHandler extends ScopedHandler
{
@ -52,9 +52,8 @@ public class SessionHandler extends ScopedHandler
private SessionManager _sessionManager;
/* ------------------------------------------------------------ */
/** Constructor.
* Construct a SessionHandler witha a HashSessionManager with a standard
* java.util.Random generator is created.
/**
* Constructor. Construct a SessionHandler witha a HashSessionManager with a standard java.util.Random generator is created.
*/
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)
{
@ -81,7 +81,8 @@ public class SessionHandler extends ScopedHandler
/* ------------------------------------------------------------ */
/**
* @param sessionManager The sessionManager to set.
* @param sessionManager
* The sessionManager to set.
*/
public void setSessionManager(SessionManager sessionManager)
{
@ -104,6 +105,7 @@ public class SessionHandler extends ScopedHandler
setSessionManager(new HashSessionManager());
super.doStart();
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.thread.AbstractLifeCycle#doStop()
@ -121,8 +123,7 @@ public class SessionHandler extends ScopedHandler
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/
@Override
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
public void doScope(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
SessionManager old_session_manager = null;
HttpSession old_session = null;
@ -201,8 +202,7 @@ public class SessionHandler extends ScopedHandler
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/
@Override
public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
public void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// start manual inline of nextHandle(target,baseRequest,request,response);
if (never())
@ -215,7 +215,9 @@ public class SessionHandler extends ScopedHandler
}
/* ------------------------------------------------------------ */
/** 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 request
*/
@ -251,14 +253,24 @@ public class SessionHandler extends ScopedHandler
{
requested_session_id = cookies[i].getValue();
requested_session_id_from_cookie = true;
if(LOG.isDebugEnabled())
LOG.debug("Got Session ID {} from cookie",requested_session_id);
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");
}
}
}
}
}

View File

@ -36,13 +36,15 @@ import java.net.URL;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.Exchanger;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
@ -53,6 +55,8 @@ import org.junit.Assert;
import org.junit.Test;
import org.junit.matchers.JUnitMatchers;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertTrue;
/**
*
*/
@ -174,6 +178,80 @@ 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
*/
@ -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.
protected static class BigBlockHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
byte[] buf = new byte[128 * 1024];
@ -1262,6 +1341,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
new Thread()
{
@Override
public void run()
{
try
@ -1295,6 +1375,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
public class NoopHandler extends AbstractHandler
{
@Override
public void handle(String target, Request baseRequest,
HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException

View File

@ -18,14 +18,18 @@
package org.eclipse.jetty.server;
import static org.junit.Assert.assertNotNull;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.Assert;
import org.junit.Test;
@ -206,6 +210,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
public void testUnicodeErrors() throws UnsupportedEncodingException
{

View File

@ -38,7 +38,9 @@ import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.ServletRequestEvent;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
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.ContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -133,7 +136,7 @@ public class RequestTest
}
@Test
public void testMultiPart() throws Exception
public void testMultiPartNoConfig() throws Exception
{
_handler._checker = new RequestTester()
{
@ -143,14 +146,16 @@ public class RequestTest
try
{
Part foo = request.getPart("stuff");
assertNotNull(foo);
String value = request.getParameter("stuff");
byte[] expected = "000000000000000000000000000000000000000000000000000".getBytes("ISO-8859-1");
return value.equals(new String(expected, "ISO-8859-1"));
return false;
}
catch (IllegalStateException e)
{
//expected exception because no multipart config is set up
assertTrue(e.getMessage().startsWith("No multipart config"));
return true;
}
catch (Exception e)
{
e.printStackTrace();
return false;
}
}
@ -179,6 +184,66 @@ public class RequestTest
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
public void testBadUtf8ParamExtraction() throws Exception
{
@ -945,4 +1010,43 @@ public class RequestTest
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.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -29,6 +30,9 @@ import java.io.LineNumberReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
@ -565,6 +569,42 @@ public class ResponseTest
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()
{
_channel.reset();

View File

@ -18,15 +18,12 @@
package org.eclipse.jetty.server.ssl;
import static org.junit.Assert.assertEquals;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.security.KeyStore;
import java.util.Arrays;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
@ -39,6 +36,8 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* 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
@Ignore
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.URL;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
@ -667,6 +668,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
String ifms=request.getHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
if (ifms!=null)
{
//Get jetty's Response impl
Response r = Response.getResponse(response);
if (content!=null)
{
String mdlm=content.getLastModified();
@ -674,9 +678,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{
if (ifms.equals(mdlm))
{
response.reset();
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.flushBuffer();
r.reset(true);
r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
r.flushBuffer();
return false;
}
}
@ -687,9 +691,9 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
{
if (resource.lastModified()/1000 <= ifmsl/1000)
{
response.reset();
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
response.flushBuffer();
r.reset(true);
r.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
r.flushBuffer();
return false;
}
}
@ -942,8 +946,6 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
pos=start;
}
System.err.println("PART "+ibr);
IO.copy(in,multi,size);
pos+=size;
}
@ -1023,6 +1025,8 @@ public class DefaultServlet extends HttpServlet implements ResourceFactory
response.setHeader(HttpHeader.CACHE_CONTROL.asString(),_cacheControl);
}
/* ------------------------------------------------------------ */
/*
* @see javax.servlet.Servlet#destroy()

View File

@ -47,6 +47,7 @@ import javax.servlet.descriptor.JspPropertyGroupDescriptor;
import javax.servlet.descriptor.TaglibDescriptor;
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.server.Dispatcher;
@ -59,6 +60,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
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 servletSecurityElement new security info
* @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)
{
//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();
}

View File

@ -509,6 +509,8 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
initJspServlet();
}
initMultiPart();
_servlet.init(_config);
}
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;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
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.ResourceHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.TypeUtil;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@ -111,6 +113,22 @@ public class DispatcherTest
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
public void testForwardWithParam() throws Exception
{
@ -299,6 +317,7 @@ public class DispatcherTest
dispatcher = getServletContext().getRequestDispatcher("/IncludeServlet/includepath?do=assertforwardinclude");
else if(request.getParameter("do").equals("assertincludeforward"))
dispatcher = getServletContext().getRequestDispatcher("/AssertIncludeForwardServlet/assertpath?do=end");
else if(request.getParameter("do").equals("assertforward"))
dispatcher = getServletContext().getRequestDispatcher("/AssertForwardServlet?do=end&do=the");
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
* forwarding bits using filters.
@ -528,6 +559,45 @@ public class DispatcherTest
}
}
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
{
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException

View File

@ -89,7 +89,6 @@ public class ResponseHeadersTest
}
int port = connector.getLocalPort();
serverUri = new URI(String.format("http://%s:%d/",host,port));
System.out.printf("Server URI: %s%n",serverUri);
}
@AfterClass
@ -130,10 +129,8 @@ public class ResponseHeadersTest
// Read response
String respHeader = readResponseHeader(in);
System.out.println("RESPONSE: " + respHeader);
// Now test for properly formatted HTTP Response Headers.
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 Connection",respHeader,containsString("Connection: Upgrade\r\n"));

View File

@ -106,6 +106,7 @@ public class GzipFilter extends UserAgentFilter
public final static String GZIP="gzip";
public final static String DEFLATE="deflate";
protected Set<String> _mimeTypes;
protected int _bufferSize=8192;
protected int _minGzipSize=256;
@ -116,6 +117,11 @@ public class GzipFilter extends UserAgentFilter
protected Set<String> _excludedPaths;
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,16 +268,90 @@ public class GzipFilter extends UserAgentFilter
{
// TODO, this could be a little more robust.
// prefer gzip over deflate
String compression = null;
if (encodingHeader!=null)
{
if (encodingHeader.toLowerCase().contains(GZIP))
return GZIP;
else if (encodingHeader.toLowerCase().contains(DEFLATE))
return DEFLATE;
String[] encodings = getEncodings(encodingHeader);
if (encodings != null)
{
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 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)
{
CompressedResponseWrapper wrappedResponse = null;

View File

@ -77,7 +77,7 @@ import org.eclipse.jetty.util.StringUtil;
public class MultiPartFilter implements Filter
{
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 boolean _deleteFiles;
private ServletContext _context;
@ -141,7 +141,8 @@ public class MultiPartFilter implements Filter
MultipartConfigElement config = new MultipartConfigElement(tempdir.getCanonicalPath(), _maxFileSize, _maxRequestSize, _fileOutputBuffer);
MultiPartInputStream mpis = new MultiPartInputStream(in, content_type, config, tempdir);
mpis.setDeleteOnExit(_deleteFiles);
request.setAttribute(MULTIPART, mpis);
try
{
Collection<Part> parts = mpis.getParts();
@ -161,18 +162,6 @@ public class MultiPartFilter implements Filter
if (mp.getContentType() != null)
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
{
@ -194,23 +183,26 @@ public class MultiPartFilter implements Filter
}
}
/* ------------------------------------------------------------ */
private void deleteFiles(ServletRequest request)
{
ArrayList<File> files = (ArrayList<File>)request.getAttribute(FILES);
if (files!=null)
{
for (File file : files)
if (!_deleteFiles)
return;
MultiPartInputStream mpis = (MultiPartInputStream)request.getAttribute(MULTIPART);
if (mpis != null)
{
try
{
file.delete();
mpis.deleteParts();
}
catch (Exception e)
{
_context.log("failed to delete " + file, e);
}
_context.log("Error deleting multipart tmp files", e);
}
}
request.removeAttribute(MULTIPART);
}
/* ------------------------------------------------------------------------------- */

View File

@ -65,6 +65,10 @@ public class GzipFilterDefaultNoRecompressTest
{ "jetty_logo.tiff", "image/tiff", GzipFilter.GZIP },
{ "jetty_logo.xcf", "image/xcf", 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
// 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
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
public void testIsNotGzipCompressed() throws Exception
{

View File

@ -113,7 +113,7 @@ public class MultipartFilterTest
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.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
Assert.assertThat("Response.header[Content-Length]",response.get("Content-Length"),notNullValue());
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.
File serverFile = testdir.getFile(serverFilename);
@ -111,11 +115,11 @@ public class GzipTester
try
{
bais = new ByteArrayInputStream(response.getContentBytes());
if (compressionType.equals(GzipFilter.GZIP))
if (compressionType.startsWith(GzipFilter.GZIP))
{
in = new GZIPInputStream(bais);
}
else if (compressionType.equals(GzipFilter.DEFLATE))
else if (compressionType.startsWith(GzipFilter.DEFLATE))
{
in = new InflaterInputStream(bais, new Inflater(true));
}
@ -248,6 +252,7 @@ public class GzipTester
}
}
/**
* Asserts that the request results in a properly structured GzipFilter response, where the content is
* not compressed, and the content-length is returned appropriately.

View File

@ -41,6 +41,7 @@ public class CommandLineBuilder
*/
public void addArg(String arg)
{
if (arg != null)
args.add(quote(arg));
}
@ -84,6 +85,7 @@ public class CommandLineBuilder
*/
public void addRawArg(String arg)
{
if (arg != null)
args.add(arg);
}
@ -133,7 +135,7 @@ public class CommandLineBuilder
{
buf.append(' ');
}
buf.append(arg);
buf.append(quote(arg));
delim = true;
}

View File

@ -30,6 +30,7 @@ import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
@ -167,7 +168,15 @@ public class Main
{
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
String key = Config.getProperty("STOP.KEY",null);
stop(port,key);
stop(port,key, false);
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;
}
@ -268,7 +277,7 @@ public class Main
{
String opts[] = assign[1].split(",");
for (String opt : opts)
_config.addActiveOption(opt);
_config.addActiveOption(opt.trim());
}
else
{
@ -982,6 +991,12 @@ public class Main
* Stop a running jetty instance.
*/
public void stop(int port, String key)
{
stop (port,key,false);
}
public void stop (int port, String key, boolean wait)
{
int _port = port;
String _key = key;
@ -1005,6 +1020,14 @@ public class Main
OutputStream out = s.getOutputStream();
out.write((_key + "\r\nstop\r\n").getBytes());
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
{

View File

@ -17,6 +17,7 @@
//
package org.eclipse.jetty.start;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.InetAddress;
@ -113,8 +114,6 @@ public class Monitor extends Thread
Config.debug("command=" + 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 we have a child process, wait for it to finish before we stop
@ -122,12 +121,16 @@ public class Monitor extends Thread
{
_process.destroy();
_process.waitFor();
}
catch (InterruptedException e)
{
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);
}
else if ("status".equals(cmd))

View File

@ -24,7 +24,10 @@ Command Line Options:
contains -X or -D arguments, but creates an extra
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
redirected to ${jetty.log}/start.log

View File

@ -18,20 +18,28 @@
package org.eclipse.jetty.start;
import static org.hamcrest.Matchers.is;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
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
public void testSimpleCommandline()
{
CommandLineBuilder cmd = new CommandLineBuilder("java");
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"));
}
@ -53,6 +61,12 @@ public class CommandLineBuilderTest
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)
{
String actual = CommandLineBuilder.quote(raw);

View File

@ -26,13 +26,17 @@ import java.util.List;
import java.util.Vector;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
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.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/* ------------------------------------------------------------ */
/**
@ -106,14 +110,24 @@ public class MainTest
Classpath classpath = nastyWayToCreateAClasspathObject("/jetty/home with spaces/");
CommandLineBuilder cmd = main.buildCommandLine(classpath,xmls);
Assert.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"));
assertThat("CommandLineBuilder shouldn't be null",cmd,notNullValue());
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

View File

@ -32,6 +32,7 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
@ -53,7 +54,7 @@ public class MultiPartInputStream
protected MultiMap _parts;
protected File _tmpDir;
protected File _contextTmpDir;
protected boolean _deleteOnExit;
@ -67,6 +68,7 @@ public class MultiPartInputStream
protected String _contentType;
protected MultiMap _headers;
protected long _size = 0;
protected boolean _temporary = true;
public MultiPart (String name, String filename)
throws IOException
@ -135,6 +137,8 @@ public class MultiPartInputStream
throws IOException
{
_file = File.createTempFile("MultiPart", "", MultiPartInputStream.this._tmpDir);
if (_deleteOnExit)
_file.deleteOnExit();
FileOutputStream fos = new FileOutputStream(_file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
@ -197,11 +201,12 @@ public class MultiPartInputStream
{
if (_file != null)
{
//written to a file, whether temporary or not
return new BufferedInputStream (new FileInputStream(_file));
}
else
{
//part content is in a ByteArrayOutputStream
//part content is in memory
return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size());
}
}
@ -236,8 +241,11 @@ public class MultiPartInputStream
{
if (_file == null)
{
_temporary = false;
//part data is only in the ByteArrayOutputStream and never been written to disk
_file = new File (_tmpDir, fileName);
BufferedOutputStream bos = null;
try
{
@ -255,6 +263,8 @@ public class MultiPartInputStream
else
{
//the part data is already written to a temporary file, just rename it
_temporary = false;
File f = new File(_tmpDir, fileName);
if (_file.renameTo(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()
*/
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();
}
@ -308,12 +331,65 @@ public class MultiPartInputStream
_contextTmpDir = contextTmpDir;
if (_contextTmpDir == null)
_contextTmpDir = new File (System.getProperty("java.io.tmpdir"));
if (_config == null)
_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()
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)
throws IOException, ServletException
{
@ -337,6 +421,12 @@ public class MultiPartInputStream
}
/**
* Parse, if necessary, the multipart stream.
*
* @throws IOException
* @throws ServletException
*/
protected void parse ()
throws IOException, ServletException
{
@ -583,6 +673,19 @@ public class MultiPartInputStream
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);
}
}

View File

@ -102,7 +102,7 @@ public class StringUtil
ByteBuffer slice = b.slice();
slice.position(position);
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.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
@ -54,6 +55,40 @@ public class MultiPartInputStreamTest
protected String _contentType = "multipart/form-data, boundary=AaB03x";
protected String _multi = createMultipartRequestString(FILENAME);
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
public void testNonMultiPartRequest()
@ -63,7 +98,8 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()),
"Content-type: text/plain",
config,
new File(_dirname));
_tmpDir);
mpis.setDeleteOnExit(true);
assertTrue(mpis.getParts().isEmpty());
}
@ -75,7 +111,8 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
new File(_dirname));
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertFalse(parts.isEmpty());
}
@ -88,11 +125,12 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
new File(_dirname));
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try
{
mpis.getParts();
parts = mpis.getParts();
fail("Request should have exceeded maxRequestSize");
}
catch (IllegalStateException e)
@ -109,11 +147,12 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(_multi.getBytes()),
_contentType,
config,
new File(_dirname));
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = null;
try
{
mpis.getParts();
parts = mpis.getParts();
fail("stuff.txt should have been larger than maxFileSize");
}
catch (IllegalStateException e)
@ -123,6 +162,48 @@ public class MultiPartInputStreamTest
}
@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 ()
throws Exception
{
@ -141,11 +222,11 @@ public class MultiPartInputStreamTest
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(createMultipartRequestString(filename).getBytes()),
_contentType,
config,
new File(_dirname));
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
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.getName(),is("field1"));
InputStream is = field1.getInputStream();
@ -159,12 +240,13 @@ public class MultiPartInputStreamTest
assertNull(((MultiPartInputStream.MultiPart)field1).getBytes());//no longer in internal buffer
File f = new File (_dirname+File.separator+"field1.txt");
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");
assertTrue(f2.exists());
assertFalse(f.exists()); //should have been renamed
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");
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.getHeaderNames().size(),is(2));
assertThat(stuff.getSize(),is(51L));
f = stuff.getFile();
assertThat(f,notNullValue()); // longer than 100 bytes, should already be a file
assertThat(stuff.getBytes(),nullValue()); //not in internal buffer any more
assertThat(f.exists(),is(true));
assertThat(f.getName(),is(not("stuff with space.txt")));
File tmpfile = ((MultiPartInputStream.MultiPart)stuff).getFile();
assertThat(tmpfile,notNullValue()); // longer than 100 bytes, should already be a tmp file
assertThat(((MultiPartInputStream.MultiPart)stuff).getBytes(),nullValue()); //not in an internal buffer
assertThat(tmpfile.exists(),is(true));
assertThat(tmpfile.getName(),is(not("stuff with space.txt")));
stuff.write(filename);
f = new File(_dirname+File.separator+filename);
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
@ -197,15 +289,15 @@ public class MultiPartInputStreamTest
"content-disposition: form-data; name=\"stuff\"; filename=\"stuff2.txt\"\r\n"+
"Content-Type: text/plain\r\n"+
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"110000000000000000000000000000000000000000000000000\r\n"+
"--AaB03x--\r\n";
MultipartConfigElement config = new MultipartConfigElement(_dirname, 1024, 3072, 50);
MultiPartInputStream mpis = new MultiPartInputStream(new ByteArrayInputStream(sameNames.getBytes()),
_contentType,
config,
new File(_dirname));
_tmpDir);
mpis.setDeleteOnExit(true);
Collection<Part> parts = mpis.getParts();
assertEquals(2, parts.size());
for (Part p:parts)
@ -219,6 +311,18 @@ public class MultiPartInputStreamTest
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"+
"content-disposition: form-data; name=\"field1\"\r\n"+
"\r\n"+
@ -226,8 +330,8 @@ public class MultiPartInputStreamTest
"--AaB03x\r\n"+
"content-disposition: form-data; name=\"stuff\"; filename=\"" + filename + "\"\r\n"+
"Content-Type: text/plain\r\n"+
"\r\n"+
"000000000000000000000000000000000000000000000000000\r\n"+
"\r\n"+name+
filler.toString()+"\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.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.resource.Resource;
/**
* DiscoveredAnnotation
@ -36,16 +37,28 @@ public abstract class DiscoveredAnnotation
protected WebAppContext _context;
protected String _className;
protected Class<?> _clazz;
protected Resource _resource; //resource it was discovered on, can be null (eg from WEB-INF/classes)
public abstract void apply();
public DiscoveredAnnotation (WebAppContext context, String className)
{
_context = context;
_className = className;
this(context,className, null);
}
public DiscoveredAnnotation(WebAppContext context, String className, Resource resource)
{
_context = context;
_className = className;
_resource = resource;
}
public Resource getResource ()
{
return _resource;
}
public Class<?> getTargetClass()
{
if (_clazz != null)

View File

@ -200,7 +200,7 @@ public class MetaData
_metaDataComplete=true;
break;
case False:
_metaDataComplete=true;
_metaDataComplete=false;
break;
case NotSet:
break;
@ -270,12 +270,42 @@ public class MetaData
*/
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)
{
_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)
@ -508,6 +538,15 @@ public class MetaData
_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()
{
return _metaDataComplete;

View File

@ -18,4 +18,4 @@
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
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);
break;
}
@ -629,14 +629,14 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
//otherwise just ignore it
if (!(descriptor instanceof FragmentDescriptor))
{
addServletMapping(servlet_name, node, context);
addServletMapping(servlet_name, node, context, descriptor);
}
break;
}
case WebFragment:
{
//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;
}
}
@ -1161,7 +1161,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
* @param node
* @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();
mapping.setServletName(servletName);
@ -1173,6 +1173,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
String p = iter.next().toString(false, true);
p = normalizePattern(p);
paths.add(p);
context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, descriptor);
}
mapping.setPathSpecs((String[]) paths.toArray(new String[paths.size()]));
context.getServletHandler().addServletMapping(mapping);
@ -1184,7 +1185,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
* @param node
* @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();
mapping.setFilterName(filterName);
@ -1196,6 +1197,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
String p = iter.next().toString(false, true);
p = normalizePattern(p);
paths.add(p);
context.getMetaData().setOrigin(filterName+".filter.mapping."+p, descriptor);
}
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.setDeferredSyntaxAllowedAsLiteral(group.getString("deferred-syntax-allowed-as-literal", 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.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
//across fragments
//TODO: need to remember origin of the constraints
try
{
XmlParser.Node auths = node.get("auth-constraint");
@ -1400,10 +1404,18 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{
String url = iter2.next().toString(false, true);
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> iter4 = collection.iterator("http-method-omission");
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())
{
String method = ((XmlParser.Node) iter3.next()).toString(false, true);
@ -1411,12 +1423,25 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
mapping.setMethod(method);
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);
((ConstraintAware)context.getSecurityHandler()).addConstraintMapping(mapping);
}
}
else
{
//No http-methods or http-method-omissions specified, the constraint applies to all
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec(url);
mapping.setConstraint(sc);
@ -1768,7 +1793,7 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor
{
//no filtermappings for this filter yet defined
context.getMetaData().setOrigin(filter_name+".filter.mappings", descriptor);
addFilterMapping(filter_name, node, context);
addFilterMapping(filter_name, node, context, descriptor);
break;
}
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.
if (!(descriptor instanceof FragmentDescriptor))
{
addFilterMapping(filter_name, node, context);
addFilterMapping(filter_name, node, context, descriptor);
}
break;
}
case WebFragment:
{
//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;
}
}

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
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;
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.security.PermissionCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.HttpMethodConstraintElement;
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.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingListener;
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.server.Connector;
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.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;
import org.eclipse.jetty.util.security.Constraint;
/* ------------------------------------------------------------ */
/** Web Application Context Handler.
@ -1253,6 +1265,79 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
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
{
@ -1301,6 +1386,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
return servletContext;
}
}
}
/* ------------------------------------------------------------ */

View File

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

View File

@ -15,15 +15,6 @@
<param-value>a context value</param-value>
</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 -->
<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;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextEvent;
@ -26,6 +27,12 @@ import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
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.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener;
@ -33,6 +40,9 @@ import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionEvent;
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 void attributeAdded(HttpSessionBindingEvent se)
@ -62,16 +72,30 @@ public class TestListener implements HttpSessionListener, HttpSessionAttributeL
public void contextInitialized(ServletContextEvent sce)
{
/* TODO for servlet 3.0
* FilterRegistration registration=context.addFilter("TestFilter",TestFilter.class.getName());
//configure programmatic security
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.addMappingForUrlPatterns(
EnumSet.of(DispatcherType.ERROR,DispatcherType.ASYNC,DispatcherType.FORWARD,DispatcherType.INCLUDE,DispatcherType.REQUEST),
true,
new String[]{"/dump/*","/dispatch/*","*.dump"});
*/
new String[]{"/*"});
}
public void contextDestroyed(ServletContextEvent sce)

View File

@ -18,20 +18,6 @@
<listener-class>com.acme.TestListener</listener-class>
</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-name>QoSFilter</filter-name>
@ -120,7 +106,6 @@
</filter-mapping>
-->
<servlet>
<servlet-name>Hello</servlet-name>
<servlet-class>com.acme.HelloWorld</servlet-class>
@ -276,6 +261,18 @@
<location>/error404.html</location>
</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>
<web-resource-collection>
<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/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="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>
<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);
}
}
}
}