diff --git a/modules/org.restlet.test/pom.xml b/modules/org.restlet.test/pom.xml
index d8c95c6db4..dd928003c1 100644
--- a/modules/org.restlet.test/pom.xml
+++ b/modules/org.restlet.test/pom.xml
@@ -12,7 +12,7 @@
Restlet Unit Tests
All Restlet unit tests.
- ${basedir}/src
+ ${basedir}/src/main/java
diff --git a/modules/org.restlet.test/src/main/java/org/restlet/test/engine/header/CookieSettingReaderTestCase.java b/modules/org.restlet.test/src/main/java/org/restlet/test/engine/header/CookieSettingReaderTestCase.java
new file mode 100644
index 0000000000..56f1d52ace
--- /dev/null
+++ b/modules/org.restlet.test/src/main/java/org/restlet/test/engine/header/CookieSettingReaderTestCase.java
@@ -0,0 +1,31 @@
+package org.restlet.test.engine.header;
+
+import org.restlet.data.CookieSetting;
+import org.restlet.data.CookieSetting.SameSite;
+import org.restlet.engine.header.CookieSettingReader;
+import org.restlet.test.RestletTestCase;
+
+public class CookieSettingReaderTestCase extends RestletTestCase {
+
+ public void testReadingWithoutSameSite() {
+ CookieSetting readSetting = CookieSettingReader.read("cookie=value");
+ assertNull(readSetting.getSameSite());
+ }
+
+ public void testReadingOfSameSite() {
+ for(SameSite sameSite : SameSite.values()) {
+ CookieSetting readSetting = CookieSettingReader.read("cookie=value; SameSite=" + sameSite);
+ assertEquals(sameSite, readSetting.getSameSite());
+ }
+ }
+
+ public void testReadingOfInvalidSameSite() {
+ CookieSetting readSetting = CookieSettingReader.read("cookie=value; SameSite=InvalidSameSiteValue");
+ assertNull( readSetting.getSameSite());
+ }
+
+ public void testReadingOfEmptySameSite() {
+ CookieSetting readSetting = CookieSettingReader.read("cookie=value; SameSite=");
+ assertNull( readSetting.getSameSite());
+ }
+}
diff --git a/modules/org.restlet.test/src/main/java/org/restlet/test/engine/header/CookieSettingWriterTestCase.java b/modules/org.restlet.test/src/main/java/org/restlet/test/engine/header/CookieSettingWriterTestCase.java
new file mode 100644
index 0000000000..7a214275be
--- /dev/null
+++ b/modules/org.restlet.test/src/main/java/org/restlet/test/engine/header/CookieSettingWriterTestCase.java
@@ -0,0 +1,24 @@
+package org.restlet.test.engine.header;
+
+import org.restlet.data.CookieSetting;
+import org.restlet.data.CookieSetting.SameSite;
+import org.restlet.engine.header.CookieSettingWriter;
+import org.restlet.test.RestletTestCase;
+
+public class CookieSettingWriterTestCase extends RestletTestCase {
+
+ public void testWritingOfUnsetSameSite() {
+ CookieSetting testSetting = new CookieSetting("cookie", "value");
+ assertEquals("cookie=value", CookieSettingWriter.write(testSetting));
+
+ assertEquals("cookie=value", CookieSettingWriter.write(testSetting));
+ }
+
+ public void testWritingOfSameSite() {
+ for(SameSite sameSite : SameSite.values()) {
+ CookieSetting testSetting = new CookieSetting("cookie", "value");
+ testSetting.setSameSite(sameSite);
+ assertEquals("cookie=value; SameSite=" + sameSite, CookieSettingWriter.write(testSetting));
+ }
+ }
+}
diff --git a/modules/org.restlet/src/main/java/org/restlet/data/CookieSetting.java b/modules/org.restlet/src/main/java/org/restlet/data/CookieSetting.java
index c529e2c9de..4823ae1598 100644
--- a/modules/org.restlet/src/main/java/org/restlet/data/CookieSetting.java
+++ b/modules/org.restlet/src/main/java/org/restlet/data/CookieSetting.java
@@ -58,6 +58,24 @@ public final class CookieSetting extends Cookie {
/** Indicates if cookie should only be transmitted by secure means. */
private volatile boolean secure;
+ /** Explicitly specifies a same site policy for browsers. */
+ private volatile SameSite sameSite;
+
+ public enum SameSite {
+ LAX("Lax"),
+ STRICT("Strict"),
+ NONE("None");
+
+ final String value;
+ SameSite(String value) {
+ this.value = value;
+ }
+
+ public String toString() {
+ return value;
+ }
+ }
+
/**
* Default constructor.
*/
@@ -164,6 +182,47 @@ public CookieSetting(int version, String name, String value, String path,
this.secure = secure;
this.accessRestricted = accessRestricted;
}
+
+ /**
+ * Constructor.
+ *
+ * @param version
+ * The cookie's version.
+ * @param name
+ * The cookie's name.
+ * @param value
+ * The cookie's value.
+ * @param path
+ * The cookie's path.
+ * @param domain
+ * The cookie's domain name.
+ * @param comment
+ * The cookie's comment.
+ * @param maxAge
+ * Sets the maximum age in seconds.
+ * 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 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.
+ * @param sameSite
+ * The cookie's same site policy.
+ */
+ public CookieSetting(int version, String name, String value, String path,
+ String domain, String comment, int maxAge, boolean secure,
+ boolean accessRestricted, SameSite sameSite) {
+ super(version, name, value, path, domain);
+ this.comment = comment;
+ this.maxAge = maxAge;
+ this.secure = secure;
+ this.accessRestricted = accessRestricted;
+ this.sameSite = sameSite;
+ }
/**
* Preferred constructor.
@@ -192,7 +251,8 @@ public boolean equals(Object obj) {
return super.equals(obj)
&& this.maxAge == that.maxAge
&& this.secure == that.secure
- && Objects.equals(this.comment, that.comment);
+ && Objects.equals(this.comment, that.comment)
+ && Objects.equals(this.sameSite, that.sameSite);
}
/**
@@ -228,7 +288,7 @@ public int getMaxAge() {
@Override
public int hashCode() {
return SystemUtils.hashCode(super.hashCode(), getComment(),
- getMaxAge(), isSecure());
+ getMaxAge(), isSecure(), getSameSite());
}
/**
@@ -251,6 +311,17 @@ public boolean isSecure() {
return this.secure;
}
+
+ /**
+ * Returns the currently set same site policy.
+ *
+ * @return sameSite
+ * The currently set same site attribute setting.
+ */
+ public SameSite getSameSite() {
+ return this.sameSite;
+ }
+
/**
* Indicates whether to restrict cookie access to untrusted parties.
* Currently this toggles the non-standard but widely supported HttpOnly
@@ -293,6 +364,16 @@ public void setMaxAge(int maxAge) {
public void setSecure(boolean secure) {
this.secure = secure;
}
+
+ /**
+ * Sets the same site policy for the browser to apply to this cookie.
+ *
+ * @param sameSite
+ * The new same site policy to set.
+ */
+ public void setSameSite(SameSite sameSite) {
+ this.sameSite = sameSite;
+ }
@Override
public String toString() {
@@ -300,7 +381,8 @@ public String toString() {
+ ", comment=" + comment + ", maxAge=" + maxAge + ", secure="
+ secure + ", domain=" + getDomain() + ", name=" + getName()
+ ", path=" + getPath() + ", value=" + getValue()
- + ", version=" + getVersion() + "]";
+ + ", version=" + getVersion()
+ +", sameSite=" + "]";
}
}
diff --git a/modules/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingReader.java b/modules/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingReader.java
index ce7adb49c9..7c739bdf1d 100644
--- a/modules/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingReader.java
+++ b/modules/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingReader.java
@@ -34,6 +34,7 @@
import org.restlet.Context;
import org.restlet.data.CookieSetting;
import org.restlet.data.Parameter;
+import org.restlet.data.CookieSetting.SameSite;
import org.restlet.engine.util.DateUtils;
import org.restlet.engine.util.StringUtils;
@@ -65,6 +66,8 @@ public class CookieSettingReader extends HeaderReader {
private static final String NAME_SET_SECURE = "secure";
private static final String NAME_SET_VERSION = "version";
+
+ private static final String NAME_SET_SAME_SITE ="samesite";
/**
* Parses the given String to a CookieSetting
@@ -243,6 +246,16 @@ public CookieSetting readValue() throws IOException {
}
} else if (pair.getName().equalsIgnoreCase(NAME_SET_VERSION)) {
result.setVersion(Integer.valueOf(pair.getValue()));
+ } else if(pair.getName().equalsIgnoreCase(NAME_SET_SAME_SITE) && !"".equals(pair.getValue())) {
+ SameSite sameSite = null;
+ try {
+ sameSite = SameSite.valueOf(pair.getValue().toUpperCase());
+ } catch(IllegalArgumentException illigalArgumentException) {
+ Context.getCurrentLogger()
+ .warning("Unable to parse cookie setting same-site value \"" + pair.getValue()
+ + "\". Not setting same-site attribute.");
+ }
+ result.setSameSite(sameSite);
} else {
// Unexpected special attribute
// Silently ignore it as it may have been introduced by new specifications
diff --git a/modules/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java b/modules/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java
index 72ba546056..6c09abb447 100644
--- a/modules/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java
+++ b/modules/org.restlet/src/main/java/org/restlet/engine/header/CookieSettingWriter.java
@@ -28,6 +28,7 @@
import java.util.List;
import org.restlet.data.CookieSetting;
+import org.restlet.data.CookieSetting.SameSite;
import org.restlet.engine.util.DateUtils;
/**
@@ -142,6 +143,13 @@ public CookieSettingWriter append(CookieSetting cookieSetting)
if (cookieSetting.isAccessRestricted()) {
append("; HttpOnly");
}
+
+ // Append the same site attribute if it is set.
+ SameSite sameSite = cookieSetting.getSameSite();
+ if(sameSite != null) {
+ append("; SameSite=");
+ appendValue(sameSite.toString(), version);
+ }
// Append the comment
if (version > 0) {