Skip to content

Commit

Permalink
[UNDERTOW-2216] fix resource serving via predicates + test
Browse files Browse the repository at this point in the history
  • Loading branch information
baranowb committed Apr 13, 2023
1 parent fd4cc49 commit ebbfdc3
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 5 deletions.
12 changes: 12 additions & 0 deletions core/src/main/java/io/undertow/attribute/RequestURLAttribute.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

package io.undertow.attribute;

import java.util.Map;

import io.undertow.predicate.PathPrefixPredicate;
import io.undertow.predicate.PathPrefixPredicate.PathPrefixMatchRecord;
import io.undertow.predicate.Predicate;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.QueryParameterUtils;

Expand Down Expand Up @@ -61,6 +66,13 @@ public void writeAttribute(final HttpServerExchange exchange, final String newVa
exchange.getQueryParameters().putAll(QueryParameterUtils.parseQueryString(newQueryString.substring(1), QueryParameterUtils.getQueryParamEncoding(exchange)));
}

final Map<String, Object> context = exchange.getAttachment(Predicate.PREDICATE_CONTEXT);
if(context != null) {
final PathPrefixPredicate.PathPrefixMatchRecord trans = (PathPrefixMatchRecord) context.get(PathPrefixPredicate.PREFIX_MATCH_RECORD);
if(trans != null) {
trans.overWritten();
}
}
}

@Override
Expand Down
37 changes: 37 additions & 0 deletions core/src/main/java/io/undertow/predicate/PathPrefixPredicate.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
*/
public class PathPrefixPredicate implements Predicate {

public static final String PREFIX_MATCH_RECORD = "PREFIX_MATCH_RECORD";
private final PathMatcher<Boolean> pathMatcher;
private static final boolean traceEnabled;

Expand Down Expand Up @@ -71,6 +72,10 @@ public boolean resolve(final HttpServerExchange value) {
UndertowLogger.PREDICATE_LOGGER.tracef("Storing \"remaining\" string of [%s] for %s.", result.getRemaining(), value);
}
context.put("remaining", result.getRemaining());
final PathPrefixMatchRecord transformer = new PathPrefixMatchRecord();
transformer.setRemaining(result.getRemaining());
transformer.setPrefix(result.getMatched());
context.put(PREFIX_MATCH_RECORD, transformer);
}
return matches;
}
Expand Down Expand Up @@ -112,4 +117,36 @@ public Predicate build(final Map<String, Object> config) {
return new PathPrefixPredicate(path);
}
}

