/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.catalina.filters;

import java.io.IOException;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;

import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.junit.Assert;
import org.junit.Test;

import org.apache.tomcat.util.http.RequestUtil;

public class TestCorsFilter {
    private FilterChain filterChain = new TesterFilterChain();

    /*
     * Tests if a GET request is treated as simple request.
     *
     * @See http://www.w3.org/TR/cors/#simple-method
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterSimpleGET() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setMethod("GET");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.SIMPLE.name().toLowerCase(Locale.ENGLISH)));
    }

    /*
     * Tests if a POST request is treated as simple request.
     *
     * @See http://www.w3.org/TR/cors/#simple-method
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterSimplePOST() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setContentType("text/plain");
        request.setMethod("POST");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.SIMPLE.name().toLowerCase(Locale.ENGLISH)));
    }

    /*
     * Tests if a HEAD request is treated as simple request.
     *
     * @See http://www.w3.org/TR/cors/#simple-method
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterSimpleHEAD() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setMethod("HEAD");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.SIMPLE.name().toLowerCase(Locale.ENGLISH)));
    }

    /*
     * Test the presence of specific origin in response, when '*' is not used.
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterSimpleSpecificHeader() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setMethod("POST");
        request.setContentType("text/plain");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.SIMPLE.name().toLowerCase(Locale.ENGLISH)));
    }

    /*
     * Tests the that supports credentials may not be enabled with any origin, '*'.
     *
     * @throws ServletException
     */
    @Test(expected = ServletException.class)
    public void testDoFilterSimpleAnyOriginAndSupportsCredentials() throws ServletException {
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigAnyOriginAndSupportsCredentials());
    }

    /*
     * Tests the presence of the origin (and not '*') in the response, when supports credentials is enabled alongwith
     * any origin, '*'.
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterSimpleAnyOriginAndSupportsCredentialsDisabled() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setMethod("GET");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigAnyOriginAndSupportsCredentialsDisabled());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)
                .equals(TesterFilterConfigs.ANY_ORIGIN));
        Assert.assertNull(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.SIMPLE.name().toLowerCase(Locale.ENGLISH)));
    }

    /*
     * Tests the presence of exposed headers in response, if configured.
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterSimpleWithExposedHeaders() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setMethod("POST");
        request.setContentType("text/plain");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigWithExposedHeaders());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS)
                .equals(TesterFilterConfigs.EXPOSED_HEADERS));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.SIMPLE.name().toLowerCase(Locale.ENGLISH)));
    }

    /*
     * Checks if an OPTIONS request is processed as pre-flight.
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterPreflight() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");
        request.setMethod("OPTIONS");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase(Locale.ENGLISH)));
        Assert.assertTrue(
                request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals("Content-Type"));
    }

    /*
     * Checks if an OPTIONS request is processed as pre-flight where any origin is enabled.
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterPreflightAnyOrigin() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");
        request.setMethod("OPTIONS");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase(Locale.ENGLISH)));
        Assert.assertTrue(
                request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals("Content-Type"));
    }

    /*
     * Checks if an OPTIONS request is processed as pre-flight.
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test
    public void testDoFilterPreflightInvalidOrigin() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://www.example.com");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");
        request.setMethod("OPTIONS");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertEquals(response.getStatus(), HttpServletResponse.SC_FORBIDDEN);
    }

    @Test
    public void testDoFilterPreflightNegativeMaxAge() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");
        request.setMethod("OPTIONS");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfigNegativeMaxAge());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertNull(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_MAX_AGE));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase(Locale.ENGLISH)));
        Assert.assertTrue(
                request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals("Content-Type"));
    }

    @Test
    public void testDoFilterPreflightWithCredentials() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");
        request.setMethod("OPTIONS");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSecureFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(
                response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS).equals("true"));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase(Locale.ENGLISH)));
        Assert.assertTrue(
                request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals("Content-Type"));
    }

    @Test
    public void testDoFilterPreflightWithoutCredentialsAndSpecificOrigin() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");
        request.setMethod("OPTIONS");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigSpecificOriginAndSupportsCredentialsDisabled());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertNull(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.PRE_FLIGHT.name().toLowerCase(Locale.ENGLISH)));
        Assert.assertTrue(
                request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS).equals("Content-Type"));
    }

    /*
     * Negative test, when a CORS request arrives, with no origin header.
     */
    @Test
    public void testDoFilterNoOrigin() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();

        request.setMethod("POST");
        request.setContentType("text/plain");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.NOT_CORS, requestType);

        corsFilter.doFilter(request, response, filterChain);

        Assert.assertFalse(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
    }

    /*
     * Negative test, when a non-CORS request arrives, with an origin header.
     */
    @Test
    public void testDoFilterSameHostWithOrigin01() throws IOException, ServletException {
        doTestDoFilterSameHostWithOrigin("http://localhost:8080", "http", "localhost", 8080, false);
    }

    @Test
    public void testDoFilterSameHostWithOrigin02() throws IOException, ServletException {
        doTestDoFilterSameHostWithOrigin("http://localhost:8080", "https", "localhost", 8080, true);
    }

    @Test
    public void testDoFilterSameHostWithOrigin03() throws IOException, ServletException {
        doTestDoFilterSameHostWithOrigin("http://localhost:8080", "http", "localhost", 8081, true);
    }

    @Test
    public void testDoFilterSameHostWithOrigin04() throws IOException, ServletException {
        doTestDoFilterSameHostWithOrigin("http://localhost:8080", "http", "foo.dev.local", 8080, true);
    }

    @Test
    public void testDoFilterSameHostWithOrigin05() throws IOException, ServletException {
        doTestDoFilterSameHostWithOrigin("https://localhost:8443", "https", "localhost", 8443, false);
    }

    @Test
    public void testDoFilterSameHostWithOrigin06() throws IOException, ServletException {
        doTestDoFilterSameHostWithOrigin("https://localhost", "https", "localhost", 443, false);
    }

    @Test
    public void testDoFilterSameHostWithOrigin07() throws IOException, ServletException {
        doTestDoFilterSameHostWithOrigin("http://localhost", "http", "localhost", 80, false);
    }

    private void doTestDoFilterSameHostWithOrigin(String origin, String scheme, String host, int port, boolean isCors)
            throws IOException, ServletException {

        TesterHttpServletRequest request = new TesterHttpServletRequest();

        request.setMethod("POST");
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, origin);
        request.setScheme(scheme);
        request.setServerName(host);
        request.setServerPort(port);
        request.setContentType("text/plain");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        if (isCors) {
            Assert.assertNotEquals(CorsFilter.CORSRequestType.NOT_CORS, requestType);
        } else {
            Assert.assertEquals(CorsFilter.CORSRequestType.NOT_CORS, requestType);
        }

        corsFilter.doFilter(request, response, filterChain);

        if (isCors) {
            Assert.assertTrue(
                    ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        } else {
            Assert.assertFalse(
                    ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        }
    }

    @Test
    public void testDoFilterInvalidCORSOriginNotAllowed() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "www.google.com");
        request.setMethod("POST");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * A CORS request arrives with a "null" origin which is allowed by default.
     */
    @Test
    public void testDoFilterNullOriginAllowedByDefault() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();

        request.setMethod("POST");
        request.setContentType("text/plain");
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "null");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.SIMPLE, requestType);

        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
    }

    /*
     * A CORS request arrives with a "null" origin which is explicitly allowed by configuration.
     */
    @Test
    public void testDoFilterNullOriginAllowedByConfiguration() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();

        request.setMethod("POST");
        request.setContentType("text/plain");
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "null");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigSpecificOriginNullAllowed());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.SIMPLE, requestType);

        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
    }

    @Test(expected = ServletException.class)
    public void testDoFilterNullRequestNullResponse() throws IOException, ServletException {
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(null, null, filterChain);
    }

    @Test(expected = ServletException.class)
    public void testDoFilterNullRequestResponse() throws IOException, ServletException {
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(null, response, filterChain);
    }

    @Test(expected = ServletException.class)
    public void testDoFilterRequestNullResponse() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(request, null, filterChain);
    }

    @Test
    public void testInitDefaultFilterConfig() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setMethod("GET");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(new FilterConfig() {
            @Override
            public ServletContext getServletContext() {
                return null;
            }

            @Override
            public Enumeration<String> getInitParameterNames() {
                return null;
            }

            @Override
            public String getInitParameter(String name) {
                return null;
            }

            @Override
            public String getFilterName() {
                return null;
            }
        });
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertNull(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN));
        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN)
                .equals(TesterFilterConfigs.HTTPS_WWW_APACHE_ORG));
        Assert.assertTrue(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE)
                .equals(CorsFilter.CORSRequestType.SIMPLE.name().toLowerCase(Locale.ENGLISH)));
    }

    @Test(expected = ServletException.class)
    public void testInitInvalidFilterConfig() throws ServletException {
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigInvalidMaxPreflightAge());
        // If we don't get an exception at this point, then all mocked objects
        // worked as expected.
    }

    /*
     * Tests if a non-simple request is given to simple request handler.
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test(expected = IllegalArgumentException.class)
    public void testNotSimple() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");
        request.setMethod("OPTIONS");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.handleSimpleCORS(request, response, filterChain);
    }

    /*
     * When a non-preflight request is given to a pre-flight request handler.
     *
     * @throws IOException
     *
     * @throws ServletException
     */
    @Test(expected = IllegalArgumentException.class)
    public void testNotPreflight() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setMethod("GET");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.handlePreflightCORS(request, response, filterChain);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testDecorateCORSPropertiesNullRequestNullCORSRequestType() {
        CorsFilter.decorateCORSProperties(null, null);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testDecorateCORSPropertiesNullRequestValidCORSRequestType() {
        CorsFilter.decorateCORSProperties(null, CorsFilter.CORSRequestType.SIMPLE);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testDecorateCORSPropertiesValidRequestNullRequestType() {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        CorsFilter.decorateCORSProperties(request, null);
    }

    @Test
    public void testDecorateCORSPropertiesCORSRequestTypeNotCORS() {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        CorsFilter.decorateCORSProperties(request, CorsFilter.CORSRequestType.NOT_CORS);
        Assert.assertFalse(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
    }

    @Test
    public void testDecorateCORSPropertiesCORSRequestTypeInvalidCORS() {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        CorsFilter.decorateCORSProperties(request, CorsFilter.CORSRequestType.INVALID_CORS);
        Assert.assertNull(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
    }

    @Test
    public void testCheckSimpleRequestTypeAnyOrigin() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://www.w3.org");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.SIMPLE, requestType);
    }

    /*
     * Happy path test, when a valid CORS Simple request arrives.
     *
     * @throws ServletException
     */
    @Test
    public void testCheckSimpleRequestTypeGet() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.SIMPLE, requestType);
    }

    /*
     * Happy path test, when a valid CORS Simple request arrives.
     *
     * @throws ServletException
     */
    @Test
    public void testCheckSimpleRequestTypePost() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setMethod("POST");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.SIMPLE, requestType);
    }

    /*
     * Happy path test, when a valid CORS Simple request arrives.
     *
     * @throws ServletException
     */
    @Test
    public void testCheckActualRequestType() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setMethod("PUT");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.ACTUAL, requestType);
    }

    /*
     * Happy path test, when a valid CORS Simple request arrives.
     *
     * @throws ServletException
     */
    @Test
    public void testCheckActualRequestTypeMethodPOSTNotSimpleHeaders() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setMethod("POST");
        request.setContentType("application/json");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.ACTUAL, requestType);
    }

    /*
     * Happy path test, when a valid CORS Pre-flight request arrives.
     *
     * @throws ServletException
     */
    @Test
    public void testCheckPreFlightRequestType() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Content-Type");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.PRE_FLIGHT, requestType);
    }

    /*
     * when a valid CORS Pre-flight request arrives, with no Access-Control-Request-Method
     */
    @Test
    public void testCheckPreFlightRequestTypeNoACRM() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);

        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.ACTUAL, requestType);
    }

    /*
     * when a valid CORS Pre-flight request arrives, with empty Access-Control-Request-Method
     */
    @Test
    public void testCheckPreFlightRequestTypeEmptyACRM() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.INVALID_CORS, requestType);
    }

    /*
     * Happy path test, when a valid CORS Pre-flight request arrives.
     *
     * @throws ServletException
     */
    @Test
    public void testCheckPreFlightRequestTypeNoHeaders() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.PRE_FLIGHT, requestType);
    }

    /*
     * Section 6.2.3
     *
     * @throws ServletException
     *
     * @throws IOException
     */
    @Test
    public void testCheckPreFlightRequestTypeInvalidRequestMethod() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "POLITE");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * Section Section 6.2.5
     *
     * @throws ServletException
     *
     * @throws IOException
     */
    @Test
    public void testCheckPreFlightRequestTypeUnsupportedRequestMethod() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "TRACE");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * Section Section 6.2.6
     *
     * @throws ServletException
     *
     * @throws IOException
     */
    @Test
    public void testCheckPreFlightRequestTypeUnsupportedRequestHeaders() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "X-ANSWER");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSecureFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * Section Section 6.2.7
     *
     * @throws ServletException
     *
     * @throws IOException
     */
    @Test
    public void testCheckPreFlightRequestTypeAnyOriginNoWithCredentials() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "Origin");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigAnyOriginAndSupportsCredentialsDisabled());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
        Assert.assertNull(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS));
    }

    @Test
    public void testCheckPreFlightRequestTypeOriginNotAllowed() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "www.ebay.com");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSecureFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * Happy path test, when a valid CORS Pre-flight request arrives.
     *
     * @throws ServletException
     */
    @Test
    public void testCheckPreFlightRequestTypeEmptyHeaders() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTP_TOMCAT_APACHE_ORG);
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, "PUT");
        request.setHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, "");
        request.setMethod("OPTIONS");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.PRE_FLIGHT, requestType);
    }

    /*
     * Negative test, when a CORS request arrives, with an empty origin.
     *
     * @throws ServletException
     */
    @Test
    public void testCheckNotCORSRequestTypeEmptyOrigin() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.INVALID_CORS, requestType);
    }

    /*
     * Tests for failure, when a different domain is used, that's not in the allowed list of origins.
     *
     * @throws ServletException
     *
     * @throws IOException
     */
    @Test
    public void testCheckInvalidOrigin() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "www.example.com");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * Tests for failure, when the 'null' origin is used, and it's not in the list of allowed origins.
     */
    @Test
    public void testCheckNullOriginNotAllowed() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "null");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * Tests for failure, when a different sub-domain is used, that's not in the allowed list of origins.
     *
     * @throws ServletException
     *
     * @throws IOException
     */
    @Test
    public void testCheckInvalidOriginNotAllowedSubdomain() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://commons.apache.org");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * PUT is not an allowed request method.
     *
     * @throws ServletException
     *
     * @throws IOException
     */
    @Test
    public void testCheckInvalidRequestMethod() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://tomcat.apache.org");
        request.setMethod("PUT");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * When requestMethod is null
     *
     * @throws ServletException
     */
    @Test
    public void testCheckNullRequestMethod() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://tomcat.apache.org");
        request.setMethod(null);
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.INVALID_CORS, requestType);
    }

    /*
     * "http://tomcat.apache.org" is an allowed origin and "https://tomcat.apache.org" is not, because scheme doesn't
     * match
     *
     * @throws ServletException
     */
    @Test
    public void testCheckForSchemeVariance() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "https://tomcat.apache.org");
        request.setMethod("POST");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * "http://tomcat.apache.org" is an allowed origin and "http://tomcat.apache.org:8080" is not, because ports doesn't
     * match
     *
     * @throws ServletException
     *
     * @throws IOException
     */
    @Test
    public void testCheckForPortVariance() throws ServletException, IOException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        TesterHttpServletResponse response = new TesterHttpServletResponse();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://tomcat.apache.org:8080");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getSpecificOriginFilterConfig());
        corsFilter.doFilter(request, response, filterChain);
        Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
    }

    /*
     * Tests for failure, when an invalid {@link HttpServletRequest} is encountered.
     */
    @Test(expected = IllegalArgumentException.class)
    public void testCheckRequestTypeNull() {
        HttpServletRequest request = null;
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.checkRequestType(request);
    }

    @Test
    public void testJoin() {
        Set<String> elements = new LinkedHashSet<>();
        String separator = ",";
        elements.add("world");
        elements.add("peace");
        String join = CorsFilter.join(elements, separator);
        Assert.assertTrue("world,peace".equals(join));
    }

    @Test
    public void testJoinSingleElement() {
        Set<String> elements = new LinkedHashSet<>();
        String separator = ",";
        elements.add("world");
        String join = CorsFilter.join(elements, separator);
        Assert.assertTrue("world".equals(join));
    }

    @Test
    public void testJoinSepNull() {
        Set<String> elements = new LinkedHashSet<>();
        String separator = null;
        elements.add("world");
        elements.add("peace");
        String join = CorsFilter.join(elements, separator);
        Assert.assertTrue("world,peace".equals(join));
    }

    @Test
    public void testJoinElementsNull() {
        Set<String> elements = null;
        String separator = ",";
        String join = CorsFilter.join(elements, separator);

        Assert.assertNull(join);
    }

    @Test
    public void testJoinOneNullElement() {
        Set<String> elements = new LinkedHashSet<>();
        String separator = ",";
        elements.add(null);
        elements.add("peace");
        String join = CorsFilter.join(elements, separator);
        Assert.assertTrue(",peace".equals(join));
    }

    @Test
    public void testJoinAllNullElements() {
        Set<String> elements = new LinkedHashSet<>();
        String separator = ",";
        elements.add(null);
        elements.add(null);
        String join = CorsFilter.join(elements, separator);
        Assert.assertTrue("".equals(join));
    }

    @Test
    public void testJoinAllEmptyElements() {
        Set<String> elements = new LinkedHashSet<>();
        String separator = ",";
        elements.add("");
        elements.add("");
        String join = CorsFilter.join(elements, separator);
        Assert.assertTrue("".equals(join));
    }

    @Test
    public void testJoinPipeSeparator() {
        Set<String> elements = new LinkedHashSet<>();
        String separator = "|";
        elements.add("world");
        elements.add("peace");
        String join = CorsFilter.join(elements, separator);
        Assert.assertTrue("world|peace".equals(join));
    }

    @Test
    public void testWithFilterConfig() throws ServletException {
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 6);
        Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 4);
        Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
        Assert.assertTrue(corsFilter.isAnyOriginAllowed());
        Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
        Assert.assertFalse(corsFilter.isSupportsCredentials());
        Assert.assertTrue(corsFilter.getPreflightMaxAge() == 1800);
    }

    @Test(expected = ServletException.class)
    public void testWithFilterConfigInvalidPreflightAge() throws ServletException {
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigInvalidMaxPreflightAge());
    }

    @Test
    public void testWithStringParserEmpty() throws ServletException {
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getEmptyFilterConfig());
        Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 0);
        Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 0);
        Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
        Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
        Assert.assertFalse(corsFilter.isSupportsCredentials());
        Assert.assertTrue(corsFilter.getPreflightMaxAge() == 0);
    }

    /*
     * If an init param is null, it's default value will be used.
     *
     * @throws ServletException
     */
    @Test
    public void testWithStringParserNull() throws ServletException {
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getNullFilterConfig());
        Assert.assertTrue(corsFilter.getAllowedHttpHeaders().size() == 6);
        Assert.assertTrue(corsFilter.getAllowedHttpMethods().size() == 4);
        Assert.assertTrue(corsFilter.getAllowedOrigins().size() == 0);
        Assert.assertFalse(corsFilter.isAnyOriginAllowed());
        Assert.assertTrue(corsFilter.getExposedHeaders().size() == 0);
        Assert.assertFalse(corsFilter.isSupportsCredentials());
        Assert.assertTrue(corsFilter.getPreflightMaxAge() == 1800);
    }

    @Test
    public void testValidOrigin() {
        Assert.assertTrue(RequestUtil.isValidOrigin("http://www.w3.org"));
    }

    @Test
    public void testInValidOriginCRLF() {
        Assert.assertFalse(RequestUtil.isValidOrigin("http://www.w3.org\r\n"));
    }

    @Test
    public void testInValidOriginEncodedCRLF1() {
        Assert.assertFalse(RequestUtil.isValidOrigin("http://www.w3.org%0d%0a"));
    }

    @Test
    public void testInValidOriginEncodedCRLF2() {
        Assert.assertFalse(RequestUtil.isValidOrigin("http://www.w3.org%0D%0A"));
    }

    @Test
    public void testInValidOriginEncodedCRLF3() {
        Assert.assertFalse(RequestUtil.isValidOrigin("http://www.w3.org%0%0d%0ad%0%0d%0aa"));
    }

    @Test
    public void testCheckInvalidCRLF1() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://www.w3.org\r\n");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.INVALID_CORS, requestType);
    }

    @Test
    public void testCheckInvalidCRLF2() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://www.w3.org\r\n");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.INVALID_CORS, requestType);
    }

    @Test
    public void testCheckInvalidCRLF3() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://www.w3.org%0d%0a");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.INVALID_CORS, requestType);
    }

    @Test
    public void testCheckInvalidCRLF4() throws ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "http://www.w3.org%0D%0A");
        request.setMethod("GET");
        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.INVALID_CORS, requestType);
    }

    @Test
    public void testDecorateRequestDisabled() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, TesterFilterConfigs.HTTPS_WWW_APACHE_ORG);
        request.setMethod("GET");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getFilterConfigDecorateRequestDisabled());
        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(response.getHeader(CorsFilter.RESPONSE_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN).equals("*"));
        Assert.assertNull(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST));
        Assert.assertNull(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_ORIGIN));
        Assert.assertNull(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_HEADERS));
        Assert.assertNull(request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_REQUEST_TYPE));
    }

    /*
     * A CORS request arrives with a "null" origin which is allowed by default.
     */
    @Test
    public void testContentTypeWithParameter() throws IOException, ServletException {
        TesterHttpServletRequest request = new TesterHttpServletRequest();

        request.setMethod("POST");
        request.setContentType("text/plain;charset=UTF-8");
        request.setHeader(CorsFilter.REQUEST_HEADER_ORIGIN, "null");
        TesterHttpServletResponse response = new TesterHttpServletResponse();

        CorsFilter corsFilter = new CorsFilter();
        corsFilter.init(TesterFilterConfigs.getDefaultFilterConfig());
        CorsFilter.CORSRequestType requestType = corsFilter.checkRequestType(request);
        Assert.assertEquals(CorsFilter.CORSRequestType.SIMPLE, requestType);

        corsFilter.doFilter(request, response, filterChain);

        Assert.assertTrue(
                ((Boolean) request.getAttribute(CorsFilter.HTTP_REQUEST_ATTRIBUTE_IS_CORS_REQUEST)).booleanValue());
    }
}
