diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java index 50384666f53..9e5bba5d4ee 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoSessionManager.java @@ -399,8 +399,8 @@ public class MongoSessionManager extends NoSqlSessionManager // cleanup, remove values from session, that don't exist in data anymore: for (String str : session.getNames()) { - if (!attrs.keySet().contains(str)) - { + if (!attrs.keySet().contains(encodeName(str))) + { session.doPutOrRemove(str,null); session.unbindValue(str,session.getAttribute(str)); } diff --git a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java index 09bb4ccdbd0..ca3f027bdeb 100644 --- a/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java +++ b/jetty-plus/src/main/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessor.java @@ -892,7 +892,7 @@ public class PlusDescriptorProcessor extends IterativeDescriptorProcessor if (defaultNE!=null) defaultNE.bindToENC(name); else - throw new IllegalStateException("Nothing to bind for name "+nameInEnvironment); + throw new IllegalStateException("Nothing to bind for name " + name); } diff --git a/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessorTest.java b/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessorTest.java index c6ff2f7b3ac..8199afd19d3 100644 --- a/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessorTest.java +++ b/jetty-plus/src/test/java/org/eclipse/jetty/plus/webapp/PlusDescriptorProcessorTest.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.plus.webapp; +import java.lang.reflect.InvocationTargetException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -50,6 +51,7 @@ public class PlusDescriptorProcessorTest protected FragmentDescriptor fragDescriptor1; protected FragmentDescriptor fragDescriptor2; protected FragmentDescriptor fragDescriptor3; + protected FragmentDescriptor fragDescriptor4; protected WebAppContext context; /** * @throws java.lang.Exception @@ -81,6 +83,9 @@ public class PlusDescriptorProcessorTest URL frag3Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-3.xml"); fragDescriptor3 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag3Xml)); fragDescriptor3.parse(); + URL frag4Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-4.xml"); + fragDescriptor4 = new FragmentDescriptor(org.eclipse.jetty.util.resource.Resource.newResource(frag4Xml)); + fragDescriptor4.parse(); } @After @@ -94,6 +99,32 @@ public class PlusDescriptorProcessorTest Thread.currentThread().setContextClassLoader(oldLoader); } + @Test + public void testMissingResourceDeclaration() + throws Exception + { + ClassLoader oldLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(context.getClassLoader()); + + try + { + PlusDescriptorProcessor pdp = new PlusDescriptorProcessor(); + pdp.process(context, fragDescriptor4); + fail("Expected missing resource declaration"); + } + catch (InvocationTargetException ex) + { + Throwable cause = ex.getCause(); + assertNotNull(cause); + assertNotNull(cause.getMessage()); + assertTrue(cause.getMessage().contains("jdbc/mymissingdatasource")); + } + finally + { + Thread.currentThread().setContextClassLoader(oldLoader); + } + } + @Test public void testWebXmlResourceDeclarations() throws Exception diff --git a/jetty-plus/src/test/resources/web-fragment-4.xml b/jetty-plus/src/test/resources/web-fragment-4.xml new file mode 100644 index 00000000000..c2158ea156a --- /dev/null +++ b/jetty-plus/src/test/resources/web-fragment-4.xml @@ -0,0 +1,16 @@ + + + + + Fragment 4 + + + jdbc/mymissingdatasource + javax.sql.DataSource + Container + + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java new file mode 100644 index 00000000000..756e15a6db7 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java @@ -0,0 +1,66 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.server.HttpChannel; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.URIUtil; + +/** + * Secured Redirect Handler + *

