package org.eclipse.jetty.http;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HttpCookie
{
private static final Logger LOG = LoggerFactory.getLogger(HttpCookie.class);
private static final String __COOKIE_DELIM = "\",;\\ \t";
private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
public static final String = "__HTTP_ONLY__";
private static final String = "__SAME_SITE_";
public static final String = SAME_SITE_COMMENT + "NONE__";
public static final String = SAME_SITE_COMMENT + "LAX__";
public static final String = SAME_SITE_COMMENT + "STRICT__";
public static final String SAME_SITE_DEFAULT_ATTRIBUTE = "org.eclipse.jetty.cookie.sameSiteDefault";
public enum SameSite
{
NONE("None"), STRICT("Strict"), LAX("Lax");
private String attributeValue;
SameSite(String attributeValue)
{
this.attributeValue = attributeValue;
}
public String getAttributeValue()
{
return this.attributeValue;
}
}
private final String _name;
private final String _value;
private final String ;
private final String _domain;
private final long _maxAge;
private final String _path;
private final boolean _secure;
private final int _version;
private final boolean _httpOnly;
private final long _expiration;
private final SameSite _sameSite;
public HttpCookie(String name, String value)
{
this(name, value, -1);
}
public HttpCookie(String name, String value, String domain, String path)
{
this(name, value, domain, path, -1, false, false);
}
public HttpCookie(String name, String value, long maxAge)
{
this(name, value, null, null, maxAge, false, false);
}
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure)
{
this(name, value, domain, path, maxAge, httpOnly, secure, null, 0);
}
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version)
{
this(name, value, domain, path, maxAge, httpOnly, secure, comment, version, null);
}
public HttpCookie(String name, String value, String domain, String path, long maxAge, boolean httpOnly, boolean secure, String comment, int version, SameSite sameSite)
{
_name = name;
_value = value;
_domain = domain;
_path = path;
_maxAge = maxAge;
_httpOnly = httpOnly;
_secure = secure;
_comment = comment;
_version = version;
_expiration = maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(maxAge);
_sameSite = sameSite;
}
public HttpCookie(String setCookie)
{
List<java.net.HttpCookie> cookies = java.net.HttpCookie.parse(setCookie);
if (cookies.size() != 1)
throw new IllegalStateException();
java.net.HttpCookie cookie = cookies.get(0);
_name = cookie.getName();
_value = cookie.getValue();
_domain = cookie.getDomain();
_path = cookie.getPath();
_maxAge = cookie.getMaxAge();
_httpOnly = cookie.isHttpOnly();
_secure = cookie.getSecure();
_comment = cookie.getComment();
_version = cookie.getVersion();
_expiration = _maxAge < 0 ? -1 : System.nanoTime() + TimeUnit.SECONDS.toNanos(_maxAge);
_sameSite = getSameSiteFromComment(cookie.getComment());
}
public String getName()
{
return _name;
}
public String getValue()
{
return _value;
}
public String ()
{
return _comment;
}
public String getDomain()
{
return _domain;
}
public long getMaxAge()
{
return _maxAge;
}
public String getPath()
{
return _path;
}
public boolean isSecure()
{
return _secure;
}
public int getVersion()
{
return _version;
}
public SameSite getSameSite()
{
return _sameSite;
}
public boolean isHttpOnly()
{
return _httpOnly;
}
public boolean isExpired(long timeNanos)
{
return _expiration >= 0 && timeNanos >= _expiration;
}
public String asString()
{
StringBuilder builder = new StringBuilder();
builder.append(getName()).append("=").append(getValue());
if (getDomain() != null)
builder.append(";$Domain=").append(getDomain());
if (getPath() != null)
builder.append(";$Path=").append(getPath());
return builder.toString();
}
private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote)
{
if (quote)
QuotedStringTokenizer.quoteOnly(buf, s);
else
buf.append(s);
}
private static boolean isQuoteNeededForCookie(String s)
{
if (s == null || s.length() == 0)
return true;
if (QuotedStringTokenizer.isQuoted(s))
return false;
for (int i = 0; i < s.length(); i++)
{
char c = s.charAt(i);
if (__COOKIE_DELIM.indexOf(c) >= 0)
return true;
if (c < 0x20 || c >= 0x7f)
throw new IllegalArgumentException("Illegal character in cookie value");
}
return false;
}
public String getSetCookie(CookieCompliance compliance)
{
if (compliance == CookieCompliance.RFC6265)
return getRFC6265SetCookie();
if (compliance == CookieCompliance.RFC2965)
return getRFC2965SetCookie();
throw new IllegalStateException();
}
public String getRFC2965SetCookie()
{
if (_name == null || _name.length() == 0)
throw new IllegalArgumentException("Bad cookie name");
StringBuilder buf = new StringBuilder();
boolean quoteName = isQuoteNeededForCookie(_name);
quoteOnlyOrAppend(buf, _name, quoteName);
buf.append('=');
boolean quoteValue = isQuoteNeededForCookie(_value);
quoteOnlyOrAppend(buf, _value, quoteValue);
boolean hasDomain = _domain != null && _domain.length() > 0;
boolean quoteDomain = hasDomain && isQuoteNeededForCookie(_domain);
boolean hasPath = _path != null && _path.length() > 0;
boolean quotePath = hasPath && isQuoteNeededForCookie(_path);
int version = _version;
if (version == 0 && (_comment != null || quoteName || quoteValue || quoteDomain || quotePath ||
QuotedStringTokenizer.isQuoted(_name) || QuotedStringTokenizer.isQuoted(_value) ||
QuotedStringTokenizer.isQuoted(_path) || QuotedStringTokenizer.isQuoted(_domain)))
version = 1;
if (version == 1)
buf.append(";Version=1");
else if (version > 1)
buf.append(";Version=").append(version);
if (hasPath)
{
buf.append(";Path=");
quoteOnlyOrAppend(buf, _path, quotePath);
}
if (hasDomain)
{
buf.append(";Domain=");
quoteOnlyOrAppend(buf, _domain, quoteDomain);
}
if (_maxAge >= 0)
{
buf.append(";Expires=");
if (_maxAge == 0)
buf.append(__01Jan1970_COOKIE);
else
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * _maxAge);
if (version >= 1)
{
buf.append(";Max-Age=");
buf.append(_maxAge);
}
}
if (_secure)
buf.append(";Secure");
if (_httpOnly)
buf.append(";HttpOnly");
if (_comment != null)
{
buf.append(";Comment=");
quoteOnlyOrAppend(buf, _comment, isQuoteNeededForCookie(_comment));
}
return buf.toString();
}
public String getRFC6265SetCookie()
{
if (_name == null || _name.length() == 0)
throw new IllegalArgumentException("Bad cookie name");
Syntax.requireValidRFC2616Token(_name, "RFC6265 Cookie name");
Syntax.requireValidRFC6265CookieValue(_value);
StringBuilder buf = new StringBuilder();
buf.append(_name).append('=').append(_value == null ? "" : _value);
if (_path != null && _path.length() > 0)
buf.append("; Path=").append(_path);
if (_domain != null && _domain.length() > 0)
buf.append("; Domain=").append(_domain);
if (_maxAge >= 0)
{
buf.append("; Expires=");
if (_maxAge == 0)
buf.append(__01Jan1970_COOKIE);
else
DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * _maxAge);
buf.append("; Max-Age=");
buf.append(_maxAge);
}
if (_secure)
buf.append("; Secure");
if (_httpOnly)
buf.append("; HttpOnly");
if (_sameSite != null)
{
buf.append("; SameSite=");
buf.append(_sameSite.getAttributeValue());
}
return buf.toString();
}
public static boolean (String comment)
{
return comment != null && comment.contains(HTTP_ONLY_COMMENT);
}
public static SameSite (String comment)
{
if (comment != null)
{
if (comment.contains(SAME_SITE_STRICT_COMMENT))
{
return SameSite.STRICT;
}
if (comment.contains(SAME_SITE_LAX_COMMENT))
{
return SameSite.LAX;
}
if (comment.contains(SAME_SITE_NONE_COMMENT))
{
return SameSite.NONE;
}
}
return null;
}
public static SameSite getSameSiteDefault(Attributes contextAttributes)
{
if (contextAttributes == null)
return null;
Object o = contextAttributes.getAttribute(SAME_SITE_DEFAULT_ATTRIBUTE);
if (o == null)
{
if (LOG.isDebugEnabled())
LOG.debug("No default value for SameSite");
return null;
}
if (o instanceof SameSite)
return (SameSite)o;
try
{
SameSite samesite = Enum.valueOf(SameSite.class, o.toString().trim().toUpperCase(Locale.ENGLISH));
contextAttributes.setAttribute(SAME_SITE_DEFAULT_ATTRIBUTE, samesite);
return samesite;
}
catch (Exception e)
{
LOG.warn("Bad default value {} for SameSite", o);
throw new IllegalStateException(e);
}
}
public static String (String comment)
{
if (comment == null)
{
return null;
}
String strippedComment = comment.trim();
strippedComment = StringUtil.strip(strippedComment, HTTP_ONLY_COMMENT);
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_NONE_COMMENT);
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_LAX_COMMENT);
strippedComment = StringUtil.strip(strippedComment, SAME_SITE_STRICT_COMMENT);
return strippedComment.length() == 0 ? null : strippedComment;
}
public static String (String comment, boolean httpOnly, SameSite sameSite)
{
if (comment == null && sameSite == null)
return null;
StringBuilder builder = new StringBuilder();
if (StringUtil.isNotBlank(comment))
{
comment = getCommentWithoutAttributes(comment);
if (StringUtil.isNotBlank(comment))
builder.append(comment);
}
if (httpOnly)
builder.append(HTTP_ONLY_COMMENT);
if (sameSite != null)
{
switch (sameSite)
{
case NONE:
builder.append(SAME_SITE_NONE_COMMENT);
break;
case STRICT:
builder.append(SAME_SITE_STRICT_COMMENT);
break;
case LAX:
builder.append(SAME_SITE_LAX_COMMENT);
break;
default:
throw new IllegalArgumentException(sameSite.toString());
}
}
if (builder.length() == 0)
return null;
return builder.toString();
}
public static class SetCookieHttpField extends HttpField
{
final HttpCookie _cookie;
public SetCookieHttpField(HttpCookie cookie, CookieCompliance compliance)
{
super(HttpHeader.SET_COOKIE, cookie.getSetCookie(compliance));
this._cookie = cookie;
}
public HttpCookie getHttpCookie()
{
return _cookie;
}
}
}