blob: 41f359c8f3121f18fceaa0bc5763c23f1a9eaf3b [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.scripting.sightly.engine;
import javax.servlet.Servlet;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
import org.jetbrains.annotations.NotNull;
/**
* Utility class which is used by the HTL engine & extensions to resolve resources.
*/
public final class ResourceResolution {
/**
* Maximum number of iterations that can be performed when searching for a resource in
* the resource superType chain
*/
private static final int RECURSION_LIMIT = 100;
/**
* <p>
* Resolves a resource from the search path relative to the {@code base} resource by traversing the {@code sling:resourceSuperType}
* chain.
* </p>
* <p>
* Since this method will traverse the {@code sling:resourceSuperType} chain, the {@code ResourceResolver} used for resolving the
* {@code base} resource should be able to read the super type resources.
* </p>
*
* @param base the base resource from which to start the lookup
* @param path the relative path to the resource; if the path is absolute the {@code Resource} identified by this path will be
* returned
* @return the resource identified by the relative {@code path} or {@code null} if no resource was found
* @throws java.lang.UnsupportedOperationException if the resource is not in the resource resolver's search path
* @throws java.lang.IllegalStateException if the number of steps necessary to search for the resource on the resource
* superType chain has reached the maximum limit
* @see ResourceResolver#getSearchPath()
*/
public static Resource getResourceFromSearchPath(Resource base, String path) {
if (base == null || path == null) {
return null;
}
if (path.startsWith("/")) {
Resource resource = getScriptResource(base.getResourceResolver(), path);
if (resource != null) {
return searchPathChecked(resource);
}
}
Resource internalBase;
if ("nt:file".equals(base.getResourceType()) || base.adaptTo(Servlet.class) != null) {
internalBase = base.getParent();
} else {
internalBase = base;
}
return resolveComponentInternal(internalBase, path);
}
/**
* <p>
* Resolves the resource accessed by a {@code request}. Since the {@code request} can use an anonymous {@code ResourceResolver}, the
* passed {@code resolver} parameter should have read access rights to resources from the search path.
* </p>
*
* @param resolver a {@link ResourceResolver} that has read access rights to resources from the search path
* @param request the request
* @return the resource identified by the {@code request} or {@code null} if no resource was found
*/
public static Resource getResourceForRequest(ResourceResolver resolver, SlingHttpServletRequest request) {
if (resolver == null || request == null) {
return null;
}
String resourceType = request.getResource().getResourceType();
if (StringUtils.isNotEmpty(resourceType)) {
return getScriptResource(resolver, resourceType);
}
return null;
}
/**
* Resolve the resource in the specified component
*
* @param base The resource for the component. It can be null if path is absolute
* @param path the path to the resource
* @return the retrieved resource or null if no resource was found
* @throws java.lang.UnsupportedOperationException if the resource is not in the resource resolver's search path
* @throws java.lang.IllegalStateException if more than {@link ResourceResolution#RECURSION_LIMIT} steps were
* necessary to search for the resource on the resource superType chain
*/
private static Resource resolveComponentInternal(Resource base, String path) {
if (base == null || path == null) {
throw new NullPointerException("Arguments cannot be null");
}
Resource resource = recursiveResolution(base, path);
if (resource == null) {
resource = locateInSearchPath(base.getResourceResolver(), path);
}
return resource != null ? searchPathChecked(resource) : null;
}
private static Resource recursiveResolution(Resource base, String path) {
ResourceResolver resolver = base.getResourceResolver();
for (int iteration = 0; iteration < RECURSION_LIMIT; iteration++) {
Resource resource = null;
if (path.startsWith("/")) {
resource = getScriptResource(resolver, path);
} else {
String normalizedPath = ResourceUtil.normalize(base.getPath() + "/" + path);
if (normalizedPath != null) {
resource = getScriptResource(resolver, normalizedPath);
}
}
if (resource != null) {
return resource;
}
base = findSuperComponent(base);
if (base == null) {
return null;
}
}
//at this point we have reached recursion limit
throw new IllegalStateException("Searching for resource in component chain took more than " +
RECURSION_LIMIT + " steps");
}
private static Resource locateInSearchPath(ResourceResolver resourceResolver, String path) {
for (String searchPath : resourceResolver.getSearchPath()) {
String fullPath = searchPath + path;
Resource resource = resourceResolver.getResource(fullPath);
if (resource != null && resource.getPath().startsWith(searchPath)) { //prevent path traversal attack
return resource;
}
}
return null;
}
private static boolean isInSearchPath(Resource resource) {
String resourcePath = resource.getPath();
ResourceResolver resolver = resource.getResourceResolver();
for (String path : resolver.getSearchPath()) {
if (resourcePath.startsWith(path)) {
return true;
}
}
return false;
}
private static Resource findSuperComponent(Resource base) {
ResourceResolver resolver = base.getResourceResolver();
String superType = resolver.getParentResourceType(base);
if (superType == null) {
return null;
}
return getScriptResource(resolver, superType);
}
private static Resource searchPathChecked(Resource resource) {
if (!isInSearchPath(resource)) {
throw new UnsupportedOperationException("Access to resource " + resource.getPath() + " is denied, since the resource does not" +
" reside on the search path");
}
return resource;
}
private static Resource getScriptResource(@NotNull ResourceResolver resourceResolver, @NotNull String path) {
if (path.startsWith("/")) {
Resource resource = resourceResolver.resolve(path);
if (ResourceUtil.isNonExistingResource(resource)) {
return null;
}
return resource;
} else {
for (String searchPath : resourceResolver.getSearchPath()) {
Resource resource = resourceResolver.resolve(searchPath + path);
if (!ResourceUtil.isNonExistingResource(resource)) {
return resource;
}
}
}
return null;
}
}