From 6783fe1af002cf238d28c0092a25f10585473952 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 10 Mar 2026 09:13:02 +0100 Subject: [PATCH 1/6] File support improvements --- .../dev/vml/es/acm/core/code/FileManager.java | 7 ++- .../vml/es/acm/core/repo/RepoResource.java | 44 +++++++++++++---- .../vml/es/acm/core/util/AemConstants.java | 14 ++++++ .../dev/vml/es/acm/core/util/JcrUtils.java | 47 +++++++++++++++++++ ui.frontend/src/components/FileUploader.tsx | 6 +-- ui.frontend/src/types/main.ts | 4 ++ 6 files changed, 110 insertions(+), 12 deletions(-) create mode 100644 core/src/main/java/dev/vml/es/acm/core/util/AemConstants.java diff --git a/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java b/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java index 39fba9055..d3615ec6c 100644 --- a/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java +++ b/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java @@ -17,13 +17,15 @@ public class FileManager { + private static final String ROOT = AcmConstants.VAR_ROOT + "/file"; + private Repo repo; private RepoResource root; public FileManager(ResourceResolver resolver) { this.repo = Repo.quiet(resolver); - this.root = repo.get(AcmConstants.VAR_ROOT + "/file"); + this.root = repo.get(ROOT); } public Optional find(String path) { @@ -68,6 +70,9 @@ public String delete(String path) { if (resource == null) { throw new AcmException(String.format("File to be deleted does not exist '%s'!", path)); } + if (!StringUtils.startsWith(resource.getPath(), root.getPath() + "/")) { + throw new AcmException(String.format("File is outside managed root and cannot be deleted '%s'!", path)); + } resource.delete(); return resource.getPath(); } diff --git a/core/src/main/java/dev/vml/es/acm/core/repo/RepoResource.java b/core/src/main/java/dev/vml/es/acm/core/repo/RepoResource.java index fabff0181..ad87c0b1b 100644 --- a/core/src/main/java/dev/vml/es/acm/core/repo/RepoResource.java +++ b/core/src/main/java/dev/vml/es/acm/core/repo/RepoResource.java @@ -667,15 +667,11 @@ private void setFileContent( } public InputStream readFileAsStream() { - Resource resource = require(); - Resource contentResource = resource.getChild(JcrConstants.JCR_CONTENT); - if (contentResource == null) { - throw new RepoException(String.format("Cannot read file at path '%s' as it does not have content!", path)); - } - InputStream inputStream = contentResource.adaptTo(InputStream.class); + RepoResource fileResource = requireFile(); + InputStream inputStream = fileResource.resolve().adaptTo(InputStream.class); if (inputStream == null) { throw new RepoException( - String.format("Cannot read file at path '%s' as it does not have content stream!", path)); + String.format("Cannot read file at path '%s' as it does not have binary data!", path)); } return inputStream; } @@ -688,8 +684,40 @@ public String readFileAsString() { } } + public RepoResource resolveFile() { + return file().orElse(null); + } + + public RepoResource requireFile() { + return file().orElseThrow(() -> + new RepoException(String.format("Resource at path '%s' does not have file content!", path))); + } + + private Optional file() { + Resource resource = resolve(); + if (resource == null) { + return Optional.empty(); + } + Resource contentResource = resource.getChild(JcrConstants.JCR_CONTENT); + if (contentResource == null) { + return Optional.empty(); + } + if (path.startsWith(AemConstants.DAM_ROOT + "/")) { + String damOriginalPath = AemConstants.DAM_RENDITION_FOLDER + "/" + AemConstants.DAM_RENDITION_ORIGINAL + "/" + + JcrConstants.JCR_CONTENT; + Resource damContent = contentResource.getChild(damOriginalPath); + if (damContent != null && JcrUtils.hasBinaryData(damContent.adaptTo(Node.class))) { + return Optional.of(new RepoResource(repo, damContent.getPath())); + } + } + if (JcrUtils.hasBinaryData(contentResource.adaptTo(Node.class))) { + return Optional.of(new RepoResource(repo, contentResource.getPath())); + } + return Optional.empty(); + } + public boolean isFile() { - return isType(JcrConstants.NT_FILE); + return file().isPresent(); } public String type() { diff --git a/core/src/main/java/dev/vml/es/acm/core/util/AemConstants.java b/core/src/main/java/dev/vml/es/acm/core/util/AemConstants.java new file mode 100644 index 000000000..f6aff2f86 --- /dev/null +++ b/core/src/main/java/dev/vml/es/acm/core/util/AemConstants.java @@ -0,0 +1,14 @@ +package dev.vml.es.acm.core.util; + +public final class AemConstants { + + public static final String DAM_ROOT = "/content/dam"; + + public static final String DAM_RENDITION_FOLDER = "renditions"; + + public static final String DAM_RENDITION_ORIGINAL = "original"; + + private AemConstants() { + // constants only + } +} diff --git a/core/src/main/java/dev/vml/es/acm/core/util/JcrUtils.java b/core/src/main/java/dev/vml/es/acm/core/util/JcrUtils.java index b91ee7afe..e150bdb11 100644 --- a/core/src/main/java/dev/vml/es/acm/core/util/JcrUtils.java +++ b/core/src/main/java/dev/vml/es/acm/core/util/JcrUtils.java @@ -1,9 +1,19 @@ package dev.vml.es.acm.core.util; +import static javax.jcr.Property.JCR_CONTENT; +import static javax.jcr.Property.JCR_DATA; +import static javax.jcr.Property.JCR_FROZEN_PRIMARY_TYPE; +import static javax.jcr.nodetype.NodeType.NT_FILE; +import static javax.jcr.nodetype.NodeType.NT_FROZEN_NODE; +import static javax.jcr.nodetype.NodeType.NT_LINKED_FILE; + import java.util.Iterator; import java.util.stream.Stream; +import javax.jcr.Item; import javax.jcr.Node; import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.RepositoryException; public class JcrUtils { @@ -23,4 +33,41 @@ public Node next() { } }); } + + /** + * Returns the binary data property of the given node (e.g. for nt:file, nt:linkedFile, dam:Asset). + * Copied from internal Sling class. + * + * @see NodeUtil#getPrimaryProperty + */ + public static Property getBinaryDataProperty(Node node) throws RepositoryException { + Node content = (node.isNodeType(NT_FILE) + || (node.isNodeType(NT_FROZEN_NODE) + && node.getProperty(JCR_FROZEN_PRIMARY_TYPE) + .getString() + .equals(NT_FILE))) + ? node.getNode(JCR_CONTENT) + : node.isNodeType(NT_LINKED_FILE) + ? node.getProperty(JCR_CONTENT).getNode() + : node; + if (content.hasProperty(JCR_DATA)) { + return content.getProperty(JCR_DATA); + } + Item item = content.getPrimaryItem(); + while (item.isNode()) { + item = ((Node) item).getPrimaryItem(); + } + return (Property) item; + } + + public static boolean hasBinaryData(Node node) { + if (node == null) { + return false; + } + try { + return getBinaryDataProperty(node) != null; + } catch (RepositoryException e) { + return false; + } + } } diff --git a/ui.frontend/src/components/FileUploader.tsx b/ui.frontend/src/components/FileUploader.tsx index 7992d9b17..618e2a90c 100644 --- a/ui.frontend/src/components/FileUploader.tsx +++ b/ui.frontend/src/components/FileUploader.tsx @@ -2,7 +2,7 @@ import { Button, FileTrigger, Flex, Item, ListView, ProgressCircle, Text } from import Delete from '@spectrum-icons/workflow/Delete'; import FileAdd from '@spectrum-icons/workflow/FileAdd'; import { useState } from 'react'; -import { FileOutput } from '../types/main.ts'; +import { FileOutput, isFileManaged } from '../types/main.ts'; import { toastRequest } from '../utils/api'; interface FileFieldProps { @@ -133,11 +133,11 @@ const FileUploader: React.FC = ({ value, onChange, mimeTypes, al ) : file.deleting ? ( - ) : ( + ) : isFileManaged(file.path) ? ( - )} + ) : null} {file.name} diff --git a/ui.frontend/src/types/main.ts b/ui.frontend/src/types/main.ts index 045ca28d3..d3f421acd 100644 --- a/ui.frontend/src/types/main.ts +++ b/ui.frontend/src/types/main.ts @@ -139,6 +139,10 @@ export type FileOutput = { files: string[]; }; +export const FileRoot = '/var/acm/file'; + +export const isFileManaged = (path: string): boolean => path.startsWith(FileRoot + '/'); + export enum EventType { EXECUTOR_RESET = 'executor_reset', HISTORY_CLEAR = 'history_clear', From 725018bf69c5060ebabc2d9a61f3d0b330741d40 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 10 Mar 2026 09:37:31 +0100 Subject: [PATCH 2/6] Misused types --- ui.frontend/src/components/FileUploader.tsx | 6 +++--- ui.frontend/src/types/main.ts | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/ui.frontend/src/components/FileUploader.tsx b/ui.frontend/src/components/FileUploader.tsx index 618e2a90c..7992d9b17 100644 --- a/ui.frontend/src/components/FileUploader.tsx +++ b/ui.frontend/src/components/FileUploader.tsx @@ -2,7 +2,7 @@ import { Button, FileTrigger, Flex, Item, ListView, ProgressCircle, Text } from import Delete from '@spectrum-icons/workflow/Delete'; import FileAdd from '@spectrum-icons/workflow/FileAdd'; import { useState } from 'react'; -import { FileOutput, isFileManaged } from '../types/main.ts'; +import { FileOutput } from '../types/main.ts'; import { toastRequest } from '../utils/api'; interface FileFieldProps { @@ -133,11 +133,11 @@ const FileUploader: React.FC = ({ value, onChange, mimeTypes, al ) : file.deleting ? ( - ) : isFileManaged(file.path) ? ( + ) : ( - ) : null} + )} {file.name} diff --git a/ui.frontend/src/types/main.ts b/ui.frontend/src/types/main.ts index d3f421acd..045ca28d3 100644 --- a/ui.frontend/src/types/main.ts +++ b/ui.frontend/src/types/main.ts @@ -139,10 +139,6 @@ export type FileOutput = { files: string[]; }; -export const FileRoot = '/var/acm/file'; - -export const isFileManaged = (path: string): boolean => path.startsWith(FileRoot + '/'); - export enum EventType { EXECUTOR_RESET = 'executor_reset', HISTORY_CLEAR = 'history_clear', From 6f707f0f60537bfaea484b49a284fee12d601594 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 10 Mar 2026 10:02:17 +0100 Subject: [PATCH 3/6] Validation --- .../java/dev/vml/es/acm/core/code/FileManager.java | 2 +- .../es/acm/core/code/input/AbstractFileInput.java | 12 ++++++++++++ .../dev/vml/es/acm/core/code/input/FileInput.java | 6 ++++++ .../vml/es/acm/core/code/input/MultiFileInput.java | 10 ++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java b/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java index d3615ec6c..e5bc10771 100644 --- a/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java +++ b/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java @@ -17,7 +17,7 @@ public class FileManager { - private static final String ROOT = AcmConstants.VAR_ROOT + "/file"; + public static final String ROOT = AcmConstants.VAR_ROOT + "/file"; private Repo repo; diff --git a/core/src/main/java/dev/vml/es/acm/core/code/input/AbstractFileInput.java b/core/src/main/java/dev/vml/es/acm/core/code/input/AbstractFileInput.java index 7ce6dfd8b..ac57574af 100644 --- a/core/src/main/java/dev/vml/es/acm/core/code/input/AbstractFileInput.java +++ b/core/src/main/java/dev/vml/es/acm/core/code/input/AbstractFileInput.java @@ -1,10 +1,13 @@ package dev.vml.es.acm.core.code.input; +import dev.vml.es.acm.core.AcmException; +import dev.vml.es.acm.core.code.FileManager; import dev.vml.es.acm.core.code.Input; import dev.vml.es.acm.core.code.InputType; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.StringUtils; abstract class AbstractFileInput extends Input { @@ -14,6 +17,15 @@ public AbstractFileInput(String name, InputType type, Class valueType) { super(name, type, valueType); } + protected void validatePath(String path) { + if (StringUtils.isNotBlank(path) && !path.startsWith(FileManager.ROOT + "/")) { + throw new AcmException(String.format( + "File input '%s' cannot have a value '%s' pointing outside ACM file storage '%s'. " + + "For selecting files from DAM or other repository locations, use path input instead.", + getName(), path, FileManager.ROOT)); + } + } + public List getMimeTypes() { return mimeTypes; } diff --git a/core/src/main/java/dev/vml/es/acm/core/code/input/FileInput.java b/core/src/main/java/dev/vml/es/acm/core/code/input/FileInput.java index c8eb74f9f..66b9de692 100644 --- a/core/src/main/java/dev/vml/es/acm/core/code/input/FileInput.java +++ b/core/src/main/java/dev/vml/es/acm/core/code/input/FileInput.java @@ -7,4 +7,10 @@ public class FileInput extends AbstractFileInput { public FileInput(String name) { super(name, InputType.FILE, String.class); } + + @Override + public void setValue(String value) { + validatePath(value); + super.setValue(value); + } } diff --git a/core/src/main/java/dev/vml/es/acm/core/code/input/MultiFileInput.java b/core/src/main/java/dev/vml/es/acm/core/code/input/MultiFileInput.java index 9b8155425..aba53641d 100644 --- a/core/src/main/java/dev/vml/es/acm/core/code/input/MultiFileInput.java +++ b/core/src/main/java/dev/vml/es/acm/core/code/input/MultiFileInput.java @@ -12,6 +12,16 @@ public MultiFileInput(String name) { super(name, InputType.MULTIFILE, String[].class); } + @Override + public void setValue(String[] values) { + if (values != null) { + for (String value : values) { + validatePath(value); + } + } + super.setValue(values); + } + public Integer getMin() { return min; } From f7a4d3d2ba52c3c304c4a78b569a50612ade5c77 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 10 Mar 2026 10:13:14 +0100 Subject: [PATCH 4/6] XLS input read example --- .../manual/example/ACME-204_input-xls.groovy | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-204_input-xls.groovy diff --git a/ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-204_input-xls.groovy b/ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-204_input-xls.groovy new file mode 100644 index 000000000..68bcb9e26 --- /dev/null +++ b/ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-204_input-xls.groovy @@ -0,0 +1,92 @@ +import org.apache.poi.ss.usermodel.* +import org.apache.poi.xssf.usermodel.XSSFWorkbook +import java.time.LocalDate +import java.time.ZoneId + +/* +--- +author: +--- +Reads an XLSX file (e.g. generated by ACME-203) and outputs summary statistics. +Demonstrates how to use file input for uploading and parsing spreadsheet files. + +```mermaid +graph TD + A[Upload XLS File] --> B[Open Workbook] + B --> C[Read Sheet] + C --> D[Parse Rows] + D --> E[Calculate Statistics] + E --> F[Output Summary] +``` +*/ + +void describeRun() { + inputs.file("xlsFile") { + label = "XLS/XLSX File" + description = "Upload a spreadsheet file to analyze (e.g. report.xlsx from ACME-203)" + mimeTypes = [ + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel" + ] + } +} + +boolean canRun() { + return conditions.always() +} + +void doRun() { + out.info "Reading XLS file..." + + def xlsFile = repo.get(inputs.value("xlsFile")) + + try { + Workbook workbook = new XSSFWorkbook(xlsFile.readFileAsStream()) + Sheet sheet = workbook.getSheetAt(0) + + def rowCount = 0 + def names = [] as Set + def surnames = [] as Set + def oldestDate = null + def youngestDate = null + + for (Row row : sheet) { + if (row.getRowNum() == 0) continue // skip header + + context.checkAborted() + rowCount++ + + def nameCell = row.getCell(0) + def surnameCell = row.getCell(1) + def dateCell = row.getCell(2) + + if (nameCell) names.add(nameCell.getStringCellValue()) + if (surnameCell) surnames.add(surnameCell.getStringCellValue()) + + if (dateCell && dateCell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(dateCell)) { + def date = dateCell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDate() + if (oldestDate == null || date.isBefore(oldestDate)) oldestDate = date + if (youngestDate == null || date.isAfter(youngestDate)) youngestDate = date + } + + if (rowCount % 1000 == 0) out.info("Processed ${rowCount} rows...") + } + + workbook.close() + + outputs.text("summary") { + label = "Summary" + value = """ + |**Rows processed:** ${rowCount} + |**Unique names:** ${names.size()} + |**Unique surnames:** ${surnames.size()} + |**Oldest birth date:** ${oldestDate ?: 'N/A'} + |**Youngest birth date:** ${youngestDate ?: 'N/A'} + """.stripMargin().trim() + } + + out.success "XLS file analysis completed" + } finally { + xlsFile.delete() + } +} From 8a6f8875a33d6087f77acd103a5291f6fb93f667 Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 10 Mar 2026 10:47:41 +0100 Subject: [PATCH 5/6] XLS/XLSX reading example --- .../dev/vml/es/acm/core/code/Executor.java | 2 +- .../dev/vml/es/acm/core/code/TextOutput.java | 15 +++++++ .../manual/example/ACME-204_input-xls.groovy | 43 +++++++++++++------ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/dev/vml/es/acm/core/code/Executor.java b/core/src/main/java/dev/vml/es/acm/core/code/Executor.java index 8510e9949..ed751d651 100644 --- a/core/src/main/java/dev/vml/es/acm/core/code/Executor.java +++ b/core/src/main/java/dev/vml/es/acm/core/code/Executor.java @@ -248,7 +248,7 @@ private ContextualExecution executeInternal(ExecutionContext context) { useLocker(resolverFactory, l -> l.lock(lockName)); } context.notifyStatus(ExecutionStatus.RUNNING); - if (config.logPrintingEnabled()) { + if (!healthChecking && config.logPrintingEnabled()) { context.getOut().fromSelfLogger(); context.getOut().fromLoggers(config.logPrintingNames()); context.getOut().setLoggerTimestamps(config.logPrintingTimestamps()); diff --git a/core/src/main/java/dev/vml/es/acm/core/code/TextOutput.java b/core/src/main/java/dev/vml/es/acm/core/code/TextOutput.java index 8e8ec0683..f5e395c9b 100644 --- a/core/src/main/java/dev/vml/es/acm/core/code/TextOutput.java +++ b/core/src/main/java/dev/vml/es/acm/core/code/TextOutput.java @@ -1,5 +1,6 @@ package dev.vml.es.acm.core.code; +import java.util.Collection; import java.util.Map; import java.util.stream.Collectors; @@ -49,4 +50,18 @@ public String links(Map links) { .map(entry -> "- [" + entry.getKey() + "](" + entry.getValue() + ")") .collect(Collectors.joining(System.lineSeparator())); } + + public String lines(String[] lines) { + return String.join(System.lineSeparator(), lines); + } + + public String lines(Collection lines) { + return String.join(System.lineSeparator(), lines); + } + + public String lines(Map values) { + return values.entrySet().stream() + .map(entry -> "- **" + entry.getKey() + ":** " + entry.getValue()) + .collect(Collectors.joining(System.lineSeparator())); + } } diff --git a/ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-204_input-xls.groovy b/ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-204_input-xls.groovy index 68bcb9e26..591041275 100644 --- a/ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-204_input-xls.groovy +++ b/ui.content.example/src/main/content/jcr_root/conf/acm/settings/script/manual/example/ACME-204_input-xls.groovy @@ -44,6 +44,14 @@ void doRun() { Workbook workbook = new XSSFWorkbook(xlsFile.readFileAsStream()) Sheet sheet = workbook.getSheetAt(0) + // Build column name -> index map from header row + Row headerRow = sheet.getRow(0) + def columns = [:] + for (Cell cell : headerRow) { + columns[cell.getStringCellValue()] = cell.getColumnIndex() + } + out.info "Found columns: ${columns.keySet().join(', ')}" + def rowCount = 0 def names = [] as Set def surnames = [] as Set @@ -56,17 +64,24 @@ void doRun() { context.checkAborted() rowCount++ - def nameCell = row.getCell(0) - def surnameCell = row.getCell(1) - def dateCell = row.getCell(2) + def nameCell = row.getCell(columns["Name"]) + def surnameCell = row.getCell(columns["Surname"]) + def dateCell = row.getCell(columns["Birth Date"]) if (nameCell) names.add(nameCell.getStringCellValue()) if (surnameCell) surnames.add(surnameCell.getStringCellValue()) - if (dateCell && dateCell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(dateCell)) { - def date = dateCell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDate() - if (oldestDate == null || date.isBefore(oldestDate)) oldestDate = date - if (youngestDate == null || date.isAfter(youngestDate)) youngestDate = date + if (dateCell) { + def date = null + if (DateUtil.isCellDateFormatted(dateCell)) { + date = dateCell.getDateCellValue().toInstant().atZone(ZoneId.systemDefault()).toLocalDate() + } else if (dateCell.getCellType() == CellType.STRING) { + date = LocalDate.parse(dateCell.getStringCellValue()) + } + if (date != null) { + if (oldestDate == null || date.isBefore(oldestDate)) oldestDate = date + if (youngestDate == null || date.isAfter(youngestDate)) youngestDate = date + } } if (rowCount % 1000 == 0) out.info("Processed ${rowCount} rows...") @@ -76,13 +91,13 @@ void doRun() { outputs.text("summary") { label = "Summary" - value = """ - |**Rows processed:** ${rowCount} - |**Unique names:** ${names.size()} - |**Unique surnames:** ${surnames.size()} - |**Oldest birth date:** ${oldestDate ?: 'N/A'} - |**Youngest birth date:** ${youngestDate ?: 'N/A'} - """.stripMargin().trim() + value = lines([ + "Rows processed": rowCount, + "Unique names": names.size(), + "Unique surnames": surnames.size(), + "Oldest birth date": oldestDate ?: 'N/A', + "Youngest birth date": youngestDate ?: 'N/A' + ]) } out.success "XLS file analysis completed" From 0e28e7f4308baa0aaced18c8c0286857003b9c5f Mon Sep 17 00:00:00 2001 From: Krystian Panek Date: Tue, 10 Mar 2026 10:50:56 +0100 Subject: [PATCH 6/6] Minor --- .../src/main/java/dev/vml/es/acm/core/code/FileManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java b/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java index e5bc10771..8ba34b1b3 100644 --- a/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java +++ b/core/src/main/java/dev/vml/es/acm/core/code/FileManager.java @@ -66,13 +66,13 @@ public List deleteAll(List paths) { } public String delete(String path) { + if (path.startsWith("/") && !path.startsWith(ROOT + "/")) { + throw new AcmException(String.format("File is outside managed root and cannot be deleted '%s'!", path)); + } RepoResource resource = findResource(path); if (resource == null) { throw new AcmException(String.format("File to be deleted does not exist '%s'!", path)); } - if (!StringUtils.startsWith(resource.getPath(), root.getPath() + "/")) { - throw new AcmException(String.format("File is outside managed root and cannot be deleted '%s'!", path)); - } resource.delete(); return resource.getPath(); }