diff --git a/integration-tests/src/main/resources/faildex.html b/integration-tests/src/main/resources/faildex.html deleted file mode 100644 index 680a2f4a7e..0000000000 --- a/integration-tests/src/main/resources/faildex.html +++ /dev/null @@ -1 +0,0 @@ -This is faildex.html. diff --git a/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/StaticContentCommonTests.java b/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/StaticContentCommonTests.java index 55972e0d02..f6d55e94ed 100644 --- a/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/StaticContentCommonTests.java +++ b/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/StaticContentCommonTests.java @@ -12,8 +12,6 @@ default void assertBasicStaticContentWorks(String context) throws Exception { // Ensure index files are used assertContains(context, "This is index.html."); assertContains(context + "foo", "This is foo/index.html."); - // Ensure content under src/main/resources is NOT served up - assertNotFound(context + "faildex.html"); // Ensure we don't serve up Java class files assertNotFound(context + "java/lang/Object.class"); // And doubly ensure we don't serve up application class files diff --git a/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/deployment/StaticContentDeploymentTest.java b/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/deployment/StaticContentDeploymentTest.java index 5d6d810ec2..8b59c4eabd 100644 --- a/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/deployment/StaticContentDeploymentTest.java +++ b/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/deployment/StaticContentDeploymentTest.java @@ -66,6 +66,21 @@ public void testStaticContentWithContext() throws Exception { } } + @Test + public void testStaticContentWithBase() throws Exception { + Container container = newContainer(); + container.start(); + try { + WARArchive deployment = ShrinkWrap.create(WARArchive.class); + deployment.staticContent("foo"); + container.deploy(deployment); + assertContains("", "This is foo/index.html."); + assertContains("index.html", "This is foo/index.html."); + } finally { + container.stop(); + } + } + private void assertFileChangesReflected(String context) throws Exception { if (context.length() > 0 && !context.endsWith("/")) { context = context + "/"; diff --git a/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/war/StaticContentWarSubdirTest.java b/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/war/StaticContentWarSubdirTest.java new file mode 100644 index 0000000000..1d0bcd3247 --- /dev/null +++ b/integration-tests/src/test/java/org/wildfly/swarm/integration/staticcontent/war/StaticContentWarSubdirTest.java @@ -0,0 +1,57 @@ +package org.wildfly.swarm.integration.staticcontent.war; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.arquillian.drone.api.annotation.Drone; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openqa.selenium.WebDriver; +import org.wildfly.swarm.arquillian.adapter.ArtifactDependencies; +import org.wildfly.swarm.undertow.WARArchive; + +import java.net.URL; +import java.util.Arrays; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; + +@RunWith(Arquillian.class) +public class StaticContentWarSubdirTest { + @ArquillianResource + URL contextRoot; + + @Drone + WebDriver browser; + + @Deployment + public static Archive createDeployment() throws Exception { + WARArchive deployment = ShrinkWrap.create(WARArchive.class); + deployment.staticContent("foo"); + // Make sure we're testing from contents inside the jar only + deployment.delete("WEB-INF/undertow-external-mounts.conf"); + return deployment; + } + + @ArtifactDependencies + public static List appDependencies() { + return Arrays.asList( + "org.wildfly.swarm:wildfly-swarm-undertow" + ); + } + + @RunAsClient + @Test + public void testStaticContent() throws Exception { + assertContains("", "This is foo/index.html."); + assertContains("index.html", "This is foo/index.html."); + } + + public void assertContains(String path, String content) throws Exception { + browser.navigate().to(contextRoot + path); + assertThat(browser.getPageSource()).contains(content); + } +} diff --git a/undertow/api/src/main/java/org/wildfly/swarm/undertow/DefaultWarDeploymentFactory.java b/undertow/api/src/main/java/org/wildfly/swarm/undertow/DefaultWarDeploymentFactory.java index 17364c4842..89b0ab62d9 100644 --- a/undertow/api/src/main/java/org/wildfly/swarm/undertow/DefaultWarDeploymentFactory.java +++ b/undertow/api/src/main/java/org/wildfly/swarm/undertow/DefaultWarDeploymentFactory.java @@ -47,14 +47,17 @@ public String getType() { @Override public Archive create(Container container) throws Exception { + return archiveFromCurrentApp(); + } + + public static WARArchive archiveFromCurrentApp() throws Exception { WARArchive archive = ShrinkWrap.create(WARArchive.class, determineName()); - setup( archive ); + setup(archive); archive.addModule("org.wildfly.swarm.undertow", "runtime"); - archive.addAsServiceProvider("io.undertow.server.handlers.builder.HandlerBuilder", "org.wildfly.swarm.undertow.runtime.StaticHandlerBuilder"); return archive; } - protected String determineName() { + protected static String determineName() { String prop = System.getProperty( "wildfly.swarm.app.path" ); if ( prop != null ) { File file = new File( prop ); @@ -73,11 +76,11 @@ protected String determineName() { return UUID.randomUUID().toString() + ".war"; } - protected void setup(DependenciesContainer archive) throws Exception { + protected static void setup(DependenciesContainer archive) throws Exception { boolean result = setupUsingAppPath(archive) || setupUsingAppArtifact(archive) || setupUsingMaven(archive); } - protected boolean setupUsingAppPath(DependenciesContainer archive) throws IOException { + protected static boolean setupUsingAppPath(DependenciesContainer archive) throws IOException { String appPath = System.getProperty("wildfly.swarm.app.path"); if (appPath != null) { @@ -101,7 +104,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO return false; } - protected boolean setupUsingAppArtifact(DependenciesContainer archive) throws IOException { + protected static boolean setupUsingAppArtifact(DependenciesContainer archive) throws IOException { String appArtifact = System.getProperty("wildfly.swarm.app.artifact"); if (appArtifact != null) { @@ -115,7 +118,7 @@ protected boolean setupUsingAppArtifact(DependenciesContainer archive) throws return false; } - protected boolean setupUsingMaven(DependenciesContainer archive) throws Exception { + protected static boolean setupUsingMaven(DependenciesContainer archive) throws Exception { Path pwd = Paths.get(System.getProperty("user.dir")); final Path classes = pwd.resolve("target").resolve("classes"); @@ -153,7 +156,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO return success; } - protected String convertSeparators(Path path) { + protected static String convertSeparators(Path path) { String convertedPath = path.toString(); if (convertedPath.contains(File.separator)) { diff --git a/undertow/api/src/main/java/org/wildfly/swarm/undertow/StaticContentContainer.java b/undertow/api/src/main/java/org/wildfly/swarm/undertow/StaticContentContainer.java index 11fbff45d3..2e9f4bff61 100644 --- a/undertow/api/src/main/java/org/wildfly/swarm/undertow/StaticContentContainer.java +++ b/undertow/api/src/main/java/org/wildfly/swarm/undertow/StaticContentContainer.java @@ -16,50 +16,101 @@ package org.wildfly.swarm.undertow; import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.ArchivePath; +import org.jboss.shrinkwrap.api.Filter; +import org.jboss.shrinkwrap.api.Filters; import org.jboss.shrinkwrap.api.Node; -import org.jboss.shrinkwrap.api.importer.ExplodedImporter; +import org.jboss.shrinkwrap.impl.base.Validate; +import org.jboss.shrinkwrap.impl.base.path.BasicPath; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @author Bob McWhirter */ public interface StaticContentContainer> extends Archive { - default T staticContent() { - return staticContent( "/", "." ); - } + Logger log = Logger.getLogger(StaticContentContainer.class.getName()); - default T staticContent(String context) { - return staticContent( context, "." ); + default T staticContent() { + return staticContent(""); } - default T staticContent(String context, String base) { + default T staticContent(String base) { as(WARArchive.class).addModule("org.wildfly.swarm.undertow", "runtime"); - as(WARArchive.class).addAsServiceProvider("io.undertow.server.handlers.builder.HandlerBuilder", "org.wildfly.swarm.undertow.runtime.StaticHandlerBuilder"); - Node node = as(WARArchive.class).get("WEB-INF/undertow-handlers.conf"); + try { + // Add all the static content from the current app to the archive + Archive allResources = DefaultWarDeploymentFactory.archiveFromCurrentApp(); + // Here we define static as basically anything that's not a + // Java class file or under WEB-INF or META-INF + mergeIgnoringDuplicates(allResources, base, Filters.exclude(".*\\.class$")); + } catch (Exception ex) { + log.log(Level.WARNING, "Error setting up static resources", ex); + } - UndertowHandlersAsset asset = null; + Node node = get("WEB-INF/undertow-external-mounts.conf"); + UndertowExternalMountsAsset asset = null; if ( node == null ) { - asset = new UndertowHandlersAsset(); - as(WARArchive.class).add( asset, "WEB-INF/undertow-handlers.conf" ); + asset = new UndertowExternalMountsAsset(); + add(asset, "WEB-INF/undertow-external-mounts.conf"); } else { - asset = (UndertowHandlersAsset) node.getAsset(); + asset = (UndertowExternalMountsAsset) node.getAsset(); } - asset.staticContent( context, base ); - + // Add external mounts for static content so changes are picked up + // immediately during development Path webResources = Paths.get(System.getProperty("user.dir"), "src", "main", "webapp"); if (base != null ) { webResources = webResources.resolve(base); } if (Files.exists(webResources)) { - as(ExplodedImporter.class).importDirectory(webResources.toFile()); + asset.externalMount(webResources.toString()); + } + webResources = Paths.get(System.getProperty("user.dir"), "src", "main", "resources"); + if (base != null ) { + webResources = webResources.resolve(base); + } + if (Files.exists(webResources)) { + asset.externalMount(webResources.toString()); + } + + return (T) this; + } + + default T mergeIgnoringDuplicates(Archive source, String base, Filter filter) { + if (!base.startsWith("/")) { + base = "/" + base; } + // Get existing contents from source archive + final Map sourceContent = source.getContent(); + // Add each asset from the source archive + for (final Map.Entry contentEntry : sourceContent.entrySet()) { + final Node node = contentEntry.getValue(); + ArchivePath nodePath = contentEntry.getKey(); + if (!nodePath.get().startsWith(base)) { + continue; + } + if (!filter.include(nodePath)) { + continue; + } + if (contains(nodePath)) { + continue; + } + nodePath = new BasicPath(nodePath.get().replaceFirst(base, "")); + // Delegate + if (node.getAsset() == null) { + addAsDirectory(nodePath); + } else { + add(node.getAsset(), nodePath); + } + } return (T) this; } } diff --git a/undertow/api/src/main/java/org/wildfly/swarm/undertow/UndertowExternalMountsAsset.java b/undertow/api/src/main/java/org/wildfly/swarm/undertow/UndertowExternalMountsAsset.java new file mode 100644 index 0000000000..62b8950672 --- /dev/null +++ b/undertow/api/src/main/java/org/wildfly/swarm/undertow/UndertowExternalMountsAsset.java @@ -0,0 +1,25 @@ +package org.wildfly.swarm.undertow; + +import org.jboss.shrinkwrap.api.asset.Asset; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class UndertowExternalMountsAsset implements Asset { + private List externalMounts = new ArrayList<>(); + + public void externalMount(String path) { + externalMounts.add(path); + } + + @Override + public InputStream openStream() { + StringBuilder conf = new StringBuilder(); + for (String each : this.externalMounts) { + conf.append(each + "\n"); + } + return new ByteArrayInputStream(conf.toString().getBytes()); + } +} diff --git a/undertow/api/src/main/java/org/wildfly/swarm/undertow/UndertowHandlersAsset.java b/undertow/api/src/main/java/org/wildfly/swarm/undertow/UndertowHandlersAsset.java deleted file mode 100644 index a8047cecec..0000000000 --- a/undertow/api/src/main/java/org/wildfly/swarm/undertow/UndertowHandlersAsset.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2015 Red Hat, Inc, and individual contributors. - * - * Licensed 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.wildfly.swarm.undertow; - -import org.jboss.shrinkwrap.api.asset.Asset; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * @author Bob McWhirter - */ -public class UndertowHandlersAsset implements Asset { - - private List staticContent = new ArrayList<>(); - - public void staticContent(String context, String base) { - staticContent.add(new String[]{context, base}); - } - - @Override - public InputStream openStream() { - - StringBuilder conf = new StringBuilder(); - for (String[] each : this.staticContent) { - conf.append("path-prefix('" + each[0] + "') -> static-content(base='" + each[1] + "', prefix='" + each[0] + "')\n"); - } - - return new ByteArrayInputStream(conf.toString().getBytes()); - } -} diff --git a/undertow/runtime/src/main/java/org/wildfly/swarm/undertow/runtime/StaticHandlerBuilder.java b/undertow/runtime/src/main/java/org/wildfly/swarm/undertow/runtime/StaticHandlerBuilder.java deleted file mode 100644 index 25c376e360..0000000000 --- a/undertow/runtime/src/main/java/org/wildfly/swarm/undertow/runtime/StaticHandlerBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Copyright 2015 Red Hat, Inc, and individual contributors. - * - * Licensed 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.wildfly.swarm.undertow.runtime; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import io.undertow.server.HandlerWrapper; -import io.undertow.server.HttpHandler; -import io.undertow.server.handlers.builder.HandlerBuilder; -import io.undertow.server.handlers.resource.ClassPathResourceManager; -import io.undertow.server.handlers.resource.FileResourceManager; -import org.jboss.modules.Module; -import org.jboss.modules.ModuleIdentifier; -import org.jboss.modules.ModuleLoadException; - -/** - * @author Bob McWhirter - */ -public class StaticHandlerBuilder implements HandlerBuilder { - @Override - public String name() { - return "static-content"; - } - - @Override - public Map> parameters() { - HashMap> params = new HashMap<>(); - params.put( "base", String.class ); - params.put( "prefix", String.class ); - return params; - } - - @Override - public Set requiredParameters() { - return Collections.emptySet(); - } - - @Override - public String defaultParameter() { - return ""; - } - - @Override - public HandlerWrapper build(Map map) { - final String base = (String) map.get( "base" ); - final String prefix = (String) map.get( "prefix" ); - - - return new HandlerWrapper() { - @Override - public HttpHandler wrap(final HttpHandler next) { - - HttpHandler cur = next; - - Path f = Paths.get(System.getProperty("user.dir"), "src", "main", "webapp"); - if ( base != null ) { - f = f.resolve(base); - } - if (Files.exists(f)) { - cur = new StaticResourceHandler(prefix, new FileResourceManager(f.toFile(), 1024), cur); - } - - return cur; - } - }; - } -} diff --git a/undertow/runtime/src/main/java/org/wildfly/swarm/undertow/runtime/StaticResourceHandler.java b/undertow/runtime/src/main/java/org/wildfly/swarm/undertow/runtime/StaticResourceHandler.java deleted file mode 100644 index 40662fcf50..0000000000 --- a/undertow/runtime/src/main/java/org/wildfly/swarm/undertow/runtime/StaticResourceHandler.java +++ /dev/null @@ -1,505 +0,0 @@ -/** - * Copyright 2015 Red Hat, Inc, and individual contributors. - * - * Licensed 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.wildfly.swarm.undertow.runtime; - - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CopyOnWriteArrayList; - -import io.undertow.UndertowLogger; -import io.undertow.io.IoCallback; -import io.undertow.predicate.Predicate; -import io.undertow.predicate.Predicates; -import io.undertow.server.HandlerWrapper; -import io.undertow.server.HttpHandler; -import io.undertow.server.HttpServerExchange; -import io.undertow.server.handlers.ResponseCodeHandler; -import io.undertow.server.handlers.builder.HandlerBuilder; -import io.undertow.server.handlers.cache.ResponseCache; -import io.undertow.server.handlers.encoding.ContentEncodedResource; -import io.undertow.server.handlers.encoding.ContentEncodedResourceManager; -import io.undertow.server.handlers.resource.FileResourceManager; -import io.undertow.server.handlers.resource.RangeAwareResource; -import io.undertow.server.handlers.resource.Resource; -import io.undertow.server.handlers.resource.ResourceManager; -import io.undertow.util.ByteRange; -import io.undertow.util.CanonicalPathUtils; -import io.undertow.util.DateUtils; -import io.undertow.util.ETag; -import io.undertow.util.ETagUtils; -import io.undertow.util.Headers; -import io.undertow.util.Methods; -import io.undertow.util.MimeMappings; -import io.undertow.util.RedirectBuilder; -import io.undertow.util.StatusCodes; - -/** - * @author Stuart Douglas - */ -public class StaticResourceHandler implements HttpHandler { - - private final List welcomeFiles = new CopyOnWriteArrayList<>(new String[]{"index.html", "index.htm", "default.html", "default.htm"}); - - private final String prefix; - - /** - * If directory listing is enabled. - */ - private volatile boolean directoryListingEnabled = false; - - /** - * If the canonical version of paths should be passed into the resource manager. - */ - private volatile boolean canonicalizePaths = true; - - /** - * The mime mappings that are used to determine the content type. - */ - private volatile MimeMappings mimeMappings = MimeMappings.DEFAULT; - private volatile Predicate cachable = Predicates.truePredicate(); - private volatile Predicate allowed = Predicates.truePredicate(); - private volatile ResourceManager resourceManager; - /** - * If this is set this will be the maximum time the client will cache the resource. - *

- * Note: Do not set this for private resources, as it will cause a Cache-Control: public - * to be sent. - *

- * TODO: make this more flexible - *

- * This will only be used if the {@link #cachable} predicate returns true - */ - private volatile Integer cacheTime; - /** - * we do not calculate a new expiry date every request. Instead calculate it once - * and cache it until it is in the past. - *

- * TODO: do we need this policy to be pluggable - */ - private volatile long lastExpiryDate; - private volatile String lastExpiryHeader; - - private volatile ContentEncodedResourceManager contentEncodedResourceManager; - - /** - * Handler that is called if no resource is found - */ - private final HttpHandler next; - - public StaticResourceHandler(ResourceManager resourceManager) { - this(null, resourceManager); - } - - public StaticResourceHandler(String prefix, ResourceManager resourceManager) { - this(prefix, resourceManager, ResponseCodeHandler.HANDLE_404); - } - - public StaticResourceHandler(String prefix, ResourceManager resourceManager, HttpHandler next) { - this.resourceManager = resourceManager; - this.next = next; - this.prefix = prefix; - } - - - /** - * You should use {@link StaticResourceHandler(ResourceManager)} instead. - */ - @Deprecated - public StaticResourceHandler() { - this.next = ResponseCodeHandler.HANDLE_404; - this.prefix = null; - } - - @Override - public void handleRequest(final HttpServerExchange exchange) throws Exception { - if (exchange.getRequestMethod().equals(Methods.GET) || - exchange.getRequestMethod().equals(Methods.POST)) { - serveResource(exchange, true); - } else if (exchange.getRequestMethod().equals(Methods.HEAD)) { - serveResource(exchange, false); - } else { - //exchange.setResponseCode(StatusCodes.METHOD_NOT_ALLOWED); - //exchange.endExchange(); - this.next.handleRequest(exchange); - } - } - - private void serveResource(final HttpServerExchange exchange, final boolean sendContent) throws Exception { - - String tmpPath = exchange.getRelativePath(); - - // if provided a prefix, remove it. - if ( this.prefix != null ) { - if ( tmpPath.startsWith( this.prefix ) ) { - tmpPath = tmpPath.substring( this.prefix.length() ); - } - } - - final String relativePath = tmpPath; - - ResponseCache cache = exchange.getAttachment(ResponseCache.ATTACHMENT_KEY); - final boolean cachable = this.cachable.resolve(exchange); - - //we set caching headers before we try and serve from the cache - if (cachable && cacheTime != null) { - exchange.getResponseHeaders().put(Headers.CACHE_CONTROL, "public, max-age=" + cacheTime); - if (System.currentTimeMillis() > lastExpiryDate) { - long date = System.currentTimeMillis(); - lastExpiryHeader = DateUtils.toDateString(new Date(date)); - lastExpiryDate = date; - } - exchange.getResponseHeaders().put(Headers.EXPIRES, lastExpiryHeader); - } - - if (cache != null && cachable) { - if (cache.tryServeResponse()) { - return; - } - } - - //we now dispatch to a worker thread - //as resource manager methods are potentially blocking - HttpHandler dispatchTask = new HttpHandler() { - @Override - public void handleRequest(HttpServerExchange exchange) throws Exception { - Resource resource = null; - try { - if (File.separatorChar == '/' || !relativePath.contains(File.separator)) { - //we don't process resources that contain the sperator character if this is not / - //this prevents attacks where people use windows path seperators in file URLS's - resource = resourceManager.getResource(canonicalize(relativePath)); - } - } catch (IOException e) { - UndertowLogger.REQUEST_IO_LOGGER.ioException(e); - exchange.setResponseCode(StatusCodes.INTERNAL_SERVER_ERROR); - exchange.endExchange(); - return; - } - if (resource == null) { - //usually a 404 handler - next.handleRequest(exchange); - return; - } - - if (resource.isDirectory()) { - Resource indexResource; - try { - indexResource = getIndexFiles(resourceManager, resource.getPath(), welcomeFiles); - } catch (IOException e) { - UndertowLogger.REQUEST_IO_LOGGER.ioException(e); - exchange.setResponseCode(StatusCodes.INTERNAL_SERVER_ERROR); - exchange.endExchange(); - return; - } - if (indexResource == null) { - next.handleRequest(exchange); - return; - } else if (!exchange.getRequestPath().endsWith("/")) { - exchange.setResponseCode(StatusCodes.FOUND); - exchange.getResponseHeaders().put(Headers.LOCATION, RedirectBuilder.redirect(exchange, relativePath + "/", true)); - exchange.endExchange(); - return; - } - resource = indexResource; - } - - final ETag etag = resource.getETag(); - final Date lastModified = resource.getLastModified(); - if (!ETagUtils.handleIfMatch(exchange, etag, false) || - !DateUtils.handleIfUnmodifiedSince(exchange, lastModified)) { - exchange.setResponseCode(StatusCodes.PRECONDITION_FAILED); - exchange.endExchange(); - return; - } - if (!ETagUtils.handleIfNoneMatch(exchange, etag, true) || - !DateUtils.handleIfModifiedSince(exchange, lastModified)) { - exchange.setResponseCode(StatusCodes.NOT_MODIFIED); - exchange.endExchange(); - return; - } - final ContentEncodedResourceManager contentEncodedResourceManager = StaticResourceHandler.this.contentEncodedResourceManager; - Long contentLength = resource.getContentLength(); - - if (contentLength != null && !exchange.getResponseHeaders().contains(Headers.TRANSFER_ENCODING)) { - exchange.setResponseContentLength(contentLength); - } - ByteRange range = null; - long start = -1, end = -1; - if (resource instanceof RangeAwareResource && ((RangeAwareResource) resource).isRangeSupported() && contentLength != null && contentEncodedResourceManager == null) { - //TODO: figure out what to do with the content encoded resource manager - range = ByteRange.parse(exchange.getRequestHeaders().getFirst(Headers.RANGE)); - if (range != null && range.getRanges() == 1) { - start = range.getStart(0); - end = range.getEnd(0); - if (start == -1) { - //suffix range - long toWrite = end; - if (toWrite >= 0) { - exchange.setResponseContentLength(toWrite); - } else { - //ignore the range request - range = null; - } - start = contentLength - end; - end = contentLength; - } else if (end == -1) { - //prefix range - long toWrite = contentLength - start; - if (toWrite >= 0) { - exchange.setResponseContentLength(toWrite); - } else { - //ignore the range request - range = null; - } - end = contentLength; - } else { - long toWrite = end - start + 1; - exchange.setResponseContentLength(toWrite); - } - if (range != null) { - exchange.setResponseCode(StatusCodes.PARTIAL_CONTENT); - exchange.getResponseHeaders().put(Headers.CONTENT_RANGE, range.getStart(0) + "-" + range.getEnd(0) + "/" + contentLength); - } - } - } - //we are going to proceed. Set the appropriate headers - - if (!exchange.getResponseHeaders().contains(Headers.CONTENT_TYPE)) { - final String contentType = resource.getContentType(mimeMappings); - if (contentType != null) { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, contentType); - } else { - exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/octet-stream"); - } - } - if (lastModified != null) { - exchange.getResponseHeaders().put(Headers.LAST_MODIFIED, resource.getLastModifiedString()); - } - if (etag != null) { - exchange.getResponseHeaders().put(Headers.ETAG, etag.toString()); - } - - if (contentEncodedResourceManager != null) { - try { - ContentEncodedResource encoded = contentEncodedResourceManager.getResource(resource, exchange); - if (encoded != null) { - exchange.getResponseHeaders().put(Headers.CONTENT_ENCODING, encoded.getContentEncoding()); - exchange.getResponseHeaders().put(Headers.CONTENT_LENGTH, encoded.getResource().getContentLength()); - encoded.getResource().serve(exchange.getResponseSender(), exchange, IoCallback.END_EXCHANGE); - return; - } - - } catch (IOException e) { - //TODO: should this be fatal - UndertowLogger.REQUEST_IO_LOGGER.ioException(e); - exchange.setResponseCode(StatusCodes.INTERNAL_SERVER_ERROR); - exchange.endExchange(); - return; - } - } - - if (!sendContent) { - exchange.endExchange(); - } else if (range != null) { - ((RangeAwareResource) resource).serveRange(exchange.getResponseSender(), exchange, start, end, IoCallback.END_EXCHANGE); - } else { - resource.serve(exchange.getResponseSender(), exchange, IoCallback.END_EXCHANGE); - } - } - }; - if (exchange.isInIoThread()) { - exchange.dispatch(dispatchTask); - } else { - dispatchTask.handleRequest(exchange); - } - - - } - - private Resource getIndexFiles(ResourceManager resourceManager, final String base, List possible) throws IOException { - String realBase; - if (base.endsWith("/")) { - realBase = base; - } else { - realBase = base + "/"; - } - for (String possibility : possible) { - Resource index = resourceManager.getResource(canonicalize(realBase + possibility)); - if (index != null) { - return index; - } - } - return null; - } - - private String canonicalize(String s) { - if (canonicalizePaths) { - return CanonicalPathUtils.canonicalize(s); - } - return s; - } - - public boolean isDirectoryListingEnabled() { - return directoryListingEnabled; - } - - public StaticResourceHandler setDirectoryListingEnabled(final boolean directoryListingEnabled) { - this.directoryListingEnabled = directoryListingEnabled; - return this; - } - - public StaticResourceHandler addWelcomeFiles(String... files) { - this.welcomeFiles.addAll(Arrays.asList(files)); - return this; - } - - public StaticResourceHandler setWelcomeFiles(String... files) { - this.welcomeFiles.clear(); - this.welcomeFiles.addAll(Arrays.asList(files)); - return this; - } - - public MimeMappings getMimeMappings() { - return mimeMappings; - } - - public StaticResourceHandler setMimeMappings(final MimeMappings mimeMappings) { - this.mimeMappings = mimeMappings; - return this; - } - - public Predicate getCachable() { - return cachable; - } - - public StaticResourceHandler setCachable(final Predicate cachable) { - this.cachable = cachable; - return this; - } - - public Predicate getAllowed() { - return allowed; - } - - public StaticResourceHandler setAllowed(final Predicate allowed) { - this.allowed = allowed; - return this; - } - - public ResourceManager getResourceManager() { - return resourceManager; - } - - public StaticResourceHandler setResourceManager(final ResourceManager resourceManager) { - this.resourceManager = resourceManager; - return this; - } - - public Integer getCacheTime() { - return cacheTime; - } - - public StaticResourceHandler setCacheTime(final Integer cacheTime) { - this.cacheTime = cacheTime; - return this; - } - - public ContentEncodedResourceManager getContentEncodedResourceManager() { - return contentEncodedResourceManager; - } - - public StaticResourceHandler setContentEncodedResourceManager(ContentEncodedResourceManager contentEncodedResourceManager) { - this.contentEncodedResourceManager = contentEncodedResourceManager; - return this; - } - - public boolean isCanonicalizePaths() { - return canonicalizePaths; - } - - /** - * If this handler should use canonicalized paths. - *

- * WARNING: If this is not true and {@link io.undertow.server.handlers.CanonicalPathHandler} is not installed in - * the handler chain then is may be possible to perform a directory traversal attack. If you set this to false make - * sure you have some kind of check in place to control the path. - * - * @param canonicalizePaths If paths should be canonicalized - */ - public void setCanonicalizePaths(boolean canonicalizePaths) { - this.canonicalizePaths = canonicalizePaths; - } - - public static class Builder implements HandlerBuilder { - - @Override - public String name() { - return "resource"; - } - - @Override - public Map> parameters() { - Map> params = new HashMap<>(); - params.put("location", String.class); - params.put("allow-listing", boolean.class); - return params; - } - - @Override - public Set requiredParameters() { - return Collections.singleton("location"); - } - - @Override - public String defaultParameter() { - return "location"; - } - - @Override - public HandlerWrapper build(Map config) { - return new Wrapper((String) config.get("location"), (Boolean) config.get("allow-listing")); - } - - } - - private static class Wrapper implements HandlerWrapper { - - private final String location; - private final boolean allowDirectoryListing; - - private Wrapper(String location, boolean allowDirectoryListing) { - this.location = location; - this.allowDirectoryListing = allowDirectoryListing; - } - - @Override - public HttpHandler wrap(HttpHandler handler) { - ResourceManager rm = new FileResourceManager(new File(location), 1024); - StaticResourceHandler resourceHandler = new StaticResourceHandler(rm); - resourceHandler.setDirectoryListingEnabled(allowDirectoryListing); - return resourceHandler; - } - } -} -