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

org.glassfish.grizzly.http.util.CookieParserUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
 * Copyright 2004 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.glassfish.grizzly.http.util;

import static org.glassfish.grizzly.http.util.CookieUtils.COOKIE_VERSION_ONE_STRICT_COMPLIANCE;
import static org.glassfish.grizzly.http.util.CookieUtils.OLD_COOKIE_FORMAT;
import static org.glassfish.grizzly.http.util.CookieUtils.RFC_6265_SUPPORT_ENABLED;
import static org.glassfish.grizzly.http.util.CookieUtils.equalsIgnoreCase;
import static org.glassfish.grizzly.http.util.CookieUtils.getQuotedValueEndPosition;
import static org.glassfish.grizzly.http.util.CookieUtils.getTokenEndPosition;
import static org.glassfish.grizzly.http.util.CookieUtils.isSeparator;
import static org.glassfish.grizzly.http.util.CookieUtils.isWhiteSpace;

import java.text.ParseException;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.Grizzly;
import org.glassfish.grizzly.http.Cookie;
import org.glassfish.grizzly.http.Cookies;
import org.glassfish.grizzly.http.LazyCookieState;
import org.glassfish.grizzly.utils.Charsets;

/**
 * The set of Cookie utility methods for cookie parsing.
 *
 * There is duplication of logic within which we know to be frowned upon, however it is done with performance in mind.
 *
 * @author Grizzly team
 */

public class CookieParserUtils {
    private static final Logger LOGGER = Grizzly.logger(CookieParserUtils.class);

    /**
     * Parses a cookie header after the initial "Cookie:" [WS][$]token[WS]=[WS](token|QV)[;|,] RFC 2965 JVK
     */
    public static void parseClientCookies(Cookies cookies, Buffer buffer, int off, int len) {
        parseClientCookies(cookies, buffer, off, len, COOKIE_VERSION_ONE_STRICT_COMPLIANCE, RFC_6265_SUPPORT_ENABLED);
    }

    /**
     * Parses a cookie header after the initial "Cookie:" [WS][$]token[WS]=[WS](token|QV)[;|,] RFC 2965 JVK
     */
    public static void parseClientCookies(Cookies cookies, Buffer buffer, int off, int len, boolean versionOneStrictCompliance, boolean rfc6265Enabled) {
        if (cookies == null) {
            throw new IllegalArgumentException("cookies cannot be null.");
        }
        if (buffer == null) {
            throw new IllegalArgumentException("buffer cannot be null.");
        }
        if (len <= 0) {
            return;
        }

        if (buffer.hasArray()) {
            parseClientCookies(cookies,
                    /* buffer, */
                    buffer.array(), off + buffer.arrayOffset(), len, versionOneStrictCompliance, rfc6265Enabled);
            return;
        }

        int end = off + len;
        int pos = off;
        int nameStart;
        int nameEnd;
        int valueStart;
        int valueEnd;
        int version = 0;

        Cookie cookie = null;
        LazyCookieState lazyCookie = null;

        boolean isSpecial;
        boolean isQuoted;

        while (pos < end) {
            isSpecial = false;
            isQuoted = false;

            // Skip whitespace and non-token characters (separators)
            while (pos < end && (isSeparator(buffer.get(pos)) || isWhiteSpace(buffer.get(pos)))) {
                pos++;
            }

            if (pos >= end) {
                return;
            }

            // Detect Special cookies
            if (buffer.get(pos) == '$') {
                isSpecial = true;
                pos++;
            }

            // Get the cookie name. This must be a token
            nameStart = pos;
            pos = nameEnd = getTokenEndPosition(buffer, pos, end);

            // Skip whitespace
            while (pos < end && isWhiteSpace(buffer.get(pos))) {
                pos++;
            }

            // Check for an '=' -- This could also be a name-only
            // cookie at the end of the cookie header, so if we
            // are past the end of the header, but we have a name
            // skip to the name-only part.
            if (pos < end && buffer.get(pos) == '=') {

                // Skip whitespace
                do {
                    pos++;
                } while (pos < end && isWhiteSpace(buffer.get(pos)));

                if (pos >= end) {
                    return;
                }

                // Determine what type of value this is, quoted value,
                // token, name-only with an '=', or other (bad)
                switch (buffer.get(pos)) {
                case '"':
                    // Quoted Value
                    isQuoted = true;
                    valueStart = pos + 1; // strip "
                    // getQuotedValue returns the position before
                    // at the last qoute. This must be dealt with
                    // when the bytes are copied into the cookie
                    valueEnd = getQuotedValueEndPosition(buffer, valueStart, end);
                    // We need pos to advance
                    pos = valueEnd;
                    // Handles cases where the quoted value is
                    // unterminated and at the end of the header,
                    // e.g. [myname="value]
                    if (pos >= end) {
                        return;
                    }
                    break;
                case ';':
                case ',':
                    // Name-only cookie with an '=' after the name token
                    // This may not be RFC compliant
                    valueStart = valueEnd = -1;
                    // The position is OK (On a delimiter)
                    break;
                default:
                    if (!isSeparator(buffer.get(pos), versionOneStrictCompliance)) {
                        // Token
                        // Token
                        valueStart = pos;
                        // getToken returns the position at the delimeter
                        // or other non-token character
                        valueEnd = getTokenEndPosition(buffer, valueStart, end, versionOneStrictCompliance);
                        // We need pos to advance
                        pos = valueEnd;
                    } else {
                        // INVALID COOKIE, advance to next delimiter
                        // The starting character of the cookie value was
                        // not valid.
                        LOGGER.fine("Invalid cookie. Value not a token or quoted value");
                        while (pos < end && buffer.get(pos) != ';' && buffer.get(pos) != ',') {
                            pos++;
                        }

                        pos++;
                        // Make sure no special avpairs can be attributed to
                        // the previous cookie by setting the current cookie
                        // to null
                        cookie = null;
                        lazyCookie = null;
                        continue;
                    }
                }
            } else {
                // Name only cookie
                valueStart = valueEnd = -1;
                pos = nameEnd;

            }

            // We should have an avpair or name-only cookie at this
            // point. Perform some basic checks to make sure we are
            // in a good state.

            // Skip whitespace
            while (pos < end && isWhiteSpace(buffer.get(pos))) {
                pos++;
            }

            // Make sure that after the cookie we have a separator. This
            // is only important if this is not the last cookie pair
            while (pos < end && buffer.get(pos) != ';' && buffer.get(pos) != ',') {
                pos++;
            }

            pos++;

            // All checks passed. Add the cookie, start with the
            // special avpairs first
            if (isSpecial) {
                isSpecial = false;
                // $Version must be the first avpair in the cookie header
                // (sc must be null)
                if (CookieUtils.equals("Version", buffer, nameStart, nameEnd) && cookie == null) {
                    if (rfc6265Enabled) {
                        continue;
                    }
                    // Set version
                    if (buffer.get(valueStart) == '1' && valueEnd == valueStart + 1) {
                        version = 1;
                    } else {
                        // unknown version (Versioning is not very strict)
                    }
                    continue;
                }

                // We need an active cookie for Path/Port/etc.
                if (cookie == null) {
                    continue;
                }

                // Domain is more common, so it goes first
                if (CookieUtils.equals("Domain", buffer, nameStart, nameEnd)) {
                    lazyCookie.getDomain().setBuffer(buffer, valueStart, valueEnd);
                    continue;
                }

                if (CookieUtils.equals("Path", buffer, nameStart, nameEnd)) {
                    lazyCookie.getPath().setBuffer(buffer, valueStart, valueEnd);
                    continue;
                }

//                if (CookieUtils.equals("Port", buffer, nameStart, nameEnd)) {
//                    // sc.getPort is not currently implemented.
//                    // sc.getPort().setBytes( bytes,
//                    //                        valueStart,
//                    //                        valueEnd-valueStart );
//                    continue;
//                }

                // Unknown cookie, complain
                LOGGER.fine("Unknown Special Cookie");

            } else { // Normal Cookie
                cookie = cookies.getNextUnusedCookie();
                lazyCookie = cookie.getLazyCookieState();
                if (!rfc6265Enabled && !cookie.isVersionSet()) {
                    cookie.setVersion(version);
                }
                lazyCookie.getName().setBuffer(buffer, nameStart, nameEnd);

                if (valueStart != -1) { // Normal AVPair
                    lazyCookie.getValue().setBuffer(buffer, valueStart, valueEnd);
                    if (isQuoted) {
                        // We know this is a byte value so this is safe
                        unescapeDoubleQuotes(lazyCookie.getValue());
                    }
                } else {
                    // Name Only
                    lazyCookie.getValue().setString("");
                }
            }
        }
    }

