blob: 7da2df82fb4e92ea64a3bd09578a0ec2d925ad98 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 Cognos Incorporated, IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Cognos Incorporated - initial API and implementation
* IBM Corporation - bug fixes and enhancements
*******************************************************************************/
package org.eclipse.equinox.jsp.jasper;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.equinox.internal.jsp.jasper.JspClassLoader;
import org.osgi.framework.Bundle;
/**
* <p>
* JSPServlet wraps the Apache Jasper Servlet making it appropriate for running in an OSGi environment under the Http Service.
* The Jasper JSPServlet makes use of the Thread Context Classloader to support compile and runtime of JSPs and to accommodate running
* in an OSGi environment, a Bundle is used to provide the similar context normally provided by the webapp.
* </p>
* <p>
* The Jasper Servlet will search the ServletContext to find JSPs, tag library descriptors, and additional information in the web.xml
* as per the JSP 2.0 specification. In addition to the ServletContext this implementation will search the bundle (but not attached
* fragments) for matching resources in a manner consistent with the Http Service's notion of a resource. By using alias and bundleResourcePath the JSP lookup should be in
* line with the resource mapping specified in {102.4} of the OSGi HttpService.
* </p>
* <p>
* TLD discovery is slightly different, to clarify it occurs in one of three ways:
* <ol>
* <li> declarations found in /WEB-INF/web.xml (found either on the bundleResourcePath in the bundle or in the ServletContext)</li>
* <li> tld files found under /WEB-INF (found either on the bundleResourcePath in the bundle or in the ServletContext)</li>
* <li> tld files found in jars on the Bundle-Classpath (see org.eclipse.equinox.internal.jsp.jasper.JSPClassLoader)</li>
* </ol>
* </p>
* <p>
* Other than the setting and resetting of the thread context classloader and additional resource lookups in the bundle the JSPServlet
* is behaviourally consistent with the JSP 2.0 specification and regular Jasper operation.
* </p>
* @noextend This class is not intended to be subclassed by clients.
*/
public class JspServlet extends HttpServlet {
private static class BundlePermissionCollection extends PermissionCollection {
private static final long serialVersionUID = -6365478608043900677L;
private Bundle bundle;
public BundlePermissionCollection(Bundle bundle) {
this.bundle = bundle;
super.setReadOnly();
}
@Override
public void add(Permission permission) {
throw new SecurityException();
}
@Override
public boolean implies(Permission permission) {
return bundle.hasPermission(permission);
}
@Override
public Enumeration<Permission> elements() {
return Collections.enumeration(Collections.emptyList());
}
}
private static final long serialVersionUID = -4110476909131707652L;
private Servlet jspServlet = new org.apache.jasper.servlet.JspServlet();
Bundle bundle;
private URLClassLoader jspLoader;
String bundleResourcePath;
String alias;
public JspServlet(Bundle bundle, String bundleResourcePath, String alias) {
this.bundle = bundle;
this.bundleResourcePath = (bundleResourcePath == null || bundleResourcePath.equals("/")) ? "" : bundleResourcePath; //$NON-NLS-1$ //$NON-NLS-2$
this.alias = (alias == null || alias.equals("/")) ? null : alias; //$NON-NLS-1$
jspLoader = new JspClassLoader(bundle);
}
public JspServlet(Bundle bundle, String bundleResourcePath) {
this(bundle, bundleResourcePath, null);
}
@Override
public void init(ServletConfig config) throws ServletException {
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(jspLoader);
jspServlet.init(new ServletConfigAdaptor(config));
// If a SecurityManager is set we need to override the permissions collection set in Jasper's JSPRuntimeContext
if (System.getSecurityManager() != null) {
try {
Field jspRuntimeContextField = jspServlet.getClass().getDeclaredField("rctxt"); //$NON-NLS-1$
jspRuntimeContextField.setAccessible(true);
Object jspRuntimeContext = jspRuntimeContextField.get(jspServlet);
Field permissionCollectionField = jspRuntimeContext.getClass().getDeclaredField("permissionCollection"); //$NON-NLS-1$
permissionCollectionField.setAccessible(true);
permissionCollectionField.set(jspRuntimeContext, new BundlePermissionCollection(bundle));
} catch (Exception e) {
throw new ServletException("Cannot initialize JSPServlet. Failed to set JSPRuntimeContext permission collection."); //$NON-NLS-1$
}
}
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
@Override
public void destroy() {
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(jspLoader);
jspServlet.destroy();
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String pathInfo = request.getPathInfo();
if (pathInfo != null && pathInfo.startsWith("/WEB-INF/")) { //$NON-NLS-1$
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
ClassLoader original = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(jspLoader);
jspServlet.service(request, response);
} finally {
Thread.currentThread().setContextClassLoader(original);
}
}
@Override
public ServletConfig getServletConfig() {
return jspServlet.getServletConfig();
}
@Override
public String getServletInfo() {
return jspServlet.getServletInfo();
}
private class ServletConfigAdaptor implements ServletConfig {
private ServletConfig config;
private ServletContext context;
public ServletConfigAdaptor(ServletConfig config) {
this.config = config;
this.context = createServletContext();
}
private ServletContext createServletContext() {
ServletContext configServletContext = config.getServletContext();
ServletContextAdaptor adaptor = new ServletContextAdaptor(configServletContext);
return adaptor.createServletContext();
}
@Override
public String getInitParameter(String arg0) {
return config.getInitParameter(arg0);
}
@Override
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
@Override
public ServletContext getServletContext() {
return context;
}
@Override
public String getServletName() {
return config.getServletName();
}
}
final static Map<Method, Method> contextToHandlerMethods;
static {
contextToHandlerMethods = createContextToHandlerMethods();
}
private static Map<Method, Method> createContextToHandlerMethods() {
Map<Method, Method> methods = new HashMap<>();
Method[] handlerMethods = ServletContextAdaptor.class.getDeclaredMethods();
for (int i = 0; i < handlerMethods.length; i++) {
Method handlerMethod = handlerMethods[i];
String name = handlerMethod.getName();
Class<?>[] parameterTypes = handlerMethod.getParameterTypes();
try {
Method m = ServletContext.class.getMethod(name, parameterTypes);
methods.put(m, handlerMethod);
} catch (NoSuchMethodException e) {
// do nothing
}
}
return methods;
}
class ServletContextAdaptor {
private ServletContext delegate;
public ServletContextAdaptor(ServletContext delegate) {
this.delegate = delegate;
}
public ServletContext createServletContext() {
Class<?> clazz = getClass();
ClassLoader classLoader = clazz.getClassLoader();
Class<?>[] interfaces = new Class[] {ServletContext.class};
InvocationHandler handler = createInvocationHandler();
return (ServletContext) Proxy.newProxyInstance(classLoader, interfaces, handler);
}
private InvocationHandler createInvocationHandler() {
return (proxy, method, args) -> ServletContextAdaptor.this.invoke(proxy, method, args);
}
Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Method m = JspServlet.contextToHandlerMethods.get(method);
if (m != null) {
return m.invoke(this, args);
}
return method.invoke(delegate, args);
}
public URL getResource(String name) throws MalformedURLException {
if (alias != null && name.startsWith(alias))
name = name.substring(alias.length());
String resourceName = bundleResourcePath + name;
int lastSlash = resourceName.lastIndexOf('/');
if (lastSlash == -1)
return null;
String path = resourceName.substring(0, lastSlash);
if (path.length() == 0)
path = "/"; //$NON-NLS-1$
String file = sanitizeEntryName(resourceName.substring(lastSlash + 1));
Enumeration<URL> entryPaths = bundle.findEntries(path, file, false);
if (entryPaths != null && entryPaths.hasMoreElements())
return entryPaths.nextElement();
return delegate.getResource(name);
}
private String sanitizeEntryName(String name) {
StringBuffer buffer = null;
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
switch (c) {
case '*' :
case '\\' :
// we need to escape '*' and '\'
if (buffer == null) {
buffer = new StringBuffer(name.length() + 16);
buffer.append(name.substring(0, i));
}
buffer.append('\\').append(c);
break;
default :
if (buffer != null)
buffer.append(c);
break;
}
}
return (buffer == null) ? name : buffer.toString();
}
public InputStream getResourceAsStream(String name) {
try {
URL resourceURL = getResource(name);
if (resourceURL != null)
return resourceURL.openStream();
} catch (IOException e) {
log("Error opening stream for resource '" + name + "'", e); //$NON-NLS-1$ //$NON-NLS-2$
}
return null;
}
public Set<String> getResourcePaths(String name) {
Set<String> result = delegate.getResourcePaths(name);
Enumeration<URL> e = bundle.findEntries(bundleResourcePath + name, null, false);
if (e != null) {
if (result == null)
result = new HashSet<>();
while (e.hasMoreElements()) {
URL entryURL = e.nextElement();
result.add(entryURL.getFile().substring(bundleResourcePath.length()));
}
}
return result;
}
}
}