diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieDecoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieDecoder.java
index 74cace7385..01a35f3b95 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieDecoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ClientCookieDecoder.java
@@ -16,6 +16,7 @@
package io.netty.handler.codec.http.cookie;
import io.netty.handler.codec.DateFormatter;
+import io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite;
import java.util.Date;
@@ -154,6 +155,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
private int expiresEnd;
private boolean secure;
private boolean httpOnly;
+ private SameSite sameSite;
CookieBuilder(DefaultCookie cookie, String header) {
this.cookie = cookie;
@@ -180,6 +182,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
cookie.setMaxAge(mergeMaxAgeAndExpires());
cookie.setSecure(secure);
cookie.setHttpOnly(httpOnly);
+ cookie.setSameSite(sameSite);
return cookie;
}
@@ -206,7 +209,7 @@ public final class ClientCookieDecoder extends CookieDecoder {
} else if (length == 7) {
parse7(keyStart, valueStart, valueEnd);
} else if (length == 8) {
- parse8(keyStart);
+ parse8(keyStart, valueStart, valueEnd);
}
}
@@ -241,9 +244,11 @@ public final class ClientCookieDecoder extends CookieDecoder {
}
}
- private void parse8(int nameStart) {
+ private void parse8(int nameStart, int valueStart, int valueEnd) {
if (header.regionMatches(true, nameStart, CookieHeaderNames.HTTPONLY, 0, 8)) {
httpOnly = true;
+ } else if (header.regionMatches(true, nameStart, CookieHeaderNames.SAMESITE, 0, 8)) {
+ sameSite = SameSite.of(computeValue(valueStart, valueEnd));
}
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/CookieHeaderNames.java b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/CookieHeaderNames.java
index 6d2e7f577c..fef0567218 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/CookieHeaderNames.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/CookieHeaderNames.java
@@ -28,6 +28,35 @@ public final class CookieHeaderNames {
public static final String HTTPONLY = "HTTPOnly";
+ public static final String SAMESITE = "SameSite";
+
+ /**
+ * Possible values for the SameSite attribute.
+ * See changes to RFC6265bis
+ */
+ public enum SameSite {
+ Lax,
+ Strict,
+ None;
+
+ /**
+ * Return the enum value corresponding to the passed in same-site-flag, using a case insensitive comparison.
+ *
+ * @param name value for the SameSite Attribute
+ * @return enum value for the provided name or null
+ */
+ static SameSite of(String name) {
+ if (name != null) {
+ for (SameSite each : SameSite.class.getEnumConstants()) {
+ if (each.name().equalsIgnoreCase(name)) {
+ return each;
+ }
+ }
+ }
+ return null;
+ }
+ }
+
private CookieHeaderNames() {
// Unused.
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/DefaultCookie.java b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/DefaultCookie.java
index cbd54cd092..137a65d49a 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/DefaultCookie.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/DefaultCookie.java
@@ -15,7 +15,10 @@
*/
package io.netty.handler.codec.http.cookie;
-import static io.netty.handler.codec.http.cookie.CookieUtil.*;
+import io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite;
+
+import static io.netty.handler.codec.http.cookie.CookieUtil.stringBuilder;
+import static io.netty.handler.codec.http.cookie.CookieUtil.validateAttributeValue;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
/**
@@ -31,6 +34,7 @@ public class DefaultCookie implements Cookie {
private long maxAge = UNDEFINED_MAX_AGE;
private boolean secure;
private boolean httpOnly;
+ private SameSite sameSite;
/**
* Creates a new cookie with the specified name and value.
@@ -119,6 +123,26 @@ public class DefaultCookie implements Cookie {
this.httpOnly = httpOnly;
}
+ /**
+ * Checks to see if this {@link Cookie} can be sent along cross-site requests.
+ * For more information, please look
+ * here
+ * @return same-site-flag value
+ */
+ public SameSite sameSite() {
+ return sameSite;
+ }
+
+ /**
+ * Determines if this this {@link Cookie} can be sent along cross-site requests.
+ * For more information, please look
+ * here
+ * @param sameSite same-site-flag value
+ */
+ public void setSameSite(SameSite sameSite) {
+ this.sameSite = sameSite;
+ }
+
@Override
public int hashCode() {
return name().hashCode();
@@ -232,6 +256,9 @@ public class DefaultCookie implements Cookie {
if (isHttpOnly()) {
buf.append(", HTTPOnly");
}
+ if (sameSite() != null) {
+ buf.append(", SameSite=").append(sameSite());
+ }
return buf.toString();
}
}
diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java
index c5a1d7d0e7..b0ee21a92c 100644
--- a/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java
+++ b/codec-http/src/main/java/io/netty/handler/codec/http/cookie/ServerCookieEncoder.java
@@ -15,12 +15,6 @@
*/
package io.netty.handler.codec.http.cookie;
-import static io.netty.handler.codec.http.cookie.CookieUtil.add;
-import static io.netty.handler.codec.http.cookie.CookieUtil.addQuoted;
-import static io.netty.handler.codec.http.cookie.CookieUtil.stringBuilder;
-import static io.netty.handler.codec.http.cookie.CookieUtil.stripTrailingSeparator;
-import static io.netty.util.internal.ObjectUtil.checkNotNull;
-
import io.netty.handler.codec.DateFormatter;
import io.netty.handler.codec.http.HttpConstants;
import io.netty.handler.codec.http.HttpResponse;
@@ -34,6 +28,12 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import static io.netty.handler.codec.http.cookie.CookieUtil.add;
+import static io.netty.handler.codec.http.cookie.CookieUtil.addQuoted;
+import static io.netty.handler.codec.http.cookie.CookieUtil.stringBuilder;
+import static io.netty.handler.codec.http.cookie.CookieUtil.stripTrailingSeparator;
+import static io.netty.util.internal.ObjectUtil.checkNotNull;
+
/**
* A RFC6265 compliant cookie encoder to be used server side,
* so some fields are sent (Version is typically ignored).
@@ -124,6 +124,12 @@ public final class ServerCookieEncoder extends CookieEncoder {
if (cookie.isHttpOnly()) {
add(buf, CookieHeaderNames.HTTPONLY);
}
+ if (cookie instanceof DefaultCookie) {
+ DefaultCookie c = (DefaultCookie) cookie;
+ if (c.sameSite() != null) {
+ add(buf, CookieHeaderNames.SAMESITE, c.sameSite().name());
+ }
+ }
return stripTrailingSeparator(buf);
}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieDecoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieDecoderTest.java
index 2cd69f98dc..24e3361d17 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieDecoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ClientCookieDecoderTest.java
@@ -16,6 +16,7 @@
package io.netty.handler.codec.http.cookie;
import io.netty.handler.codec.DateFormatter;
+import io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite;
import org.junit.Test;
import java.util.ArrayList;
@@ -25,6 +26,8 @@ import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
public class ClientCookieDecoderTest {
@@ -32,7 +35,7 @@ public class ClientCookieDecoderTest {
public void testDecodingSingleCookieV0() {
String cookieString = "myCookie=myValue;expires="
+ DateFormatter.format(new Date(System.currentTimeMillis() + 50000))
- + ";path=/apathsomewhere;domain=.adomainsomewhere;secure;";
+ + ";path=/apathsomewhere;domain=.adomainsomewhere;secure;SameSite=None";
Cookie cookie = ClientCookieDecoder.STRICT.decode(cookieString);
assertNotNull(cookie);
@@ -44,6 +47,9 @@ public class ClientCookieDecoderTest {
cookie.maxAge() >= 40 && cookie.maxAge() <= 60);
assertEquals("/apathsomewhere", cookie.path());
assertTrue(cookie.isSecure());
+
+ assertThat(cookie, is(instanceOf(DefaultCookie.class)));
+ assertEquals(SameSite.None, ((DefaultCookie) cookie).sameSite());
}
@Test
@@ -259,7 +265,7 @@ public class ClientCookieDecoderTest {
"'=KqtH";
Cookie cookie = ClientCookieDecoder.STRICT.decode("bh=\"" + longValue
- + "\";");
+ + "\";");
assertEquals("bh", cookie.name());
assertEquals(longValue, cookie.value());
}
diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java
index 82f813ff47..42f9c21f05 100644
--- a/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java
+++ b/codec-http/src/test/java/io/netty/handler/codec/http/cookie/ServerCookieEncoderTest.java
@@ -32,6 +32,7 @@ import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import io.netty.handler.codec.http.cookie.CookieHeaderNames.SameSite;
import org.junit.Test;
public class ServerCookieEncoderTest {
@@ -41,13 +42,14 @@ public class ServerCookieEncoderTest {
int maxAge = 50;
- String result =
- "myCookie=myValue; Max-Age=50; Expires=(.+?); Path=/apathsomewhere; Domain=.adomainsomewhere; Secure";
- Cookie cookie = new DefaultCookie("myCookie", "myValue");
+ String result = "myCookie=myValue; Max-Age=50; Expires=(.+?); Path=/apathsomewhere;" +
+ " Domain=.adomainsomewhere; Secure; SameSite=Lax";
+ DefaultCookie cookie = new DefaultCookie("myCookie", "myValue");
cookie.setDomain(".adomainsomewhere");
cookie.setMaxAge(maxAge);
cookie.setPath("/apathsomewhere");
cookie.setSecure(true);
+ cookie.setSameSite(SameSite.Lax);
String encodedCookie = ServerCookieEncoder.STRICT.encode(cookie);