public static class PathPrefixMatchRecord {

private String prefix;
private String remaining;
private boolean overWritten;

public void setPrefix(final String prefix) {
this.prefix = prefix;
}

public void setRemaining(final String remaining) {
this.remaining = remaining;
}

public void overWritten() {
this.overWritten = true;
}

public boolean isOverWritten() {
return this.overWritten;
}

public String getPrefix() {
return prefix;
}

public String getRemaining() {
return remaining;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
package io.undertow.server.handlers.resource;

import io.undertow.UndertowLogger;
import io.undertow.predicate.PathPrefixPredicate;
import io.undertow.predicate.Predicate;
import io.undertow.predicate.PathPrefixPredicate.PathPrefixMatchRecord;
import io.undertow.server.HttpServerExchange;
import io.undertow.util.DateUtils;
import io.undertow.util.ETag;
Expand All @@ -39,6 +42,7 @@
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Map;

/**
* @author Stuart Douglas
Expand Down Expand Up @@ -90,7 +94,7 @@ public static boolean sendRequestedBlobs(HttpServerExchange exchange) {
return false;
}

public static StringBuilder renderDirectoryListing(String path, Resource resource) {
public static StringBuilder renderDirectoryListing(final HttpServerExchange exchange, String path, final Resource resource) {
if (!path.endsWith("/")){
path += "/";
}
Expand Down Expand Up @@ -123,20 +127,33 @@ public static StringBuilder renderDirectoryListing(String path, Resource resourc
}
}

String relative = null;
final Map<String, Object> context = exchange.getAttachment(Predicate.PREDICATE_CONTEXT);
if(context != null) {
final PathPrefixPredicate.PathPrefixMatchRecord trans = (PathPrefixMatchRecord) context.get(PathPrefixPredicate.PREFIX_MATCH_RECORD);
if(trans != null) {
if(trans.isOverWritten()) {
relative = trans.getPrefix();
if(!relative.endsWith("/") && !path.startsWith("/")) {
relative+="/";
}
}
}
}

SimpleDateFormat format = new SimpleDateFormat("MMM dd, yyyy HH:mm:ss", Locale.US);
int i = 0;
if (parent != null) {
i++;
builder.append("<tr class='odd'><td><a class='icon up' href='").append(parent).append("'>[..]</a></td><td>");
builder.append("<tr class='odd'><td><a class='icon up' href='").append(relative == null ? parent : relative + parent).append(parent.endsWith("/") ? "" : "/").append("'>[..]</a></td><td>");
builder.append(format.format((resource.getLastModified() == null ? new Date(0L) : resource.getLastModified())))
.append("</td><td>--</td></tr>\n");
}

for (Resource entry : resource.list()) {
builder.append("<tr class='").append((++i & 1) == 1 ? "odd" : "even").append("'><td><a class='icon ");
builder.append(entry.isDirectory() ? "dir" : "file");
builder.append("' href='").append(path).append(entry.getName()).append("'>").append(entry.getName()).append("</a></td><td>");
builder.append("' href='").append(relative == null ? path : relative + path).append(entry.getName()).append(entry.isDirectory() ? "/" : "").append("'>").append(entry.getName()).append("</a></td><td>");
builder.append(format.format((entry.getLastModified() == null) ? new Date(0L) : entry.getLastModified()))
.append("</td><td>");
if (entry.isDirectory()) {
Expand All @@ -161,7 +178,7 @@ public static void renderDirectoryListing(HttpServerExchange exchange, Resource
return;
}

StringBuilder builder = renderDirectoryListing(requestPath, resource);
StringBuilder builder = renderDirectoryListing(exchange, requestPath, resource);

try {
ByteBuffer output = ByteBuffer.wrap(builder.toString().getBytes(StandardCharsets.UTF_8));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2023 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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 io.undertow.server.handlers;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileAttribute;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;

import io.undertow.Handlers;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.builder.PredicatedHandler;
import io.undertow.server.handlers.builder.PredicatedHandlersParser;
import io.undertow.testutils.DefaultServer;
import io.undertow.testutils.TestHttpClient;
import io.undertow.util.StatusCodes;

/**
* Test basic resource serving via predicate handlers
*
* @author baranowb
*
*/
@RunWith(DefaultServer.class)
public class ResourcePredicateHandlerTestCase {

private static final String DIR_PREFIXED = "prefix-resource-dir";
private static final String DIR_PATH = "path-resource-dir";
private static final String FILE_NAME_LEVEL_0 = "file0";
private static final String FILE_NAME_LEVEL_1 = "file1";
private static final String DIR_SUB = "sub_dir";
private static final String GIBBERISH = "Gibberish, what did you expect?";

private static final String TEST_PREFIX = "prefixToTest";

@Test
public void testPrefixMatchWithURIAltering() throws IOException {
final PathsRetainer pathsRetainer = createTestDir(DIR_PREFIXED, false);
DefaultServer.setRootHandler(Handlers.predicates(

PredicatedHandlersParser
.parse("path-prefix(/" + TEST_PREFIX + ")-> { set(attribute=%U,value=${remaining}); resource(location='"
+ pathsRetainer.root.toString() + "',allow-listing=true) }", getClass().getClassLoader()),
new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
}
}));
testURLListing(pathsRetainer, true);
}

@Test
public void testPrefixMatchNoURIAltering() throws IOException {
final PathsRetainer pathsRetainer = createTestDir(DIR_PREFIXED, true);
DefaultServer.setRootHandler(Handlers.predicates(

PredicatedHandlersParser
.parse("path-prefix(/" + TEST_PREFIX + ")-> { resource(location='"
+ pathsRetainer.root.toString() + "',allow-listing=true) }", getClass().getClassLoader()),
new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
}
}));
testURLListing(pathsRetainer, true);
}