    /**
     * Parses a cookie header after the initial "Cookie:" [WS][$]token[WS]=[WS](token|QV)[;|,] RFC 2965 JVK
     */
    public static void parseClientCookies(Cookies cookies, byte[] bytes, int off, int len) {
        parseClientCookies(cookies, bytes, off, len, COOKIE_VERSION_ONE_STRICT_COMPLIANCE, false);
    }

    /**
     * Parses a cookie header after the initial "Cookie:" [WS][$]token[WS]=[WS](token|QV)[;|,] RFC 2965 JVK
     */
    public static void parseClientCookies(Cookies cookies, byte[] bytes, int off, int len, boolean versionOneStrictCompliance, boolean rfc6265Enabled) {

        if (cookies == null) {
            throw new IllegalArgumentException("cookies cannot be null.");
        }
        if (bytes == null) {
            throw new IllegalArgumentException("bytes cannot be null.");
        }
        if (len <= 0) {
            return;
        }
        // keep note of the array offset - we need it for translation
        // into the byte[] but it's also needed when translating positions
        // *back* into the buffer.
//        int arrayOffset = buffer.arrayOffset();
        int end = off /* + arrayOffset */ + len;
        int pos = off /* + arrayOffset */;
        int nameStart;
        int nameEnd;
        int valueStart;
        int valueEnd;
        int version = 0;

        Cookie cookie = null;
        LazyCookieState lazyCookie = null;

        boolean isSpecial;
        boolean isQuoted;

        while (pos < end) {
            isSpecial = false;
            isQuoted = false;

            // Skip whitespace and non-token characters (separators)
            while (pos < end && (isSeparator(bytes[pos]) || isWhiteSpace(bytes[pos]))) {
                pos++;
            }

            if (pos >= end) {
                return;
            }

            // Detect Special cookies
            if (bytes[pos] == '$') {
                isSpecial = true;
                pos++;
            }

            // Get the cookie name. This must be a token
            nameStart = pos;
            pos = nameEnd = getTokenEndPosition(bytes, pos, end);

            // Skip whitespace
            while (pos < end && isWhiteSpace(bytes[pos])) {
                pos++;
            }

            // Check for an '=' -- This could also be a name-only
            // cookie at the end of the cookie header, so if we
            // are past the end of the header, but we have a name
            // skip to the name-only part.
            if (pos < end && bytes[pos] == '=') {

                // Skip whitespace
                do {
                    pos++;
                } while (pos < end && isWhiteSpace(bytes[pos]));

                if (pos >= end) {
                    return;
                }

                // Determine what type of value this is, quoted value,
                // token, name-only with an '=', or other (bad)
                switch (bytes[pos]) {
                case '"':
                    // Quoted Value
                    isQuoted = true;
                    valueStart = pos + 1; // strip "
                    // getQuotedValue returns the position before
                    // at the last qoute. This must be dealt with
                    // when the bytes are copied into the cookie
                    valueEnd = getQuotedValueEndPosition(bytes, valueStart, end);
                    // We need pos to advance
                    pos = valueEnd;
                    // Handles cases where the quoted value is
                    // unterminated and at the end of the header,
                    // e.g. [myname="value]
                    if (pos >= end) {
                        return;
                    }
                    break;
                case ';':
                case ',':
                    // Name-only cookie with an '=' after the name token
                    // This may not be RFC compliant
                    valueStart = valueEnd = -1;
                    // The position is OK (On a delimiter)
                    break;
                default:
                    if (!isSeparator(bytes[pos], versionOneStrictCompliance)) {
                        // Token
                        // Token
                        valueStart = pos;
                        // getToken returns the position at the delimeter
                        // or other non-token character
                        valueEnd = getTokenEndPosition(bytes, valueStart, end, versionOneStrictCompliance);
                        // We need pos to advance
                        pos = valueEnd;
                    } else {
                        // INVALID COOKIE, advance to next delimiter
                        // The starting character of the cookie value was
                        // not valid.
                        LOGGER.fine("Invalid cookie. Value not a token or quoted value");
                        while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') {
                            pos++;
                        }

                        pos++;
                        // Make sure no special avpairs can be attributed to
                        // the previous cookie by setting the current cookie
                        // to null
                        cookie = null;
                        lazyCookie = null;
                        continue;
                    }
                }
            } else {
                // Name only cookie
                valueStart = valueEnd = -1;
                pos = nameEnd;

            }

            // We should have an avpair or name-only cookie at this
            // point. Perform some basic checks to make sure we are
            // in a good state.

            // Skip whitespace
            while (pos < end && isWhiteSpace(bytes[pos])) {
                pos++;
            }

            // Make sure that after the cookie we have a separator. This
            // is only important if this is not the last cookie pair
            while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') {
                pos++;
            }

