Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy ServletHolder class/instance properly during startWebapp #6214

Merged
merged 10 commits into from May 16, 2021
Expand Up @@ -187,7 +187,6 @@ public String toString()

protected class HolderConfig
{

public ServletContext getServletContext()
{
return getServletHandler().getServletContext();
Expand Down
Expand Up @@ -294,6 +294,14 @@ public void setForcedPath(String forcedPath)
_forcedPath = forcedPath;
}

private void setClassFrom(ServletHolder holder) throws ServletException
{
if (_servlet != null || getInstance() != null)
throw new IllegalStateException();
this.setClassName(holder.getClassName());
this.setHeldClass(holder.getHeldClass());
}

public boolean isEnabled()
{
return _enabled;
Expand Down Expand Up @@ -325,8 +333,8 @@ public void doStart()
{
if (LOG.isDebugEnabled())
LOG.debug("JSP file {} for {} mapped to Servlet {}", _forcedPath, getName(), jsp.getClassName());
// set the className for this servlet to the precompiled one
setClassName(jsp.getClassName());
// set the className/servlet/instance for this servlet to the precompiled one
setClassFrom(jsp);
}
else
{
Expand All @@ -336,7 +344,7 @@ public void doStart()
{
if (LOG.isDebugEnabled())
LOG.debug("JSP file {} for {} mapped to JspServlet class {}", _forcedPath, getName(), jsp.getClassName());
setClassName(jsp.getClassName());
setClassFrom(jsp);
//copy jsp init params that don't exist for this servlet
for (Map.Entry<String, String> entry : jsp.getInitParameters().entrySet())
{
Expand Down Expand Up @@ -964,7 +972,6 @@ protected void appendPath(StringBuffer path, String element)

protected class Config extends HolderConfig implements ServletConfig
{

@Override
public String getServletName()
{
Expand Down Expand Up @@ -1225,9 +1232,9 @@ public void dump(Appendable out, String indent) throws IOException
@Override
public String toString()
{
return String.format("%s==%s@%x{jsp=%s,order=%d,inst=%b,async=%b,src=%s}",
return String.format("%s==%s@%x{jsp=%s,order=%d,inst=%b,async=%b,src=%s,%s}",
getName(), getClassName(), hashCode(),
_forcedPath, _initOrder, _servlet != null, isAsyncSupported(), getSource());
_forcedPath, _initOrder, _servlet != null, isAsyncSupported(), getSource(), getState());
}

private class UnavailableServlet extends Wrapper
Expand Down
@@ -0,0 +1,236 @@
//
// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.webapp;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Iterator;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.resource.PathResource;
import org.eclipse.jetty.webapp.jsp.FakePrecompiledJSP;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class ForcedServletTest
{
private Server server;
private LocalConnector connector;

@BeforeEach
public void setup() throws Exception
{
server = new Server();
connector = new LocalConnector(server);
server.addConnector(connector);

WebAppContext context = new ForcedWebAppContext();

// Lets setup the Webapp base resource properly
Path basePath = MavenTestingUtils.getTargetTestingPath(ForcedServletTest.class.getName()).resolve("webapp");
FS.ensureEmpty(basePath);
Path srcWebApp = MavenTestingUtils.getProjectDirPath("src/test/webapp-alt-jsp");
copyDir(srcWebApp, basePath);
copyClass(FakePrecompiledJSP.class, basePath.resolve("WEB-INF/classes"));

// Use the new base
context.setWarResource(new PathResource(basePath));

server.setHandler(context);
// server.setDumpAfterStart(true);
server.start();
}

private void copyClass(Class<?> clazz, Path destClasses) throws IOException
{
String classRelativeFilename = clazz.getName().replace('.', '/') + ".class";
Path destFile = destClasses.resolve(classRelativeFilename);
FS.ensureDirExists(destFile.getParent());

Path srcFile = MavenTestingUtils.getTargetPath("test-classes/" + classRelativeFilename);
Files.copy(srcFile, destFile);
}

private void copyDir(Path src, Path dest) throws IOException
{
FS.ensureDirExists(dest);
for (Iterator<Path> it = Files.list(src).iterator(); it.hasNext(); )
{
Path path = it.next();
if (Files.isRegularFile(path))
Files.copy(path, dest.resolve(path.getFileName()));
else if (Files.isDirectory(path))
copyDir(path, dest.resolve(path.getFileName()));
}
}

@AfterEach
public void teardown()
{
LifeCycle.stop(server);
}

/**
* Test access to a jsp resource defined in the web.xml, but doesn't actually exist.
* <p>
* Think of this as a precompiled jsp entry, but the class doesn't exist.
* </p>
*/
@Test
public void testAccessBadDescriptorEntry() throws Exception
{
StringBuilder rawRequest = new StringBuilder();
rawRequest.append("GET /does/not/exist/index.jsp HTTP/1.1\r\n");
rawRequest.append("Host: local\r\n");
rawRequest.append("Connection: close\r\n");
rawRequest.append("\r\n");

String rawResponse = connector.getResponse(rawRequest.toString());
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
// Since this was a request to a resource ending in `*.jsp`, the RejectUncompiledJspServlet responded
assertEquals(555, response.getStatus());
}

/**
* Test access of a jsp resource that doesn't exist in the base resource or the descriptor.
*/
@Test
public void testAccessNonExistentEntry() throws Exception
{
StringBuilder rawRequest = new StringBuilder();
rawRequest.append("GET /bogus.jsp HTTP/1.1\r\n");
rawRequest.append("Host: local\r\n");
rawRequest.append("Connection: close\r\n");
rawRequest.append("\r\n");

String rawResponse = connector.getResponse(rawRequest.toString());
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
// Since this was a request to a resource ending in `*.jsp`, the RejectUncompiledJspServlet responded
assertEquals(555, response.getStatus());
}

/**
* Test access of a jsp resource that exist in the base resource, but not in the descriptor.
*/
@Test
public void testAccessUncompiledEntry() throws Exception
{
StringBuilder rawRequest = new StringBuilder();
rawRequest.append("GET /hello.jsp HTTP/1.1\r\n");
rawRequest.append("Host: local\r\n");
rawRequest.append("Connection: close\r\n");
rawRequest.append("\r\n");

String rawResponse = connector.getResponse(rawRequest.toString());
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
// status code 555 is from RejectUncompiledJspServlet
assertEquals(555, response.getStatus());
}

/**
* Test access of a jsp resource that does not exist in the base resource, but in the descriptor, as a precompiled jsp entry
*/
@Test
public void testPrecompiledEntry() throws Exception
{
StringBuilder rawRequest = new StringBuilder();
rawRequest.append("GET /precompiled/world.jsp HTTP/1.1\r\n");
rawRequest.append("Host: local\r\n");
rawRequest.append("Connection: close\r\n");
rawRequest.append("\r\n");

String rawResponse = connector.getResponse(rawRequest.toString());
HttpTester.Response response = HttpTester.parseResponse(rawResponse);
assertEquals(200, response.getStatus());

String responseBody = response.getContent();
assertThat(responseBody, containsString("This is the FakePrecompiledJSP"));
}

public static class ForcedWebAppContext extends WebAppContext
{
@Override
protected void startWebapp() throws Exception
{
// This will result in a 404 for all requests that don't belong to a more precise servlet
forceServlet("default", ServletHandler.Default404Servlet.class);
addServletMapping("default", "/");

// This will result in any attempt to use an JSP that isn't precompiled and in the descriptor with status code 555
forceServlet("jsp", RejectUncompiledJspServlet.class);
addServletMapping("jsp", "*.jsp");

super.startWebapp();
}

private void addServletMapping(String name, String pathSpec)
{
ServletMapping mapping = new ServletMapping();
mapping.setServletName(name);
mapping.setPathSpec(pathSpec);
getServletHandler().addServletMapping(mapping);
}

private void forceServlet(String name, Class<? extends HttpServlet> servlet) throws Exception
{
ServletHandler handler = getServletHandler();

// Remove existing holder
handler.setServlets(Arrays.stream(handler.getServlets())
.filter(h -> !h.getName().equals(name))
.toArray(ServletHolder[]::new));

// add the forced servlet
ServletHolder holder = new ServletHolder(servlet.getConstructor().newInstance());
holder.setInitOrder(1);
holder.setName(name);
holder.setAsyncSupported(true);
handler.addServlet(holder);
}
}

public static class RejectUncompiledJspServlet extends HttpServlet
{
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
log(String.format("Uncompiled JSPs not supported by %s", request.getRequestURI()));
response.sendError(555);
}
}
}
@@ -0,0 +1,37 @@
//
gregw marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this one an inner class of ForcedServletTest too.

// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.webapp.jsp;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FakeJspServlet extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().println("This is the FakeJspServlet");
}
}
@@ -0,0 +1,37 @@
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this an inner class.

// ========================================================================
// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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.webapp.jsp;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FakePrecompiledJSP extends HttpServlet
{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
resp.setStatus(HttpServletResponse.SC_OK);
resp.getWriter().println("This is the FakePrecompiledJSP");
}
}