| /******************************************************************************* |
| * 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: |
| * </p> |
| * <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> |
| * 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; |
| } |
| } |
| } |