            pos++;

            // All checks passed. Add the cookie, start with the
            // special avpairs first
            if (isSpecial) {
                isSpecial = false;
                // $Version must be the first avpair in the cookie header
                // (sc must be null)
                if (CookieUtils.equals("Version", bytes, nameStart, nameEnd) && cookie == null) {
                    if (rfc6265Enabled) {
                        continue;
                    }
                    // Set version
                    if (bytes[valueStart] == '1' && valueEnd == valueStart + 1) {
                        version = 1;
                    } else {
                        // unknown version (Versioning is not very strict)
                    }
                    continue;
                }

                // We need an active cookie for Path/Port/etc.
                if (cookie == null) {
                    continue;
                }

                // Domain is more common, so it goes first
                if (CookieUtils.equals("Domain", bytes, nameStart, nameEnd)) {
                    lazyCookie.getDomain().setBytes(bytes, valueStart, valueEnd);
                    continue;
                }

                if (CookieUtils.equals("Path", bytes, nameStart, nameEnd)) {
                    lazyCookie.getPath().setBytes(bytes, valueStart, valueEnd);
                    continue;
                }

                // Unknown cookie, complain
                LOGGER.fine("Unknown Special Cookie");

            } else { // Normal Cookie
                cookie = cookies.getNextUnusedCookie();
                lazyCookie = cookie.getLazyCookieState();
                if (!rfc6265Enabled && !cookie.isVersionSet()) {
                    cookie.setVersion(version);
                }
                lazyCookie.getName().setBytes(bytes, nameStart, nameEnd);

                if (valueStart != -1) { // Normal AVPair
                    lazyCookie.getValue().setBytes(bytes, valueStart, valueEnd);
                    if (isQuoted) {
                        // We know this is a byte value so this is safe
                        unescapeDoubleQuotes(lazyCookie.getValue());
                    }
                } else {
                    // Name Only
                    lazyCookie.getValue().setString("");
                }
            }
        }
    }

    /**
     * Parses a cookie header after the initial "Cookie:" [WS][$]token[WS]=[WS](token|QV)[;|,] RFC 2965 JVK
     */
    public static void parseClientCookies(Cookies cookies, String cookiesStr, boolean versionOneStrictCompliance, boolean rfc6265Enabled) {
        if (cookies == null) {
            throw new IllegalArgumentException("cookies cannot be null.");
        }
        if (cookiesStr == null) {
            throw new IllegalArgumentException("cookieStr cannot be null.");
        }
        if (cookiesStr.length() == 0) {
            return;
        }

        int end = cookiesStr.length();
        int pos = 0;
        int nameStart;
        int nameEnd;
        int valueStart;
        int valueEnd;
        int version = 0;

        Cookie cookie = null;

        boolean isSpecial;
        boolean isQuoted;

        while (pos < end) {
            isSpecial = false;
            isQuoted = false;

            // Skip whitespace and non-token characters (separators)
            while (pos < end && (isSeparator(cookiesStr.charAt(pos)) || isWhiteSpace(cookiesStr.charAt(pos)))) {
                pos++;
            }

            if (pos >= end) {
                return;
            }

            // Detect Special cookies
            if (cookiesStr.charAt(pos) == '$') {
                isSpecial = true;
                pos++;
            }

            // Get the cookie name. This must be a token
            nameStart = pos;
            pos = nameEnd = getTokenEndPosition(cookiesStr, pos, end);

            // Skip whitespace
            while (pos < end && isWhiteSpace(cookiesStr.charAt(pos))) {
                pos++;
            }

            // Check for an '=' -- This could also be a name-only
            // cookie at the end of the cookie header, so if we
            // are past the end of the header, but we have a name
            // skip to the name-only part.
            if (pos < end && cookiesStr.charAt(pos) == '=') {

                // Skip whitespace
                do {
                    pos++;
                } while (pos < end && isWhiteSpace(cookiesStr.charAt(pos)));

                if (pos >= end) {
                    return;
                }

                // Determine what type of value this is, quoted value,
                // token, name-only with an '=', or other (bad)
                switch (cookiesStr.charAt(pos)) {
                case '"':
                    // Quoted Value
                    isQuoted = true;
                    valueStart = pos + 1; // strip "
                    // getQuotedValue returns the position before
                    // at the last qoute. This must be dealt with
                    // when the bytes are copied into the cookie
                    valueEnd = getQuotedValueEndPosition(cookiesStr, valueStart, end);
                    // We need pos to advance
                    pos = valueEnd;
                    // Handles cases where the quoted value is
                    // unterminated and at the end of the header,
                    // e.g. [myname="value]
                    if (pos >= end) {
                        return;
                    }
                    break;
                case ';':
                case ',':
                    // Name-only cookie with an '=' after the name token
                    // This may not be RFC compliant
                    valueStart = valueEnd = -1;
                    // The position is OK (On a delimiter)
                    break;
                default:
                    if (!isSeparator(cookiesStr.charAt(pos), versionOneStrictCompliance)) {
                        // Token
                        // Token
                        valueStart = pos;
                        // getToken returns the position at the delimeter
                        // or other non-token character
                        valueEnd = getTokenEndPosition(cookiesStr, valueStart, end, versionOneStrictCompliance);
                        // We need pos to advance
                        pos = valueEnd;
                    } else {
                        // INVALID COOKIE, advance to next delimiter
                        // The starting character of the cookie value was
                        // not valid.
                        LOGGER.fine("Invalid cookie. Value not a token or quoted value");
                        while (pos < end && cookiesStr.charAt(pos) != ';' && cookiesStr.charAt(pos) != ',') {
                            pos++;
                        }

                        pos++;
                        // Make sure no special avpairs can be attributed to
                        // the previous cookie by setting the current cookie
                        // to null
                        cookie = null;
                        continue;
                    }
                }
            } else {
                // Name only cookie
                valueStart = valueEnd = -1;
                pos = nameEnd;

            }

            // We should have an avpair or name-only cookie at this
            // point. Perform some basic checks to make sure we are
            // in a good state.

            // Skip whitespace
            while (pos < end && isWhiteSpace(cookiesStr.charAt(pos))) {
                pos++;
            }

            // Make sure that after the cookie we have a separator. This
            // is only important if this is not the last cookie pair
            while (pos < end && cookiesStr.charAt(pos) != ';' && cookiesStr.charAt(pos) != ',') {
                pos++;
            }

            pos++;

            // All checks passed. Add the cookie, start with the
            // special avpairs first
            if (isSpecial) {
                isSpecial = false;
                // $Version must be the first avpair in the cookie header
                // (sc must be null)
                if (CookieUtils.equals("Version", cookiesStr, nameStart, nameEnd) && cookie == null) {
                    if (rfc6265Enabled) {
                        continue;
                    }
                    // Set version
                    if (cookiesStr.charAt(valueStart) == '1' && valueEnd == valueStart + 1) {
                        version = 1;
                    } else {
                        // unknown version (Versioning is not very strict)
                    }
                    continue;
                }

                // We need an active cookie for Path/Port/etc.
                if (cookie == null) {
                    continue;
                }

                // Domain is more common, so it goes first
                if (CookieUtils.equals("Domain", cookiesStr, nameStart, nameEnd)) {
                    cookie.setDomain(cookiesStr.substring(valueStart, valueEnd));
                    continue;
                }

                if (CookieUtils.equals("Path", cookiesStr, nameStart, nameEnd)) {
                    cookie.setPath(cookiesStr.substring(valueStart, valueEnd));
                    continue;
                }

                // Unknown cookie, complain
                LOGGER.fine("Unknown Special Cookie");

            } else { // Normal Cookie

                String name = cookiesStr.substring(nameStart, nameEnd);
                String value;

                if (valueStart != -1) { // Normal AVPair
                    if (isQuoted) {
                        // We know this is a byte value so this is safe
                        value = unescapeDoubleQuotes(cookiesStr, valueStart, valueEnd - valueStart);
                    } else {
                        value = cookiesStr.substring(valueStart, valueEnd);
                    }
                } else {
                    // Name Only
                    value = "";
                }

                cookie = cookies.getNextUnusedCookie();
                cookie.setName(name);
                cookie.setValue(value);
                if (!rfc6265Enabled && !cookie.isVersionSet()) {
                    cookie.setVersion(version);
                }
            }
        }
    }

    public static void parseServerCookies(Cookies cookies, byte[] bytes, int off, int len, boolean versionOneStrictCompliance, boolean rfc6265Enabled) {

        if (cookies == null) {
            throw new IllegalArgumentException("cookies cannot be null.");
        }
        if (bytes == null) {
            throw new IllegalArgumentException("bytes cannot be null.");
        }
        if (len <= 0) {
            return;
        }

        // keep note of the array offset - we need it for translation
        // into the byte[] but it's also needed when translating positions
        // *back* into the buffer.
//        int arrayOffset = buffer.arrayOffset();
        int end = off + /* arrayOffset + */ len;
        int pos = off /* + arrayOffset */;
        int nameStart;
        int nameEnd;
        int valueStart;
        int valueEnd;

        Cookie cookie = null;
        LazyCookieState lazyCookie = null;

        boolean isQuoted;

        while (pos < end) {
            isQuoted = false;

            // Skip whitespace and non-token characters (separators)
            while (pos < end && (isSeparator(bytes[pos]) || isWhiteSpace(bytes[pos]))) {
                pos++;
            }

            if (pos >= end) {
                return;
            }

            // Get the cookie name. This must be a token
            nameStart = pos;
            pos = nameEnd = getTokenEndPosition(bytes, pos, end);

            // Skip whitespace
            while (pos < end && isWhiteSpace(bytes[pos])) {
                pos++;
            }

            // Check for an '=' -- This could also be a name-only
            // cookie at the end of the cookie header, so if we
            // are past the end of the header, but we have a name
            // skip to the name-only part.
            if (pos < end && bytes[pos] == '=') {

                // Skip whitespace
                do {
                    pos++;
                } while (pos < end && isWhiteSpace(bytes[pos]));

                if (pos >= end) {
                    return;
                }

                // Determine what type of value this is, quoted value,
                // token, name-only with an '=', or other (bad)
                switch (bytes[pos]) {
                case '"':
                    // Quoted Value
                    isQuoted = true;
                    valueStart = pos + 1; // strip "
                    // getQuotedValue returns the position before
                    // at the last qoute. This must be dealt with
                    // when the bytes are copied into the cookie
                    valueEnd = getQuotedValueEndPosition(bytes, valueStart, end);
                    // We need pos to advance
                    pos = valueEnd;
                    // Handles cases where the quoted value is
                    // unterminated and at the end of the header,
                    // e.g. [myname="value]
                    if (pos >= end) {
                        return;
                    }
                    break;
                case ';':
                case ',':
                    // Name-only cookie with an '=' after the name token
                    // This may not be RFC compliant
                    valueStart = valueEnd = -1;
                    // The position is OK (On a delimiter)
                    break;
                default:
                    if (!isSeparator(bytes[pos], versionOneStrictCompliance)) {
                        // Token
                        // Token
                        valueStart = pos;
                        // getToken returns the position at the delimeter
                        // or other non-token character
                        valueEnd = getTokenEndPosition(bytes, valueStart, end, versionOneStrictCompliance);
                        // We need pos to advance
                        pos = valueEnd;
                    } else {
                        // INVALID COOKIE, advance to next delimiter
                        // The starting character of the cookie value was
                        // not valid.
                        LOGGER.fine("Invalid cookie. Value not a token or quoted value");
                        while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') {
                            pos++;
                        }

                        pos++;
                        // Make sure no special avpairs can be attributed to
                        // the previous cookie by setting the current cookie
                        // to null
                        cookie = null;
                        lazyCookie = null;
                        continue;
                    }
                }
            } else {
                // Name only cookie
                valueStart = valueEnd = -1;
                pos = nameEnd;

            }

            // We should have an avpair or name-only cookie at this
            // point. Perform some basic checks to make sure we are
            // in a good state.

            // Skip whitespace
            while (pos < end && isWhiteSpace(bytes[pos])) {
                pos++;
            }

            // Make sure that after the cookie we have a separator. This
            // is only important if this is not the last cookie pair
            while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') {
                pos++;
            }

            pos++;

            // All checks passed. Add the cookie, start with the
            // special avpairs first
            if (cookie != null) {
                // Domain is more common, so it goes first
                if (lazyCookie.getDomain().isNull() && equalsIgnoreCase("Domain", bytes, nameStart, nameEnd)) {
                    lazyCookie.getDomain().setBytes(bytes, valueStart, valueEnd);
                    continue;
                }

                // Path
                if (lazyCookie.getPath().isNull() && equalsIgnoreCase("Path", bytes, nameStart, nameEnd)) {
                    lazyCookie.getPath().setBytes(bytes, valueStart, valueEnd);
                    continue;
                }

                // Version
                if (CookieUtils.equals("Version", bytes, nameStart, nameEnd)) {
                    if (rfc6265Enabled) {
                        continue;
                    }
                    // Set version
                    if (bytes[valueStart] == '1' && valueEnd == valueStart + 1) {
                        cookie.setVersion(1);
                    } else {
                        // unknown version (Versioning is not very strict)
                    }
                    continue;
                }

                // Comment
                if (lazyCookie.getComment().isNull() && CookieUtils.equals("Comment", bytes, nameStart, nameEnd)) {
                    lazyCookie.getComment().setBytes(bytes, valueStart, valueEnd);
                    continue;
                }

                // Max-Age
                if (cookie.getMaxAge() == -1 && CookieUtils.equals("Max-Age", bytes, nameStart, nameEnd)) {
                    cookie.setMaxAge(Ascii.parseInt(bytes, valueStart, valueEnd - valueStart));
                    continue;
                }

                // Expires
                if ((cookie.getVersion() == 0 || !cookie.isVersionSet()) && cookie.getMaxAge() == -1
                        && equalsIgnoreCase("Expires", bytes, nameStart, nameEnd)) {
                    try {
                        valueEnd = getTokenEndPosition(bytes, valueEnd + 1, end, false);
                        pos = valueEnd + 1;

                        final String expiresDate = new String(bytes, valueStart, valueEnd - valueStart, Charsets.ASCII_CHARSET);

                        final Date date = OLD_COOKIE_FORMAT.get().parse(expiresDate);
                        cookie.setMaxAge(getMaxAgeDelta(date.getTime(), System.currentTimeMillis()) / 1000);
                    } catch (ParseException ignore) {
                    }

                    continue;
                }

                // Secure
                if (!cookie.isSecure() && equalsIgnoreCase("Secure", bytes, nameStart, nameEnd)) {
                    lazyCookie.setSecure(true);
                    continue;
                }

                // HttpOnly
                if (!cookie.isHttpOnly() && CookieUtils.equals("HttpOnly", bytes, nameStart, nameEnd)) {
                    cookie.setHttpOnly(true);
                    continue;
                }

                if (CookieUtils.equals("Discard", bytes, nameStart, nameEnd)) {
                    continue;
                }
            }

            // Normal Cookie
            cookie = cookies.getNextUnusedCookie();
            if (!rfc6265Enabled && !cookie.isVersionSet()) {
                cookie.setVersion(0);
            }
            lazyCookie = cookie.getLazyCookieState();

            lazyCookie.getName().setBytes(bytes, nameStart, nameEnd);

            if (valueStart != -1) { // Normal AVPair
                lazyCookie.getValue().setBytes(bytes, valueStart, valueEnd);
                if (isQuoted) {
                    // We know this is a byte value so this is safe
                    unescapeDoubleQuotes(lazyCookie.getValue());
                }
            } else {
                // Name Only
                lazyCookie.getValue().setString("");
            }
        }
    }

    public static void parseServerCookies(Cookies cookies, Buffer buffer, int off, int len, boolean versionOneStrictCompliance, boolean rfc6265Enabled) {

        if (cookies == null) {
            throw new IllegalArgumentException("cookies cannot be null.");
        }
        if (buffer == null) {
            throw new IllegalArgumentException("buffer cannot be null.");
        }
        if (len <= 0) {
            return;
        }

        if (buffer.hasArray()) {
            parseServerCookies(cookies,
                    /* buffer, */
                    buffer.array(), off + buffer.arrayOffset(), len, versionOneStrictCompliance, rfc6265Enabled);
            return;
        }

        int end = off + len;
        int pos = off;
        int nameStart;
        int nameEnd;
        int valueStart;
        int valueEnd;

        Cookie cookie = null;
        LazyCookieState lazyCookie = null;

        boolean isQuoted;

        while (pos < end) {
            isQuoted = false;

            // Skip whitespace and non-token characters (separators)
            while (pos < end && (isSeparator(buffer.get(pos)) || isWhiteSpace(buffer.get(pos)))) {
                pos++;
            }

            if (pos >= end) {
                return;
            }

            // Get the cookie name. This must be a token
            nameStart = pos;
            pos = nameEnd = getTokenEndPosition(buffer, pos, end);

            // Skip whitespace
            while (pos < end && isWhiteSpace(buffer.get(pos))) {
                pos++;
            }

            // Check for an '=' -- This could also be a name-only
            // cookie at the end of the cookie header, so if we
            // are past the end of the header, but we have a name
            // skip to the name-only part.
            if (pos < end && buffer.get(pos) == '=') {

                // Skip whitespace
                do {
                    pos++;
                } while (pos < end && isWhiteSpace(buffer.get(pos)));

                if (pos >= end) {
                    return;
                }

                // Determine what type of value this is, quoted value,
                // token, name-only with an '=', or other (bad)
                switch (buffer.get(pos)) {
                case '"':
                    // Quoted Value
                    isQuoted = true;
                    valueStart = pos + 1; // strip "
                    // getQuotedValue returns the position before
                    // at the last qoute. This must be dealt with
                    // when the bytes are copied into the cookie
                    valueEnd = getQuotedValueEndPosition(buffer, valueStart, end);
                    // We need pos to advance
                    pos = valueEnd;
                    // Handles cases where the quoted value is
                    // unterminated and at the end of the header,
                    // e.g. [myname="value]
                    if (pos >= end) {
                        return;
                    }
                    break;
                case ';':
                case ',':
                    // Name-only cookie with an '=' after the name token
                    // This may not be RFC compliant
                    valueStart = valueEnd = -1;
                    // The position is OK (On a delimiter)
                    break;
                default:
                    if (!isSeparator(buffer.get(pos), versionOneStrictCompliance)) {
                        // Token
                        // Token
                        valueStart = pos;
                        // getToken returns the position at the delimeter
                        // or other non-token character
                        valueEnd = getTokenEndPosition(buffer, valueStart, end, versionOneStrictCompliance);
                        // We need pos to advance
                        pos = valueEnd;
                    } else {
                        // INVALID COOKIE, advance to next delimiter
                        // The starting character of the cookie value was
                        // not valid.
                        LOGGER.fine("Invalid cookie. Value not a token or quoted value");
                        while (pos < end && buffer.get(pos) != ';' && buffer.get(pos) != ',') {
                            pos++;
                        }

                        pos++;
                        // Make sure no special avpairs can be attributed to
                        // the previous cookie by setting the current cookie
                        // to null
                        cookie = null;
                        lazyCookie = null;
                        continue;
                    }
                }
            } else {
                // Name only cookie
                valueStart = valueEnd = -1;
                pos = nameEnd;

            }

            // We should have an avpair or name-only cookie at this
            // point. Perform some basic checks to make sure we are
            // in a good state.

            // Skip whitespace
            while (pos < end && isWhiteSpace(buffer.get(pos))) {
                pos++;
            }

            // Make sure that after the cookie we have a separator. This
            // is only important if this is not the last cookie pair
            while (pos < end && buffer.get(pos) != ';' && buffer.get(pos) != ',') {
                pos++;
            }

            pos++;

            // All checks passed. Add the cookie, start with the
            // special avpairs first
            if (cookie != null) {
                // Domain is more common, so it goes first
                if (lazyCookie.getDomain().isNull() && equalsIgnoreCase("Domain", buffer, nameStart, nameEnd)) {
                    lazyCookie.getDomain().setBuffer(buffer, valueStart, valueEnd);
                    continue;
                }

                // Path
                if (lazyCookie.getPath().isNull() && equalsIgnoreCase("Path", buffer, nameStart, nameEnd)) {
                    lazyCookie.getPath().setBuffer(buffer, valueStart, valueEnd);
                    continue;
                }

                // Version
                if (CookieUtils.equals("Version", buffer, nameStart, nameEnd)) {
                    if (rfc6265Enabled) {
                        continue;
                    }
                    // Set version
                    if (buffer.get(valueStart) == '1' && valueEnd == valueStart + 1) {
                        cookie.setVersion(1);
                    } else {
                        // unknown version (Versioning is not very strict)
                    }
                    continue;
                }

                // Comment
                if (lazyCookie.getComment().isNull() && CookieUtils.equals("Comment", buffer, nameStart, nameEnd)) {
                    lazyCookie.getComment().setBuffer(buffer, valueStart, valueEnd);
                    continue;
                }

                // Max-Age
                if (cookie.getMaxAge() == -1 && CookieUtils.equals("Max-Age", buffer, nameStart, nameEnd)) {
                    cookie.setMaxAge(Ascii.parseInt(buffer, valueStart, valueEnd - valueStart));
                    continue;
                }

                // Expires
                if ((cookie.getVersion() == 0 || !cookie.isVersionSet()) && cookie.getMaxAge() == -1
                        && equalsIgnoreCase("Expires", buffer, nameStart, nameEnd)) {
                    try {
                        valueEnd = getTokenEndPosition(buffer, valueEnd + 1, end, false);
                        pos = valueEnd + 1;

                        final String expiresDate = buffer.toStringContent(Charsets.ASCII_CHARSET, valueStart, valueEnd);
                        final Date date = OLD_COOKIE_FORMAT.get().parse(expiresDate);
                        cookie.setMaxAge(getMaxAgeDelta(date.getTime(), System.currentTimeMillis()) / 1000);
                    } catch (ParseException ignore) {
                    }

                    continue;
                }

                // Secure
                if (!cookie.isSecure() && equalsIgnoreCase("Secure", buffer, nameStart, nameEnd)) {
                    lazyCookie.setSecure(true);
                    continue;
                }

                // HttpOnly
                if (!cookie.isHttpOnly() && CookieUtils.equals("HttpOnly", buffer, nameStart, nameEnd)) {
                    cookie.setHttpOnly(true);
                    continue;
                }

                if (CookieUtils.equals("Discard", buffer, nameStart, nameEnd)) {
                    continue;
                }
            }

            // Normal Cookie
            cookie = cookies.getNextUnusedCookie();
            if (!rfc6265Enabled && !cookie.isVersionSet()) {
                cookie.setVersion(0);
            }
            lazyCookie = cookie.getLazyCookieState();

            lazyCookie.getName().setBuffer(buffer, nameStart, nameEnd);

            if (valueStart != -1) { // Normal AVPair
                lazyCookie.getValue().setBuffer(buffer, valueStart, valueEnd);
                if (isQuoted) {
                    // We know this is a byte value so this is safe
                    unescapeDoubleQuotes(lazyCookie.getValue());
                }
            } else {
                // Name Only
                lazyCookie.getValue().setString("");
            }
        }
    }

    public static void parseServerCookies(Cookies cookies, String cookiesStr, boolean versionOneStrictCompliance, boolean rfc6265Enabled) {

        if (cookies == null) {
            throw new IllegalArgumentException("cookies cannot be null.");
        }

        if (cookiesStr == null) {
            throw new IllegalArgumentException();
        }
        if (cookiesStr.length() == 0) {
            return;
        }

        int end = cookiesStr.length();
        int pos = 0;
        int nameStart;
        int nameEnd;
        int valueStart;
        int valueEnd;

        Cookie cookie = null;

        boolean isQuoted;

        while (pos < end) {
            isQuoted = false;

            // Skip whitespace and non-token characters (separators)
            while (pos < end && (isSeparator(cookiesStr.charAt(pos)) || isWhiteSpace(cookiesStr.charAt(pos)))) {
                pos++;
            }

            if (pos >= end) {
                return;
            }

            // Get the cookie name. This must be a token
            nameStart = pos;
            pos = nameEnd = getTokenEndPosition(cookiesStr, pos, end);

            // Skip whitespace
            while (pos < end && isWhiteSpace(cookiesStr.charAt(pos))) {
                pos++;
            }

            // Check for an '=' -- This could also be a name-only
            // cookie at the end of the cookie header, so if we
            // are past the end of the header, but we have a name
            // skip to the name-only part.
            if (pos < end && cookiesStr.charAt(pos) == '=') {

                // Skip whitespace
                do {
                    pos++;
                } while (pos < end && isWhiteSpace(cookiesStr.charAt(pos)));

                if (pos >= end) {
                    return;
                }

                // Determine what type of value this is, quoted value,
                // token, name-only with an '=', or other (bad)
                switch (cookiesStr.charAt(pos)) {
                case '"':
                    // Quoted Value
                    isQuoted = true;
                    valueStart = pos + 1; // strip "
                    // getQuotedValue returns the position before
                    // at the last qoute. This must be dealt with
                    // when the bytes are copied into the cookie
                    valueEnd = getQuotedValueEndPosition(cookiesStr, valueStart, end);
                    // We need pos to advance
                    pos = valueEnd;
                    // Handles cases where the quoted value is
                    // unterminated and at the end of the header,
                    // e.g. [myname="value]
                    if (pos >= end) {
                        return;
                    }
                    break;
                case ';':
                case ',':
                    // Name-only cookie with an '=' after the name token
                    // This may not be RFC compliant
                    valueStart = valueEnd = -1;
                    // The position is OK (On a delimiter)
                    break;
                default:
                    if (!isSeparator(cookiesStr.charAt(pos), versionOneStrictCompliance)) {
                        // Token
                        // Token
                        valueStart = pos;
                        // getToken returns the position at the delimeter
                        // or other non-token character
                        valueEnd = getTokenEndPosition(cookiesStr, valueStart, end, versionOneStrictCompliance);
                        // We need pos to advance
                        pos = valueEnd;
                    } else {
                        // INVALID COOKIE, advance to next delimiter
                        // The starting character of the cookie value was
                        // not valid.
                        LOGGER.fine("Invalid cookie. Value not a token or quoted value");
                        while (pos < end && cookiesStr.charAt(pos) != ';' && cookiesStr.charAt(pos) != ',') {
                            pos++;
                        }

                        pos++;
                        // Make sure no special avpairs can be attributed to
                        // the previous cookie by setting the current cookie
                        // to null
                        cookie = null;
                        continue;
                    }
                }
            } else {
                // Name only cookie
                valueStart = valueEnd = -1;
                pos = nameEnd;

            }

            // We should have an avpair or name-only cookie at this
            // point. Perform some basic checks to make sure we are
            // in a good state.

            // Skip whitespace
            while (pos < end && isWhiteSpace(cookiesStr.charAt(pos))) {
                pos++;
            }

            // Make sure that after the cookie we have a separator. This
            // is only important if this is not the last cookie pair
            while (pos < end && cookiesStr.charAt(pos) != ';' && cookiesStr.charAt(pos) != ',') {
                pos++;
            }

            pos++;

            // All checks passed. Add the cookie, start with the
            // special avpairs first
            if (cookie != null) {
                // Domain is more common, so it goes first
                if (cookie.getDomain() == null && equalsIgnoreCase("Domain", cookiesStr, nameStart, nameEnd)) {
                    cookie.setDomain(cookiesStr.substring(valueStart, valueEnd));
                    continue;
                }

                // Path
                if (cookie.getPath() == null && equalsIgnoreCase("Path", cookiesStr, nameStart, nameEnd)) {
                    cookie.setPath(cookiesStr.substring(valueStart, valueEnd));
                    continue;
                }

                // Version
                if (CookieUtils.equals("Version", cookiesStr, nameStart, nameEnd)) {
                    if (rfc6265Enabled) {
                        continue;
                    }
                    // Set version
                    if (cookiesStr.charAt(valueStart) == '1' && valueEnd == valueStart + 1) {
                        cookie.setVersion(1);
                    } else {
                        if (!rfc6265Enabled) {
                            cookie.setVersion(0);
                        }
                    }
                    continue;
                }

                // Comment
                if (cookie.getComment() == null && CookieUtils.equals("Comment", cookiesStr, nameStart, nameEnd)) {
                    cookie.setComment(cookiesStr.substring(valueStart, valueEnd));
                    continue;
                }

                // Max-Age
                if (cookie.getMaxAge() == -1 && CookieUtils.equals("Max-Age", cookiesStr, nameStart, nameEnd)) {
                    cookie.setMaxAge(Integer.parseInt(cookiesStr.substring(valueStart, valueEnd)));
                    continue;
                }

                // Expires
                if ((cookie.getVersion() == 0 || cookie.isVersionSet()) && cookie.getMaxAge() == -1
                        && equalsIgnoreCase("Expires", cookiesStr, nameStart, nameEnd)) {
                    try {
                        valueEnd = getTokenEndPosition(cookiesStr, valueEnd + 1, end, false);
                        pos = valueEnd + 1;
                        final String expiresDate = cookiesStr.substring(valueStart, valueEnd);
                        final Date date = OLD_COOKIE_FORMAT.get().parse(expiresDate);
                        cookie.setMaxAge(getMaxAgeDelta(date.getTime(), System.currentTimeMillis()) / 1000);
                    } catch (ParseException ignore) {
                    }

                    continue;
                }

                // Secure
                if (!cookie.isSecure() && equalsIgnoreCase("Secure", cookiesStr, nameStart, nameEnd)) {
                    cookie.setSecure(true);
                    continue;
                }

                // HttpOnly
                if (!cookie.isHttpOnly() && CookieUtils.equals("HttpOnly", cookiesStr, nameStart, nameEnd)) {
                    cookie.setHttpOnly(true);
                    continue;
                }

                if (CookieUtils.equals("Discard", cookiesStr, nameStart, nameEnd)) {
                    continue;
                }
            }

            // Normal Cookie
            String name = cookiesStr.substring(nameStart, nameEnd);
            String value;

            if (valueStart != -1) { // Normal AVPair
                if (isQuoted) {
                    // We know this is a byte value so this is safe
                    value = unescapeDoubleQuotes(cookiesStr, valueStart, valueEnd - valueStart);
                } else {
                    value = cookiesStr.substring(valueStart, valueEnd);
                }
            } else {
                // Name Only
                value = "";
            }

            cookie = cookies.getNextUnusedCookie();
            if (!rfc6265Enabled && !cookie.isVersionSet()) {
                cookie.setVersion(0);
            }
            cookie.setName(name);
            cookie.setValue(value);
        }
    }

    /**
     * Unescapes any double quotes in the given cookie value.
     *
     * @param dc The cookie value to modify
     */
    public static void unescapeDoubleQuotes(DataChunk dc) {
        switch (dc.getType()) {
        case Bytes:
            unescapeDoubleQuotes(dc.getByteChunk());
            return;
        case Buffer:
            unescapeDoubleQuotes(dc.getBufferChunk());
            return;
        case String:
            final String s = dc.toString();
            dc.setString(unescapeDoubleQuotes(s, 0, s.length()));
            return;
        case Chars:

        default:
            throw new NullPointerException();

        }
    }

    /**
     * Unescapes any double quotes in the given cookie value.
     *
     * @param bc The cookie value to modify
     */
    public static void unescapeDoubleQuotes(final ByteChunk bc) {

        if (bc == null || bc.getLength() == 0) {
            return;
        }

        int src = bc.getStart();
        int end = bc.getEnd();
        int dest = src;
        final byte[] buffer = bc.getBuffer();

        while (src < end) {
            if (buffer[src] == '\\' && src < end && buffer[src + 1] == '"') {
                src++;
            }
            buffer[dest] = buffer[src];
            dest++;
            src++;
        }

        bc.setEnd(dest);
    }

    /**
     * Unescapes any double quotes in the given cookie value.
     *
     * @param bc The cookie value to modify
     */
    public static void unescapeDoubleQuotes(BufferChunk bc) {

        if (bc == null || bc.getLength() == 0) {
            return;
        }

        int src = bc.getStart();
        int end = bc.getEnd();
        int dest = src;
        Buffer buffer = bc.getBuffer();

        while (src < end) {
            if (buffer.get(src) == '\\' && src < end && buffer.get(src + 1) == '"') {
                src++;
            }
            buffer.put(dest, buffer.get(src));
            dest++;
            src++;
        }

        bc.setEnd(dest);
    }

    /**
     * Unescapes any double quotes in the given cookie value.
     *
     * @param cc The cookie value to modify
     */
    @SuppressWarnings("UnusedDeclaration")
    public static void unescapeDoubleQuotes(CharChunk cc) {

        if (cc == null || cc.getLength() == 0) {
            return;
        }

        int src = cc.getStart();
        int end = cc.getLimit();
        int dest = src;
        char[] buffer = cc.getBuffer();

        while (src < end) {
            if (buffer[src] == '\\' && src < end && buffer[src + 1] == '"') {
                src++;
            }
            buffer[dest] = buffer[src];
            dest++;
            src++;
        }

        cc.setLimit(dest);
    }

    /**
     * Un-escapes any double quotes in the given cookie value.
     *
     * @param buffer the cookie buffer.
     * @param start start position.
     * @param length number of bytes to un-escape.
     * @return new length
     */
    @SuppressWarnings("UnusedDeclaration")
    public static int unescapeDoubleQuotes(Buffer buffer, int start, int length) {

        if (buffer == null || length <= 0) {
            return length;
        }

        int src = start;
        int end = src + length;
        int dest = src;

        while (src < end) {
            if (buffer.get(src) == '\\' && src < end && buffer.get(src + 1) == '"') {
                src++;
            }
            buffer.put(dest, buffer.get(src));
            dest++;
            src++;
        }

        return dest - start;
    }

    /**
     * Unescapes any double quotes in the given cookie value.
     *
     * @param s The cookie value to modify
     * @return new String
     */
    public static String unescapeDoubleQuotes(String s, int start, int length) {

        if (s == null || s.length() == 0) {
            return s;
        }

        final StringBuilder sb = new StringBuilder(s.length());

        int src = start;
        int end = src + length;

        while (src < end) {
            if (s.charAt(src) == '\\' && src < end && s.charAt(src + 1) == '"') {
                src++;
            }

            sb.append(s.charAt(src));
            src++;
        }

        return sb.toString();
    }

    private static int getMaxAgeDelta(long date1, long date2) {
        long result = date1 - date2;
        if (result > Integer.MAX_VALUE) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine("Integer overflow when calculating max age delta.  Date: " + date1 + ", current date: " + date2
                        + ".  Using Integer.MAX_VALUE for further calculation.");
            }
            return Integer.MAX_VALUE;
        } else {
            return (int) result;
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy