CookiesTestCase.java

/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 io.undertow.util;

import io.undertow.UndertowOptions;
import io.undertow.server.CookieStore;
import io.undertow.server.handlers.Cookie;
import io.undertow.testutils.category.UnitTest;
import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.xnio.OptionMap;

import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

/**
 * @author Stuart Douglas
 */
@Category(UnitTest.class)
public class CookiesTestCase {

    @Test
    public void testParsingSetCookieHeaderV0() {

        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        Assert.assertEquals("/", cookie.getPath());
        Assert.assertEquals(date(1999, 11, 9, 23, 12, 40), cookie.getExpires());


        cookie = Cookies.parseSetCookieHeader("SHIPPING=FEDEX; path=/foo; secure");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertEquals("/foo", cookie.getPath());
        Assert.assertTrue(cookie.isSecure());

        cookie = Cookies.parseSetCookieHeader("SHIPPING=FEDEX");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
    }

    @Test
    public void testParsingSetCookieHeaderV1() {
        Cookie cookie = Cookies.parseSetCookieHeader("Customer=\"WILE_E_COYOTE\"; Version=\"1\"; Path=\"/acme\"");
        Assert.assertEquals("Customer", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        Assert.assertEquals("/acme", cookie.getPath());
        Assert.assertEquals(1, cookie.getVersion());


        cookie = Cookies.parseSetCookieHeader("SHIPPING=\"FEDEX\"; path=\"/foo\"; secure; Version=\"1\";");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertEquals("/foo", cookie.getPath());
        Assert.assertTrue(cookie.isSecure());
        Assert.assertEquals(1, cookie.getVersion());
    }

    private static Date date(int year, int month, int day, int hour, int minute, int second) {
        Calendar c = Calendar.getInstance();
        c.set(Calendar.MILLISECOND, 0);
        c.setTimeZone(TimeZone.getTimeZone("GMT"));
        c.set(year, month-1, day, hour, minute, second);
        return c.getTime();
    }

    @Test
    public void testInvalidCookie() {
        final OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 1, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false);
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("\"; CUSTOMER=WILE_E_COYOTE"), options);

        Assert.assertFalse(cookies.containsKey("$Domain"));
        Assert.assertFalse(cookies.containsKey("$Version"));
        Assert.assertFalse(cookies.containsKey("$Path"));

        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());

        cookies = parseRequestCookies(Arrays.asList("; CUSTOMER=WILE_E_COYOTE"), options);

        cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());

        cookies = parseRequestCookies(Arrays.asList("foobar; CUSTOMER=WILE_E_COYOTE"), options);

        cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
    }
    @Test
    public void testRequestCookieDomainPathVersion() {
        final OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 4).
                set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false).
                set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, true).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList(
                "$Version=1; CUSTOMER=WILE_E_COYOTE; $Domain=LOONEY_TUNES; $Path=/"), options);

        // RFC 6265 treats the domain, path and version attributes of an RFC 2109 cookie as a separate cookies
        Assert.assertTrue(cookies.containsKey("$Domain"));
        Assert.assertTrue(cookies.containsKey("$Version"));
        Assert.assertTrue(cookies.containsKey("$Path"));

        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        Assert.assertEquals("LOONEY_TUNES", cookie.getDomain());
        Assert.assertEquals(1, cookie.getVersion());
        Assert.assertEquals("/", cookie.getPath());
    }

    @Test
    public void testRequestCookieDomainPathVersion2() {
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 4).
                set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false).
                set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, true).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList(
                "$Version=1; name1=value1; $Domain=localhost; $Path=/servlet_pluh_cookie_web"), options);

        // RFC 6265 treats the domain, path and version attributes of an RFC 2109 cookie as a separate cookies
        Assert.assertTrue(cookies.containsKey("$Domain"));
        Assert.assertTrue(cookies.containsKey("$Version"));
        Assert.assertTrue(cookies.containsKey("$Path"));

        Cookie cookie = cookies.get("name1");
        Assert.assertEquals("name1", cookie.getName());
        Assert.assertEquals("value1", cookie.getValue());
        Assert.assertEquals("localhost", cookie.getDomain());
        Assert.assertEquals(1, cookie.getVersion());
        Assert.assertEquals("/servlet_pluh_cookie_web", cookie.getPath());
    }

    @Test
    public void testRequestCookieDomainPathVersion3() {
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList(
                "name1=\"value1\"; Domain=example.com; Path=/servlet_pluh_cookie_web"), OptionMap.EMPTY);

        // RFC 6265 treats the domain, path and version attributes of an RFC 2109 cookie as a separate cookies
        Assert.assertTrue(cookies.containsKey("Domain"));
        Assert.assertTrue(cookies.containsKey("Path"));

        Cookie cookie = cookies.get("name1");
        Assert.assertEquals("name1", cookie.getName());
        Assert.assertEquals("\"value1\"", cookie.getValue());
        Assert.assertNull("example.com", cookie.getDomain());
