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