i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious();)
{
try
{
- _contextListeners.get(i).exitScope(_context, request);
+ i.previous().exitScope(_context, request);
}
catch (Throwable e)
{
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java
index 6905a3ce258..eade4137899 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java
@@ -405,6 +405,15 @@ public class HttpConnection extends AbstractMetaDataConnection implements Runnab
if (LOG.isDebugEnabled())
LOG.debug("HANDLE {} {}", request, this);
+ // If the buffer is empty and no body is expected, then release the buffer
+ if (isRequestBufferEmpty() && !_parser.hasContent())
+ {
+ // try parsing now to the end of the message
+ parseRequestBuffer();
+ if (_parser.isComplete())
+ releaseRequestBuffer();
+ }
+
// Handle the request by running the task.
_handling.set(true);
Runnable onRequest = _onRequest;
@@ -431,7 +440,15 @@ public class HttpConnection extends AbstractMetaDataConnection implements Runnab
{
if (LOG.isDebugEnabled())
LOG.debug("upgraded {} -> {}", this, getEndPoint().getConnection());
- releaseRequestBuffer();
+ if (_requestBuffer != null)
+ releaseRequestBuffer();
+ break;
+ }
+
+ // If we have already released the request buffer, then use fill interest before allocating another
+ if (_requestBuffer == null)
+ {
+ fillInterested();
break;
}
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
index c96a75f6c23..dba38e2ef4f 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/ConcurrentPool.java
@@ -79,36 +79,6 @@ public class ConcurrentPool implements Pool
, Dumpable
this(strategyType, maxSize, pooled -> 1);
}
- /**
- *
Creates an instance with the specified strategy.
- *
- * @param strategyType the strategy to used to lookup entries
- * @param maxSize the maximum number of pooled entries
- * @param cache whether a {@link ThreadLocal} cache should be used for the most recently released entry
- * @deprecated cache is no longer supported. Use {@link StrategyType#THREAD_ID}
- */
- @Deprecated
- public ConcurrentPool(StrategyType strategyType, int maxSize, boolean cache)
- {
- this(strategyType, maxSize, pooled -> 1);
- }
-
- /**
- * Creates an instance with the specified strategy.
- * and a function that returns the max multiplex count for a given pooled object.
- *
- * @param strategyType the strategy to used to lookup entries
- * @param maxSize the maximum number of pooled entries
- * @param cache whether a {@link ThreadLocal} cache should be used for the most recently released entry
- * @param maxMultiplex a function that given the pooled object returns the max multiplex count
- * @deprecated cache is no longer supported. Use {@link StrategyType#THREAD_ID}
- */
- @Deprecated
- public ConcurrentPool(StrategyType strategyType, int maxSize, boolean cache, ToIntFunction maxMultiplex)
- {
- this(strategyType, maxSize, maxMultiplex);
- }
-
/**
*
Creates an instance with the specified strategy.
* and a function that returns the max multiplex count for a given pooled object.
@@ -148,7 +118,7 @@ public class ConcurrentPool implements Pool
, Dumpable
{
leaked.increment();
if (LOG.isDebugEnabled())
- LOG.debug("Leaked " + holder);
+ LOG.debug("Leaked {}", holder);
leaked();
}
@@ -195,15 +165,14 @@ public class ConcurrentPool
implements Pool
, Dumpable
void sweep()
{
- for (int i = 0; i < entries.size(); i++)
+ // Remove entries atomically with respect to remove(Entry).
+ entries.removeIf(holder ->
{
- Holder
holder = entries.get(i);
- if (holder.getEntry() == null)
- {
- entries.remove(i--);
+ boolean remove = holder.getEntry() == null;
+ if (remove)
leaked(holder);
- }
- }
+ return remove;
+ });
}
@Override
@@ -285,8 +254,7 @@ public class ConcurrentPool
implements Pool
, Dumpable
if (!removed)
return false;
- // No need to lock, no race with reserve()
- // and the race with terminate() is harmless.
+ // In a harmless race with reserve()/sweep()/terminate().
Holder
holder = ((ConcurrentEntry
)entry).getHolder();
boolean evicted = entries.remove(holder);
if (LOG.isDebugEnabled())
@@ -313,10 +281,7 @@ public class ConcurrentPool
implements Pool
, Dumpable
// Field this.terminated must be modified with the lock held
// because the list of entries is modified, see reserve().
terminated = true;
- copy = entries.stream()
- .map(Holder::getEntry)
- .filter(Objects::nonNull)
- .toList();
+ copy = stream().toList();
entries.clear();
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
index 96b05f42735..10910bc2c7c 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/IteratingCallback.java
@@ -292,7 +292,7 @@ public abstract class IteratingCallback implements Callback
{
ExceptionUtil.callAndThen(cause, this::onAborted, this::onFailure);
}
-
+
private void doOnAbortedOnFailureOnCompleted(Throwable cause)
{
ExceptionUtil.callAndThen(cause, this::doOnAbortedOnFailure, _onCompleted);
@@ -423,6 +423,7 @@ public abstract class IteratingCallback implements Callback
{
// we won the race against the callback, so the callback has to process and we can break processing
_state = State.PENDING;
+ _reprocess = false;
if (_aborted)
{
onAbortedOnFailureIfNotPendingDoCompleted = _failure;
@@ -482,6 +483,7 @@ public abstract class IteratingCallback implements Callback
}
callOnSuccess = true;
_state = State.PROCESSING;
+ _reprocess = false;
break;
}
@@ -818,6 +820,14 @@ public abstract class IteratingCallback implements Callback
return true;
}
+ boolean isPending()
+ {
+ try (AutoLock ignored = _lock.lock())
+ {
+ return _state == State.PENDING;
+ }
+ }
+
/**
* @return whether this callback is idle, and {@link #iterate()} needs to be called
*/
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
index 7c21e92731d..7d0bdb94331 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java
@@ -34,6 +34,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.Objects;
import java.util.Optional;
import java.util.ServiceConfigurationError;
@@ -173,6 +174,32 @@ public class TypeUtil
return Arrays.asList(a);
}
+ /**
+ *
Returns a {@link ListIterator} positioned at the last item in a list.
+ * @param list the list
+ * @param the element type
+ * @return A {@link ListIterator} positioned at the last item of the list.
+ */
+ public static ListIterator listIteratorAtEnd(List list)
+ {
+ try
+ {
+ int size = list.size();
+ if (size == 0)
+ return Collections.emptyListIterator();
+ return list.listIterator(size);
+ }
+ catch (IndexOutOfBoundsException e)
+ {
+ // list was concurrently modified, so do this the hard way
+ ListIterator i = list.listIterator();
+ while (i.hasNext())
+ i.next();
+
+ return i;
+ }
+ }
+
/**
* Class from a canonical name for a type.
*
diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java
index 686e1397704..4a4bc1e2140 100644
--- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java
+++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/IteratingCallbackTest.java
@@ -33,7 +33,9 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.instanceOf;
@@ -63,6 +65,45 @@ public class IteratingCallbackTest
scheduler.stop();
}
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ public void testIterateWhileProcessingLoopCount(boolean succeededWinsRace)
+ {
+ var icb = new IteratingCallback()
+ {
+ int counter = 0;
+
+ @Override
+ protected Action process()
+ {
+ int counter = this.counter++;
+ if (counter == 0)
+ {
+ iterate();
+ if (succeededWinsRace)
+ {
+ succeeded();
+ }
+ else
+ {
+ new Thread(() ->
+ {
+ await().atMost(5, TimeUnit.SECONDS).until(this::isPending, is(true));
+ succeeded();
+ }).start();
+ }
+ return Action.SCHEDULED;
+ }
+ return Action.IDLE;
+ }
+ };
+
+ icb.iterate();
+
+ await().atMost(10, TimeUnit.SECONDS).until(icb::isIdle, is(true));
+ assertEquals(2, icb.counter);
+ }
+
@Test
public void testNonWaitingProcess() throws Exception
{
diff --git a/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/AsyncMiddleManServletTest.java b/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/AsyncMiddleManServletTest.java
index c1218587ab0..b32b99487ad 100644
--- a/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/AsyncMiddleManServletTest.java
+++ b/jetty-ee10/jetty-ee10-proxy/src/test/java/org/eclipse/jetty/ee10/proxy/AsyncMiddleManServletTest.java
@@ -61,6 +61,7 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.Request;
import org.eclipse.jetty.client.Response;
+import org.eclipse.jetty.client.StringRequestContent;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.servlet.ServletHolder;
import org.eclipse.jetty.http.HttpHeader;
@@ -79,6 +80,7 @@ import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.ajax.JSON;
+import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -163,11 +165,32 @@ public class AsyncMiddleManServletTest
}
@AfterEach
- public void dispose() throws Exception
+ public void dispose()
{
- client.stop();
- proxy.stop();
- server.stop();
+ LifeCycle.stop(client);
+ LifeCycle.stop(proxy);
+ LifeCycle.stop(server);
+ }
+
+ @Test
+ public void testExpect100WithBody() throws Exception
+ {
+ startServer(new EchoHttpServlet());
+ startProxy(new AsyncMiddleManServlet());
+ startClient();
+
+ for (int i = 0; i < 100; i++)
+ {
+ String body = Character.toString('a' + (i % 26)); // only use 'a' to 'z'
+ ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
+ .path("/" + body)
+ .headers(h -> h.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
+ .timeout(5, TimeUnit.SECONDS)
+ .body(new StringRequestContent(body))
+ .send();
+ assertEquals(200, response.getStatus());
+ assertEquals(body, response.getContentAsString());
+ }
}
@Test
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
index 5031b641d02..48293310927 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletContextHandler.java
@@ -31,6 +31,7 @@ import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@@ -95,6 +96,7 @@ import org.eclipse.jetty.util.DeprecationWarning;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@@ -516,12 +518,11 @@ public class ServletContextHandler extends ContextHandler
//Call context listeners
Throwable multiException = null;
ServletContextEvent event = new ServletContextEvent(getServletContext());
- Collections.reverse(_destroyServletContextListeners);
- for (ServletContextListener listener : _destroyServletContextListeners)
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_destroyServletContextListeners); i.hasPrevious();)
{
try
{
- callContextDestroyed(listener, event);
+ callContextDestroyed(i.previous(), event);
}
catch (Exception x)
{
@@ -568,17 +569,17 @@ public class ServletContextHandler extends ContextHandler
if (!_servletRequestListeners.isEmpty())
{
final ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), request);
- for (int i = _servletRequestListeners.size(); i-- > 0; )
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestListeners); i.hasPrevious();)
{
- _servletRequestListeners.get(i).requestDestroyed(sre);
+ i.previous().requestDestroyed(sre);
}
}
if (!_servletRequestAttributeListeners.isEmpty())
{
- for (int i = _servletRequestAttributeListeners.size(); i-- > 0; )
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestAttributeListeners); i.hasPrevious();)
{
- scopedRequest.removeEventListener(_servletRequestAttributeListeners.get(i));
+ scopedRequest.removeEventListener(i.previous());
}
}
}
@@ -1217,11 +1218,11 @@ public class ServletContextHandler extends ContextHandler
ServletContextRequest scopedRequest = Request.as(request, ServletContextRequest.class);
if (!_contextListeners.isEmpty())
{
- for (int i = _contextListeners.size(); i-- > 0; )
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious(); )
{
try
{
- _contextListeners.get(i).exitScope(getContext(), scopedRequest);
+ i.previous().exitScope(getContext(), scopedRequest);
}
catch (Throwable e)
{
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/SessionHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/SessionHandler.java
index 788f24f68c3..0023f99d4bd 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/SessionHandler.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/SessionHandler.java
@@ -19,6 +19,7 @@ import java.util.Enumeration;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -47,6 +48,7 @@ import org.eclipse.jetty.session.AbstractSessionManager;
import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.SessionConfig;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.TypeUtil;
public class SessionHandler extends AbstractSessionManager implements Handler.Singleton
{
@@ -253,6 +255,14 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
SessionHandler.this.setSecureCookies(secure);
}
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[name=%s,domain=%s,path=%s,max-age=%d,secure=%b,http-only=%b,comment=%s,attributes=%s]",
+ this.getClass().getName(), this.hashCode(), getName(), getDomain(), getPath(),
+ getMaxAge(), isSecure(), isHttpOnly(), getComment(), getSessionCookieAttributes().toString());
+ }
+
private void checkState()
{
//It is allowable to call the CookieConfig.setXX methods after the SessionHandler has started,
@@ -383,6 +393,10 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
public SessionHandler()
{
setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES);
+ installBean(_cookieConfig);
+ installBean(_sessionListeners);
+ installBean(_sessionIdListeners);
+ installBean(_sessionAttributeListeners);
}
@Override
@@ -559,9 +573,9 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
getSessionContext().run(() ->
{
HttpSessionEvent event = new HttpSessionEvent(session.getApi());
- for (int i = _sessionListeners.size() - 1; i >= 0; i--)
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_sessionListeners); i.hasPrevious();)
{
- _sessionListeners.get(i).sessionDestroyed(event);
+ i.previous().sessionDestroyed(event);
}
});
}
diff --git a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ComplianceViolations2616Test.java b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ComplianceViolations2616Test.java
index f3bea6be187..a617a9f9aab 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ComplianceViolations2616Test.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ComplianceViolations2616Test.java
@@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.HttpCompliance;
+import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LocalConnector;
@@ -87,8 +88,8 @@ public class ComplianceViolations2616Test
{
resp.setContentType("text/plain");
PrintWriter out = resp.getWriter();
- List headerNames = new ArrayList<>();
- headerNames.addAll(Collections.list(req.getHeaderNames()));
+ out.printf("%s %s%s%s\n", req.getMethod(), req.getContextPath(), req.getServletPath(), req.getPathInfo());
+ List headerNames = new ArrayList<>(Collections.list(req.getHeaderNames()));
Collections.sort(headerNames);
for (String name : headerNames)
{
@@ -183,4 +184,25 @@ public class ComplianceViolations2616Test
assertThat("Response headers", response, containsString("X-Http-Violation-0: Line Folding not supported"));
assertThat("Response body", response, containsString("[Name] = [Some Value]"));
}
+
+ @Test
+ public void testAmbiguousSlash() throws Exception
+ {
+ String request = """
+ GET /dump/foo//bar HTTP/1.1\r
+ Host: local\r
+ Connection: close\r
+ \r
+ """;
+
+ String response = connector.getResponse(request);
+ assertThat(response, containsString("HTTP/1.1 400 Bad"));
+
+ connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986.with("test", UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT));
+ server.getContainedBeans(ServletHandler.class).stream().findFirst().get().setDecodeAmbiguousURIs(true);
+
+ response = connector.getResponse(request);
+ assertThat(response, containsString("HTTP/1.1 200 OK"));
+ assertThat(response, containsString("GET /dump/foo//bar"));
+ }
}
diff --git a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletContextHandler.java b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletContextHandler.java
index f97021240da..872ababa6e1 100644
--- a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletContextHandler.java
+++ b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/ServletContextHandler.java
@@ -31,6 +31,7 @@ import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@@ -95,6 +96,7 @@ import org.eclipse.jetty.util.DeprecationWarning;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
@@ -515,12 +517,11 @@ public class ServletContextHandler extends ContextHandler
//Call context listeners
Throwable multiException = null;
ServletContextEvent event = new ServletContextEvent(getServletContext());
- Collections.reverse(_destroyServletContextListeners);
- for (ServletContextListener listener : _destroyServletContextListeners)
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_destroyServletContextListeners); i.hasPrevious();)
{
try
{
- callContextDestroyed(listener, event);
+ callContextDestroyed(i.previous(), event);
}
catch (Exception x)
{
@@ -566,18 +567,18 @@ public class ServletContextHandler extends ContextHandler
// Handle more REALLY SILLY request events!
if (!_servletRequestListeners.isEmpty())
{
- final ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), request);
- for (int i = _servletRequestListeners.size(); i-- > 0; )
+ ServletRequestEvent sre = new ServletRequestEvent(getServletContext(), request);
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestListeners); i.hasPrevious();)
{
- _servletRequestListeners.get(i).requestDestroyed(sre);
+ i.previous().requestDestroyed(sre);
}
}
if (!_servletRequestAttributeListeners.isEmpty())
{
- for (int i = _servletRequestAttributeListeners.size(); i-- > 0; )
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestAttributeListeners); i.hasPrevious();)
{
- scopedRequest.removeEventListener(_servletRequestAttributeListeners.get(i));
+ scopedRequest.removeEventListener(i.previous());
}
}
}
@@ -1217,11 +1218,11 @@ public class ServletContextHandler extends ContextHandler
ServletContextRequest scopedRequest = Request.as(request, ServletContextRequest.class);
if (!_contextListeners.isEmpty())
{
- for (int i = _contextListeners.size(); i-- > 0; )
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious(); )
{
try
{
- _contextListeners.get(i).exitScope(getContext(), scopedRequest);
+ i.previous().exitScope(getContext(), scopedRequest);
}
catch (Throwable e)
{
diff --git a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/SessionHandler.java b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/SessionHandler.java
index 9c887e97bc7..917745a9332 100644
--- a/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/SessionHandler.java
+++ b/jetty-ee11/jetty-ee11-servlet/src/main/java/org/eclipse/jetty/ee11/servlet/SessionHandler.java
@@ -19,6 +19,7 @@ import java.util.Enumeration;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -48,6 +49,7 @@ import org.eclipse.jetty.session.AbstractSessionManager;
import org.eclipse.jetty.session.ManagedSession;
import org.eclipse.jetty.session.SessionConfig;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.TypeUtil;
public class SessionHandler extends AbstractSessionManager implements Handler.Singleton
{
@@ -254,6 +256,14 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
SessionHandler.this.setSecureCookies(secure);
}
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[name=%s,domain=%s,path=%s,max-age=%d,secure=%b,http-only=%b,comment=%s,attributes=%s]",
+ this.getClass().getName(), this.hashCode(), getName(), getDomain(), getPath(),
+ getMaxAge(), isSecure(), isHttpOnly(), getComment(), getSessionCookieAttributes().toString());
+ }
+
private void checkState()
{
//It is allowable to call the CookieConfig.setXX methods after the SessionHandler has started,
@@ -427,6 +437,10 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
public SessionHandler()
{
setSessionTrackingModes(DEFAULT_SESSION_TRACKING_MODES);
+ installBean(_cookieConfig);
+ installBean(_sessionListeners);
+ installBean(_sessionIdListeners);
+ installBean(_sessionAttributeListeners);
}
@Override
@@ -603,9 +617,9 @@ public class SessionHandler extends AbstractSessionManager implements Handler.Si
getSessionContext().run(() ->
{
HttpSessionEvent event = new HttpSessionEvent(session.getApi());
- for (int i = _sessionListeners.size() - 1; i >= 0; i--)
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_sessionListeners); i.hasPrevious();)
{
- _sessionListeners.get(i).sessionDestroyed(event);
+ i.previous().sessionDestroyed(event);
}
});
}
diff --git a/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ComplianceViolations2616Test.java b/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ComplianceViolations2616Test.java
index b80086245b8..5da1f083d4b 100644
--- a/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ComplianceViolations2616Test.java
+++ b/jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ComplianceViolations2616Test.java
@@ -32,6 +32,7 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.HttpCompliance;
+import org.eclipse.jetty.http.UriCompliance;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.LocalConnector;
@@ -87,8 +88,8 @@ public class ComplianceViolations2616Test
{
resp.setContentType("text/plain");
PrintWriter out = resp.getWriter();
- List headerNames = new ArrayList<>();
- headerNames.addAll(Collections.list(req.getHeaderNames()));
+ out.printf("%s %s%s%s\n", req.getMethod(), req.getContextPath(), req.getServletPath(), req.getPathInfo());
+ List headerNames = new ArrayList<>(Collections.list(req.getHeaderNames()));
Collections.sort(headerNames);
for (String name : headerNames)
{
@@ -183,4 +184,25 @@ public class ComplianceViolations2616Test
assertThat("Response headers", response, containsString("X-Http-Violation-0: Line Folding not supported"));
assertThat("Response body", response, containsString("[Name] = [Some Value]"));
}
+
+ @Test
+ public void testAmbiguousSlash() throws Exception
+ {
+ String request = """
+ GET /dump/foo//bar HTTP/1.1\r
+ Host: local\r
+ Connection: close\r
+ \r
+ """;
+
+ String response = connector.getResponse(request);
+ assertThat(response, containsString("HTTP/1.1 400 Bad"));
+
+ connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setUriCompliance(UriCompliance.RFC3986.with("test", UriCompliance.Violation.AMBIGUOUS_EMPTY_SEGMENT));
+ server.getContainedBeans(ServletHandler.class).stream().findFirst().get().setDecodeAmbiguousURIs(true);
+
+ response = connector.getResponse(request);
+ assertThat(response, containsString("HTTP/1.1 200 OK"));
+ assertThat(response, containsString("GET /dump/foo//bar"));
+ }
}
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
index dfe73d44112..db3e1ab6afd 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/ContextHandler.java
@@ -29,6 +29,7 @@ import java.util.EventListener;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
@@ -1000,17 +1001,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
if (!_servletRequestListeners.isEmpty())
{
final ServletRequestEvent sre = new ServletRequestEvent(_apiContext, request);
- for (int i = _servletRequestListeners.size(); i-- > 0; )
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestListeners); i.hasPrevious();)
{
- _servletRequestListeners.get(i).requestDestroyed(sre);
+ i.previous().requestDestroyed(sre);
}
}
if (!_servletRequestAttributeListeners.isEmpty())
{
- for (int i = _servletRequestAttributeListeners.size(); i-- > 0; )
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_servletRequestAttributeListeners); i.hasPrevious();)
{
- baseRequest.removeEventListener(_servletRequestAttributeListeners.get(i));
+ baseRequest.removeEventListener(i.previous());
}
}
}
@@ -1070,11 +1071,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Supplie
{
if (!_contextListeners.isEmpty())
{
- for (int i = _contextListeners.size(); i-- > 0; )
+ for (ListIterator i = TypeUtil.listIteratorAtEnd(_contextListeners); i.hasPrevious();)
{
try
{
- _contextListeners.get(i).exitScope(_apiContext, request);
+ i.previous().exitScope(_apiContext, request);
}
catch (Throwable e)
{
diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java
index 78df43d272c..b115e3d6cb4 100644
--- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java
+++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/Response.java
@@ -76,20 +76,20 @@ public class Response implements HttpServletResponse
* String used in the {@code Comment} attribute of {@link Cookie}
* to support the {@code HttpOnly} attribute.
**/
- private static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
+ protected static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
/**
* String used in the {@code Comment} attribute of {@link Cookie}
* to support the {@code Partitioned} attribute.
**/
- private static final String PARTITIONED_COMMENT = "__PARTITIONED__";
+ protected static final String PARTITIONED_COMMENT = "__PARTITIONED__";
/**
* The strings used in the {@code Comment} attribute of {@link Cookie}
* to support the {@code SameSite} attribute.
**/
- private static final String SAME_SITE_COMMENT = "__SAME_SITE_";
- private static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
- private static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__";
- private static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__";
+ protected static final String SAME_SITE_COMMENT = "__SAME_SITE_";
+ protected static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";
+ protected static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__";
+ protected static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__";
public enum OutputType
{
@@ -1465,7 +1465,7 @@ public class Response implements HttpServletResponse
return (HttpServletResponse)servletResponse;
}
- private static class HttpFieldsSupplier implements Supplier
+ protected static class HttpFieldsSupplier implements Supplier
{
private final Supplier