001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2023-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.web;
019
020import static com.google.common.util.concurrent.Futures.immediateFuture;
021
022import com.google.common.util.concurrent.ListenableFuture;
023
024import dev.enola.common.io.resource.ClasspathResource;
025import dev.enola.common.io.resource.FileResource;
026import dev.enola.common.io.resource.ReadableResource;
027import dev.enola.common.io.resource.ResourceProvider;
028
029import java.io.File;
030import java.net.URI;
031
032/**
033 * {@link WebHandler} which servers static web content using Enola Resource I/O.
034 *
035 * <p>Useful e.g. for fixed HTML, CSS + JS files, or <a href="https://www.webjars.org">WebJars</a>.
036 */
037public class StaticWebHandler implements WebHandler {
038
039    private final String uriPrefix;
040    private final ResourceProvider resourceProvider;
041
042    public StaticWebHandler(String uriPrefix, String classpathPrefix) {
043        this.uriPrefix = uriPrefix;
044        this.resourceProvider = new ClasspathResource.Provider(classpathPrefix);
045    }
046
047    public StaticWebHandler(String uriPrefix, File directory) {
048        this.uriPrefix = uriPrefix;
049        this.resourceProvider = new FileResource.Provider(directory);
050    }
051
052    @Override
053    public ListenableFuture<ReadableResource> handle(URI uri) {
054        var path = uri.getPath();
055        if (path.contains("..")) {
056            throw new IllegalArgumentException("URI cannot contain '..':" + path);
057        }
058        if (!path.startsWith(uriPrefix)) {
059            throw new IllegalStateException(path + " does not start with " + uriPrefix);
060        }
061        var cutPath = path.substring(uriPrefix.length());
062        var resource = resourceProvider.get(cutPath);
063        return immediateFuture(resource);
064    }
065}