001// Copyright 2010, 2013, 2026 The Apache Software Foundation
002//
003// Licensed under the Apache License, Version 2.0 (the "License");
004// you may not use this file except in compliance with the License.
005// You may obtain a copy of the License at
006//
007// http://www.apache.org/licenses/LICENSE-2.0
008//
009// Unless required by applicable law or agreed to in writing, software
010// distributed under the License is distributed on an "AS IS" BASIS,
011// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012// See the License for the specific language governing permissions and
013// limitations under the License.
014
015package org.apache.tapestry5.internal.services.assets;
016
017import org.apache.tapestry5.commons.Resource;
018import org.apache.tapestry5.http.services.Request;
019import org.apache.tapestry5.http.services.Response;
020import org.apache.tapestry5.internal.services.ResourceStreamer;
021import org.apache.tapestry5.services.ContextAssetProtectionRule;
022import org.apache.tapestry5.services.assets.AssetRequestHandler;
023import org.slf4j.Logger;
024import org.slf4j.LoggerFactory;
025
026import java.io.IOException;
027import java.util.regex.Pattern;
028
029/**
030 * Handles requests for context assets, screening out attempt to
031 * access anything under WEB-INF or META-INF.
032 *
033 * @since 5.2.0
034 */
035public class ContextAssetRequestHandler implements AssetRequestHandler
036{
037    private final static Logger LOGGER = LoggerFactory.getLogger(ContextAssetRequestHandler.class);
038
039    private final ResourceStreamer resourceStreamer;
040
041    private final Resource rootContextResource;
042
043    private final ContextAssetProtectionRule contextAssetProtectionRule;
044
045    private final Pattern illegal = Pattern.compile("^([\\\\/]*((web|meta)-inf.*)|(.*\\.tml$))", Pattern.CASE_INSENSITIVE);
046
047    public ContextAssetRequestHandler(ResourceStreamer resourceStreamer, Resource rootContextResource,
048                                       ContextAssetProtectionRule contextAssetProtectionRule)
049    {
050        this.resourceStreamer = resourceStreamer;
051        this.rootContextResource = rootContextResource;
052        this.contextAssetProtectionRule = contextAssetProtectionRule;
053    }
054
055    public boolean handleAssetRequest(Request request, Response response, String extraPath) throws IOException
056    {
057        ChecksumPath path = new ChecksumPath(resourceStreamer, null, extraPath);
058
059        final Resource resource;
060
061        try
062        {
063            resource = rootContextResource.forFile(path.resourcePath);
064        }
065        catch (IllegalStateException ex)
066        {
067            // Fail closed: treat it as blocked rather than surfacing it as a server error.
068            if (LOGGER.isWarnEnabled())
069            {
070                LOGGER.warn("Blocked request for context asset '" + path.resourcePath +
071                        "': " + ex.getMessage());
072            }
073            return false;
074        }
075
076        final String resourcePath = resource.getPath();
077
078        if (illegal.matcher(resourcePath).matches())
079        {
080            if (LOGGER.isWarnEnabled())
081            {
082                LOGGER.warn("Blocked request for context asset '" + resourcePath +
083                        "': the path resolves to a protected location (WEB-INF, META-INF or a template).");
084            }
085            return false;
086        }
087
088        // TAP5-2835: Allow custom protection rules mirroring ClasspathAssetProtectionRule
089        if (contextAssetProtectionRule.block(resourcePath))
090        {
091            if (LOGGER.isWarnEnabled())
092            {
093                LOGGER.warn("Blocked request for context asset '" + resourcePath +
094                        "'. Contribute a new ContextAssetProtectionRule if you need this asset to be publicly accessible.");
095            }
096            return false;
097        }
098
099        return path.stream(resource);
100    }
101
102}