From 4e44c0fddeeb4c077652f3cdeeaf200471c6c1c9 Mon Sep 17 00:00:00 2001 From: labkey-tchad Date: Mon, 10 Nov 2025 14:25:45 -0800 Subject: [PATCH 1/3] Handle InvalidPathException in PipelinePathForm --- .../api/pipeline/browse/PipelinePathForm.java | 22 +++++++++++++++++-- .../org/labkey/pipeline/api/PipeRootImpl.java | 3 +++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java b/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java index b037848edf1..b791c5c7371 100644 --- a/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java +++ b/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java @@ -15,6 +15,7 @@ */ package org.labkey.api.pipeline.browse; +import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.exp.api.ExpData; import org.labkey.api.exp.api.ExperimentService; @@ -28,6 +29,7 @@ import java.io.File; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -100,7 +102,15 @@ public List getValidatedFiles(Container c, boolean allowNonExistentFiles) { PipeRoot pr = getPipeRoot(c); - File dir = pr.resolvePath(getPath()); + @Nullable File dir; + try + { + dir = pr.resolvePath(getPath()); + } + catch (InvalidPathException e) + { + throw new NotFoundException("Invalid path: " + e.getMessage(), e); + } if (dir == null || !dir.exists()) throw new NotFoundException("Could not find path " + getPath()); @@ -112,7 +122,15 @@ public List getValidatedFiles(Container c, boolean allowNonExistentFiles) List result = new ArrayList<>(); for (String fileName : _file) { - File f = pr.resolvePath(getPath() + "/" + fileName); + File f; + try + { + f = pr.resolvePath(getPath() + "/" + fileName); + } + catch (InvalidPathException e) + { + throw new NotFoundException("Invalid file: " + e.getMessage(), e); + } if (!allowNonExistentFiles && !NetworkDrive.exists(f)) { throw new NotFoundException("Could not find file '" + fileName + "' in '" + getPath() + "'"); diff --git a/pipeline/src/org/labkey/pipeline/api/PipeRootImpl.java b/pipeline/src/org/labkey/pipeline/api/PipeRootImpl.java index 51af9631832..8838f9079fa 100644 --- a/pipeline/src/org/labkey/pipeline/api/PipeRootImpl.java +++ b/pipeline/src/org/labkey/pipeline/api/PipeRootImpl.java @@ -382,6 +382,9 @@ public File resolvePath(org.labkey.api.util.Path path) @Override public @Nullable FileLike resolvePathToFileLike(String relativePath) { + if (null == relativePath) + throw new NotFoundException("Must specify a file path"); + var parsedPath = org.labkey.api.util.Path.parse(relativePath); var pair = _resolveRoot(parsedPath); From 39adfa596e7dbcadb26885b8e1066af3a672b912 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Tue, 11 Nov 2025 18:13:08 -0800 Subject: [PATCH 2/3] Specialized exception and JavaDoc updates --- api/src/org/labkey/api/pipeline/PipeRoot.java | 12 +++--- .../api/pipeline/browse/PipelinePathForm.java | 20 +--------- .../org/labkey/api/util/ExceptionUtil.java | 15 ++++++++ api/src/org/labkey/api/util/FileUtil.java | 38 +++++++++++++++---- .../org/labkey/pipeline/api/PipeRootImpl.java | 7 ++-- 5 files changed, 56 insertions(+), 36 deletions(-) diff --git a/api/src/org/labkey/api/pipeline/PipeRoot.java b/api/src/org/labkey/api/pipeline/PipeRoot.java index d71491f9586..850cc631b81 100644 --- a/api/src/org/labkey/api/pipeline/PipeRoot.java +++ b/api/src/org/labkey/api/pipeline/PipeRoot.java @@ -71,22 +71,22 @@ public interface PipeRoot extends SecurableResource Path resolveToNioPathFromUrl(String url); /** - * @return the file that's at the given relativePath from the pipeline root. Will be null if the relative path - * attempts to reference something that's not under the root (such as "../../etc/passwd". When the root + * @return the file that's at the given relativePath from the pipeline root. When the root * is configured with an alternative file path, we'll check to see if the file exists there. If not, we'll return * a path relative to the root's primary path. + * @throws org.labkey.api.util.FileUtil.InvalidPathReferenceException if the relative path + * attempts to reference something not under the root (such as "../../etc/passwd") */ @Deprecated // prefer resolvePathToFileLike() - @Nullable File resolvePath(String relativePath); /** - * @return the file that's at the given relativePath from the pipeline root. Will be null if the relative path - * attempts to reference something that's not under the root (such as "../../etc/passwd". When the root + * @return the file that's at the given relativePath from the pipeline root. When the root * is configured with an alternative file path, we'll check to see if the file exists there. If not, we'll return * a path relative to the root's primary path. + * @throws org.labkey.api.util.FileUtil.InvalidPathReferenceException if the relative path + * attempts to reference something not under the root (such as "../../etc/passwd") */ - @Nullable FileLike resolvePathToFileLike(String relativePath); /** diff --git a/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java b/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java index b791c5c7371..8b01faceaad 100644 --- a/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java +++ b/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java @@ -102,15 +102,7 @@ public List getValidatedFiles(Container c, boolean allowNonExistentFiles) { PipeRoot pr = getPipeRoot(c); - @Nullable File dir; - try - { - dir = pr.resolvePath(getPath()); - } - catch (InvalidPathException e) - { - throw new NotFoundException("Invalid path: " + e.getMessage(), e); - } + File dir = pr.resolvePath(getPath()); if (dir == null || !dir.exists()) throw new NotFoundException("Could not find path " + getPath()); @@ -122,15 +114,7 @@ public List getValidatedFiles(Container c, boolean allowNonExistentFiles) List result = new ArrayList<>(); for (String fileName : _file) { - File f; - try - { - f = pr.resolvePath(getPath() + "/" + fileName); - } - catch (InvalidPathException e) - { - throw new NotFoundException("Invalid file: " + e.getMessage(), e); - } + File f = pr.resolvePath(getPath() + "/" + fileName); if (!allowNonExistentFiles && !NetworkDrive.exists(f)) { throw new NotFoundException("Could not find file '" + fileName + "' in '" + getPath() + "'"); diff --git a/api/src/org/labkey/api/util/ExceptionUtil.java b/api/src/org/labkey/api/util/ExceptionUtil.java index 6c623fe76c6..3649564a0ba 100644 --- a/api/src/org/labkey/api/util/ExceptionUtil.java +++ b/api/src/org/labkey/api/util/ExceptionUtil.java @@ -865,6 +865,21 @@ else if (ex instanceof BadRequestException) message = ex.getMessage(); unhandledException = null; } + else if (ex instanceof FileUtil.InvalidPathReferenceException ipre) + { + responseStatus = HttpServletResponse.SC_NOT_FOUND; + errorType = ErrorRenderer.ErrorType.notFound; + if (ex.getMessage() != null) + { + message = ex.getMessage(); + responseStatusMessage = message; + } + else + message = responseStatus + ": Page not Found"; + unhandledException = null; + log.info("InvalidPathReferenceException: {}", ipre.getInput()); + log.debug("InvalidPathReferenceException", ex); + } else if (ex instanceof NotFoundException) { responseStatus = HttpServletResponse.SC_NOT_FOUND; diff --git a/api/src/org/labkey/api/util/FileUtil.java b/api/src/org/labkey/api/util/FileUtil.java index d7675c3092d..d61983e5142 100644 --- a/api/src/org/labkey/api/util/FileUtil.java +++ b/api/src/org/labkey/api/util/FileUtil.java @@ -826,11 +826,20 @@ public static File appendPath(File dir, org.labkey.api.util.Path originalPath) { org.labkey.api.util.Path path = originalPath.normalize(); if (path == null || (!path.isEmpty() && "..".equals(path.get(0)))) - throw new InvalidPathException(originalPath.toString(), "Path to parent not allowed"); + throw new InvalidPathReferenceException(originalPath.toString(), "Path to parent not allowed"); @SuppressWarnings("SSBasedInspection") var ret = new File(dir, path.toString()); - if (!ret.toPath().normalize().startsWith(dir.toPath().normalize())) - throw new InvalidPathException(originalPath.toString(), "Path to parent not allowed"); + boolean valid; + try + { + valid = ret.toPath().normalize().startsWith(dir.toPath().normalize()); + } + catch (InvalidPathException e) + { + throw new InvalidPathReferenceException(originalPath.toString(), e.getMessage()); + } + if (!valid) + throw new InvalidPathReferenceException(originalPath.toString(), "Path to parent not allowed"); return ret; } @@ -840,7 +849,7 @@ public static FileLike appendPath(FileLike dir, org.labkey.api.util.Path path) { path = path.normalize(); if (!path.isEmpty() && "..".equals(path.get(0))) - throw new InvalidPathException(path.toString(), "Path to parent not allowed"); + throw new InvalidPathReferenceException(path.toString(), "Path to parent not allowed"); return dir.resolveFile(path); } @@ -871,7 +880,7 @@ public static File appendName(File dir, String name) var ret = new File(dir, name); if (!ret.toPath().normalize().startsWith(dir.toPath().normalize())) - throw new InvalidPathException(name, "Path to parent not allowed"); + throw new InvalidPathReferenceException(name, "Path to parent not allowed"); return ret; } @@ -882,7 +891,7 @@ public static Path appendName(Path dir, String name) var ret = dir.resolve(name); if (!ret.normalize().startsWith(dir.normalize())) - throw new InvalidPathException(name, "Path to parent not allowed"); + throw new InvalidPathReferenceException(name, "Path to parent not allowed"); return ret; } @@ -893,11 +902,24 @@ public static void legalPathPartThrow(String name) { int invalidCharacterIndex = StringUtils.indexOfAny(name, '/', File.separatorChar); if (invalidCharacterIndex >= 0) - throw new InvalidPathException(name, "Invalid file or directory name", invalidCharacterIndex); + throw new InvalidPathReferenceException(name, "Invalid file or directory name", invalidCharacterIndex); if (".".equals(name) || "..".equals(name)) - throw new InvalidPathException(name, "Invalid file or directory name"); + throw new InvalidPathReferenceException(name, "Invalid file or directory name"); } + /** Our own subclass for bogus paths that we can special-case in ExceptionUtil in terms of HTTP response codes and logging */ + public static class InvalidPathReferenceException extends InvalidPathException + { + public InvalidPathReferenceException(String path, String reason) + { + super(path, reason); + } + + public InvalidPathReferenceException(String name, String reason, int index) + { + super(name, reason, index); + } + } public static String decodeSpaces(@NotNull String str) { diff --git a/pipeline/src/org/labkey/pipeline/api/PipeRootImpl.java b/pipeline/src/org/labkey/pipeline/api/PipeRootImpl.java index 8838f9079fa..b9069d2fd11 100644 --- a/pipeline/src/org/labkey/pipeline/api/PipeRootImpl.java +++ b/pipeline/src/org/labkey/pipeline/api/PipeRootImpl.java @@ -359,7 +359,6 @@ private Path findRootPath(Path file) @Override - @Nullable public File resolvePath(String pathStr) { if (null == pathStr) @@ -370,7 +369,7 @@ public File resolvePath(String pathStr) @Nullable - public File resolvePath(org.labkey.api.util.Path path) + private File resolvePath(org.labkey.api.util.Path path) { var pair = _resolveRoot(path); if (null == pair) @@ -380,10 +379,10 @@ public File resolvePath(org.labkey.api.util.Path path) @Override - public @Nullable FileLike resolvePathToFileLike(String relativePath) + public FileLike resolvePathToFileLike(String relativePath) { if (null == relativePath) - throw new NotFoundException("Must specify a file path"); + throw new FileUtil.InvalidPathReferenceException(null, "Must specify a file path"); var parsedPath = org.labkey.api.util.Path.parse(relativePath); From e130ce13be58840a1d929fee3bbc6d346927d475 Mon Sep 17 00:00:00 2001 From: labkey-jeckels Date: Wed, 12 Nov 2025 15:35:55 -0800 Subject: [PATCH 3/3] Imports --- api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java b/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java index 8b01faceaad..b037848edf1 100644 --- a/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java +++ b/api/src/org/labkey/api/pipeline/browse/PipelinePathForm.java @@ -15,7 +15,6 @@ */ package org.labkey.api.pipeline.browse; -import org.jetbrains.annotations.Nullable; import org.labkey.api.data.Container; import org.labkey.api.exp.api.ExpData; import org.labkey.api.exp.api.ExperimentService; @@ -29,7 +28,6 @@ import java.io.File; import java.nio.file.Files; -import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.ArrayList; import java.util.List;