parse SameSite specifier from cookie comment and move HttpOnly comment parsing to org.eclipse.jetty.http.HttpCookie

Signed-off-by: Bruce MacDonald <brucewmacdonald@gmail.com>
This commit is contained in:
Bruce 2019-08-13 23:56:05 -07:00 committed by Greg Wilkins
parent 7c691acbbe
commit 2e5f6fca4b
3 changed files with 99 additions and 25 deletions

View File

@ -22,6 +22,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
// TODO consider replacing this with java.net.HttpCookie
public class HttpCookie
@ -29,6 +30,18 @@ public class HttpCookie
private static final String __COOKIE_DELIM = "\",;\\ \t";
private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
/**
*If this string is found within the comment parsed with {@link #isHttpOnlyInComment(String)} the check will return true
**/
private static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
/**
*These strings are used by {@link #getSameSiteFromComment(String)} to check for a SameSite specifier in the comment
**/
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__";
public enum SameSite
{
NONE("None"), STRICT("Strict"), LAX("Lax");
@ -88,12 +101,13 @@ public class HttpCookie
_domain = domain;
_path = path;
_maxAge = maxAge;
_httpOnly = httpOnly;
// HttpOnly was supported as a comment in cookie flags before the java.net.HttpCookie implementation so need to check that
_httpOnly = httpOnly || isHttpOnlyInComment(comment);
_secure = secure;
_comment = comment;
_comment = getCommentWithoutFlags(comment);
_version = version;
_expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
_sameSite = sameSite;
_sameSite = sameSite == null ? getSameSiteFromComment(comment) : sameSite;
}
public HttpCookie(String setCookie)
@ -109,13 +123,13 @@ public class HttpCookie
_domain = cookie.getDomain();
_path = cookie.getPath();
_maxAge = cookie.getMaxAge();
_httpOnly = cookie.isHttpOnly();
_httpOnly = cookie.isHttpOnly() || isHttpOnlyInComment(cookie.getComment());
_secure = cookie.getSecure();
_comment = cookie.getComment();
_comment = getCommentWithoutFlags(cookie.getComment());
_version = cookie.getVersion();
_expiration = _maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(_maxAge);
// TODO support for SameSite values has not yet been added to java.net.HttpCookie
_sameSite = null;
// TODO support for SameSite values has not yet been added to java.net.HttpCookie so check comment only
_sameSite = getSameSiteFromComment(cookie.getComment());
}
/**
@ -407,6 +421,68 @@ public class HttpCookie
return buf.toString();
}
private static boolean isHttpOnlyInComment(String comment)
{
return comment != null && comment.contains(HTTP_ONLY_COMMENT);
}
private static SameSite getSameSiteFromComment(String comment)
{
if (comment != null)
{
if (comment.contains(SAME_SITE_NONE_COMMENT))
{
return SameSite.NONE;
}
if (comment.contains(SAME_SITE_LAX_COMMENT))
{
return SameSite.LAX;
}
if (comment.contains(SAME_SITE_STRICT_COMMENT))
{
return SameSite.STRICT;
}
}
return null;
}
private static String getCommentWithoutFlags(String comment)
{
if (comment == null)
{
return null;
}
String strippedComment = comment.trim();
if (isHttpOnlyInComment(strippedComment))
{
strippedComment = StringUtil.strip(strippedComment, HTTP_ONLY_COMMENT);
}
SameSite commentSameSite = getSameSiteFromComment(strippedComment);
if (commentSameSite != null)
{
switch (commentSameSite)
{
case NONE:
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_NONE_COMMENT);
break;
case LAX:
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_LAX_COMMENT);
break;
case STRICT:
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_STRICT_COMMENT);
break;
default:
break;
}
}
return strippedComment.length() == 0 ? null : strippedComment;
}
public static class SetCookieHttpField extends HttpField
{
final HttpCookie _cookie;

View File

@ -100,6 +100,22 @@ public class HttpCookieTest
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, null, -1, HttpCookie.SameSite.STRICT);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict", httpCookie.getRFC6265SetCookie());
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, false, true, "comment__HTTP_ONLY__", -1, null);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly", httpCookie.getRFC6265SetCookie());
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, "__SAME_SITE_NONE__", -1, null);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=None", httpCookie.getRFC6265SetCookie());
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, "__SAME_SITE_LAX__", -1, null);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax", httpCookie.getRFC6265SetCookie());
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, "__SAME_SITE_STRICT__", -1, null);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Strict", httpCookie.getRFC6265SetCookie());
// specified SameSite takes precedence over comment value
httpCookie = new HttpCookie("everything", "value", "domain", "path", 0, true, true, "comment__SAME_SITE_STRICT__", -1, HttpCookie.SameSite.LAX);
assertEquals("everything=value; Path=path; Domain=domain; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Secure; HttpOnly; SameSite=Lax", httpCookie.getRFC6265SetCookie());
String[] badNameExamples = {
"\"name\"",
"name\t",

View File

@ -85,12 +85,6 @@ public class Response implements HttpServletResponse
*/
public static final String SET_INCLUDE_HEADER_PREFIX = "org.eclipse.jetty.server.include.";
/**
* If this string is found within the comment of a cookie added with {@link #addCookie(Cookie)}, then the cookie
* will be set as HTTP ONLY.
*/
public static final String HTTP_ONLY_COMMENT = "__HTTP_ONLY__";
private final HttpChannel _channel;
private final HttpFields _fields = new HttpFields();
private final AtomicInteger _include = new AtomicInteger();
@ -237,18 +231,6 @@ public class Response implements HttpServletResponse
String comment = cookie.getComment();
boolean httpOnly = cookie.isHttpOnly();
if (comment != null)
{
int i = comment.indexOf(HTTP_ONLY_COMMENT);
if (i >= 0)
{
httpOnly = true;
comment = StringUtil.strip(comment.trim(), HTTP_ONLY_COMMENT);
if (comment.length() == 0)
comment = null;
}
}
addCookie(new HttpCookie(
cookie.getName(),
cookie.getValue(),