//
//  ========================================================================
//  Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;

import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletTester;
import org.hamcrest.Matchers;
import org.junit.Test;

import com.acme.DispatchServlet;

/**
 * Simple tests against DispatchServlet.
 */
public class DispatchServletTest
{
    /**
     * As filed in JETTY-978.
     *
     * Security problems in demo dispatch servlet.
     *
     * <blockquote>
     * <p>
     * The dispatcher servlet (com.acme.DispatchServlet) is prone to a Denial of
     * Service vulnerability.
     * </p>
     * <p>
     * This example servlet is meant to be used as a resources dispatcher,
     * however a malicious aggressor may abuse this functionality in order to
     * cause a recursive inclusion. In details, it is possible to abuse the
     * method com.acme.DispatchServlet.doGet(DispatchServlet.java:203) forcing
     * the application to recursively include the "Dispatch" servlet.
     * </p>
     * <p>
     * Dispatch com.acme.DispatchServlet 1 Dispatch /dispatch/* As a result, it
     * is possible to trigger a "java.lang.StackOverflowError" and consequently
     * an internal server error (500).
     * </p>
     * <p>
     * Multiple requests may easily affect the availability of the servlet
     * container. Since this attack can cause the server to consume resources in
     * a non-linear relationship to the size of inputs, it should be considered
     * as a server flaw.
     * </p>
     * <p>
     * The vulnerability seems confined to the example servlet and it does not
     * afflict the Jetty's core."
     * </p>
     * </blockquote>
     *
     * @throws Exception
     */
    @Test
    public void testSelfRefForwardDenialOfService() throws Exception
    {
        ServletTester tester = new ServletTester();
        tester.setContextPath("/tests");

        ServletHolder dispatch = tester.addServlet(DispatchServlet.class,"/dispatch/*");
        tester.addServlet(DefaultServlet.class,"/");
        tester.start();

        StringBuilder req1 = new StringBuilder();
        req1.append("GET /tests/dispatch/includeN/").append(dispatch.getName()).append(" HTTP/1.1\n");
        req1.append("Host: tester\n");
        req1.append("Connection: close\n");
        req1.append("\n");

        String response = tester.getResponses(req1.toString());

        String msg = "Response code on SelfRefDoS";

        assertFalse(msg + " should not be code 500.",response.startsWith("HTTP/1.1 500 "));
        assertTrue(msg + " should return error code 403 (Forbidden)", response.startsWith("HTTP/1.1 403 "));
    }

    @Test
    public void testSelfRefDeep() throws Exception
    {
        ServletTester tester = new ServletTester();
        tester.setContextPath("/tests");
        tester.addServlet(DispatchServlet.class,"/dispatch/*");
        tester.addServlet(DefaultServlet.class,"/");
        tester.start();

        String selfRefs[] =
        { "/dispatch/forward", "/dispatch/includeS", "/dispatch/includeW", "/dispatch/includeN", };

        /*
         * Number of nested dispatch requests. 220 is a good value, as it won't
         * trigger an Error 413 response (Entity too large). Anything larger
         * than 220 will trigger a 413 response.
         */
        int nestedDepth = 220;

        for (String selfRef : selfRefs)
        {
            StringBuilder req1 = new StringBuilder();
            req1.append("GET /tests");
            for (int i = 0; i < nestedDepth; i++)
            {
                req1.append(selfRef);
            }

            req1.append("/ HTTP/1.1\n");
            req1.append("Host: tester\n");
            req1.append("Connection: close\n");
            req1.append("\n");

            String response = tester.getResponses(req1.toString());

            StringBuilder msg = new StringBuilder();
            msg.append("Response code on nested \"").append(selfRef).append("\"");
            msg.append(" (depth:").append(nestedDepth).append(")");

            assertFalse(msg + " should not be code 413 (Request Entity Too Large)," +
                    "the nestedDepth in the TestCase is too large (reduce it)",
                    response.startsWith("HTTP/1.1 413 "));

            assertFalse(msg + " should not be code 500.", response.startsWith("HTTP/1.1 500 "));
            assertThat(response,Matchers.startsWith("HTTP/1.1 403 "));
        }
    }
}