+ * Using information present in the {@link HttpConfiguration}, will attempt to redirect to the {@link HttpConfiguration#getSecureScheme()} and + * {@link HttpConfiguration#getSecurePort()} for any request that {@link HttpServletRequest#isSecure()} == false. + */ +public class SecuredRedirectHandler extends AbstractHandler +{ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + HttpConfiguration httpConfig = HttpChannel.getCurrentHttpChannel().getHttpConfiguration(); + + if (baseRequest.isSecure()) + { + return; // all done + } + + if (httpConfig.getSecurePort() > 0) + { + String scheme = httpConfig.getSecureScheme(); + int port = httpConfig.getSecurePort(); + + String url = URIUtil.newURI(scheme,baseRequest.getServerName(),port,baseRequest.getRequestURI(),baseRequest.getQueryString()); + response.setContentLength(0); + response.sendRedirect(url); + } + else + { + response.sendError(HttpStatus.FORBIDDEN_403,"Not Secure"); + } + baseRequest.setHandled(true); + } +} \ No newline at end of file diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java index 53ccd6398e1..5bcac2c2851 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java @@ -85,8 +85,9 @@ public class HashSessionManager extends AbstractSessionManager } finally { - if (_timer != null && _timer.isRunning()) - _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS); + if (_timer != null && _timer.isRunning()) { + _task = _timer.schedule(this, _scavengePeriodMs, TimeUnit.MILLISECONDS); + } } } } @@ -111,7 +112,7 @@ public class HashSessionManager extends AbstractSessionManager finally { if (_timer != null && _timer.isRunning()) - _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS); + _saveTask = _timer.schedule(this, _savePeriodMs, TimeUnit.MILLISECONDS); } } } @@ -138,7 +139,7 @@ public class HashSessionManager extends AbstractSessionManager ServletContext context = ContextHandler.getCurrentContext(); if (context!=null) _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer"); - } + } if (_timer == null) { @@ -148,7 +149,7 @@ public class HashSessionManager extends AbstractSessionManager } else addBean(_timer,false); - + super.doStart(); setScavengePeriod(getScavengePeriod()); @@ -177,12 +178,15 @@ public class HashSessionManager extends AbstractSessionManager { if (_saveTask!=null) _saveTask.cancel(); + _saveTask=null; if (_task!=null) _task.cancel(); + _task=null; _timer=null; } + // This will callback invalidate sessions - where we decide if we will save super.doStop(); @@ -314,6 +318,7 @@ public class HashSessionManager extends AbstractSessionManager _task.cancel(); _task = null; } + _task = _timer.schedule(new Scavenger(),_scavengePeriodMs, TimeUnit.MILLISECONDS); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java index 434cf9dfde7..f633e5b93c7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionIdManager.java @@ -604,7 +604,7 @@ public class JDBCSessionIdManager extends AbstractSessionIdManager finally { if (_scheduler != null && _scheduler.isRunning()) - _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS); + _task = _scheduler.schedule(this, _scavengeIntervalMs, TimeUnit.MILLISECONDS); } } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java new file mode 100644 index 00000000000..0c203076f3e --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/AllowAllVerifier.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +public class AllowAllVerifier implements HostnameVerifier +{ + @Override + public boolean verify(String hostname, SSLSession session) + { + return true; + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java new file mode 100644 index 00000000000..ebcf20a1e5b --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/SecuredRedirectHandlerTest.java @@ -0,0 +1,288 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.server.handler; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class SecuredRedirectHandlerTest +{ + private static Server server; + private static HostnameVerifier origVerifier; + private static SSLSocketFactory origSsf; + private static URI serverHttpUri; + private static URI serverHttpsUri; + + @BeforeClass + public static void startServer() throws Exception + { + // Setup SSL + File keystore = MavenTestingUtils.getTestResourceFile("keystore"); + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(keystore.getAbsolutePath()); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setKeyManagerPassword("keypwd"); + sslContextFactory.setTrustStorePath(keystore.getAbsolutePath()); + sslContextFactory.setTrustStorePassword("storepwd"); + + server = new Server(); + + int port = 12080; + int securePort = 12443; + + // Setup HTTP Configuration + HttpConfiguration httpConf = new HttpConfiguration(); + httpConf.setSecurePort(securePort); + httpConf.setSecureScheme("https"); + + ServerConnector httpConnector = new ServerConnector(server,new HttpConnectionFactory(httpConf)); + httpConnector.setName("unsecured"); + httpConnector.setPort(port); + + // Setup HTTPS Configuration + HttpConfiguration httpsConf = new HttpConfiguration(httpConf); + httpsConf.addCustomizer(new SecureRequestCustomizer()); + + ServerConnector httpsConnector = new ServerConnector(server,new SslConnectionFactory(sslContextFactory,"http/1.1"),new HttpConnectionFactory(httpsConf)); + httpsConnector.setName("secured"); + httpsConnector.setPort(securePort); + + // Add connectors + server.setConnectors(new Connector[] { httpConnector, httpsConnector }); + + // Wire up contexts + String secureHosts[] = new String[] { "@secured" }; + + ContextHandler test1Context = new ContextHandler(); + test1Context.setContextPath("/test1"); + test1Context.setHandler(new HelloHandler("Hello1")); + test1Context.setVirtualHosts(secureHosts); + + ContextHandler test2Context = new ContextHandler(); + test2Context.setContextPath("/test2"); + test2Context.setHandler(new HelloHandler("Hello2")); + test2Context.setVirtualHosts(secureHosts); + + ContextHandler rootContext = new ContextHandler(); + rootContext.setContextPath("/"); + rootContext.setHandler(new RootHandler("/test1","/test2")); + rootContext.setVirtualHosts(secureHosts); + + // Wire up context for unsecure handling to only + // the named 'unsecured' connector + ContextHandler redirectHandler = new ContextHandler(); + redirectHandler.setContextPath("/"); + redirectHandler.setHandler(new SecuredRedirectHandler()); + redirectHandler.setVirtualHosts(new String[] { "@unsecured" }); + + // Establish all handlers that have a context + ContextHandlerCollection contextHandlers = new ContextHandlerCollection(); + contextHandlers.setHandlers(new Handler[] { redirectHandler, rootContext, test1Context, test2Context }); + + // Create server level handler tree + HandlerList handlers = new HandlerList(); + handlers.addHandler(contextHandlers); + handlers.addHandler(new DefaultHandler()); // round things out + + server.setHandler(handlers); + + server.start(); + + // calculate serverUri + String host = httpConnector.getHost(); + if (host == null) + { + host = "localhost"; + } + serverHttpUri = new URI(String.format("http://%s:%d/",host,httpConnector.getLocalPort())); + serverHttpsUri = new URI(String.format("https://%s:%d/",host,httpsConnector.getLocalPort())); + + origVerifier = HttpsURLConnection.getDefaultHostnameVerifier(); + origSsf = HttpsURLConnection.getDefaultSSLSocketFactory(); + + HttpsURLConnection.setDefaultHostnameVerifier(new AllowAllVerifier()); + HttpsURLConnection.setDefaultSSLSocketFactory(sslContextFactory.getSslContext().getSocketFactory()); + } + + @AfterClass + public static void stopServer() throws Exception + { + HttpsURLConnection.setDefaultSSLSocketFactory(origSsf); + HttpsURLConnection.setDefaultHostnameVerifier(origVerifier); + + server.stop(); + server.join(); + } + + @Test + public void testRedirectUnsecuredRoot() throws Exception + { + URL url = serverHttpUri.resolve("/").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(302)); + assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/").toASCIIString())); + connection.disconnect(); + } + + @Test + public void testRedirectSecuredRoot() throws Exception + { + URL url = serverHttpsUri.resolve("/").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(200)); + String content = getContent(connection); + assertThat("response content",content,containsString("")); + connection.disconnect(); + } + + @Test + public void testAccessUnsecuredHandler() throws Exception + { + URL url = serverHttpUri.resolve("/test1").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(302)); + assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/test1").toASCIIString())); + connection.disconnect(); + } + + @Test + public void testAccessUnsecured404() throws Exception + { + URL url = serverHttpUri.resolve("/nothing/here").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(302)); + assertThat("location header",connection.getHeaderField("Location"),is(serverHttpsUri.resolve("/nothing/here").toASCIIString())); + connection.disconnect(); + } + + @Test + public void testAccessSecured404() throws Exception + { + URL url = serverHttpsUri.resolve("/nothing/here").toURL(); + HttpURLConnection connection = (HttpURLConnection)url.openConnection(); + connection.setInstanceFollowRedirects(false); + connection.setAllowUserInteraction(false); + assertThat("response code",connection.getResponseCode(),is(404)); + connection.disconnect(); + } + + private String getContent(HttpURLConnection connection) throws IOException + { + try (InputStream in = connection.getInputStream(); InputStreamReader reader = new InputStreamReader(in)) + { + StringWriter writer = new StringWriter(); + IO.copy(reader,writer); + return writer.toString(); + } + } + + public static class HelloHandler extends AbstractHandler + { + private final String msg; + + public HelloHandler(String msg) + { + this.msg = msg; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.setContentType("text/plain"); + response.getWriter().printf("%s%n",msg); + baseRequest.setHandled(true); + } + } + + public static class RootHandler extends AbstractHandler + { + private final String[] childContexts; + + public RootHandler(String... children) + { + this.childContexts = children; + } + + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!"/".equals(target)) + { + response.sendError(404); + return; + } + + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println(""); + out.println("Contexts"); + out.println(""); + out.println("

Child Contexts

"); + out.println(""); + out.println(""); + baseRequest.setHandled(true); + } + } +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java index 0d39ab52a90..1942270e62b 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SslUploadTest.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.server.ssl; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -40,6 +41,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.junit.AfterClass; @@ -58,12 +60,13 @@ public class SslUploadTest @BeforeClass public static void startServer() throws Exception { - String keystorePath = System.getProperty("basedir",".") + "/src/test/resources/keystore"; + File keystore = MavenTestingUtils.getTestResourceFile("keystore"); + SslContextFactory sslContextFactory = new SslContextFactory(); - sslContextFactory.setKeyStorePath(keystorePath); + sslContextFactory.setKeyStorePath(keystore.getAbsolutePath()); sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setKeyManagerPassword("keypwd"); - sslContextFactory.setTrustStorePath(keystorePath); + sslContextFactory.setTrustStorePath(keystore.getAbsolutePath()); sslContextFactory.setTrustStorePassword("storepwd"); server = new Server(); diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java index 5f8d62a65ac..56da1041b75 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/ScheduledExecutorScheduler.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.util.thread; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadFactory; @@ -38,16 +39,23 @@ public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Sch private final String name; private final boolean daemon; private volatile ScheduledThreadPoolExecutor scheduler; + private ClassLoader classloader; public ScheduledExecutorScheduler() { this(null, false); - } + } public ScheduledExecutorScheduler(String name, boolean daemon) + { + this (name,daemon, Thread.currentThread().getContextClassLoader()); + } + + public ScheduledExecutorScheduler(String name, boolean daemon, ClassLoader threadFactoryClassLoader) { this.name = name == null ? "Scheduler-" + hashCode() : name; this.daemon = daemon; + this.classloader = threadFactoryClassLoader; } @Override @@ -60,6 +68,7 @@ public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Sch { Thread thread = new Thread(r, name); thread.setDaemon(daemon); + thread.setContextClassLoader(classloader); return thread; } }); @@ -67,6 +76,8 @@ public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Sch super.doStart(); } + + @Override protected void doStop() throws Exception { @@ -81,6 +92,7 @@ public class ScheduledExecutorScheduler extends AbstractLifeCycle implements Sch ScheduledFuture result = scheduler.schedule(task, delay, unit); return new ScheduledFutureTask(result); } + private class ScheduledFutureTask implements Task { diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java index 7edaaaf2c45..f3bdee9072a 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/deploy/WebSocketServerContainerInitializer.java @@ -50,7 +50,7 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit *

* Note: this will add the Upgrade filter to the existing list, with no regard for order. It will just be tacked onto the end of the list. */ - public static ServerContainer configureContext(ServletContextHandler context) + public static ServerContainer configureContext(ServletContextHandler context) throws ServletException { // Create Filter WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context); @@ -70,7 +70,7 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit *

* This will use Servlet 3.1 techniques on the {@link ServletContext} to add a filter at the start of the filter chain. */ - public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) + public static ServerContainer configureContext(ServletContext context, ServletContextHandler jettyContext) throws ServletException { // Create Filter WebSocketUpgradeFilter filter = WebSocketUpgradeFilter.configureContext(context); @@ -84,49 +84,71 @@ public class WebSocketServerContainerInitializer implements ServletContainerInit return jettyContainer; } - - @Override - public void onStartup(Set> c, ServletContext context) throws ServletException + + private boolean isEnabled(Set> c, ServletContext context) { - Object enable = context.getAttribute(ENABLE_KEY); - - // Disable if explicitly disabled - if (TypeUtil.isFalse(enable)) + // Try context parameters first + String cp = context.getInitParameter(ENABLE_KEY); + if(TypeUtil.isTrue(cp)) { - if (c.isEmpty()) - { - if (LOG.isDebugEnabled()) - { - LOG.debug("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context); - } - } - else - { - LOG.warn("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context); - } - return; + // forced on + return true; } - // Disabled if not explicitly enabled and there are no discovered annotations or interfaces - if (!TypeUtil.isTrue(enable) && c.isEmpty()) + if(TypeUtil.isFalse(cp)) + { + // forced off + LOG.warn("JSR-356 support disabled via parameter on context {} - {}",context.getContextPath(),context); + return false; + } + + // Next, try attribute on context + Object enable = context.getAttribute(ENABLE_KEY); + + if(TypeUtil.isTrue(enable)) + { + // forced on + return true; + } + + if (TypeUtil.isFalse(enable)) + { + // forced off + LOG.warn("JSR-356 support disabled via attribute on context {} - {}",context.getContextPath(),context); + return false; + } + + // if not forced on or off, determine behavior based on annotations. + if (c.isEmpty()) { if (LOG.isDebugEnabled()) { LOG.debug("No JSR-356 annotations or interfaces discovered. JSR-356 support disabled",context.getContextPath(),context); } + return false; + } + + return true; + } + + @Override + public void onStartup(Set> c, ServletContext context) throws ServletException + { + if(!isEnabled(c,context)) + { return; } - + ContextHandler handler = ContextHandler.getContextHandler(context); if (handler == null) { - throw new ServletException("Not running on Jetty, JSR-356 support disabled"); + throw new ServletException("Not running on Jetty, JSR-356 support unavailable"); } if (!(handler instanceof ServletContextHandler)) { - throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support disabled"); + throw new ServletException("Not running in Jetty ServletContextHandler, JSR-356 support unavailable"); } ServletContextHandler jettyContext = (ServletContextHandler)handler; diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java index 78c1574ed43..70d2242ea27 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/browser/JsrBrowserDebugTool.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.websocket.jsr356.server.browser; +import javax.servlet.ServletException; import javax.websocket.DeploymentException; import org.eclipse.jetty.server.Server; @@ -75,7 +76,7 @@ public class JsrBrowserDebugTool server.join(); } - private void setupServer(int port) throws DeploymentException + private void setupServer(int port) throws DeploymentException, ServletException { server = new Server(); ServerConnector connector = new ServerConnector(server); diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java index 70e3eac1929..1be0734fa67 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketUpgradeFilter.java @@ -43,7 +43,6 @@ import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.server.pathmap.PathMappings; import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource; @@ -56,12 +55,21 @@ import org.eclipse.jetty.websocket.servlet.WebSocketCreator; @ManagedObject("WebSocket Upgrade Filter") public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter, MappedWebSocketCreator, Dumpable { + public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey"; private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class); - public static WebSocketUpgradeFilter configureContext(ServletContextHandler context) + public static WebSocketUpgradeFilter configureContext(ServletContextHandler context) throws ServletException { - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - WebSocketUpgradeFilter filter = new WebSocketUpgradeFilter(policy); + // Prevent double configure + WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName()); + if (filter != null) + { + return filter; + } + + // Dynamically add filter + filter = new WebSocketUpgradeFilter(); + filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName()); String name = "Jetty_WebSocketUpgradeFilter"; String pathSpec = "/*"; @@ -69,6 +77,7 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter FilterHolder fholder = new FilterHolder(filter); fholder.setName(name); + fholder.setInitParameter(CONTEXT_ATTRIBUTE_KEY,WebSocketUpgradeFilter.class.getName()); context.addFilter(fholder,pathSpec,dispatcherTypes); if (LOG.isDebugEnabled()) @@ -76,16 +85,21 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter LOG.debug("Adding [{}] {} mapped to {} to {}",name,filter,pathSpec,context); } - // Store reference to the WebSocketUpgradeFilter - context.setAttribute(WebSocketUpgradeFilter.class.getName(),filter); - return filter; } - - public static WebSocketUpgradeFilter configureContext(ServletContext context) + + public static WebSocketUpgradeFilter configureContext(ServletContext context) throws ServletException { - WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER); - WebSocketUpgradeFilter filter = new WebSocketUpgradeFilter(policy); + // Prevent double configure + WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName()); + if (filter != null) + { + return filter; + } + + // Dynamically add filter + filter = new WebSocketUpgradeFilter(); + filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName()); String name = "Jetty_Dynamic_WebSocketUpgradeFilter"; String pathSpec = "/*"; @@ -94,6 +108,7 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter String urlPatterns[] = { pathSpec }; FilterRegistration.Dynamic dyn = context.addFilter(name,filter); + dyn.setInitParameter(CONTEXT_ATTRIBUTE_KEY,WebSocketUpgradeFilter.class.getName()); dyn.addMappingForUrlPatterns(dispatcherTypes,isMatchAfter,urlPatterns); if (LOG.isDebugEnabled()) @@ -101,20 +116,19 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter LOG.debug("Adding [{}] {} mapped to {} to {}",name,filter,pathSpec,context); } - // Store reference to the WebSocketUpgradeFilter - context.setAttribute(WebSocketUpgradeFilter.class.getName(),filter); - return filter; } private final WebSocketServerFactory factory; private final PathMappings pathmap = new PathMappings<>(); + private String fname; + private boolean alreadySetToAttribute = false; - public WebSocketUpgradeFilter(WebSocketPolicy policy) + public WebSocketUpgradeFilter() { - this(policy, new MappedByteBufferPool()); + this(WebSocketPolicy.newServerPolicy(),new MappedByteBufferPool()); } - + public WebSocketUpgradeFilter(WebSocketPolicy policy, ByteBufferPool bufferPool) { factory = new WebSocketServerFactory(policy,bufferPool); @@ -146,6 +160,11 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter return; } + if (LOG.isDebugEnabled()) + { + LOG.debug(".doFilter({}) - {}",fname,chain); + } + if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) { HttpServletRequest httpreq = (HttpServletRequest)request; @@ -232,6 +251,8 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter @Override public void init(FilterConfig config) throws ServletException { + fname = config.getFilterName(); + try { WebSocketPolicy policy = factory.getPolicy(); @@ -260,6 +281,15 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter policy.setInputBufferSize(Integer.parseInt(max)); } + String key = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY); + if (key == null) + { + // assume default + key = WebSocketUpgradeFilter.class.getName(); + } + + setToAttribute(config.getServletContext(), key); + factory.start(); } catch (Exception x) @@ -267,6 +297,44 @@ public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter throw new ServletException(x); } } + + private void setToAttribute(ServletContextHandler context, String key) throws ServletException + { + if(alreadySetToAttribute) + { + return; + } + + if (context.getAttribute(key) != null) + { + throw new ServletException(WebSocketUpgradeFilter.class.getName() + + " is defined twice for the same context attribute key '" + key + + "'. Make sure you have different init-param '" + + CONTEXT_ATTRIBUTE_KEY + "' values set"); + } + context.setAttribute(key,this); + + alreadySetToAttribute = true; + } + + public void setToAttribute(ServletContext context, String key) throws ServletException + { + if(alreadySetToAttribute) + { + return; + } + + if (context.getAttribute(key) != null) + { + throw new ServletException(WebSocketUpgradeFilter.class.getName() + + " is defined twice for the same context attribute key '" + key + + "'. Make sure you have different init-param '" + + CONTEXT_ATTRIBUTE_KEY + "' values set"); + } + context.setAttribute(key,this); + + alreadySetToAttribute = true; + } @Override public String toString() diff --git a/tests/test-sessions/pom.xml b/tests/test-sessions/pom.xml index 66a04b80f7b..fbb4ffe2a27 100644 --- a/tests/test-sessions/pom.xml +++ b/tests/test-sessions/pom.xml @@ -34,6 +34,6 @@ test-hash-sessions test-jdbc-sessions - + test-mongodb-sessions diff --git a/tests/test-sessions/test-mongodb-sessions/pom.xml b/tests/test-sessions/test-mongodb-sessions/pom.xml index 74ee04521b2..2f13223f444 100644 --- a/tests/test-sessions/test-mongodb-sessions/pom.xml +++ b/tests/test-sessions/test-mongodb-sessions/pom.xml @@ -21,7 +21,7 @@ org.eclipse.jetty.tests test-sessions-parent - 9.2.2-SNAPSHOT + 9.2.4-SNAPSHOT test-mongodb-sessions Jetty Tests :: Sessions :: Mongo diff --git a/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java new file mode 100644 index 00000000000..2465f8efc59 --- /dev/null +++ b/tests/test-sessions/test-mongodb-sessions/src/test/java/org/eclipse/jetty/nosql/mongodb/AttributeNameTest.java @@ -0,0 +1,163 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.nosql.mongodb; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.nosql.NoSqlSession; +import org.eclipse.jetty.server.session.AbstractTestServer; +import org.junit.Test; + +/** + * AttributeNameTest + * + * Test that attribute names that have special characters with meaning to mongo (eg ".") are + * properly escaped and not accidentally removed. + * See bug: https://bugs.eclipse.org/bugs/show_bug.cgi?id=444595 + */ +public class AttributeNameTest +{ + + + + public AbstractTestServer createServer(int port, int max, int scavenge) + throws Exception + { + MongoTestServer server = new MongoTestServer(port,max,scavenge,true); + + return server; + + } + + @Test + public void testAttributeNamesWithDots() throws Exception + { + String contextPath = ""; + String servletMapping = "/server"; + int maxInactivePeriod = 10000; + int scavengePeriod = 20000; + AbstractTestServer server1 = createServer(0,maxInactivePeriod,scavengePeriod); + server1.addContext(contextPath).addServlet(TestServlet.class,servletMapping); + server1.start(); + int port1 = server1.getPort(); + + AbstractTestServer server2 = createServer(0,maxInactivePeriod,scavengePeriod); + server2.addContext(contextPath).addServlet(TestServlet.class,servletMapping); + server2.start(); + int port2 = server2.getPort(); + + try + { + + HttpClient client = new HttpClient(); + client.start(); + try + { + + // Perform one request to server1 to create a session with attribute with dotted name + ContentResponse response = client.GET("http://localhost:" + port1 + contextPath + servletMapping + "?action=init"); + + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + + String resp = response.getContentAsString(); + + String[] sessionTestResponse = resp.split("/"); + assertEquals("a.b.c",sessionTestResponse[0]); + + + String sessionCookie = response.getHeaders().getStringField("Set-Cookie"); + assertTrue(sessionCookie != null); + //Mangle the cookie, replacing Path with $Path, etc. + sessionCookie = sessionCookie.replaceFirst("(\\W)(P|p)ath=","$1\\$Path="); + + //Make a request to the 2nd server which will do a refresh, use TestServlet to ensure that the + //session attribute with dotted name is not removed + Request request2 = client.newRequest("http://localhost:" + port2 + contextPath + servletMapping + "?action=get"); + request2.header("Cookie", sessionCookie); + ContentResponse response2 = request2.send(); + assertEquals(HttpServletResponse.SC_OK,response2.getStatus()); + + } + finally + { + client.stop(); + } + } + finally + { + server1.stop(); + server2.stop(); + } + } + + public static class TestServlet extends HttpServlet + { + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException + { + String action = request.getParameter("action"); + if ("init".equals(action)) + { + NoSqlSession session = (NoSqlSession)request.getSession(true); + session.setAttribute("a.b.c",System.currentTimeMillis()); + sendResult(session,httpServletResponse.getWriter()); + + } + else + { + NoSqlSession session = (NoSqlSession)request.getSession(false); + assertNotNull(session); + assertNotNull(session.getAttribute("a.b.c")); + sendResult(session,httpServletResponse.getWriter()); + } + + } + + private void sendResult(NoSqlSession session, PrintWriter writer) + { + if (session != null) + { + if (session.getAttribute("a.b.c") != null) + writer.print("a.b.c/"+session.getAttribute("a.b.c")); + else + writer.print("-/0"); + } + else + { + writer.print("0/0"); + } + } + + } + + +}