@Test
public void testPathMatchNoURIAltering() throws IOException {
final PathsRetainer pathsRetainer = createTestDir(DIR_PATH, true);
//No idea why parsing does not work in one go
final List<PredicatedHandler> lst = new ArrayList<>();
lst.addAll(PredicatedHandlersParser
.parse("path(/" + TEST_PREFIX + ")-> { resource(location='"
+ pathsRetainer.root.toString() + "',allow-listing=true)", getClass().getClassLoader()));
lst.addAll(PredicatedHandlersParser
.parse("path(/" + TEST_PREFIX + "/" +pathsRetainer.sub.getFileName() + ")-> { resource(location='"
+ pathsRetainer.root.toString() + "',allow-listing=true) }", getClass().getClassLoader()));
DefaultServer.setRootHandler(Handlers.predicates(lst,
new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
Assert.assertFalse(true);
}
}));
testURLListing(pathsRetainer, false);
}

private void testURLListing(final PathsRetainer pathsRetainer, boolean testFile) throws IOException {

TestHttpClient client = new TestHttpClient();
HttpGet get = new HttpGet(DefaultServer.getDefaultServerURL() + "/" + TEST_PREFIX+"/");
HttpResponse result = client.execute(get);
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
String bodyToTest = EntityUtils.toString(result.getEntity());
//this is not optimal...
Assert.assertTrue(bodyToTest.contains("href='/"+TEST_PREFIX+"/"+pathsRetainer.sub.getFileName()+"/'>"+pathsRetainer.sub.getFileName()+"</a>"));
Assert.assertTrue(bodyToTest.contains("href='/"+TEST_PREFIX+"/"+pathsRetainer.rootFile.getFileName()+"'>"+pathsRetainer.rootFile.getFileName()+"</a>"));
get = new HttpGet(DefaultServer.getDefaultServerURL() + "/" + TEST_PREFIX+ "/" +pathsRetainer.sub.getFileName()+ "/");
result = client.execute(get);
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
bodyToTest = EntityUtils.toString(result.getEntity());
Assert.assertTrue(bodyToTest.contains("href='/"+TEST_PREFIX+"/'>[..]</a>"));
Assert.assertTrue(bodyToTest.contains("href='/"+TEST_PREFIX+"/"+pathsRetainer.sub.getFileName()+"/"+pathsRetainer.subFile.getFileName()+"'>"+pathsRetainer.subFile.getFileName()+"</a>"));
if(testFile) {
get = new HttpGet(DefaultServer.getDefaultServerURL() + "/"+TEST_PREFIX+"/"+pathsRetainer.sub.getFileName()+"/"+pathsRetainer.subFile.getFileName());
result = client.execute(get);
Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode());
bodyToTest = EntityUtils.toString(result.getEntity());
Assert.assertEquals(GIBBERISH, bodyToTest);
}
}

private PathsRetainer createTestDir(final String dirName, final boolean prefixDirectory) throws IOException {
final FileAttribute<?>[] attribs = new FileAttribute<?>[] {};
final PathsRetainer pathsRetainer = new PathsRetainer();
Path dir = Files.createTempDirectory(dirName);
if (prefixDirectory) {
//dont use temp, as it will add random stuff
//parent is already temp
File f = dir.toFile();
f = new File(f,TEST_PREFIX);
Assert.assertTrue(f.mkdir());
pathsRetainer.root = dir;
dir = f.toPath();
} else {
pathsRetainer.root = dir;
}

Path file = Files.createTempFile(dir, FILE_NAME_LEVEL_0,".txt", attribs);
pathsRetainer.rootFile = file;
writeGibberish(file);
final Path subdir = Files.createTempDirectory(dir, DIR_SUB);
pathsRetainer.sub = subdir;
file = Files.createTempFile(subdir, FILE_NAME_LEVEL_1,".txt", attribs);
pathsRetainer.subFile = file;
writeGibberish(file);
return pathsRetainer;
}

private void writeGibberish(final Path p) throws IOException {
Files.write(p,GIBBERISH.getBytes());
}
private static class PathsRetainer{
private Path root;
private Path rootFile;
private Path sub;
private Path subFile;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ protected void doGet(final HttpServletRequest req, final HttpServletResponse res
return;
}
resp.setContentType("text/html");
StringBuilder output = DirectoryUtils.renderDirectoryListing(req.getRequestURI(), resource);
StringBuilder output = DirectoryUtils.renderDirectoryListing(exchange, req.getRequestURI(), resource);
resp.getWriter().write(output.toString());
} else {
resp.sendError(StatusCodes.FORBIDDEN);
Expand Down

0 comments on commit ebbfdc3

Please sign in to comment.