From d6ab9eb06615139270b078e190dea0d62b48ab83 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 5 Mar 2023 11:39:53 +0000 Subject: [PATCH] Rework of service validation, to allow a service to be specified as a single file resource. This removes some complexity and duplication from custom validation functions. Q-Chat QDN functionality will need a re-test. --- .../org/qortal/arbitrary/misc/Service.java | 87 ++++++++----------- .../org/qortal/utils/FilesystemUtils.java | 4 +- .../test/arbitrary/ArbitraryServiceTests.java | 6 +- 3 files changed, 42 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/qortal/arbitrary/misc/Service.java b/src/main/java/org/qortal/arbitrary/misc/Service.java index 3a549180..8ca62433 100644 --- a/src/main/java/org/qortal/arbitrary/misc/Service.java +++ b/src/main/java/org/qortal/arbitrary/misc/Service.java @@ -19,9 +19,9 @@ import static java.util.Arrays.stream; import static java.util.stream.Collectors.toMap; public enum Service { - AUTO_UPDATE(1, false, null, null), - ARBITRARY_DATA(100, false, null, null), - QCHAT_ATTACHMENT(120, true, 1024*1024L, null) { + AUTO_UPDATE(1, false, null, false, null), + ARBITRARY_DATA(100, false, null, false, null), + QCHAT_ATTACHMENT(120, true, 1024*1024L, true, null) { @Override public ValidationResult validate(Path path) throws IOException { ValidationResult superclassResult = super.validate(path); @@ -29,37 +29,24 @@ public enum Service { return superclassResult; } - // Custom validation function to require a single file, with a whitelisted extension - int fileCount = 0; File[] files = path.toFile().listFiles(); // If already a single file, replace the list with one that contains that file only if (files == null && path.toFile().isFile()) { files = new File[] { path.toFile() }; } - if (files != null) { - for (File file : files) { - if (file.getName().equals(".qortal")) { - continue; - } - if (file.isDirectory()) { - return ValidationResult.DIRECTORIES_NOT_ALLOWED; - } - final String extension = FilenameUtils.getExtension(file.getName()).toLowerCase(); - // We must allow blank file extensions because these are used by data published from a plaintext or base64-encoded string - final List allowedExtensions = Arrays.asList("zip", "pdf", "txt", "odt", "ods", "doc", "docx", "xls", "xlsx", "ppt", "pptx", ""); - if (extension == null || !allowedExtensions.contains(extension)) { - return ValidationResult.INVALID_FILE_EXTENSION; - } - fileCount++; + // Now validate the file's extension + if (files != null && files[0] != null) { + final String extension = FilenameUtils.getExtension(files[0].getName()).toLowerCase(); + // We must allow blank file extensions because these are used by data published from a plaintext or base64-encoded string + final List allowedExtensions = Arrays.asList("zip", "pdf", "txt", "odt", "ods", "doc", "docx", "xls", "xlsx", "ppt", "pptx", ""); + if (extension == null || !allowedExtensions.contains(extension)) { + return ValidationResult.INVALID_FILE_EXTENSION; } } - if (fileCount != 1) { - return ValidationResult.INVALID_FILE_COUNT; - } return ValidationResult.OK; } }, - WEBSITE(200, true, null, null) { + WEBSITE(200, true, null, false, null) { @Override public ValidationResult validate(Path path) throws IOException { ValidationResult superclassResult = super.validate(path); @@ -81,23 +68,23 @@ public enum Service { return ValidationResult.MISSING_INDEX_FILE; } }, - GIT_REPOSITORY(300, false, null, null), - IMAGE(400, true, 10*1024*1024L, null), - THUMBNAIL(410, true, 500*1024L, null), - QCHAT_IMAGE(420, true, 500*1024L, null), - VIDEO(500, false, null, null), - AUDIO(600, false, null, null), - QCHAT_AUDIO(610, true, 10*1024*1024L, null), - QCHAT_VOICE(620, true, 10*1024*1024L, null), - BLOG(700, false, null, null), - BLOG_POST(777, false, null, null), - BLOG_COMMENT(778, false, null, null), - DOCUMENT(800, false, null, null), - LIST(900, true, null, null), - PLAYLIST(910, true, null, null), - APP(1000, false, null, null), - METADATA(1100, false, null, null), - JSON(1110, true, 25*1024L, null) { + GIT_REPOSITORY(300, false, null, false, null), + IMAGE(400, true, 10*1024*1024L, true, null), + THUMBNAIL(410, true, 500*1024L, true, null), + QCHAT_IMAGE(420, true, 500*1024L, true, null), + VIDEO(500, false, null, true, null), + AUDIO(600, false, null, true, null), + QCHAT_AUDIO(610, true, 10*1024*1024L, true, null), + QCHAT_VOICE(620, true, 10*1024*1024L, true, null), + BLOG(700, false, null, false, null), + BLOG_POST(777, false, null, true, null), + BLOG_COMMENT(778, false, null, true, null), + DOCUMENT(800, false, null, true, null), + LIST(900, true, null, true, null), + PLAYLIST(910, true, null, true, null), + APP(1000, false, null, false, null), + METADATA(1100, false, null, true, null), + JSON(1110, true, 25*1024L, true, null) { @Override public ValidationResult validate(Path path) throws IOException { ValidationResult superclassResult = super.validate(path); @@ -105,13 +92,6 @@ public enum Service { return superclassResult; } - File[] files = path.toFile().listFiles(); - - // Require a single file - if (files != null || !path.toFile().isFile()) { - return ValidationResult.INVALID_FILE_COUNT; - } - // Require valid JSON String json = Files.readString(path); try { @@ -122,7 +102,7 @@ public enum Service { } } }, - GIF_REPOSITORY(1200, true, 25*1024*1024L, null) { + GIF_REPOSITORY(1200, true, 25*1024*1024L, false, null) { @Override public ValidationResult validate(Path path) throws IOException { ValidationResult superclassResult = super.validate(path); @@ -162,6 +142,7 @@ public enum Service { public final int value; private final boolean requiresValidation; private final Long maxSize; + private final boolean single; private final List requiredKeys; private static final Map map = stream(Service.values()) @@ -170,10 +151,11 @@ public enum Service { // For JSON validation private static final ObjectMapper objectMapper = new ObjectMapper(); - Service(int value, boolean requiresValidation, Long maxSize, List requiredKeys) { + Service(int value, boolean requiresValidation, Long maxSize, boolean single, List requiredKeys) { this.value = value; this.requiresValidation = requiresValidation; this.maxSize = maxSize; + this.single = single; this.requiredKeys = requiredKeys; } @@ -192,6 +174,11 @@ public enum Service { } } + // Validate file count if needed + if (this.single && data == null) { + return ValidationResult.INVALID_FILE_COUNT; + } + // Validate required keys if needed if (this.requiredKeys != null) { if (data == null) { diff --git a/src/main/java/org/qortal/utils/FilesystemUtils.java b/src/main/java/org/qortal/utils/FilesystemUtils.java index 1b3de544..64148f5e 100644 --- a/src/main/java/org/qortal/utils/FilesystemUtils.java +++ b/src/main/java/org/qortal/utils/FilesystemUtils.java @@ -241,7 +241,9 @@ public class FilesystemUtils { String[] files = ArrayUtils.removeElement(path.toFile().list(), ".qortal"); if (files.length == 1) { Path filePath = Paths.get(path.toString(), files[0]); - data = Files.readAllBytes(filePath); + if (filePath.toFile().isFile()) { + data = Files.readAllBytes(filePath); + } } } diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java index 8978a3df..940b33a9 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryServiceTests.java @@ -319,17 +319,15 @@ public class ArbitraryServiceTests extends Common { // Write the data to several files in a temp path Path path = Files.createTempDirectory("testValidateMultiLayerQChatAttachment"); path.toFile().deleteOnExit(); - Files.write(Paths.get(path.toString(), "file1.txt"), data, StandardOpenOption.CREATE); Path subdirectory = Paths.get(path.toString(), "subdirectory"); Files.createDirectories(subdirectory); - Files.write(Paths.get(subdirectory.toString(), "file2.txt"), data, StandardOpenOption.CREATE); - Files.write(Paths.get(subdirectory.toString(), "file3.txt"), data, StandardOpenOption.CREATE); + Files.write(Paths.get(subdirectory.toString(), "file.txt"), data, StandardOpenOption.CREATE); Service service = Service.QCHAT_ATTACHMENT; assertTrue(service.isValidationRequired()); - assertEquals(ValidationResult.DIRECTORIES_NOT_ALLOWED, service.validate(path)); + assertEquals(ValidationResult.INVALID_FILE_COUNT, service.validate(path)); } @Test