blob: 3b23e070c577d5d6ccc3c33ff5a09388d50347b0 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.sling.servlets.resolver.internal;
import static org.apache.sling.api.SlingConstants.ERROR_MESSAGE;
import static org.apache.sling.api.SlingConstants.ERROR_SERVLET_NAME;
import static org.apache.sling.api.SlingConstants.ERROR_STATUS;
import static org.apache.sling.api.SlingConstants.SLING_CURRENT_SERVLET_NAME;
import static org.apache.sling.api.servlets.ServletResolverConstants.DEFAULT_ERROR_HANDLER_RESOURCE_TYPE;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.Servlet;
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.apache.sling.api.SlingConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.request.RequestProgressTracker;
import org.apache.sling.api.request.RequestUtil;
import org.apache.sling.api.request.SlingRequestEvent;
import org.apache.sling.api.request.SlingRequestListener;
import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.SyntheticResource;
import org.apache.sling.api.servlets.OptingServlet;
import org.apache.sling.api.servlets.ServletResolver;
import org.apache.sling.api.servlets.ServletResolverConstants;
import org.apache.sling.engine.servlets.ErrorHandler;
import org.apache.sling.serviceusermapping.ServiceUserMapped;
import org.apache.sling.servlets.resolver.internal.defaults.DefaultErrorHandlerServlet;
import org.apache.sling.servlets.resolver.internal.defaults.DefaultServlet;
import org.apache.sling.servlets.resolver.internal.helper.AbstractResourceCollector;
import org.apache.sling.servlets.resolver.internal.helper.NamedScriptResourceCollector;
import org.apache.sling.servlets.resolver.internal.helper.ResourceCollector;
import org.apache.sling.servlets.resolver.internal.resolution.ResolutionCache;
import org.apache.sling.servlets.resolver.internal.resource.MergingServletResourceProvider;
import org.apache.sling.servlets.resolver.internal.resource.SlingServletConfig;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>SlingServletResolver</code> resolves a
* servlet for a request by implementing the {@link ServletResolver} interface.
*
* The resolver uses an own session to find the scripts.
*
*/
@Component(name = ResolverConfig.PID,
service = { ServletResolver.class, ErrorHandler.class, SlingRequestListener.class },
property = {
Constants.SERVICE_DESCRIPTION + "=Apache Sling Servlet Resolver and Error Handler",
Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
})
@Designate(ocd = ResolverConfig.class)
public class SlingServletResolver
implements ServletResolver,
SlingRequestListener,
ErrorHandler {
private static final String SERVICE_USER = "scripts";
/** Servlet resolver logger */
public static final Logger LOGGER = LoggerFactory.getLogger(SlingServletResolver.class);
@Reference
private ResourceResolverFactory resourceResolverFactory;
@Reference(target = "(|(" + ServiceUserMapped.SUBSERVICENAME + "=" + SERVICE_USER + ")(!("
+ ServiceUserMapped.SUBSERVICENAME + "=*)))")
private ServiceUserMapped scriptServiceUserMapped;
@Reference
private ResolutionCache resolutionCache;
@Reference(target="(name=org.apache.sling)")
private ServletContext servletContext;
// the default servlet if no other servlet applies for a request. This
// field is set on demand by getDefaultServlet()
private AtomicReference<Servlet> defaultServlet = new AtomicReference<>();
// the default error handler servlet if no other error servlet applies for
// a request. This field is set on demand by getDefaultErrorServlet()
private AtomicReference<Servlet> fallbackErrorServlet = new AtomicReference<>();
private AtomicReference<ResourceResolver> sharedScriptResolver = new AtomicReference<>();
private final ThreadLocal<ResourceResolver> perThreadScriptResolver = new ThreadLocal<>();
/**
* The allowed execution paths.
*/
private AtomicReference<String[]> executionPaths = new AtomicReference<>();
/**
* The default extensions
*/
private AtomicReference<String[]> defaultExtensions = new AtomicReference<>();
private final PathBasedServletAcceptor pathBasedServletAcceptor = new PathBasedServletAcceptor();
private static final Servlet forbiddenPathServlet = new HttpServlet() {
private static final long serialVersionUID = 1L;
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
};
// ---------- ServletResolver interface -----------------------------------
/**
* @see ServletResolver#resolveServlet(SlingHttpServletRequest)
*/
@Override
public Servlet resolveServlet(final SlingHttpServletRequest request) {
final Resource resource = request.getResource();
// start tracking servlet resolution
final RequestProgressTracker progressTracker = request.getRequestProgressTracker();
final String timerName = "resolveServlet(" + resource.getPath() + ")";
progressTracker.startTimer(timerName);
final String resourceType = resource.getResourceType();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("resolveServlet called for resource {}", resource);
}
final ResourceResolver scriptResolver = this.getScriptResourceResolver();
Servlet servlet = null;
if ( resourceType.length() > 0 ) {
servlet = resolveServletInternal(request, null, resourceType, scriptResolver);
}
// last resort, use the core bundle default servlet
if (servlet == null) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("No specific servlet found, trying default");
}
servlet = getDefaultServlet();
}
// track servlet resolution termination
progressTracker.logTimer(timerName, "Using servlet {0}", RequestUtil.getServletName(servlet));
// log the servlet found
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Servlet {} found for resource={}", RequestUtil.getServletName(servlet), resource);
}
return servlet;
}
/**
* @see ServletResolver#resolveServlet(Resource, java.lang.String)
*/
@Override
public Servlet resolveServlet(final Resource resource, final String scriptName) {
if ( resource == null ) {
throw new IllegalArgumentException("Resource must not be null");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("resolveServlet called for resource {} with script name {}", resource, scriptName);
}
final ResourceResolver scriptResolver = this.getScriptResourceResolver();
final Servlet servlet = resolveServletInternal(null, resource, scriptName, scriptResolver);
// log the servlet found
if (LOGGER.isDebugEnabled()) {
if (servlet != null) {
LOGGER.debug("Servlet {} found for resource {} and script name {}", RequestUtil.getServletName(servlet), resource, scriptName);
} else {
LOGGER.debug("No servlet found for resource {} and script name {}", resource, scriptName);
}
}
return servlet;
}
/**
* @see ServletResolver#resolveServlet(ResourceResolver, java.lang.String)
*/
@Override
public Servlet resolveServlet(final ResourceResolver resolver, final String scriptName) {
if ( resolver == null ) {
throw new IllegalArgumentException("Resource resolver must not be null");
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("resolveServlet called for for script name {}", scriptName);
}
final ResourceResolver scriptResolver = this.getScriptResourceResolver();
final Servlet servlet = resolveServletInternal(null, (Resource)null, scriptName, scriptResolver);
// log the servlet found
if (LOGGER.isDebugEnabled()) {
if (servlet != null) {
LOGGER.debug("Servlet {} found for script name {}", RequestUtil.getServletName(servlet), scriptName);
} else {
LOGGER.debug("No servlet found for script name {}", scriptName);
}
}
return servlet;
}
/**
* Get the servlet for the resource.
*/
private Servlet getServlet(final Resource scriptResource) {
// no resource -> no servlet
if ( scriptResource == null ) {
return null;
}
// if resource is fetched using shared resource resolver
// or resource is a servlet resource, just adapt to servlet
if (scriptResource.getResourceResolver() == this.sharedScriptResolver
|| "sling/bundle/resource".equals(scriptResource.getResourceSuperType()) ) {
return scriptResource.adaptTo(Servlet.class);
}
// return a resource wrapper to make sure the implementation
// switches from the per thread resource resolver to the shared once
// the per thread resource resolver is closed
return new ScriptResource(scriptResource, perThreadScriptResolver::get, this.sharedScriptResolver.get()).adaptTo(Servlet.class);
}
// ---------- ErrorHandler interface --------------------------------------
/**
* @see org.apache.sling.engine.servlets.ErrorHandler#handleError(int,
* String, SlingHttpServletRequest, SlingHttpServletResponse)
*/
@Override
public void handleError(final int status,
final String message,
final SlingHttpServletRequest request,
final SlingHttpServletResponse response) throws IOException {
// do not handle, if already handling ....
if (request.getAttribute(SlingConstants.ERROR_REQUEST_URI) != null) {
LOGGER.error("handleError: Recursive invocation. Not further handling status {}({})", status, message);
return;
}
// start tracker
RequestProgressTracker progressTracker = request.getRequestProgressTracker();
String timerName = "handleError:status=" + status;
progressTracker.startTimer(timerName);
final ResourceResolver scriptResolver = this.getScriptResourceResolver();
try {
// find the error handler component
Resource resource = getErrorResource(request);
// find a servlet for the status as the method name
String extension = request.getRequestPathInfo().getExtension();
ResourceCollector locationUtil = new ResourceCollector(String.valueOf(status),
DEFAULT_ERROR_HANDLER_RESOURCE_TYPE, resource,
extension, this.executionPaths.get());
Servlet servlet = getServletInternal(locationUtil, request, scriptResolver);
// fall back to default servlet if none
if (servlet == null) {
servlet = getDefaultErrorServlet(request, resource, scriptResolver);
}
// set the message properties
request.setAttribute(ERROR_STATUS, Integer.valueOf(status));
request.setAttribute(ERROR_MESSAGE, message);
// the servlet name for a sendError handling is still stored
// as the request attribute
Object servletName = request.getAttribute(SLING_CURRENT_SERVLET_NAME);
if (servletName instanceof String) {
request.setAttribute(ERROR_SERVLET_NAME, servletName);
}
// log a track entry after resolution before calling the handler
progressTracker.logTimer(timerName, "Using handler {0}", RequestUtil.getServletName(servlet));
handleError(servlet, request, response);
} finally {
progressTracker.logTimer(timerName, "Error handler finished");
}
}
/**
* @see org.apache.sling.engine.servlets.ErrorHandler#handleError(java.lang.Throwable, SlingHttpServletRequest, SlingHttpServletResponse)
*/
@Override
public void handleError(final Throwable throwable, final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws IOException {
// do not handle, if already handling ....
if (request.getAttribute(SlingConstants.ERROR_REQUEST_URI) != null) {
LOGGER.error("handleError: Recursive invocation. Not further handling Throwable:", throwable);
return;
}
// start tracker
RequestProgressTracker progressTracker = request.getRequestProgressTracker();
String timerName = "handleError:throwable=" + throwable.getClass().getName();
progressTracker.startTimer(timerName);
final ResourceResolver scriptResolver = this.getScriptResourceResolver();
try {
// find the error handler component
Servlet servlet = null;
Resource resource = getErrorResource(request);
Class<?> tClass = throwable.getClass();
while (servlet == null && tClass != Object.class) {
// find a servlet for the simple class name as the method name
String extension = request.getRequestPathInfo().getExtension();
ResourceCollector locationUtil = new ResourceCollector(tClass.getSimpleName(),
DEFAULT_ERROR_HANDLER_RESOURCE_TYPE, resource,
extension, this.executionPaths.get());
servlet = getServletInternal(locationUtil, request, scriptResolver);
// go to the base class
tClass = tClass.getSuperclass();
}
if (servlet == null) {
servlet = getDefaultErrorServlet(request, resource, scriptResolver);
}
// set the message properties
request.setAttribute(SlingConstants.ERROR_EXCEPTION, throwable);
request.setAttribute(SlingConstants.ERROR_EXCEPTION_TYPE, throwable.getClass());
request.setAttribute(SlingConstants.ERROR_MESSAGE, throwable.getMessage());
// log a track entry after resolution before calling the handler
progressTracker.logTimer(timerName, "Using handler {0}", RequestUtil.getServletName(servlet));
handleError(servlet, request, response);
} finally {
progressTracker.logTimer(timerName, "Error handler finished");
}
}
// ---------- internal helper ---------------------------------------------
private ResourceResolver getScriptResourceResolver() {
ResourceResolver scriptResolver = this.perThreadScriptResolver.get();
if ( scriptResolver == null ) {
// no per thread, let's use the shared one
synchronized ( this.sharedScriptResolver ) {
this.sharedScriptResolver.get().refresh();
}
scriptResolver = this.sharedScriptResolver.get();
}
return scriptResolver;
}
/**
* @see SlingRequestListener#onEvent(SlingRequestEvent)
*/
@Override
public void onEvent(final SlingRequestEvent event) {
if ( event.getType() == SlingRequestEvent.EventType.EVENT_INIT ) {
try {
this.perThreadScriptResolver.set(this.sharedScriptResolver.get().clone(null));
} catch (final LoginException e) {
LOGGER.error("Unable to create new script resolver clone", e);
}
} else if ( event.getType() == SlingRequestEvent.EventType.EVENT_DESTROY ) {
final ResourceResolver resolver = this.perThreadScriptResolver.get();
if ( resolver != null ) {
this.perThreadScriptResolver.remove();
resolver.close();
}
}
}
/**
* Returns the resource of the given request to be used as the basis for
* error handling. If the resource has not yet been set in the request
* because the error occurred before the resource could be set (e.g. during
* resource resolution) a synthetic resource is returned whose type is
* {@link ServletResolverConstants#DEFAULT_ERROR_HANDLER_RESOURCE_TYPE}.
*
* @param request The request whose resource is to be returned.
*/
private Resource getErrorResource(final SlingHttpServletRequest request) {
Resource res = request.getResource();
if (res == null) { // NOSONAR
res = new SyntheticResource(request.getResourceResolver(), request.getPathInfo(),
DEFAULT_ERROR_HANDLER_RESOURCE_TYPE);
}
return res;
}
/**
* Resolve an appropriate servlet for a given request and resource type
* using the provided ResourceResolver
*/
private Servlet resolveServletInternal(final SlingHttpServletRequest request,
final Resource resource,
final String scriptNameOrResourceType,
final ResourceResolver resolver) {
Servlet servlet = null;
// first check whether the type of a resource is the absolute
// path of a servlet (or script)
if (scriptNameOrResourceType.charAt(0) == '/') {
final String scriptPath = ResourceUtil.normalize(scriptNameOrResourceType);
if (scriptPath != null && isPathAllowed(scriptPath, this.executionPaths.get()) ) {
final Resource res = resolver.getResource(scriptPath);
servlet = this.getServlet(res);
if (servlet != null && !pathBasedServletAcceptor.accept(request, servlet)) {
if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Servlet {} rejected by {} returning FORBIDDEN status", RequestUtil.getServletName(servlet),
pathBasedServletAcceptor.getClass().getSimpleName());
}
servlet = forbiddenPathServlet;
} else if (servlet != null && LOGGER.isDebugEnabled()) {
LOGGER.debug("Servlet {} found using absolute resource type {}", RequestUtil.getServletName(servlet),
scriptNameOrResourceType);
}
} else {
if ( request != null ) {
request.getRequestProgressTracker().log(
"Will not look for a servlet at {0} as it is not in the list of allowed paths",
scriptNameOrResourceType
);
}
}
}
if ( servlet == null ) {
// the resource type is not absolute, so lets go for the deep search
final AbstractResourceCollector locationUtil;
if ( request != null ) {
locationUtil = ResourceCollector.create(request, this.executionPaths.get(), this.defaultExtensions.get());
} else {
locationUtil = NamedScriptResourceCollector.create(scriptNameOrResourceType, resource, this.executionPaths.get());
}
servlet = getServletInternal(locationUtil, request, resolver);
if (servlet != null && LOGGER.isDebugEnabled()) {
LOGGER.debug("getServletInternal returns servlet {}", RequestUtil.getServletName(servlet));
}
}
return servlet;
}
/**
* Returns a servlet suitable for handling a request. The
* <code>locationUtil</code> is used find any servlets or scripts usable for
* the request. Each servlet returned is in turn asked whether it is
* actually willing to handle the request in case the servlet is an
* <code>OptingServlet</code>. The first servlet willing to handle the
* request is used.
*
* @param locationUtil The helper used to find appropriate servlets ordered
* by matching priority.
* @param request The request used to give to any <code>OptingServlet</code>
* for them to decide on whether they are willing to handle the
* request
* @param resolver The <code>ResourceResolver</code> used for resolving the servlets.
* @return a servlet for handling the request or <code>null</code> if no
* such servlet willing to handle the request could be found.
*/
private Servlet getServletInternal(final AbstractResourceCollector locationUtil,
final SlingHttpServletRequest request,
final ResourceResolver resolver) {
// use local variable to avoid race condition with activate
final ResolutionCache localCache = this.resolutionCache;
final Servlet scriptServlet = localCache.get(locationUtil);
if (scriptServlet != null) {
if ( LOGGER.isDebugEnabled() ) {
LOGGER.debug("Using cached servlet {}", RequestUtil.getServletName(scriptServlet));
}
return scriptServlet;
}
final Collection<Resource> candidates = locationUtil.getServlets(resolver, localCache.getScriptEngineExtensions());
if (LOGGER.isDebugEnabled()) {
if (candidates.isEmpty()) {
LOGGER.debug("No servlet candidates found");
} else {
LOGGER.debug("Ordered list of servlet candidates follows");
for (Resource candidateResource : candidates) {
LOGGER.debug("Servlet candidate: {}", candidateResource.getPath());
}
}
}
boolean hasOptingServlet = false;
for (final Resource candidateResource : candidates) {
LOGGER.debug("Checking if candidate resource {} adapts to servlet and accepts request", candidateResource
.getPath());
Servlet candidate = this.getServlet(candidateResource);
if (candidate != null) {
final boolean isOptingServlet = candidate instanceof OptingServlet;
boolean servletAcceptsRequest = !isOptingServlet || (request != null && ((OptingServlet) candidate).accepts(request));
if (servletAcceptsRequest) {
if (!hasOptingServlet && !isOptingServlet ) {
localCache.put(locationUtil, candidate);
}
LOGGER.debug("Using servlet provided by candidate resource {}", candidateResource.getPath());
return candidate;
}
if (isOptingServlet) {
hasOptingServlet = true;
}
LOGGER.debug("Candidate {} does not accept request, ignored", candidateResource.getPath());
} else {
LOGGER.debug("Candidate {} does not adapt to a servlet, ignored", candidateResource.getPath());
}
}
// exhausted all candidates, we don't have a servlet
return null;
}
/**
* Returns the internal default servlet which is called in case no other
* servlet applies for handling a request. This servlet should really only
* be used if the default servlets have not been registered (yet).
*/
private Servlet getDefaultServlet() {
Servlet servlet = defaultServlet.get();
if (servlet == null) {
try {
servlet = new DefaultServlet();
servlet.init(new SlingServletConfig(servletContext, null, "Apache Sling Core Default Servlet"));
defaultServlet.set(servlet);
} catch (final ServletException se) {
LOGGER.error("Failed to initialize default servlet", se);
}
}
return servlet;
}
/**
* Returns the default error handler servlet, which is called in case there
* is no other - better matching - servlet registered to handle an error or
* exception.
* <p>
* The default error handler servlet is registered for the resource type
* "sling/servlet/errorhandler" and method "default". This may be
* overwritten by applications globally or according to the resource type
* hierarchy of the resource.
* <p>
* If no default error handler servlet can be found an adhoc error handler
* is used as a final fallback.
*/
private Servlet getDefaultErrorServlet(
final SlingHttpServletRequest request,
final Resource resource,
final ResourceResolver resolver) {
// find a default error handler according to the resource type
// tree of the given resource
String extension = request.getRequestPathInfo().getExtension();
final ResourceCollector locationUtil = new ResourceCollector(
ServletResolverConstants.DEFAULT_ERROR_HANDLER_METHOD,
DEFAULT_ERROR_HANDLER_RESOURCE_TYPE, resource,
extension, this.executionPaths.get());
final Servlet servlet = getServletInternal(locationUtil, request, resolver);
if (servlet != null) {
return servlet;
}
// if no registered default error handler could be found use
// the DefaultErrorHandlerServlet as an ad-hoc fallback
Servlet fallbackServlet = fallbackErrorServlet.get();
if (fallbackServlet == null) {
// fall back to an adhoc instance of the DefaultErrorHandlerServlet
// if the actual service is not registered (yet ?)
try {
fallbackServlet = new DefaultErrorHandlerServlet();
fallbackServlet.init(new SlingServletConfig(servletContext,
null, "Sling (Ad Hoc) Default Error Handler Servlet"));
fallbackErrorServlet.set(fallbackServlet);
} catch (ServletException se) {
LOGGER.error("Failed to initialize error servlet", se);
}
}
return fallbackServlet;
}
private void handleError(final Servlet errorHandler, final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws IOException {
request.setAttribute(SlingConstants.ERROR_REQUEST_URI, request.getRequestURI());
// if there is no explicitly known error causing servlet, use
// the name of the error handler servlet
if (request.getAttribute(SlingConstants.ERROR_SERVLET_NAME) == null) {
request.setAttribute(SlingConstants.ERROR_SERVLET_NAME, errorHandler.getServletConfig().getServletName());
}
// Let the error handler servlet process the request and
// forward all exceptions if it fails.
// Before SLING-4143 we only forwarded IOExceptions.
try {
// SLING-10478 - wrap the response to track if the writer is still open
// after the errorHandler has serviced the request
HandleErrorSlingHttpServletResponse wrappedResponse = new HandleErrorSlingHttpServletResponse(response);
errorHandler.service(request, wrappedResponse);
// SLING-10478 - if the response writer has not already been closed, then flush and close it
if (wrappedResponse.isOpen()) {
// commit the response
wrappedResponse.flushBuffer();
// close the response (SLING-2724)
wrappedResponse.getWriter().close();
}
} catch (final Throwable t) { // NOSONAR
LOGGER.error("Calling the error handler resulted in an error", t);
LOGGER.error("Original error " + request.getAttribute(SlingConstants.ERROR_EXCEPTION_TYPE),
(Throwable) request.getAttribute(SlingConstants.ERROR_EXCEPTION));
final IOException x = new IOException("Error handler failed: " + t.getClass().getName());
x.initCause(t);
throw x;
}
}
// ---------- SCR Integration ----------------------------------------------
private ServiceTracker<MergingServletResourceProvider, MergingServletResourceProvider> tracker;
/**
* Activate this component.
*/
@Activate
protected void activate(final BundleContext context, final ResolverConfig config) throws LoginException {
this.tracker = new ServiceTracker<>(context, MergingServletResourceProvider.class, null);
this.tracker.open();
this.sharedScriptResolver.set(
ScriptResourceResolver.wrap(resourceResolverFactory.getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object)SERVICE_USER)),
this.tracker::getService)
);
this.executionPaths.set(getExecutionPaths(config.servletresolver_paths()));
this.defaultExtensions.set(config.servletresolver_defaultExtensions());
// setup default servlet
this.getDefaultServlet();
}
@Modified
protected void modified(final BundleContext context, final ResolverConfig config) throws LoginException {
this.deactivate();
this.activate(context, config);
}
/**
* Deactivate this component.
*/
@Deactivate
protected void deactivate() {
this.tracker.close();
this.resolutionCache.flushCache();
// destroy the fallback error handler servlet
Servlet servlet = fallbackErrorServlet.get();
if (servlet != null) {
try {
servlet.destroy();
} catch (Throwable t) { // NOSONAR
// ignore
} finally {
fallbackErrorServlet.set(null);
}
}
if (this.sharedScriptResolver != null) {
ResourceResolver rr = this.sharedScriptResolver.get();
if (rr != null) {
rr.close();
}
this.sharedScriptResolver.set(null);
}
this.executionPaths.set(null);
this.defaultExtensions.set(null);
}
/**
* This method checks whether a path is allowed to be executed.
*
* @param path The path to check (must not be {@code null} or empty)
* @param executionPaths The path to check against
* @return {@code true} if the executionPaths is {@code null} or empty or if
* the path equals one entry or one of the executionPaths entries is
* a prefix to the path. Otherwise or if path is {@code null}
* {@code false} is returned.
*/
public static boolean isPathAllowed(final String path, final String[] executionPaths) {
if (executionPaths == null || executionPaths.length == 0) {
LOGGER.debug("Accepting servlet at '{}' as there are no configured execution paths.",
path);
return true;
}
if (path == null || path.length() == 0) {
LOGGER.debug("Ignoring servlet with empty path.");
return false;
}
for (final String config : executionPaths) {
if (config.endsWith("/")) {
if (path.startsWith(config)) {
LOGGER.debug(
"Accepting servlet at '{}' as the path is prefixed with configured execution path '{}'.", path,
config);
return true;
}
} else if (path.equals(config)) {
LOGGER.debug(
"Accepting servlet at '{}' as the path equals configured execution path '{}'.", path, config);
return true;
}
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"Ignoring servlet at '{}' as the path is not in the configured execution paths.", path);
}
return false;
}
/**
* Calculate the execution paths from the configured execution paths
* @param paths The configured paths
* @return The execution paths or {@code null} for all paths.
*/
public static String[] getExecutionPaths(final String[] paths) {
String[] executionPaths = paths;
if ( executionPaths != null ) {
// if we find a string combination that basically allows all paths,
// we simply set the array to null
if ( executionPaths.length == 0 ) {
executionPaths = null;
} else {
boolean hasRoot = false;
for(final String path : executionPaths) {
if ( path == null || path.length() == 0 || path.equals("/") ) {
hasRoot = true;
break;
}
}
if ( hasRoot ) {
executionPaths = null;
}
}
}
return executionPaths;
}
}