All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.http.HttpCookie Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
// 
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.Locale;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

// TODO consider replacing this with java.net.HttpCookie (once it supports RFC6265)@deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported.
@Deprecated(since = "2021-05-27")
public class HttpCookie {

    private static final Logger LOG = Log.getLogger(HttpCookie.class);

    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
     */
    public 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_";

    public static final String SAME_SITE_NONE_COMMENT = SAME_SITE_COMMENT + "NONE__";

    public static final String SAME_SITE_LAX_COMMENT = SAME_SITE_COMMENT + "LAX__";

    public static final String SAME_SITE_STRICT_COMMENT = SAME_SITE_COMMENT + "STRICT__";

    /**
     * Name of context attribute with default SameSite cookie value
     */
    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 _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;

    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 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);
        // support for SameSite values has not yet been added to java.net.HttpCookie
        _sameSite = getSameSiteFromComment(cookie.getComment());
    }

    /**
     * @return the cookie name
     */
    public String getName() {
        return _name;
    }

    /**
     * @return the cookie value
     */
    public String getValue() {
        return _value;
    }

    /**
     * @return the cookie comment
     */
    public String getComment() {
        return _comment;
    }

    /**
     * @return the cookie domain
     */
    public String getDomain() {
        return _domain;
    }

    /**
     * @return the cookie max age in seconds
     */
    public long getMaxAge() {
        return _maxAge;
    }

    /**
     * @return the cookie path
     */
    public String getPath() {
        return _path;
    }

    /**
     * @return whether the cookie is valid for secure domains
     */
    public boolean isSecure() {
        return _secure;
    }

    /**
     * @return the cookie version
     */
    public int getVersion() {
        return _version;
    }

    /**
     * @return the cookie SameSite enum attribute
     */
    public SameSite getSameSite() {
        return _sameSite;
    }

    /**
     * @return whether the cookie is valid for the http protocol only
     */
    public boolean isHttpOnly() {
        return _httpOnly;
    }

    /**
     * @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;
    }

    /**
     * @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?
     *
     * @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) {
        switch(compliance) {
            case RFC2965:
                return getRFC2965SetCookie();
            case RFC6265:
                return getRFC6265SetCookie();
            default:
                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");
        if (_sameSite != null) {
            buf.append("; SameSite=");
            buf.append(_sameSite.getAttributeValue());
        }
        return buf.toString();
    }

    public static boolean isHttpOnlyInComment(String comment) {
        return comment != null && comment.contains(HTTP_ONLY_COMMENT);
    }

    public static SameSite getSameSiteFromComment(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;
    }

    /**
     * Get the default value for SameSite cookie attribute, if one
     * has been set for the given context.
     *
     * @param context the context to check for default SameSite value
     * @return the default SameSite value or null if one does not exist
     * @throws IllegalStateException if the default value is not a permitted value
     */
    public static SameSite getSameSiteDefault(ServletContext context) {
        if (context == null)
            return null;
        Object o = context.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));
            context.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 getCommentWithoutAttributes(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 getCommentWithAttributes(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();
    }

    // @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported.
    @Deprecated(since = "2021-05-27")
    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;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy