From 49bbb8d7deecb676af225100785d42cc36678e6a Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Thu, 22 Jan 2026 17:23:33 +0100 Subject: [PATCH 1/5] Now only ResourceLocation is used for resource paths, not String --- .../server/resources/ResourceHelper.java | 53 +++++++------------ .../server/resources/ResourceLocation.java | 27 ++++++++++ 2 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java b/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java index bfaf82e..3da3757 100644 --- a/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java +++ b/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java @@ -2,7 +2,6 @@ import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -30,7 +29,7 @@ import de.igslandstuhl.database.server.Server; /** - * Helper class for managing resources in the application. + * Manages Resources in the application */ public class ResourceHelper { @@ -63,8 +62,8 @@ private static boolean isSafeZipEntryName(String entryName) { * @param pattern the pattern to match * @return the resources in the order they are found */ - public static Collection getResources(final Pattern pattern) { - final ArrayList retval = new ArrayList<>(); + public static Collection getResources(final Pattern pattern) { + final ArrayList retval = new ArrayList<>(); final String classPath = System.getProperty("java.class.path", "."); final String[] classPathElements = classPath.split(System.getProperty("path.separator")); for (final String element : classPathElements) { @@ -81,11 +80,11 @@ public static Collection getResources(final Pattern pattern) { * @param pattern the pattern to match * @return the resources in the order they are found */ - private static Collection getResources(final String element, final Pattern pattern) { - final ArrayList retval = new ArrayList<>(); + private static Collection getResources(final String element, final Pattern pattern) { + final ArrayList retval = new ArrayList<>(); final File file = new File(element); if (file.isDirectory()) { - retval.addAll(getResourcesFromDirectory(file, pattern)); + retval.addAll(getResourcesFromDirectory(file, pattern, file.toPath())); } else { retval.addAll(getResourcesFromJarFile(file, pattern)); } @@ -99,8 +98,8 @@ private static Collection getResources(final String element, final Patte * @param pattern the pattern to match * @return the resources in the order they are found */ - private static Collection getResourcesFromJarFile(final File file, final Pattern pattern) { - final ArrayList retval = new ArrayList<>(); + private static Collection getResourcesFromJarFile(final File file, final Pattern pattern) { + final ArrayList retval = new ArrayList<>(); ZipFile zf; try { zf = new ZipFile(file); @@ -121,7 +120,8 @@ private static Collection getResourcesFromJarFile(final File file, final } final boolean accept = pattern.matcher(fileName).matches(); if (accept) { - retval.add(fileName); + ResourceLocation location = ResourceLocation.fromPath(fileName); + if (location != null) retval.add(location); } } try { @@ -139,24 +139,21 @@ private static Collection getResourcesFromJarFile(final File file, final * @param pattern the pattern to match * @return the resources in the order they are found */ - private static Collection getResourcesFromDirectory(final File directory, final Pattern pattern) { - final ArrayList retval = new ArrayList<>(); + private static Collection getResourcesFromDirectory(final File directory, final Pattern pattern, final Path toplevelPath) { + final ArrayList retval = new ArrayList<>(); final File[] fileList = directory.listFiles(); if (fileList == null) { return retval; } for (final File file : fileList) { if (file.isDirectory()) { - retval.addAll(getResourcesFromDirectory(file, pattern)); + retval.addAll(getResourcesFromDirectory(file, pattern, toplevelPath)); } else { - try { - final String fileName = file.getCanonicalPath(); - final boolean accept = pattern.matcher(fileName).matches(); - if (accept) { - retval.add(fileName); - } - } catch (final IOException e) { - throw new Error(e); + final Path path = toplevelPath.relativize(file.toPath()); + final boolean accept = pattern.matcher(path.toString()).matches(); + if (accept) { + ResourceLocation location = ResourceLocation.fromPath(path); + if (location != null) retval.add(location); } } } @@ -172,19 +169,9 @@ private static Collection getResourcesFromDirectory(final File directory */ public static BufferedReader[] openResourcesAsReader(Pattern pattern) { List readers = new ArrayList<>(); - for (String resource : getResources(pattern)) { + for (ResourceLocation resource : getResources(pattern)) { try { - File file = new File(resource); - InputStream is; - if (file.exists() && file.isFile()) { - is = new FileInputStream(file); - } else { - is = ResourceHelper.class.getResourceAsStream("/" + resource); - if (is == null) { - throw new FileNotFoundException("Resource " + resource + " not found in classpath or filesystem."); - } - } - readers.add(new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))); + readers.add(new BufferedReader(new InputStreamReader(openResourceAsStream(resource)))); } catch (IOException e) { throw new IllegalStateException(e); } diff --git a/src/main/java/de/igslandstuhl/database/server/resources/ResourceLocation.java b/src/main/java/de/igslandstuhl/database/server/resources/ResourceLocation.java index 657a0f7..6c93f1b 100644 --- a/src/main/java/de/igslandstuhl/database/server/resources/ResourceLocation.java +++ b/src/main/java/de/igslandstuhl/database/server/resources/ResourceLocation.java @@ -1,5 +1,9 @@ package de.igslandstuhl.database.server.resources; +import java.io.File; +import java.nio.file.Path; +import java.util.regex.Matcher; + /** * Represents a resource location with context, namespace, and resource name. * This class is used to identify resources in the application. @@ -30,4 +34,27 @@ public static ResourceLocation get(String context, String resourceID) { public boolean isVirtual() { return context.equals("virtual"); } + public static ResourceLocation fromPath(Path path) { + Path relativePath; + try { + relativePath = Path.of(".").relativize(path); + } catch (IllegalArgumentException e) { + relativePath = path; + } + String rel = relativePath.toString(); + while (rel.startsWith(".") || rel.startsWith(File.separator)) { + rel = rel.substring(1); + } + return fromRelativePath(rel); + } + public static ResourceLocation fromPath(String path) { + return fromPath(Path.of(path)); + } + public static ResourceLocation fromRelativePath(String relativePath) { + String[] parts = relativePath.split(Matcher.quoteReplacement(File.separator)); + if (parts.length != 3) { + return null; + } + return new ResourceLocation(parts[0], parts[1], parts[2]); + } } From 0f9774bd3b9d2d45e87d61541aa9f899175ef980 Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Thu, 22 Jan 2026 17:30:12 +0100 Subject: [PATCH 2/5] Now using java.nio in ResourceHelper --- .../server/resources/ResourceHelper.java | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java b/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java index 3da3757..7a9afd5 100644 --- a/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java +++ b/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java @@ -1,12 +1,12 @@ package de.igslandstuhl.database.server.resources; import java.io.BufferedReader; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.Paths; @@ -82,11 +82,11 @@ public static Collection getResources(final Pattern pattern) { */ private static Collection getResources(final String element, final Pattern pattern) { final ArrayList retval = new ArrayList<>(); - final File file = new File(element); - if (file.isDirectory()) { - retval.addAll(getResourcesFromDirectory(file, pattern, file.toPath())); + final Path path = Path.of(element); + if (Files.isDirectory(path)) { + retval.addAll(getResourcesFromDirectory(path, pattern, path)); } else { - retval.addAll(getResourcesFromJarFile(file, pattern)); + retval.addAll(getResourcesFromJarFile(path, pattern)); } return retval; } @@ -94,15 +94,15 @@ private static Collection getResources(final String element, f /** * Get all resources from a jar file or a directory that match the given pattern. * - * @param file the jar file or directory to search in + * @param jarFilePath the jar file or directory to search in * @param pattern the pattern to match * @return the resources in the order they are found */ - private static Collection getResourcesFromJarFile(final File file, final Pattern pattern) { + private static Collection getResourcesFromJarFile(final Path jarFilePath, final Pattern pattern) { final ArrayList retval = new ArrayList<>(); ZipFile zf; try { - zf = new ZipFile(file); + zf = new ZipFile(jarFilePath.toFile()); } catch (final ZipException e) { throw new Error(e); } catch (final NoSuchFileException e) { @@ -139,23 +139,24 @@ private static Collection getResourcesFromJarFile(final File f * @param pattern the pattern to match * @return the resources in the order they are found */ - private static Collection getResourcesFromDirectory(final File directory, final Pattern pattern, final Path toplevelPath) { + private static Collection getResourcesFromDirectory(final Path directory, final Pattern pattern, final Path toplevelPath) { final ArrayList retval = new ArrayList<>(); - final File[] fileList = directory.listFiles(); - if (fileList == null) { - return retval; - } - for (final File file : fileList) { - if (file.isDirectory()) { - retval.addAll(getResourcesFromDirectory(file, pattern, toplevelPath)); - } else { - final Path path = toplevelPath.relativize(file.toPath()); - final boolean accept = pattern.matcher(path.toString()).matches(); - if (accept) { - ResourceLocation location = ResourceLocation.fromPath(path); - if (location != null) retval.add(location); + try { + Files.list(directory).forEach((path) -> { + if (Files.isDirectory(path)) { + retval.addAll(getResourcesFromDirectory(path, pattern, toplevelPath)); + } else { + final Path relativePath = toplevelPath.relativize(path); + final boolean accept = pattern.matcher(relativePath.toString()).matches(); + if (accept) { + ResourceLocation location = ResourceLocation.fromPath(relativePath); + if (location != null) retval.add(location); + } } - } + }); + } catch (IOException e) { + e.printStackTrace(); + return retval; } return retval; } From 7291465489f60ac0bdf8caa038eafced76bfb07e Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Thu, 22 Jan 2026 17:38:51 +0100 Subject: [PATCH 3/5] Refactored resource related methods to object methods of ResourceHelper --- .../igslandstuhl/database/server/Server.java | 12 ++++++++++ .../server/resources/ResourceHelper.java | 24 +++++++++---------- .../database/server/sql/SQLHelper.java | 5 ++-- .../database/server/sql/SQLiteConnection.java | 6 ++--- .../server/webserver/AccessManager.java | 4 ++-- .../database/server/webserver/WebPath.java | 4 ++-- .../webserver/responses/GetResponse.java | 11 ++++----- .../webserver/responses/HttpResponse.java | 7 +++--- .../responses/TemplatingPreprocessor.java | 4 ++-- 9 files changed, 43 insertions(+), 34 deletions(-) diff --git a/src/main/java/de/igslandstuhl/database/server/Server.java b/src/main/java/de/igslandstuhl/database/server/Server.java index 0a08e8e..009c6f0 100644 --- a/src/main/java/de/igslandstuhl/database/server/Server.java +++ b/src/main/java/de/igslandstuhl/database/server/Server.java @@ -19,6 +19,7 @@ import de.igslandstuhl.database.api.Subject; import de.igslandstuhl.database.api.Teacher; import de.igslandstuhl.database.api.User; +import de.igslandstuhl.database.server.resources.ResourceHelper; import de.igslandstuhl.database.server.sql.SQLHelper; import de.igslandstuhl.database.server.sql.SQLiteConnection; @@ -72,6 +73,17 @@ public SQLiteConnection getConnection() { public WebServer getWebServer() { return webServer; } + /** + * The resource manager for this server + */ + private final ResourceHelper resourceManager = new ResourceHelper(); + /** + * Returns the resource manager used by this server + * @return the resource manager + */ + public ResourceHelper getResourceManager() { + return resourceManager; + } /** * Private constructor to initialize the server instance. diff --git a/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java b/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java index 7a9afd5..5b70c2f 100644 --- a/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java +++ b/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java @@ -36,7 +36,7 @@ public class ResourceHelper { /** * Checks if a zip entry name is safe (no path traversal, not absolute). */ - private static boolean isSafeZipEntryName(String entryName) { + private boolean isSafeZipEntryName(String entryName) { // Reject absolute paths Path path = Paths.get(entryName).normalize(); if (path.isAbsolute()) { @@ -62,7 +62,7 @@ private static boolean isSafeZipEntryName(String entryName) { * @param pattern the pattern to match * @return the resources in the order they are found */ - public static Collection getResources(final Pattern pattern) { + public Collection getResources(final Pattern pattern) { final ArrayList retval = new ArrayList<>(); final String classPath = System.getProperty("java.class.path", "."); final String[] classPathElements = classPath.split(System.getProperty("path.separator")); @@ -80,7 +80,7 @@ public static Collection getResources(final Pattern pattern) { * @param pattern the pattern to match * @return the resources in the order they are found */ - private static Collection getResources(final String element, final Pattern pattern) { + private Collection getResources(final String element, final Pattern pattern) { final ArrayList retval = new ArrayList<>(); final Path path = Path.of(element); if (Files.isDirectory(path)) { @@ -98,7 +98,7 @@ private static Collection getResources(final String element, f * @param pattern the pattern to match * @return the resources in the order they are found */ - private static Collection getResourcesFromJarFile(final Path jarFilePath, final Pattern pattern) { + private Collection getResourcesFromJarFile(final Path jarFilePath, final Pattern pattern) { final ArrayList retval = new ArrayList<>(); ZipFile zf; try { @@ -139,7 +139,7 @@ private static Collection getResourcesFromJarFile(final Path j * @param pattern the pattern to match * @return the resources in the order they are found */ - private static Collection getResourcesFromDirectory(final Path directory, final Pattern pattern, final Path toplevelPath) { + private Collection getResourcesFromDirectory(final Path directory, final Pattern pattern, final Path toplevelPath) { final ArrayList retval = new ArrayList<>(); try { Files.list(directory).forEach((path) -> { @@ -168,7 +168,7 @@ private static Collection getResourcesFromDirectory(final Path * @param pattern the pattern to match * @return an array of BufferedReaders for the matching resources */ - public static BufferedReader[] openResourcesAsReader(Pattern pattern) { + public BufferedReader[] openResourcesAsReader(Pattern pattern) { List readers = new ArrayList<>(); for (ResourceLocation resource : getResources(pattern)) { try { @@ -188,7 +188,7 @@ public static BufferedReader[] openResourcesAsReader(Pattern pattern) { * @return an InputStream for the resource * @throws FileNotFoundException if the resource is not found */ - public static InputStream openResourceAsStream(ResourceLocation location) throws FileNotFoundException { + public InputStream openResourceAsStream(ResourceLocation location) throws FileNotFoundException { String url = "/" + location.context() + "/" + location.namespace() + "/" + location.resource(); InputStream stream = ResourceHelper.class.getResourceAsStream(url); if (stream == null) { @@ -205,7 +205,7 @@ public static InputStream openResourceAsStream(ResourceLocation location) throws * @return the content of the resource as a String * @throws FileNotFoundException if the resource is not found */ - public static String readResourceCompletely(ResourceLocation location) throws FileNotFoundException { + public String readResourceCompletely(ResourceLocation location) throws FileNotFoundException { return readResourceCompletely(new BufferedReader(new InputStreamReader(openResourceAsStream(location), StandardCharsets.UTF_8))); } @@ -216,7 +216,7 @@ public static String readResourceCompletely(ResourceLocation location) throws Fi * @param in the BufferedReader to read from * @return the content of the BufferedReader as a String */ - public static String readResourceCompletely(BufferedReader in) { + public String readResourceCompletely(BufferedReader in) { StringBuilder builder = new StringBuilder(); in.lines().forEach((s) -> { builder.append(s); @@ -234,7 +234,7 @@ public static String readResourceCompletely(BufferedReader in) { * @return the content read until an empty line is encountered * @throws IOException if an I/O error occurs */ - public static String readResourceTillEmptyLine(BufferedReader in) throws IOException { + public String readResourceTillEmptyLine(BufferedReader in) throws IOException { StringBuilder builder = new StringBuilder(); Stream lines = in.lines(); for (String line : new Iterable() { @@ -259,7 +259,7 @@ public Iterator iterator() { * @param location the ResourceLocation object representing the virtual resource * @return the content of the virtual resource as a String, or null if not applicable */ - public static String readVirtualResource(String user, ResourceLocation location) { + public String readVirtualResource(String user, ResourceLocation location) { if (!location.isVirtual()) { return null; } else if (location.namespace().equals("sql")) { @@ -269,7 +269,7 @@ public static String readVirtualResource(String user, ResourceLocation location) } } - public static Map readJsonResourceAsMap(ResourceLocation location) throws IOException { + public Map readJsonResourceAsMap(ResourceLocation location) throws IOException { try (BufferedReader in = new BufferedReader(new InputStreamReader(openResourceAsStream(location), StandardCharsets.UTF_8))) { Gson gson = new Gson(); java.lang.reflect.Type mapType = new TypeToken>(){}.getType(); diff --git a/src/main/java/de/igslandstuhl/database/server/sql/SQLHelper.java b/src/main/java/de/igslandstuhl/database/server/sql/SQLHelper.java index 4be82d9..a547b33 100644 --- a/src/main/java/de/igslandstuhl/database/server/sql/SQLHelper.java +++ b/src/main/java/de/igslandstuhl/database/server/sql/SQLHelper.java @@ -5,7 +5,6 @@ import java.sql.SQLException; import de.igslandstuhl.database.server.Server; -import de.igslandstuhl.database.server.resources.ResourceHelper; import de.igslandstuhl.database.server.resources.ResourceLocation; /** @@ -49,7 +48,7 @@ public static String getSQLQuery(String queryName) { ResourceLocation location = new ResourceLocation(CONTEXT, QUERIES, queryName + ".sql"); String query; try { - query = ResourceHelper.readResourceCompletely(location); + query = Server.getInstance().getResourceManager().readResourceCompletely(location); } catch (FileNotFoundException e) { throw new SQLCommandNotFoundException(queryName, e); } @@ -90,7 +89,7 @@ private static String getSQLStatement(String type, String object) { ResourceLocation location = new ResourceLocation(CONTEXT, PUSHES, type + "_" + object + ".sql"); String statement; try { - statement = ResourceHelper.readResourceCompletely(location); + statement = Server.getInstance().getResourceManager().readResourceCompletely(location); } catch (FileNotFoundException e) { throw new SQLCommandNotFoundException(type + "_" + object, e); } diff --git a/src/main/java/de/igslandstuhl/database/server/sql/SQLiteConnection.java b/src/main/java/de/igslandstuhl/database/server/sql/SQLiteConnection.java index 3d94222..a39a389 100644 --- a/src/main/java/de/igslandstuhl/database/server/sql/SQLiteConnection.java +++ b/src/main/java/de/igslandstuhl/database/server/sql/SQLiteConnection.java @@ -10,7 +10,7 @@ import java.sql.Statement; import java.util.regex.Pattern; -import de.igslandstuhl.database.server.resources.ResourceHelper; +import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.utils.TrackingReadWriteLock; /** @@ -47,9 +47,9 @@ public Connection getSQLConnection() { private void createTables(PreparedStatementSupplier supplier) throws SQLException { lock.writeLock().lock(); try { - for (BufferedReader in : ResourceHelper.openResourcesAsReader(Pattern.compile(".*tables.+\\.sql"))) { + for (BufferedReader in : Server.getInstance().getResourceManager().openResourcesAsReader(Pattern.compile(".*tables.+\\.sql"))) { try (in) { - String request = ResourceHelper.readResourceCompletely(in); + String request = Server.getInstance().getResourceManager().readResourceCompletely(in); supplier.executeUpdate(request); } catch (IOException e) { throw new IllegalStateException(e); diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java b/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java index 02c0b33..cd92e5a 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/AccessManager.java @@ -6,7 +6,7 @@ import java.util.Map; import de.igslandstuhl.database.api.User; -import de.igslandstuhl.database.server.resources.ResourceHelper; +import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.resources.ResourceLocation; /** @@ -70,7 +70,7 @@ private AccessManager() { String[] teacherLocations = {}; String[] adminLocations = {"students", "teachers", "classes"}; try { - Map pathData = ResourceHelper.readJsonResourceAsMap(metaLocation); + Map pathData = Server.getInstance().getResourceManager().readJsonResourceAsMap(metaLocation); List publicSpacesList = (List) pathData.get("public_spaces"); List publicLocationsList = (List) pathData.get("public_locations"); List userLocationsList = (List) pathData.get("user_locations"); diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/WebPath.java b/src/main/java/de/igslandstuhl/database/server/webserver/WebPath.java index 24bd102..7e13e61 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/WebPath.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/WebPath.java @@ -5,7 +5,7 @@ import java.util.Map; import de.igslandstuhl.database.Registry; -import de.igslandstuhl.database.server.resources.ResourceHelper; +import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.resources.ResourceLocation; import de.igslandstuhl.database.server.webserver.requests.RequestType; @@ -16,7 +16,7 @@ public static void registerPath(String path, RequestType type, String handlerTyp public static void registerPaths() throws IOException { if (Registry.webPathRegistry().stream().count() > 0) return; // already registered ResourceLocation metaLocation = new ResourceLocation("meta", "paths", "get_paths.json"); - Map pathData = ResourceHelper.readJsonResourceAsMap(metaLocation); + Map pathData = Server.getInstance().getResourceManager().readJsonResourceAsMap(metaLocation); pathData.keySet().forEach((path) -> { @SuppressWarnings("unchecked") Map pathInfo = (Map) pathData.get(path); diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java index 9658a11..1262092 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/GetResponse.java @@ -5,7 +5,6 @@ import java.io.PrintStream; import de.igslandstuhl.database.server.Server; -import de.igslandstuhl.database.server.resources.ResourceHelper; import de.igslandstuhl.database.server.resources.ResourceLocation; import de.igslandstuhl.database.server.webserver.AccessManager; import de.igslandstuhl.database.server.webserver.ContentType; @@ -171,9 +170,9 @@ public void respond(PrintStream out) { String resource = ""; if (resourceLocation != null) { if (!resourceLocation.isVirtual()) { - resource = ResourceHelper.readResourceCompletely(resourceLocation); + resource = Server.getInstance().getResourceManager().readResourceCompletely(resourceLocation); } else { - resource = ResourceHelper.readVirtualResource(user, resourceLocation); + resource = Server.getInstance().getResourceManager().readVirtualResource(user, resourceLocation); if (resource == null) throw new NullPointerException(); } } @@ -182,7 +181,7 @@ public void respond(PrintStream out) { } out.println(resource); } else { - try (InputStream in = ResourceHelper.openResourceAsStream(resourceLocation)) { + try (InputStream in = Server.getInstance().getResourceManager().openResourceAsStream(resourceLocation)) { in.transferTo(out); // Streams bytes directly } } @@ -201,9 +200,9 @@ public void respond(PrintStream out) { public String getResponseBody() throws FileNotFoundException { if (resourceLocation != null) { if (!resourceLocation.isVirtual()) { - return ResourceHelper.readResourceCompletely(resourceLocation); + return Server.getInstance().getResourceManager().readResourceCompletely(resourceLocation); } else { - return ResourceHelper.readVirtualResource(user, resourceLocation); + return Server.getInstance().getResourceManager().readVirtualResource(user, resourceLocation); } } return ""; diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/HttpResponse.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/HttpResponse.java index 94612c7..b12013e 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/responses/HttpResponse.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/HttpResponse.java @@ -3,7 +3,6 @@ import java.io.PrintStream; import de.igslandstuhl.database.server.Server; -import de.igslandstuhl.database.server.resources.ResourceHelper; import de.igslandstuhl.database.server.resources.ResourceLocation; import de.igslandstuhl.database.server.webserver.ContentType; import de.igslandstuhl.database.server.webserver.Status; @@ -36,13 +35,13 @@ public void respond(PrintStream out) { ResourceLocation resourceLocation = new ResourceLocation("html", "errors", errorStatus.getCode() + ".html"); String resource; try { - resource = ResourceHelper.readResourceCompletely(resourceLocation); + resource = Server.getInstance().getResourceManager().readResourceCompletely(resourceLocation); out.println(resource); } catch (Exception e) { if (errorStatus != Status.INTERNAL_SERVER_ERROR) { resourceLocation = new ResourceLocation("html", "errors", errorStatus.getCode() + ".html"); try { - resource = ResourceHelper.readResourceCompletely(resourceLocation); + resource = Server.getInstance().getResourceManager().readResourceCompletely(resourceLocation); out.println(resource); } catch (Exception e2) { throw new IllegalStateException(e); @@ -81,7 +80,7 @@ public void respond(PrintStream out) { ResourceLocation resourceLocation = new ResourceLocation("html", "errors", errorStatus.getCode() + ".html"); String resource; try { - resource = ResourceHelper.readResourceCompletely(resourceLocation); + resource = Server.getInstance().getResourceManager().readResourceCompletely(resourceLocation); out.println(resource); } catch (Exception e) { throw new IllegalStateException(e); diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/TemplatingPreprocessor.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/TemplatingPreprocessor.java index 7f144cb..11a28c1 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/responses/TemplatingPreprocessor.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/TemplatingPreprocessor.java @@ -7,7 +7,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import de.igslandstuhl.database.server.resources.ResourceHelper; +import de.igslandstuhl.database.server.Server; import de.igslandstuhl.database.server.resources.ResourceLocation; public class TemplatingPreprocessor { @@ -20,7 +20,7 @@ private TemplatingPreprocessor() {} private String getTemplate(String name) throws FileNotFoundException { ResourceLocation templateLocation = new ResourceLocation("templates", "html", name + ".html"); - return ResourceHelper.readResourceCompletely(templateLocation); + return Server.getInstance().getResourceManager().readResourceCompletely(templateLocation); } public String executeTemplating(String content) throws IOException { From d9bcc9d83c0e155185531855c86c0eda6b7c1398 Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Thu, 22 Jan 2026 17:39:26 +0100 Subject: [PATCH 4/5] Renamed ResourceHelper to ResourceManager --- src/main/java/de/igslandstuhl/database/server/Server.java | 6 +++--- .../resources/{ResourceHelper.java => ResourceManager.java} | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename src/main/java/de/igslandstuhl/database/server/resources/{ResourceHelper.java => ResourceManager.java} (99%) diff --git a/src/main/java/de/igslandstuhl/database/server/Server.java b/src/main/java/de/igslandstuhl/database/server/Server.java index 009c6f0..a4b2f98 100644 --- a/src/main/java/de/igslandstuhl/database/server/Server.java +++ b/src/main/java/de/igslandstuhl/database/server/Server.java @@ -19,7 +19,7 @@ import de.igslandstuhl.database.api.Subject; import de.igslandstuhl.database.api.Teacher; import de.igslandstuhl.database.api.User; -import de.igslandstuhl.database.server.resources.ResourceHelper; +import de.igslandstuhl.database.server.resources.ResourceManager; import de.igslandstuhl.database.server.sql.SQLHelper; import de.igslandstuhl.database.server.sql.SQLiteConnection; @@ -76,12 +76,12 @@ public WebServer getWebServer() { /** * The resource manager for this server */ - private final ResourceHelper resourceManager = new ResourceHelper(); + private final ResourceManager resourceManager = new ResourceManager(); /** * Returns the resource manager used by this server * @return the resource manager */ - public ResourceHelper getResourceManager() { + public ResourceManager getResourceManager() { return resourceManager; } diff --git a/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java b/src/main/java/de/igslandstuhl/database/server/resources/ResourceManager.java similarity index 99% rename from src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java rename to src/main/java/de/igslandstuhl/database/server/resources/ResourceManager.java index 5b70c2f..39435e6 100644 --- a/src/main/java/de/igslandstuhl/database/server/resources/ResourceHelper.java +++ b/src/main/java/de/igslandstuhl/database/server/resources/ResourceManager.java @@ -31,7 +31,7 @@ /** * Manages Resources in the application */ -public class ResourceHelper { +public class ResourceManager { /** * Checks if a zip entry name is safe (no path traversal, not absolute). @@ -190,7 +190,7 @@ public BufferedReader[] openResourcesAsReader(Pattern pattern) { */ public InputStream openResourceAsStream(ResourceLocation location) throws FileNotFoundException { String url = "/" + location.context() + "/" + location.namespace() + "/" + location.resource(); - InputStream stream = ResourceHelper.class.getResourceAsStream(url); + InputStream stream = ResourceManager.class.getResourceAsStream(url); if (stream == null) { throw new FileNotFoundException(url + " not found in classpath or resources."); } From 5609c48fa51647619836d93abe2385c0b20af96b Mon Sep 17 00:00:00 2001 From: Schlaumeier5 Date: Thu, 22 Jan 2026 18:06:51 +0100 Subject: [PATCH 5/5] Fixed a few security vulnerabilities --- .../server/resources/ResourceManager.java | 30 +++++++------------ .../responses/TemplatingPreprocessor.java | 15 +++++++++- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/main/java/de/igslandstuhl/database/server/resources/ResourceManager.java b/src/main/java/de/igslandstuhl/database/server/resources/ResourceManager.java index 39435e6..7b2501e 100644 --- a/src/main/java/de/igslandstuhl/database/server/resources/ResourceManager.java +++ b/src/main/java/de/igslandstuhl/database/server/resources/ResourceManager.java @@ -32,27 +32,15 @@ * Manages Resources in the application */ public class ResourceManager { - /** - * Checks if a zip entry name is safe (no path traversal, not absolute). + * Checks if a zip entry name is safe (to prevent zip slipping). */ - private boolean isSafeZipEntryName(String entryName) { - // Reject absolute paths - Path path = Paths.get(entryName).normalize(); - if (path.isAbsolute()) { - return false; - } - // Reject entries containing ".." as a path segment - for (Path part : path) { - if (part.toString().equals("..")) { - return false; - } - } - // Reject entries starting with "/" or "\" - if (entryName.startsWith("/") || entryName.startsWith("\\")) { - return false; - } - return true; + private boolean isSafeZipEntryName(String entryName, Path rootDir) { + // Resolve entry against a fixed root and normalize + Path resolvedPath = rootDir.resolve(entryName).normalize(); + + // Entry is safe if it stays within the root directory + return resolvedPath.startsWith(rootDir); } /** @@ -100,6 +88,8 @@ private Collection getResources(final String element, final Pa */ private Collection getResourcesFromJarFile(final Path jarFilePath, final Pattern pattern) { final ArrayList retval = new ArrayList<>(); + // Virtual root – no real filesystem access needed + final Path virtualRoot = Paths.get("").toAbsolutePath().normalize(); ZipFile zf; try { zf = new ZipFile(jarFilePath.toFile()); @@ -114,7 +104,7 @@ private Collection getResourcesFromJarFile(final Path jarFileP while (e.hasMoreElements()) { final ZipEntry ze = e.nextElement(); final String fileName = ze.getName(); - if (!isSafeZipEntryName(fileName)) { + if (!isSafeZipEntryName(fileName, virtualRoot)) { // Optionally log or throw, here we skip unsafe entries continue; } diff --git a/src/main/java/de/igslandstuhl/database/server/webserver/responses/TemplatingPreprocessor.java b/src/main/java/de/igslandstuhl/database/server/webserver/responses/TemplatingPreprocessor.java index 11a28c1..f8f4676 100644 --- a/src/main/java/de/igslandstuhl/database/server/webserver/responses/TemplatingPreprocessor.java +++ b/src/main/java/de/igslandstuhl/database/server/webserver/responses/TemplatingPreprocessor.java @@ -2,6 +2,8 @@ import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; @@ -18,8 +20,19 @@ public static TemplatingPreprocessor getInstance() { private TemplatingPreprocessor() {} + private static String sanitizeTemplateName(String name) { + Path base = Paths.get("templates/html"); + Path resolved = base.resolve(name + ".html").normalize(); + + if (!resolved.startsWith(base)) { + throw new IllegalArgumentException("Invalid template name"); + } + + return resolved.getFileName().toString(); + } + private String getTemplate(String name) throws FileNotFoundException { - ResourceLocation templateLocation = new ResourceLocation("templates", "html", name + ".html"); + ResourceLocation templateLocation = new ResourceLocation("templates", "html", sanitizeTemplateName(name) + ".html"); return Server.getInstance().getResourceManager().readResourceCompletely(templateLocation); }