ContextPathEncodingTest.java

/*
 * Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.servlet.internal;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.glassfish.jersey.internal.util.collection.Value;
import org.glassfish.jersey.internal.util.collection.Values;
import org.glassfish.jersey.servlet.ServletContainer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URI;

/**
 * Context encoding test. See Jersey-4949.
 */
public class ContextPathEncodingTest {
    private static final String PATH = "A%20B";
    private static final String CONTEXT = "c%20ntext";

    @Test
    public void contextEncodingTest() throws ServletException, IOException {
        // In jetty maven plugin, context is set by
        //<configuration>
        //    <scan>10</scan>
        //    <webApp>
        //        <contextPath>/c ntext</contextPath>
        //    </webApp>
        //</configuration>

        //Servlet path is not encoded, context is encoded
        final ServletRequestValues servletRequestValues = new ServletRequestValues(
                "/" + CONTEXT,
                "",
                "/" + CONTEXT + "/" + PATH
        );
        final EncodingTestServletContainer testServletContainer = new EncodingTestServletContainer(
                "/" + CONTEXT + "/",
                "/" + CONTEXT + "/" + PATH
        );
        EncodingTestData testData = new EncodingTestData(servletRequestValues, testServletContainer);

        testData.test();
    }

    @Test
    public void servletPathEncodingTest() throws ServletException, IOException {
        //Servlet path is not encoded, context is encoded
        final ServletRequestValues servletRequestValues = new ServletRequestValues(
                "/",
                "A B",
                "/" + PATH + "/" + PATH
        );
        final EncodingTestServletContainer testServletContainer = new EncodingTestServletContainer(
                "/" + PATH + "/",
                "/" + PATH + "/" + PATH
        );
        EncodingTestData testData = new EncodingTestData(servletRequestValues, testServletContainer);

        testData.test();
    }

    static class EncodingTestData {
        final ServletRequestValues servletRequestValues;
        final EncodingTestServletContainer encodingTestServletContainer;
        final HttpServletRequest httpServletRequest;

        EncodingTestData(ServletRequestValues servletRequestValues, EncodingTestServletContainer encodingTestServletContainer) {
            this.servletRequestValues = servletRequestValues;
            this.encodingTestServletContainer = encodingTestServletContainer;
            this.httpServletRequest = (HttpServletRequest) Proxy.newProxyInstance(getClass().getClassLoader(),
                    new Class[]{HttpServletRequest.class}, new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                           return servletRequestValues.handle(method.getName());
                        }
                    });
        }

        public void test() throws ServletException, IOException {
            encodingTestServletContainer.service(httpServletRequest, (HttpServletResponse) null);
        }

    }

    static class ServletRequestValues {
        final String servletPath;
        final String requestUri;
        final String contextPath;

        ServletRequestValues(String contextPath, String servletPath, String requestUri) {
            this.servletPath = servletPath;
            this.requestUri = requestUri;
            this.contextPath = contextPath;
        }

        Object handle(String name) {
            switch (name) {
                case "getServletPath":
                    return servletPath;
                case "getRequestURI":
                    return requestUri;
                case "getRequestURL":
                    return new StringBuffer(requestUri);
                case "getContextPath":
                    return contextPath;
                default:
                    return null;
            }
        }
    }

    static class EncodingTestServletContainer extends ServletContainer {
        final String baseUri;
        final String requestUri;

        EncodingTestServletContainer(String baseUri, String requestUri) {
            this.baseUri = baseUri;
            this.requestUri = requestUri;
        }

        @Override
        public Value<Integer> service(URI baseUri, URI requestUri, HttpServletRequest request, HttpServletResponse response) {
            Assertions.assertEquals(this.baseUri, baseUri.toASCIIString());
            Assertions.assertEquals(this.requestUri, requestUri.toASCIIString());
            return Values.of(0);
        }

        //Update visibility
        public void service(final HttpServletRequest request, final HttpServletResponse response)
                throws ServletException, IOException {
            super.service(request, response);
        }
    };
}