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/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-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> _supplier; @@ -1494,7 +1494,7 @@ public class Response implements HttpServletResponse } } - private static class HttpCookieFacade implements HttpCookie + protected static class HttpCookieFacade implements HttpCookie { private final Cookie _cookie; private final String _comment; @@ -1622,12 +1622,12 @@ public class Response implements HttpServletResponse return comment != null && comment.contains(HTTP_ONLY_COMMENT); } - private static boolean isPartitionedInComment(String comment) + protected static boolean isPartitionedInComment(String comment) { return comment != null && comment.contains(PARTITIONED_COMMENT); } - private static SameSite getSameSiteFromComment(String comment) + protected static SameSite getSameSiteFromComment(String comment) { if (comment == null) return null; @@ -1640,7 +1640,7 @@ public class Response implements HttpServletResponse return null; } - private static String getCommentWithoutAttributes(String comment) + protected static String getCommentWithoutAttributes(String comment) { if (comment == null) return null; diff --git a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java index c178d0bc9e0..2ea75100d9a 100644 --- a/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java +++ b/jetty-ee9/jetty-ee9-nested/src/main/java/org/eclipse/jetty/ee9/nested/SessionHandler.java @@ -53,6 +53,7 @@ import org.eclipse.jetty.session.SessionConfig; import org.eclipse.jetty.session.SessionIdManager; import org.eclipse.jetty.session.SessionManager; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -613,7 +614,8 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab * CookieConfig * * Implementation of the jakarta.servlet.SessionCookieConfig. - * SameSite configuration can be achieved by using setComment + * SameSite configuration can be achieved by using setComment. + * Partitioned configuration can be achieved by using setComment. * * @see HttpCookie */ @@ -671,7 +673,19 @@ public class SessionHandler extends ScopedHandler implements SessionConfig.Mutab public void setComment(String comment) { checkAvailable(); - _sessionManager.setSessionComment(comment); + + if (!StringUtil.isEmpty(comment)) + { + HttpCookie.SameSite sameSite = Response.HttpCookieFacade.getSameSiteFromComment(comment); + if (sameSite != null) + _sessionManager.setSameSite(sameSite); + + boolean partitioned = Response.HttpCookieFacade.isPartitionedInComment(comment); + if (partitioned) + _sessionManager.setPartitioned(partitioned); + + _sessionManager.setSessionComment(Response.HttpCookieFacade.getCommentWithoutAttributes(comment)); + } } @Override diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java index 8053c8315ca..cf0ed7df1d1 100644 --- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java +++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/SessionHandlerTest.java @@ -168,7 +168,7 @@ public class SessionHandlerTest } @Test - public void testSessionCookie() throws Exception + public void testSessionCookieConfig() throws Exception { Server server = new Server(); MockSessionIdManager idMgr = new MockSessionIdManager(server); @@ -190,12 +190,51 @@ public class SessionHandlerTest sessionCookieConfig.setSecure(false); sessionCookieConfig.setPath("/foo"); sessionCookieConfig.setMaxAge(99); + + //test setting SameSite and Partitioned the old way in the comment + sessionCookieConfig.setComment(Response.PARTITIONED_COMMENT + " " + Response.SAME_SITE_STRICT_COMMENT); - //for < ee10, SameSite cannot be set on the SessionCookieConfig, only on the SessionManager, or - //a default value on the context attribute org.eclipse.jetty.cookie.sameSiteDefault + HttpCookie cookie = mgr.getSessionManager().getSessionCookie(session, false); + assertEquals("SPECIAL", cookie.getName()); + assertEquals("universe", cookie.getDomain()); + assertEquals("/foo", cookie.getPath()); + assertFalse(cookie.isHttpOnly()); + assertFalse(cookie.isSecure()); + assertTrue(cookie.isPartitioned()); + assertEquals(99, cookie.getMaxAge()); + assertEquals(HttpCookie.SameSite.STRICT, cookie.getSameSite()); + + String cookieStr = HttpCookieUtils.getRFC6265SetCookie(cookie); + assertThat(cookieStr, containsString("; Partitioned; SameSite=Strict")); + } + + @Test + public void testSessionCookieViaSetters() throws Exception + { + Server server = new Server(); + MockSessionIdManager idMgr = new MockSessionIdManager(server); + idMgr.setWorkerName("node1"); + SessionHandler mgr = new SessionHandler(); + MockSessionCache cache = new MockSessionCache(mgr.getSessionManager()); + cache.setSessionDataStore(new NullSessionDataStore()); + mgr.setSessionCache(cache); + mgr.setSessionIdManager(idMgr); + + long now = System.currentTimeMillis(); + + ManagedSession session = new ManagedSession(mgr.getSessionManager(), new SessionData("123", "_foo", "0.0.0.0", now, now, now, 30)); + session.setExtendedId("123.node1"); + + //test setting up session cookie via setters on SessionHandler + mgr.setSessionCookie("SPECIAL"); + mgr.setSessionDomain("universe"); + mgr.setHttpOnly(false); + mgr.setSecureCookies(false); + mgr.setSessionPath("/foo"); + mgr.setMaxCookieAge(99); mgr.setSameSite(HttpCookie.SameSite.STRICT); mgr.setPartitioned(true); - + HttpCookie cookie = mgr.getSessionManager().getSessionCookie(session, false); assertEquals("SPECIAL", cookie.getName()); assertEquals("universe", cookie.getDomain());