//
//  ========================================================================
//  Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.http;

import java.util.List;
import java.util.concurrent.TimeUnit;

import org.eclipse.jetty.util.QuotedStringTokenizer;

// TODO consider replacing this with java.net.HttpCookie
public class HttpCookie
{
    private static final String __COOKIE_DELIM = "\",;\\ \t";
    private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();

    private final String _name;
    private final String _value;
    private final String _comment;
    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;

    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)
    {
        _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);
    }

    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);
    }

    
Returns:the cookie name
/** * @return the cookie name */
public String getName() { return _name; }
Returns:the cookie value
/** * @return the cookie value */
public String getValue() { return _value; }
Returns:the cookie comment
/** * @return the cookie comment */
public String getComment() { return _comment; }
Returns:the cookie domain
/** * @return the cookie domain */
public String getDomain() { return _domain; }
Returns:the cookie max age in seconds
/** * @return the cookie max age in seconds */
public long getMaxAge() { return _maxAge; }
Returns:the cookie path
/** * @return the cookie path */
public String getPath() { return _path; }
Returns:whether the cookie is valid for secure domains
/** * @return whether the cookie is valid for secure domains */
public boolean isSecure() { return _secure; }
Returns:the cookie version
/** * @return the cookie version */
public int getVersion() { return _version; }
Returns:whether the cookie is valid for the http protocol only
/** * @return whether the cookie is valid for the http protocol only */
public boolean isHttpOnly() { return _httpOnly; }
Params:
  • timeNanos – the time to check for cookie expiration, in nanoseconds
Returns:whether the cookie is expired by the given time
/** * @param timeNanos the time to check for cookie expiration, in nanoseconds * @return whether the cookie is expired by the given time */
public boolean isExpired(long timeNanos) { return _expiration >= 0 && timeNanos >= _expiration; }
Returns:a string representation of this cookie
/** * @return a string representation of this cookie */
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); }
Does a cookie value need to be quoted?
Params:
  • s – value string
Throws:
Returns:true if quoted;
/** * Does a cookie value need to be quoted? * * @param s value string * @return true if quoted; * @throws IllegalArgumentException If there a control characters in the string */
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() { // Check arguments if (_name == null || _name.length() == 0) throw new IllegalArgumentException("Bad cookie name"); // Format value and params StringBuilder buf = new StringBuilder(); // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting boolean quoteName = isQuoteNeededForCookie(_name); quoteOnlyOrAppend(buf, _name, quoteName); buf.append('='); // Append the value boolean quoteValue = isQuoteNeededForCookie(_value); quoteOnlyOrAppend(buf, _value, quoteValue); // Look for domain and path fields and check if they need to be quoted boolean hasDomain = _domain != null && _domain.length() > 0; boolean quoteDomain = hasDomain && isQuoteNeededForCookie(_domain); boolean hasPath = _path != null && _path.length() > 0; boolean quotePath = hasPath && isQuoteNeededForCookie(_path); // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted 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; // Append version if (version == 1) buf.append(";Version=1"); else if (version > 1) buf.append(";Version=").append(version); // Append path if (hasPath) { buf.append(";Path="); quoteOnlyOrAppend(buf, _path, quotePath); } // Append domain if (hasDomain) { buf.append(";Domain="); quoteOnlyOrAppend(buf, _domain, quoteDomain); } // Handle max-age and/or expires if (_maxAge >= 0) { // Always use expires // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies buf.append(";Expires="); if (_maxAge == 0) buf.append(__01Jan1970_COOKIE); else DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * _maxAge); // for v1 cookies, also send max-age if (version >= 1) { buf.append(";Max-Age="); buf.append(_maxAge); } } // add the other fields 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() { // Check arguments if (_name == null || _name.length() == 0) throw new IllegalArgumentException("Bad cookie name"); // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting // Per RFC6265, Cookie.name follows RFC2616 Section 2.2 token rules Syntax.requireValidRFC2616Token(_name, "RFC6265 Cookie name"); // Ensure that Per RFC6265, Cookie.value follows syntax rules Syntax.requireValidRFC6265CookieValue(_value); // Format value and params StringBuilder buf = new StringBuilder(); buf.append(_name).append('=').append(_value == null ? "" : _value); // Append path if (_path != null && _path.length() > 0) buf.append("; Path=").append(_path); // Append domain if (_domain != null && _domain.length() > 0) buf.append("; Domain=").append(_domain); // Handle max-age and/or expires if (_maxAge >= 0) { // Always use expires // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies 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); } // add the other fields if (_secure) buf.append("; Secure"); if (_httpOnly) buf.append("; HttpOnly"); return buf.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; } } }