//        Assert.assertNull(1, cookie.getVersion());
        Assert.assertNull("/servlet_pluh_cookie_web", cookie.getPath());
    }

    @Test
    public void testMultipleRequestCookies() {
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 5).
                set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false).
                set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, true).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList(
                "CUSTOMER=WILE_E_COYOTE; $Version=1;SHIPPING=FEDEX; $Domain=LOONEY_TUNES; $Path=/"), options, true, true);

        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());

        cookie = cookies.get("SHIPPING");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertEquals("LOONEY_TUNES", cookie.getDomain());
        Assert.assertEquals(1, cookie.getVersion());
        Assert.assertEquals("/", cookie.getPath());
    }

    @Test
    public void testEqualsInValueNotAllowed() {
        final OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 2, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false);
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("CUSTOMER=WILE_E_COYOTE=THE_COYOTE; SHIPPING=FEDEX"), options);
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
    }

    @Test
    public void testEmptyCookieNames() {
        final OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 5, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false);
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("=foo; CUSTOMER=WILE_E_COYOTE=THE_COYOTE; =foobar; SHIPPING=FEDEX; =bar"), options);
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
        cookie = cookies.get("");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("bar", cookie.getValue());
    }

    @Test
    public void testEqualsInValueAllowed() {
        OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 1, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true);
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("CUSTOMER=WILE_E_COYOTE=THE_COYOTE"), options);
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E_COYOTE=THE_COYOTE", cookie.getValue());
    }

    @Test
    public void testEqualsInValueAllowedInQuotedValue() {
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 2)
                .set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true)
                .set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, false).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE=THE_COYOTE\"; SHIPPING=FEDEX" ), options);
        Assert.assertEquals(2, cookies.size());
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("\"WILE_E_COYOTE=THE_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
    }

    @Test
    public void testEqualsInValueNotAllowedInQuotedValue() {
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 2)
                .set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true)
                .set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, false).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE=THE_COYOTE\"; SHIPPING=FEDEX" ), options);
        Assert.assertEquals(2, cookies.size());
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("\"WILE_E_COYOTE=THE_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
    }

    @Test
    public void testCommaSeparatedCookies() {
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 2)
                .set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false)
                .set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, false).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\", SHIPPING=FEDEX" ), options, true, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0);
        Assert.assertEquals(2, cookies.size());
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("\"WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());

        //also make sure semi colon works as normal
        cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\"; SHIPPING=FEDEX" ), options, true, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0);
        Assert.assertEquals(2, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("\"WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());

        options = OptionMap.builder().addAll(options).set(UndertowOptions.MAX_COOKIES, 5).getMap();
        cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\", BAD_CUSTOMER=\"APPLE\"  IGNORED=PART, SHIPPING=FEDEX" ), options, true, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0);
        Assert.assertEquals(2, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("\"WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());

    }

    @Test
    public void testCommaSeparatedCookiesLegacyMode() {
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 2).
                set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false).
                set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, true).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\", SHIPPING=FEDEX" ), options, true, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0);
        Assert.assertEquals(2, cookies.size());
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("\"WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());

        //also make sure semi colon works as normal
        cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\"; SHIPPING=FEDEX" ), options, true, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0);
        Assert.assertEquals(2, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("\"WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());

        options = OptionMap.builder().addAll(options).set(UndertowOptions.MAX_COOKIES, 5).getMap();
        cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\", BAD_CUSTOMER=\"APPLE\"  IGNORED=PART, SHIPPING=FEDEX" ),  options, true, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0);
        Assert.assertEquals(2, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("\"WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
    }

    @Test
    public void testHttpSeparatorInV0CookieValue() {
        OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 2, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false);
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), options, true, false);
        Assert.assertEquals(2, cookies.size());
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());

        cookies = parseRequestCookies( Arrays.asList("CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), options, true, true);
        Assert.assertEquals(2, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E COYOTE", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());

        cookies = parseRequestCookies(Arrays.asList("CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), options, true, false);
        Assert.assertEquals(2, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());

        cookies = parseRequestCookies(Arrays.asList("CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), options, true, true);
        Assert.assertEquals(2, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
    }

    @Test
    public void testCookieContainsColonInJvmRoute() {
        // "<hostcontroller-name>:<server-name>" (e.g. master:node1) is added as jvmRoute (instance-id) by default in WildFly domain mode.
        // ":" is http separator, so it's not allowed in V0 cookie value.
        // However, we need to allow it exceptionally by default. Because, when Undertow runs as a proxy server (like mod_cluster),
        // we need to handle jvmRoute containing ":" in the request cookie value correctly to maintain the sticky session.
        OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 3, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false);
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), options, true, false);
        Assert.assertEquals(3, cookies.size());
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
        cookie = cookies.get("JSESSIONID");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue());

        cookies = parseRequestCookies(Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E COYOTE; SHIPPING=FEDEX" ), options, true, true);
        Assert.assertEquals(3, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E COYOTE", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
        cookie = cookies.get("JSESSIONID");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue());

        cookies = parseRequestCookies(Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), options, true, false);
        Assert.assertEquals(3, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
        cookie = cookies.get("JSESSIONID");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue());

        cookies = parseRequestCookies(Arrays.asList("JSESSIONID=WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1; CUSTOMER=WILE_E_COYOTE\"; SHIPPING=FEDEX" ), options, true, true);
        Assert.assertEquals(3, cookies.size());
        cookie = cookies.get("CUSTOMER");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("SHIPPING");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("FEDEX", cookie.getValue());
        cookie = cookies.get("JSESSIONID");
        Assert.assertNotNull(cookie);
        Assert.assertEquals("WCGWBPJ8DUmv0fvREqVQZb8E6bzW92iHnzysV_q_.master:node1", cookie.getValue());
    }

    @Test
    public void testQuotedEscapedStringInRequestCookie() {
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 5)
                .set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true)
                .set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, false).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList(
                "Customer=\"WILE_\\\"E_\\\"COYOTE\"; $Version=\"1\"; SHIPPING=\"FEDEX\\\\\"; $Path=\"/acme\";"
                + "foo=\"\\\""), options);

        Cookie cookie = cookies.get("Customer");
        Assert.assertEquals("Customer", cookie.getName());
        Assert.assertEquals("WILE_\"E_\"COYOTE", cookie.getValue()); // backslash escapled double quotes in the value

        cookie = cookies.get("SHIPPING");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX\\\\", cookie.getValue()); // backslash escapled backslash in the value
        Assert.assertEquals("/acme", cookie.getPath());
        Assert.assertEquals(1, cookie.getVersion());

        cookie = cookies.get("foo");
        Assert.assertEquals("foo", cookie.getName());
        Assert.assertEquals("\"\\\"", cookie.getValue()); // unescaped backslash exists at the last of the value
    }

    @Test
    public void testSimpleJSONObjectInRequestCookies() {
        // allowEqualInValue and allowHttpSepartorsV0 needs to be enabled to handle this cookie
        // Also, commaIsSeperator needs to be set to false
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 5)
                .set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true)
                .set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, false).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList(
                "CUSTOMER={\"v1\":1, \"id\":\"some_unique_id\", \"c\":\"http://www.google.com?q=love me\"};"
                + " $Version=1; SHIPPING=FEDEX; $Domain=LOONEY_TUNES; $Path=/"), options, false, true);

        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("{\"v1\":1, \"id\":\"some_unique_id\", \"c\":\"http://www.google.com?q=love me\"}",
               cookie.getValue());

        cookie = cookies.get("SHIPPING");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertEquals("LOONEY_TUNES", cookie.getDomain());
        Assert.assertEquals(1, cookie.getVersion());
        Assert.assertEquals("/", cookie.getPath());
    }

    @Test
    public void testQuotedJSONObjectInRequestCookies() {
        // allowEqualInValue and allowHttpSepartorsV0 needs to be enabled to handle this cookie
        // Also, commaIsSeperator needs to be set to false
        OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 5, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true);
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList(
                "CUSTOMER=\"{\\\"v1\\\":1, \\\"id\\\":\\\"some_unique_id\\\", \\\"c\\\":\\\"http://www.google.com?q=love me\\\"}\";"
                + " $Version=1; SHIPPING=FEDEX; $Domain=LOONEY_TUNES; $Path=/"), options, false, true);

        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("{\"v1\":1, \"id\":\"some_unique_id\", \"c\":\"http://www.google.com?q=love me\"}",
               cookie.getValue());

        cookie = cookies.get("SHIPPING");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertEquals("LOONEY_TUNES", cookie.getDomain());
        Assert.assertEquals(1, cookie.getVersion());
        Assert.assertEquals("/", cookie.getPath());
    }

    @Test
    public void testComplexJSONObjectInRequestCookies() {
        // allowHttpSepartorsV0 needs to be enabled to handle this cookie
        // Also, commaIsSeperator needs to be set to false
        //TODO: this values dont seem correct
        OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 5, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false);
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList(
                "CUSTOMER={ \"accounting\" : [ { \"firstName\" : \"John\", \"lastName\" : \"Doe\", \"age\" : 23 },"
                + " { \"firstName\" : \"Mary\",  \"lastName\" : \"Smith\", \"age\" : 32 }], "
                + "\"sales\" : [ { \"firstName\" : \"Sally\", \"lastName\" : \"Green\", \"age\" : 27 }, "
                + "{ \"firstName\" : \"Jim\", \"lastName\" : \"Galley\", \"age\" : 41 } ] };"
                + " $Version=1; SHIPPING=FEDEX; $Domain=LOONEY_TUNES; $Path=/"), options, false, true);

        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("{ \"accounting\" : [ { \"firstName\" : \"John\", \"lastName\" : \"Doe\", \"age\" : 23 },"
                + " { \"firstName\" : \"Mary\",  \"lastName\" : \"Smith\", \"age\" : 32 }], "
                + "\"sales\" : [ { \"firstName\" : \"Sally\", \"lastName\" : \"Green\", \"age\" : 27 }, "
                + "{ \"firstName\" : \"Jim\", \"lastName\" : \"Galley\", \"age\" : 41 } ] }",
               cookie.getValue());

        cookie = cookies.get("SHIPPING");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertEquals("LOONEY_TUNES", cookie.getDomain());
        Assert.assertEquals(1, cookie.getVersion());
        Assert.assertEquals("/", cookie.getPath());
    }

    @Test
    public void testSameSiteCookie() {
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        Assert.assertEquals("/", cookie.getPath());
        Assert.assertNull(cookie.getSameSiteMode());

        cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/; SameSite=None");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        Assert.assertEquals("/", cookie.getPath());
        Assert.assertEquals("None", cookie.getSameSiteMode());

        cookie = Cookies.parseSetCookieHeader("SHIPPING=FEDEX; path=/foo; SameSite=Strict");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertEquals("/foo", cookie.getPath());
        Assert.assertEquals("Strict", cookie.getSameSiteMode());

        cookie = Cookies.parseSetCookieHeader("SHIPPING=FEDEX; path=/acme; SameSite=Lax");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertEquals("/acme", cookie.getPath());
        Assert.assertEquals("Lax", cookie.getSameSiteMode());

        cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/; SameSite=test"); // invalid SameSite mode
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("WILE_E_COYOTE", cookie.getValue());
        Assert.assertEquals("/", cookie.getPath());
        Assert.assertNull(cookie.getSameSiteMode());
    }

    @Test
    public void testNoDoubleQuoteTermination() {
        OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 4)
                .set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, true)
                .set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, false).getMap();
        Map<String, Cookie> cookies = parseRequestCookies(Arrays.asList("CUSTOMER=\"WILE_E_COYOTE\"; BAD=\"X; SHIPPING=FEDEX"), options, true, LegacyCookieSupport.ALLOW_HTTP_SEPARATORS_IN_V0);
        Assert.assertEquals(2, cookies.size());
        Cookie cookie = cookies.get("CUSTOMER");
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("\"WILE_E_COYOTE\"", cookie.getValue());
        cookie = cookies.get("BAD");
        Assert.assertNull(cookie);
        cookie = cookies.get("SHIPPING");
        Assert.assertEquals("SHIPPING", cookie.getName());
        Assert.assertEquals("FEDEX", cookie.getValue());
        Assert.assertNotNull(cookie);
    }

    // RFC6265 allows US-ASCII characters excluding CTLs, whitespace,
    // double quote, comma, semicolon and backslash as cookie value.
    // This does not change even if value is quoted.
    @Test(expected = IllegalArgumentException.class)
    public void testInvalidRfc6265CookieInValue() {
        // whitespace is not allowed
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_ E_COYOTE; path=/example; domain=example.com");
        Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
    }
    @Test(expected = IllegalArgumentException.class)
    public void testInvalidRfc6265CookieInValue1() {
        // whitespace is not allowed
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_ E_COYOTE\"; path=/example; domain=example.com");
        Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
    }
    @Test(expected = IllegalArgumentException.class)
    public void testInvalidRfc6265CookieInValue2() {
        // double quote si not allowed
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_\\\"E_COYOTE\"; path=/example; domain=example.com");
        Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
    }
    @Test(expected = IllegalArgumentException.class)
    public void testInvalidRfc6265CookieInValue3() {
        // comma is not allowed
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_,E_COYOTE\"; path=/example; domain=example.com");
        Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
    }
    @Test(expected = IllegalArgumentException.class)
    public void testInvalidRfc6265CookieInValue4() {
        // semicolon is not allowed
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_;E_COYOTE\"; path=/example; domain=example.com");
        Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
    }
    @Test(expected = IllegalArgumentException.class)
    public void testInvalidRfc6265CookieInValue5() {
        /// backslash is not allowed
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=\"WILE_\\E_COYOTE\"; path=/example; domain=example.com");
        Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
    }

    // RFC6265 allows any CHAR except CTLs or ";" as cookie path
    @Test(expected = IllegalArgumentException.class)
    public void testInvalidRfc6265CookieInPath() {
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=\"/ex;ample\"; domain=example.com");
        Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
        Rfc6265CookieSupport.validatePath(cookie.getPath());
        Rfc6265CookieSupport.validateDomain(cookie.getDomain());
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidRfc6265CookieInDomain() {
        Cookie cookie = Cookies.parseSetCookieHeader("CUSTOMER=WILE_E_COYOTE; path=/example; domain=\"ex;ample.com\"");
        Rfc6265CookieSupport.validateCookieValue(cookie.getValue());
        Rfc6265CookieSupport.validatePath(cookie.getPath());
        Rfc6265CookieSupport.validateDomain(cookie.getDomain());
    }

    @Test
    public void testMultipleRFC6265() {
        final CookieStore parsedCookies = new CookieStore(  );
        final List<String> toParse = Arrays.asList("CUSTOMER=JOE; CUSTOMER=MONICA");
        final OptionMap options = OptionMap.create(UndertowOptions.MAX_COOKIES, 4, UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false);
        Cookies.parseRequestCookies(toParse, parsedCookies, options);
        Assert.assertEquals(1, parsedCookies.size());
        List<Cookie> lst = parsedCookies.get("CUSTOMER");
        Assert.assertEquals(1, lst.size());
        Cookie cookie = lst.get(0);
        Assert.assertEquals(null, cookie.getPath());
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("MONICA", cookie.getValue());
    }

    @Test
    public void testMultipleRFC2109() {
        final CookieStore parsedCookies = new CookieStore();
        final List<String> toParse = Arrays.asList("$Version=1; CUSTOMER=JOE; $Path=/acme; CUSTOMER=MONICA; $Path=/; $Domain=my_oh_my; NO=META");
        final OptionMap options = OptionMap.builder().set(UndertowOptions.MAX_COOKIES, 8)
                .set(UndertowOptions.ALLOW_EQUALS_IN_COOKIE_VALUE, false)
                .set(UndertowOptions.ENABLE_RFC6265_COOKIE_VALIDATION, false).getMap();
        Cookies.parseRequestCookies(toParse, parsedCookies, options);
        Assert.assertEquals(""+parsedCookies,6, parsedCookies.size());
        List<Cookie> lst = parsedCookies.get("CUSTOMER");
        Assert.assertEquals(2, lst.size());
        Cookie cookie = lst.get(0);
        Assert.assertEquals(1, cookie.getVersion());
        Assert.assertEquals("/acme", cookie.getPath());
        Assert.assertEquals(null, cookie.getDomain());
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("JOE", cookie.getValue());
        cookie = lst.get(1);
        Assert.assertEquals(1, cookie.getVersion());
        Assert.assertEquals("/", cookie.getPath());
        Assert.assertEquals("my_oh_my", cookie.getDomain());
        Assert.assertEquals("CUSTOMER", cookie.getName());
        Assert.assertEquals("MONICA", cookie.getValue());
        lst = parsedCookies.get("NO");
        Assert.assertEquals(1, lst.size());
        cookie = lst.get(0);
        Assert.assertEquals(null, cookie.getPath());
        Assert.assertEquals(null, cookie.getDomain());
        Assert.assertEquals("NO", cookie.getName());
        Assert.assertEquals("META", cookie.getValue());
    }

    static Map<String, Cookie> parseRequestCookies(List<String> cookies, OptionMap options) {
        final CookieStore cookieStore = new CookieStore();
        Cookies.parseRequestCookies(cookies, cookieStore, options);
        return cookieStore.asLegacyMap();
    }

    static Map<String, Cookie> parseRequestCookies(List<String> cookies, OptionMap options, boolean commaIsSeparator, boolean allowHttpSeparatorsInV0) {
        final CookieStore cookieStore = new CookieStore();
        Cookies.parseRequestCookies(cookies, cookieStore, options, commaIsSeparator, allowHttpSeparatorsInV0);
        return cookieStore.asLegacyMap();
    }
}