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.
This commit is contained in:
CalDescent 2023-03-05 11:39:53 +00:00
parent ac60ef30a3
commit d6ab9eb066
3 changed files with 42 additions and 55 deletions

View File

@ -19,9 +19,9 @@ import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toMap;
public enum Service { public enum Service {
AUTO_UPDATE(1, false, null, null), AUTO_UPDATE(1, false, null, false, null),
ARBITRARY_DATA(100, false, null, null), ARBITRARY_DATA(100, false, null, false, null),
QCHAT_ATTACHMENT(120, true, 1024*1024L, null) { QCHAT_ATTACHMENT(120, true, 1024*1024L, true, null) {
@Override @Override
public ValidationResult validate(Path path) throws IOException { public ValidationResult validate(Path path) throws IOException {
ValidationResult superclassResult = super.validate(path); ValidationResult superclassResult = super.validate(path);
@ -29,37 +29,24 @@ public enum Service {
return superclassResult; return superclassResult;
} }
// Custom validation function to require a single file, with a whitelisted extension
int fileCount = 0;
File[] files = path.toFile().listFiles(); File[] files = path.toFile().listFiles();
// If already a single file, replace the list with one that contains that file only // If already a single file, replace the list with one that contains that file only
if (files == null && path.toFile().isFile()) { if (files == null && path.toFile().isFile()) {
files = new File[] { path.toFile() }; files = new File[] { path.toFile() };
} }
if (files != null) { // Now validate the file's extension
for (File file : files) { if (files != null && files[0] != null) {
if (file.getName().equals(".qortal")) { final String extension = FilenameUtils.getExtension(files[0].getName()).toLowerCase();
continue; // We must allow blank file extensions because these are used by data published from a plaintext or base64-encoded string
} final List<String> allowedExtensions = Arrays.asList("zip", "pdf", "txt", "odt", "ods", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "");
if (file.isDirectory()) { if (extension == null || !allowedExtensions.contains(extension)) {
return ValidationResult.DIRECTORIES_NOT_ALLOWED; return ValidationResult.INVALID_FILE_EXTENSION;
}
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<String> 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++;
} }
} }
if (fileCount != 1) {
return ValidationResult.INVALID_FILE_COUNT;
}
return ValidationResult.OK; return ValidationResult.OK;
} }
}, },
WEBSITE(200, true, null, null) { WEBSITE(200, true, null, false, null) {
@Override @Override
public ValidationResult validate(Path path) throws IOException { public ValidationResult validate(Path path) throws IOException {
ValidationResult superclassResult = super.validate(path); ValidationResult superclassResult = super.validate(path);
@ -81,23 +68,23 @@ public enum Service {
return ValidationResult.MISSING_INDEX_FILE; return ValidationResult.MISSING_INDEX_FILE;
} }
}, },
GIT_REPOSITORY(300, false, null, null), GIT_REPOSITORY(300, false, null, false, null),
IMAGE(400, true, 10*1024*1024L, null), IMAGE(400, true, 10*1024*1024L, true, null),
THUMBNAIL(410, true, 500*1024L, null), THUMBNAIL(410, true, 500*1024L, true, null),
QCHAT_IMAGE(420, true, 500*1024L, null), QCHAT_IMAGE(420, true, 500*1024L, true, null),
VIDEO(500, false, null, null), VIDEO(500, false, null, true, null),
AUDIO(600, false, null, null), AUDIO(600, false, null, true, null),
QCHAT_AUDIO(610, true, 10*1024*1024L, null), QCHAT_AUDIO(610, true, 10*1024*1024L, true, null),
QCHAT_VOICE(620, true, 10*1024*1024L, null), QCHAT_VOICE(620, true, 10*1024*1024L, true, null),
BLOG(700, false, null, null), BLOG(700, false, null, false, null),
BLOG_POST(777, false, null, null), BLOG_POST(777, false, null, true, null),
BLOG_COMMENT(778, false, null, null), BLOG_COMMENT(778, false, null, true, null),
DOCUMENT(800, false, null, null), DOCUMENT(800, false, null, true, null),
LIST(900, true, null, null), LIST(900, true, null, true, null),
PLAYLIST(910, true, null, null), PLAYLIST(910, true, null, true, null),
APP(1000, false, null, null), APP(1000, false, null, false, null),
METADATA(1100, false, null, null), METADATA(1100, false, null, true, null),
JSON(1110, true, 25*1024L, null) { JSON(1110, true, 25*1024L, true, null) {
@Override @Override
public ValidationResult validate(Path path) throws IOException { public ValidationResult validate(Path path) throws IOException {
ValidationResult superclassResult = super.validate(path); ValidationResult superclassResult = super.validate(path);
@ -105,13 +92,6 @@ public enum Service {
return superclassResult; 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 // Require valid JSON
String json = Files.readString(path); String json = Files.readString(path);
try { 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 @Override
public ValidationResult validate(Path path) throws IOException { public ValidationResult validate(Path path) throws IOException {
ValidationResult superclassResult = super.validate(path); ValidationResult superclassResult = super.validate(path);
@ -162,6 +142,7 @@ public enum Service {
public final int value; public final int value;
private final boolean requiresValidation; private final boolean requiresValidation;
private final Long maxSize; private final Long maxSize;
private final boolean single;
private final List<String> requiredKeys; private final List<String> requiredKeys;
private static final Map<Integer, Service> map = stream(Service.values()) private static final Map<Integer, Service> map = stream(Service.values())
@ -170,10 +151,11 @@ public enum Service {
// For JSON validation // For JSON validation
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
Service(int value, boolean requiresValidation, Long maxSize, List<String> requiredKeys) { Service(int value, boolean requiresValidation, Long maxSize, boolean single, List<String> requiredKeys) {
this.value = value; this.value = value;
this.requiresValidation = requiresValidation; this.requiresValidation = requiresValidation;
this.maxSize = maxSize; this.maxSize = maxSize;
this.single = single;
this.requiredKeys = requiredKeys; 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 // Validate required keys if needed
if (this.requiredKeys != null) { if (this.requiredKeys != null) {
if (data == null) { if (data == null) {

View File

@ -241,7 +241,9 @@ public class FilesystemUtils {
String[] files = ArrayUtils.removeElement(path.toFile().list(), ".qortal"); String[] files = ArrayUtils.removeElement(path.toFile().list(), ".qortal");
if (files.length == 1) { if (files.length == 1) {
Path filePath = Paths.get(path.toString(), files[0]); Path filePath = Paths.get(path.toString(), files[0]);
data = Files.readAllBytes(filePath); if (filePath.toFile().isFile()) {
data = Files.readAllBytes(filePath);
}
} }
} }

View File

@ -319,17 +319,15 @@ public class ArbitraryServiceTests extends Common {
// Write the data to several files in a temp path // Write the data to several files in a temp path
Path path = Files.createTempDirectory("testValidateMultiLayerQChatAttachment"); Path path = Files.createTempDirectory("testValidateMultiLayerQChatAttachment");
path.toFile().deleteOnExit(); path.toFile().deleteOnExit();
Files.write(Paths.get(path.toString(), "file1.txt"), data, StandardOpenOption.CREATE);
Path subdirectory = Paths.get(path.toString(), "subdirectory"); Path subdirectory = Paths.get(path.toString(), "subdirectory");
Files.createDirectories(subdirectory); Files.createDirectories(subdirectory);
Files.write(Paths.get(subdirectory.toString(), "file2.txt"), data, StandardOpenOption.CREATE); Files.write(Paths.get(subdirectory.toString(), "file.txt"), data, StandardOpenOption.CREATE);
Files.write(Paths.get(subdirectory.toString(), "file3.txt"), data, StandardOpenOption.CREATE);
Service service = Service.QCHAT_ATTACHMENT; Service service = Service.QCHAT_ATTACHMENT;
assertTrue(service.isValidationRequired()); assertTrue(service.isValidationRequired());
assertEquals(ValidationResult.DIRECTORIES_NOT_ALLOWED, service.validate(path)); assertEquals(ValidationResult.INVALID_FILE_COUNT, service.validate(path));
} }
@Test @Test