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