diff --git a/changes.md b/changes.md index a90a868004..f2967bf95c 100644 --- a/changes.md +++ b/changes.md @@ -6,9 +6,12 @@ Changes log - Upgraded Jetty library to version 12.1.14. Issue #1515. - Upgraded Jackson library to version 2.22.4. - Upgraded org.json library to version 20260814. + - Deprecated the legacy RFC 2109/2965 cookie API (cookie version, comment and related constructors), to be removed in Restlet 3.0 in favor of RFC 6265. - Bugs fixed - Slf4j bridge reports wrong line number and caller class in log4j. Issue #233. - When GET is an allowed method of a server resource, HEAD is now also listed (e.g. in the Allow header of a 405 response). Issue #266. + - The Max-Age and Path attributes of a version 1 cookie are no longer quoted in the Set-Cookie header, as browsers ignore them otherwise. Issue #1334. + - CookieAuthenticator no longer lets the next restlet overwrite the redirection sent after a login. Issue #554. - 2.7.0 (30-09-2026) - Misc diff --git a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/CookieAuthenticator.java b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/CookieAuthenticator.java index cb38076388..3f17c82f31 100644 --- a/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/CookieAuthenticator.java +++ b/org.restlet.ext.crypto/src/main/java/org/restlet/ext/crypto/CookieAuthenticator.java @@ -172,7 +172,10 @@ protected int authenticated(Request request, Response response) { getLogger().log(Level.SEVERE, "Could not format credentials cookie", e); } - return super.authenticated(request, response); + int result = super.authenticated(request, response); + + // Don't let the next restlet overwrite a login redirection (issue #554) + return response.getStatus().isRedirection() ? STOP : result; } /** diff --git a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java index dfb97cd502..745a757030 100644 --- a/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java +++ b/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java @@ -12,6 +12,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.api.Test; import org.restlet.Application; import org.restlet.Component; @@ -21,6 +22,7 @@ import org.restlet.data.CookieSetting; import org.restlet.data.Form; import org.restlet.data.MediaType; +import org.restlet.data.Method; import org.restlet.data.Status; import org.restlet.resource.ClientResource; import org.restlet.resource.ResourceException; @@ -100,4 +102,36 @@ void testCookieAuth1() { cs = logoutCr.getCookieSettings().getFirst("Credentials"); assertEquals(0, cs.getMaxAge()); } + + @Test + void login_withTargetUri_redirectsWithoutCallingNextRestlet() { + AtomicInteger nextCalls = new AtomicInteger(); + CookieAuthenticator co = + new CookieAuthenticator(null, false, "realm", "MyExtraSecretKey".getBytes()); + MapVerifier mapVerifier = new MapVerifier(); + mapVerifier.getLocalSecrets().put("scott", "tiger".toCharArray()); + co.setVerifier(mapVerifier); + co.setNext( + new Restlet() { + @Override + public void handle(Request request, Response response) { + nextCalls.incrementAndGet(); + response.setStatus(Status.SUCCESS_OK); + } + }); + + Request request = new Request(Method.POST, "http://toto.com/login?targetUri=/home"); + Form form = new Form(); + form.add("login", "scott"); + form.add("password", "tiger"); + request.getResourceRef().setBaseRef("http://toto.com"); + request.setEntity(form.getWebRepresentation()); + Response response = new Response(request); + co.handle(request, response); + + assertEquals(Status.REDIRECTION_SEE_OTHER, response.getStatus()); + assertNotNull(response.getLocationRef()); + assertNotNull(response.getCookieSettings().getFirst("Credentials")); + assertEquals(0, nextCalls.get()); + } } diff --git a/org.restlet/src/main/java/org/restlet/data/Cookie.java b/org.restlet/src/main/java/org/restlet/data/Cookie.java index 70de428008..88c8282b40 100644 --- a/org.restlet/src/main/java/org/restlet/data/Cookie.java +++ b/org.restlet/src/main/java/org/restlet/data/Cookie.java @@ -53,7 +53,10 @@ public Cookie() { * @param version The version number. * @param name The name. * @param value The value. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public Cookie(int version, String name, String value) { this(version, name, value, null, null); } @@ -66,7 +69,10 @@ public Cookie(int version, String name, String value) { * @param value The value. * @param path The validity path. * @param domain The domain name. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public Cookie(int version, String name, String value, String path, String domain) { this.version = version; this.name = name; @@ -142,7 +148,10 @@ public String getValue() { * Returns the cookie specification version. * * @return The cookie specification version. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public int getVersion() { return this.version; } @@ -193,7 +202,10 @@ public void setValue(String value) { * Sets the cookie specification version. * * @param version The cookie specification version. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public void setVersion(int version) { this.version = version; } diff --git a/org.restlet/src/main/java/org/restlet/data/CookieSetting.java b/org.restlet/src/main/java/org/restlet/data/CookieSetting.java index dd94a311e4..e3a9e01587 100644 --- a/org.restlet/src/main/java/org/restlet/data/CookieSetting.java +++ b/org.restlet/src/main/java/org/restlet/data/CookieSetting.java @@ -16,8 +16,8 @@ * Cookie setting provided by a server. This allows a server side application to add, modify, or * remove a cookie on the client.
*
- * Note that when used with HTTP connectors, this class maps to the "Set-Cookie" and "Set-Cookie2" - * headers. + * Note that when used with HTTP connectors, this class maps to the "Set-Cookie" header (RFC 6265). + * The "Set-Cookie2" header (RFC 2965) is deprecated. * * @see Response#getCookieSettings() * @author Jerome Louvel @@ -52,7 +52,10 @@ public CookieSetting() { * @param version The cookie's version. * @param name The cookie's name. * @param value The cookie's value. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public CookieSetting(int version, String name, String value) { this(version, name, value, null, null); } @@ -65,7 +68,10 @@ public CookieSetting(int version, String name, String value) { * @param value The cookie's value. * @param path The cookie's path. * @param domain The cookie's domain name. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public CookieSetting(int version, String name, String value, String path, String domain) { this(version, name, value, path, domain, null, -1, false, false); } @@ -83,7 +89,10 @@ public CookieSetting(int version, String name, String value, String path, String * Use 0 to immediately discard an existing cookie.
* Use -1 to discard the cookie at the end of the session (default). * @param secure Indicates if the cookie should only be transmitted by secure means. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public CookieSetting( int version, String name, @@ -111,7 +120,10 @@ public CookieSetting( * @param secure Indicates if the cookie should only be transmitted by secure means. * @param accessRestricted Indicates whether to restrict cookie access to untrusted parties. * Currently, this toggles the non-standard but widely supported HttpOnly cookie parameter. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public CookieSetting( int version, String name, @@ -160,7 +172,10 @@ public boolean equals(Object obj) { * Returns the comment for the user. * * @return The comment for the user. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public String getComment() { return this.comment; } @@ -223,7 +238,10 @@ public void setAccessRestricted(boolean accessRestricted) { * Sets the comment for the user. * * @param comment The comment for the user. + * @deprecated Legacy RFC 2109/2965 cookie support is dropped in Restlet 3.0, use RFC 6265 + * cookies. */ + @Deprecated(since = "2.7.1", forRemoval = true) public void setComment(String comment) { this.comment = comment; } diff --git a/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java b/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java index 2ca6d6dc3d..5fc875a9a4 100644 --- a/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java +++ b/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java @@ -69,11 +69,8 @@ public CookieSettingWriter append(CookieSetting cookieSetting) throws IllegalArg if ((path != null) && (!path.isEmpty())) { append("; Path="); - if (version == 0) { - append(path); - } else { - appendQuotedString(path); - } + // Browsers don't unquote attribute values (RFC 6265) + append(path); } // Append the expiration date @@ -91,7 +88,8 @@ public CookieSettingWriter append(CookieSetting cookieSetting) throws IllegalArg DateUtils.format(expires, DateUtils.FORMAT_RFC_1123.getFirst()), version); } else { append("; Max-Age="); - appendValue(Integer.toString(cookieSetting.getMaxAge()), version); + // Must not be quoted, browsers would ignore it (issue #1334) + append(Integer.toString(maxAge)); } } else if ((maxAge == -1) && (version > 0)) { // Discard the cookie at the end of the user's session (RFC 2965) diff --git a/org.restlet/src/test/java/org/restlet/engine/header/CookieReaderTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/CookieReaderTestCase.java index 3bfbb107a8..793abb9572 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/CookieReaderTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/CookieReaderTestCase.java @@ -145,14 +145,12 @@ void testParsing() throws IOException { testCookie( "$Version=\"1\"; Part_Number=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""); - testCookieSetting("Customer=\"WILE_E_COYOTE\"; Version=\"1\"; Path=\"/acme\"", true); + testCookieSetting("Customer=\"WILE_E_COYOTE\"; Version=\"1\"; Path=/acme", true); + testCookieSetting("Part_Number=\"Rocket_Launcher_0001\"; Version=\"1\"; Path=/acme", true); + testCookieSetting("Shipping=\"FedEx\"; Version=\"1\"; Path=/acme", true); + testCookieSetting("Part_Number=\"Rocket_Launcher_0001\"; Version=\"1\"; Path=/acme", true); testCookieSetting( - "Part_Number=\"Rocket_Launcher_0001\"; Version=\"1\"; Path=\"/acme\"", true); - testCookieSetting("Shipping=\"FedEx\"; Version=\"1\"; Path=\"/acme\"", true); - testCookieSetting( - "Part_Number=\"Rocket_Launcher_0001\"; Version=\"1\"; Path=\"/acme\"", true); - testCookieSetting( - "Part_Number=\"Riding_Rocket_0023\"; Version=\"1\"; Path=\"/acme/ammo\"", true); + "Part_Number=\"Riding_Rocket_0023\"; Version=\"1\"; Path=/acme/ammo", true); // Bug #49 testCookieSetting( diff --git a/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingWriterTestCase.java b/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingWriterTestCase.java index fe4b533388..c906a99a84 100644 --- a/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingWriterTestCase.java +++ b/org.restlet/src/test/java/org/restlet/engine/header/CookieSettingWriterTestCase.java @@ -47,9 +47,9 @@ void write_pathVersionZero_appendsUnquotedPath() { } @Test - void write_pathVersionOne_appendsQuotedPath() { + void write_pathVersionOne_appendsUnquotedPath() { CookieSetting cookieSetting = new CookieSetting(1, "name", "value", "/path", null); - assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Path=\"/path\"")); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Path=/path")); } @Test @@ -63,7 +63,7 @@ void write_maxAgeVersionZero_appendsExpiresDate() { void write_maxAgeVersionOne_appendsMaxAge() { CookieSetting cookieSetting = new CookieSetting(1, "name", "value"); cookieSetting.setMaxAge(3600); - assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Max-Age=\"3600\"")); + assertTrue(CookieSettingWriter.write(cookieSetting).contains("; Max-Age=3600")); } @Test