Skip to content

Commit be382f3

Browse files
committed
re add jar loader
1 parent 4980675 commit be382f3

File tree

7 files changed

+182
-12
lines changed

7 files changed

+182
-12
lines changed

avaje-jex/src/main/java/io/avaje/jex/AbstractStaticHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import io.avaje.jex.http.NotFoundException;
1313

1414
abstract sealed class AbstractStaticHandler implements ExchangeHandler
15-
permits StaticFileHandler, StaticClassResourceHandler {
15+
permits StaticFileHandler, PathResourceHandler, JarResourceHandler {
1616

1717
protected final Map<String, String> mimeTypes;
1818
protected final String filesystemRoot;

avaje-jex/src/main/java/io/avaje/jex/StaticClassResourceHandler.java renamed to avaje-jex/src/main/java/io/avaje/jex/JarResourceHandler.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import java.util.Map;
77
import java.util.function.Predicate;
88

9-
final class StaticClassResourceHandler extends AbstractStaticHandler implements ExchangeHandler {
9+
final class JarResourceHandler extends AbstractStaticHandler implements ExchangeHandler {
1010

1111
private final URL indexFile;
1212
private final URL singleFile;
1313
private final ClassResourceLoader resourceLoader;
1414

15-
StaticClassResourceHandler(
15+
JarResourceHandler(
1616
String urlPrefix,
1717
String filesystemRoot,
1818
Map<String, String> mimeTypes,
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package io.avaje.jex;
2+
3+
import java.io.IOException;
4+
import java.nio.file.Files;
5+
import java.nio.file.Path;
6+
import java.util.Map;
7+
import java.util.function.Predicate;
8+
9+
import io.avaje.jex.spi.SpiContext;
10+
11+
final class PathResourceHandler extends AbstractStaticHandler implements ExchangeHandler {
12+
13+
private final Path indexFile;
14+
private final Path singleFile;
15+
16+
PathResourceHandler(
17+
String urlPrefix,
18+
String filesystemRoot,
19+
Map<String, String> mimeTypes,
20+
Map<String, String> headers,
21+
Predicate<Context> skipFilePredicate,
22+
Path indexFile,
23+
Path singleFile) {
24+
super(urlPrefix, filesystemRoot, mimeTypes, headers, skipFilePredicate);
25+
26+
this.indexFile = indexFile;
27+
this.singleFile = singleFile;
28+
}
29+
30+
@Override
31+
public void handle(Context ctx) throws IOException {
32+
33+
if (singleFile != null) {
34+
sendPathIS(ctx, singleFile.toString(), singleFile);
35+
return;
36+
}
37+
38+
final var jdkExchange = ctx.exchange();
39+
if (skipFilePredicate.test(ctx)) {
40+
throw404(jdkExchange);
41+
}
42+
43+
final String wholeUrlPath = jdkExchange.getRequestURI().getPath();
44+
45+
if (wholeUrlPath.endsWith("/") || wholeUrlPath.equals(urlPrefix)) {
46+
sendPathIS(ctx, indexFile.toString(), indexFile);
47+
48+
return;
49+
}
50+
51+
final String urlPath = wholeUrlPath.substring(urlPrefix.length());
52+
53+
Path path;
54+
try {
55+
path = Path.of(filesystemRoot, urlPath).toRealPath();
56+
57+
} catch (final IOException e) {
58+
// This may be more benign (i.e. not an attack, just a 403),
59+
// but we don't want an attacker to be able to discern the difference.
60+
reportPathTraversal();
61+
return;
62+
}
63+
64+
final String canonicalPath = path.toString();
65+
if (!canonicalPath.startsWith(filesystemRoot)) {
66+
reportPathTraversal();
67+
}
68+
69+
sendPathIS(ctx, urlPath, path);
70+
}
71+
72+
private void sendPathIS(Context ctx, String urlPath, Path path) throws IOException {
73+
final var exchange = ctx.exchange();
74+
final String mimeType = lookupMime(urlPath);
75+
ctx.header("Content-Type", mimeType);
76+
ctx.headers(headers);
77+
78+
try (var fis = Files.newInputStream(path);
79+
var os = exchange.getResponseBody()) {
80+
var spiCtx = (SpiContext) ctx;
81+
82+
if (!spiCtx.compressionEnabled()) {
83+
exchange.sendResponseHeaders(200, Files.size(path));
84+
fis.transferTo(os);
85+
} else {
86+
spiCtx.write(fis);
87+
}
88+
} catch (final Exception e) {
89+
throw404(ctx.exchange());
90+
}
91+
}
92+
}

avaje-jex/src/main/java/io/avaje/jex/StaticFileHandler.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import com.sun.net.httpserver.HttpExchange;
1111

12+
import io.avaje.jex.spi.SpiContext;
13+
1214
final class StaticFileHandler extends AbstractStaticHandler implements ExchangeHandler {
1315

1416
private final File indexFile;
@@ -77,7 +79,15 @@ private void sendFile(Context ctx, HttpExchange jdkExchange, String urlPath, Fil
7779
String mimeType = lookupMime(urlPath);
7880
ctx.header("Content-Type", mimeType);
7981
ctx.headers(headers);
80-
ctx.write(fis);
82+
var spiCtx = (SpiContext) ctx;
83+
84+
if (!spiCtx.compressionEnabled()) {
85+
jdkExchange.sendResponseHeaders(200, canonicalFile.length());
86+
fis.transferTo(jdkExchange.getResponseBody());
87+
} else {
88+
spiCtx.write(fis);
89+
}
90+
8191
} catch (FileNotFoundException e) {
8292
throw404(jdkExchange);
8393
}

avaje-jex/src/main/java/io/avaje/jex/StaticResourceHandlerBuilder.java

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
import static io.avaje.jex.ResourceLocation.CLASS_PATH;
44

55
import java.io.File;
6+
import java.net.URI;
7+
import java.net.URISyntaxException;
68
import java.net.URL;
9+
import java.nio.file.Path;
10+
import java.nio.file.Paths;
711
import java.util.HashMap;
812
import java.util.Map;
913
import java.util.Objects;
@@ -16,12 +20,13 @@ final class StaticResourceHandlerBuilder implements StaticContentConfig {
1620
private static final String DIRECTORY_INDEX_FAILURE =
1721
"Failed to locate Directory Index Resource: ";
1822
private static final Predicate<Context> NO_OP_PREDICATE = ctx -> false;
19-
private static final ClassResourceLoader DEFAULT_LOADER = new DefaultResourceLoader();
23+
private static final ClassResourceLoader DEFALT_LOADER =
24+
ClassResourceLoader.fromClass(StaticResourceHandlerBuilder.class);
2025

2126
private String path = "/";
2227
private String root = "/public/";
2328
private String directoryIndex = null;
24-
private ClassResourceLoader resourceLoader = DEFAULT_LOADER;
29+
private ClassResourceLoader resourceLoader = DEFALT_LOADER;
2530
private final Map<String, String> mimeTypes = new HashMap<>();
2631
private final Map<String, String> headers = new HashMap<>();
2732
private Predicate<Context> skipFilePredicate = NO_OP_PREDICATE;
@@ -153,17 +158,71 @@ private ExchangeHandler fileLoader(Function<String, File> fileLoader) {
153158
}
154159

155160
private ExchangeHandler classPathHandler() {
161+
Function<String, URL> urlFunc = resourceLoader::loadResource;
156162

157-
URL dirIndex = null;
158-
URL singleFile = null;
163+
Function<String, URI> loaderFunc = urlFunc.andThen(this::toURI);
164+
String fsRoot;
165+
Path dirIndex = null;
166+
Path singleFile = null;
159167
if (directoryIndex != null) {
160-
dirIndex = resourceLoader.loadResource(root.transform(this::appendSlash) + directoryIndex);
168+
try {
169+
var uri = loaderFunc.apply(root.transform(this::appendSlash) + directoryIndex);
170+
171+
if ("jar".equals(uri.getScheme())) {
172+
173+
var url = uri.toURL();
174+
return jarLoader(url.toString().transform(this::getJARRoot), url, null);
175+
}
176+
dirIndex = Paths.get(uri).toRealPath();
177+
fsRoot = Paths.get(uri).getParent().toString();
161178

179+
} catch (Exception e) {
180+
181+
throw new IllegalStateException(
182+
DIRECTORY_INDEX_FAILURE + root.transform(this::appendSlash) + directoryIndex, e);
183+
}
162184
} else {
163-
singleFile = resourceLoader.loadResource(root);
185+
try {
186+
var uri = loaderFunc.apply(root);
187+
188+
if ("jar".equals(uri.getScheme())) {
189+
190+
var url = uri.toURL();
191+
192+
return jarLoader(url.toString().transform(this::getJARRoot), null, uri.toURL());
193+
}
194+
195+
singleFile = Paths.get(uri).toRealPath();
196+
197+
fsRoot = singleFile.getParent().toString();
198+
199+
} catch (Exception e) {
200+
201+
throw new IllegalStateException(FAILED_TO_LOCATE_FILE + root, e);
202+
}
164203
}
165204

166-
return new StaticClassResourceHandler(
167-
path, root, mimeTypes, headers, skipFilePredicate, resourceLoader, dirIndex, singleFile);
205+
return new PathResourceHandler(
206+
path, fsRoot, mimeTypes, headers, skipFilePredicate, dirIndex, singleFile);
207+
}
208+
209+
private String getJARRoot(String s) {
210+
return s.substring(0, s.lastIndexOf("/")).substring(s.indexOf("jar!") + 4);
211+
}
212+
213+
private ExchangeHandler jarLoader(String fsRoot, URL dirIndex, URL singleFile) {
214+
215+
return new JarResourceHandler(
216+
path, fsRoot, mimeTypes, headers, skipFilePredicate, resourceLoader, dirIndex, singleFile);
217+
}
218+
219+
private URI toURI(URL url) {
220+
221+
try {
222+
223+
return url.toURI();
224+
} catch (URISyntaxException e) {
225+
throw new IllegalStateException(e);
226+
}
168227
}
169228
}

avaje-jex/src/main/java/io/avaje/jex/jdk/JdkContext.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,4 +502,9 @@ private static BasicAuthCredentials getBasicAuthCredentials(String authorization
502502

503503
return new BasicAuthCredentials(credentials[0], credentials[1]);
504504
}
505+
506+
@Override
507+
public boolean compressionEnabled() {
508+
return compressionConfig.compressionEnabled();
509+
}
505510
}

avaje-jex/src/main/java/io/avaje/jex/spi/SpiContext.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.avaje.jex.Context;
44
import io.avaje.jex.Routing;
5+
import io.avaje.jex.compression.CompressionConfig;
56

67
import java.io.InputStream;
78
import java.io.OutputStream;
@@ -18,6 +19,9 @@ public interface SpiContext extends Context {
1819
String APPLICATION_JSON = "application/json";
1920
String APPLICATION_X_JSON_STREAM = "application/x-json-stream";
2021

22+
/** Return whether compression is enabled. */
23+
boolean compressionEnabled();
24+
2125
/**
2226
* Return the response outputStream to write content to.
2327
*/

0 commit comments

Comments
 (0)