/*
 * Copyright (c) 2010, 2017 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 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;

import static org.glassfish.grizzly.http.util.CookieUtils.*;

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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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
/** * 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.
Params:
  • dc – The cookie value to modify
/** * 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.
Params:
  • bc – The cookie value to modify
/** * 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.
Params:
  • bc – The cookie value to modify
/** * 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.
Params:
  • cc – The cookie value to modify
/** * 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.
Params:
  • buffer – the cookie buffer.
  • start – start position.
  • length – number of bytes to un-escape.
Returns:new length
/** * 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.
Params:
  • s – The cookie value to modify
Returns:new String
/** * 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; } } }