From 5e010a62e27e19cc0cc8569bb70c3a32c4302bf8 Mon Sep 17 00:00:00 2001 From: Lucas Amiaud Date: Tue, 19 Dec 2023 11:25:13 +0100 Subject: [PATCH 1/3] fix Content-Disposition header to handle UTF8 file names --- .../coreoz/plume/file/services/FileService.java | 8 ++++---- ...FileNames.java => FileExtensionCleaning.java} | 4 ++-- .../plume/file/utils}/FileNameCleaning.java | 6 +++++- ...sTest.java => FileExtensionCleaningTest.java} | 10 +++++----- .../plume/file/utils}/FileNameCleaningTest.java | 2 +- .../coreoz/plume/file/webservices/FileWs.java | 16 +++++++++++++--- .../file/validator/FileUploadValidator.java | 6 +++--- 7 files changed, 33 insertions(+), 19 deletions(-) rename plume-file-core/src/main/java/com/coreoz/plume/file/utils/{FileNames.java => FileExtensionCleaning.java} (93%) rename {plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/cleaning => plume-file-core/src/main/java/com/coreoz/plume/file/utils}/FileNameCleaning.java (96%) rename plume-file-core/src/test/java/com/coreoz/plume/file/utils/{FileNamesTest.java => FileExtensionCleaningTest.java} (62%) rename {plume-file-web-upload-jersey/src/test/java/com/coreoz/plume/file/cleaning => plume-file-core/src/test/java/com/coreoz/plume/file/utils}/FileNameCleaningTest.java (96%) diff --git a/plume-file-core/src/main/java/com/coreoz/plume/file/services/FileService.java b/plume-file-core/src/main/java/com/coreoz/plume/file/services/FileService.java index 8a3bbe0..8ad25b1 100644 --- a/plume-file-core/src/main/java/com/coreoz/plume/file/services/FileService.java +++ b/plume-file-core/src/main/java/com/coreoz/plume/file/services/FileService.java @@ -26,7 +26,7 @@ import com.coreoz.plume.file.services.metadata.FileMetadata; import com.coreoz.plume.file.services.metadata.FileMetadataService; import com.coreoz.plume.file.services.storage.FileStorageService; -import com.coreoz.plume.file.utils.FileNames; +import com.coreoz.plume.file.utils.FileExtensionCleaning; import lombok.SneakyThrows; @@ -79,7 +79,7 @@ public String add( Objects.requireNonNull(fileType); Objects.requireNonNull(fileInputStream); - String fileCleanExtension = FileNames.cleanExtensionName(fileExtension); + String fileCleanExtension = FileExtensionCleaning.cleanExtensionName(fileExtension); String fileUniqueName = UUID.randomUUID() + ((fileCleanExtension == null || fileCleanExtension.isEmpty()) ? "" : "." + fileCleanExtension); this.fileMetadataService.add( fileUniqueName, @@ -116,7 +116,7 @@ public String add(FileType fileType, InputStream fileData) throws UncheckedIOExc * then call {@link #add(FileType, InputStream, String, String, String)} */ public String add(FileType fileType, InputStream fileData, String fileName, String mimeType) throws UncheckedIOException { - return add(fileType, fileData, fileName, FileNames.parseFileNameExtension(fileName), mimeType); + return add(fileType, fileData, fileName, FileExtensionCleaning.parseFileNameExtension(fileName), mimeType); } /** @@ -131,7 +131,7 @@ public String add(FileType fileType, InputStream fileData, String fileName) thro fileType, filePeekingStream.peekedStream(), fileName, - FileNames.parseFileNameExtension(fileName), + FileExtensionCleaning.parseFileNameExtension(fileName), mimeType ); } catch (IOException e) { diff --git a/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNames.java b/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileExtensionCleaning.java similarity index 93% rename from plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNames.java rename to plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileExtensionCleaning.java index bb2c508..4e01013 100644 --- a/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNames.java +++ b/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileExtensionCleaning.java @@ -3,10 +3,10 @@ import javax.annotation.Nullable; import java.util.regex.Pattern; -public class FileNames { +public class FileExtensionCleaning { private static final Pattern fileExtensionExcludePattern = Pattern.compile("[^a-zA-Z0-9]"); - private FileNames() { + private FileExtensionCleaning() { // empty constructor } diff --git a/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/cleaning/FileNameCleaning.java b/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNameCleaning.java similarity index 96% rename from plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/cleaning/FileNameCleaning.java rename to plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNameCleaning.java index 8b40ac3..e0eb1e3 100644 --- a/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/cleaning/FileNameCleaning.java +++ b/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNameCleaning.java @@ -1,4 +1,4 @@ -package com.coreoz.plume.file.cleaning; +package com.coreoz.plume.file.utils; import javax.annotation.Nullable; import java.text.Normalizer; @@ -11,6 +11,10 @@ public class FileNameCleaning { private static final String EMPTY = ""; + private FileNameCleaning() { + // empty constructor + } + /** * Remove all weird characters while trying to ensure * the sanitize file name is close to the original one:
diff --git a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNamesTest.java b/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java similarity index 62% rename from plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNamesTest.java rename to plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java index 00082eb..b8e8bcd 100644 --- a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNamesTest.java +++ b/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java @@ -4,28 +4,28 @@ import static org.assertj.core.api.Assertions.assertThat; -public class FileNamesTest { +public class FileExtensionCleaningTest { @Test public void test_get_extension_from_jpg_should_return_jpg() { - assertThat(FileNames.parseFileNameExtension("toto.jpg")) + assertThat(FileExtensionCleaning.parseFileNameExtension("toto.jpg")) .isEqualTo("jpg"); } @Test public void test_get_extension_from_no_extension_should_return_empty() { - assertThat(FileNames.parseFileNameExtension("toto")) + assertThat(FileExtensionCleaning.parseFileNameExtension("toto")) .isNull(); } @Test public void test_clean_extension_from_jpg_should_return_jpg() { - assertThat(FileNames.cleanExtensionName(".jpg")) + assertThat(FileExtensionCleaning.cleanExtensionName(".jpg")) .isEqualTo("jpg"); } @Test public void cleanExtensionName__verify_that_non_basic_chars_are_removed() { - assertThat(FileNames.cleanExtensionName(" éi+$. \np")) + assertThat(FileExtensionCleaning.cleanExtensionName(" éi+$. \np")) .isEqualTo("ip"); } } diff --git a/plume-file-web-upload-jersey/src/test/java/com/coreoz/plume/file/cleaning/FileNameCleaningTest.java b/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java similarity index 96% rename from plume-file-web-upload-jersey/src/test/java/com/coreoz/plume/file/cleaning/FileNameCleaningTest.java rename to plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java index 854c75c..4012563 100644 --- a/plume-file-web-upload-jersey/src/test/java/com/coreoz/plume/file/cleaning/FileNameCleaningTest.java +++ b/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java @@ -1,4 +1,4 @@ -package com.coreoz.plume.file.cleaning; +package com.coreoz.plume.file.utils; import org.junit.Test; diff --git a/plume-file-web-download-jersey/src/main/java/com/coreoz/plume/file/webservices/FileWs.java b/plume-file-web-download-jersey/src/main/java/com/coreoz/plume/file/webservices/FileWs.java index 6b05496..f7b4e25 100644 --- a/plume-file-web-download-jersey/src/main/java/com/coreoz/plume/file/webservices/FileWs.java +++ b/plume-file-web-download-jersey/src/main/java/com/coreoz/plume/file/webservices/FileWs.java @@ -2,7 +2,8 @@ import com.coreoz.plume.file.service.FileDownloadJerseyService; import com.coreoz.plume.file.service.configuration.FileDownloadConfigurationService; -import com.coreoz.plume.file.utils.FileNames; +import com.coreoz.plume.file.utils.FileExtensionCleaning; +import com.coreoz.plume.file.utils.FileNameCleaning; import com.coreoz.plume.jersey.security.permission.PublicApi; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -20,6 +21,8 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.Optional; @@ -52,7 +55,7 @@ public Response fetch( @HeaderParam(HttpHeaders.IF_NONE_MATCH) String ifNoneMatchHeader ) { // fileUniqueName cannot be null as it is required by jersey PathParam - String fileExtension = FileNames.parseFileNameExtension(fileUniqueName); + String fileExtension = FileExtensionCleaning.parseFileNameExtension(fileUniqueName); String fileUid = fileUniqueName.substring( 0, fileUniqueName.length() - (fileExtension != null ? fileExtension.length() : 0) - 1 @@ -97,8 +100,15 @@ public Response fetch( if (attachment) { String attachmentFilename = Optional.ofNullable(fileMetadata.getFileOriginalName()) .orElse(fileMetadata.getUniqueName()); + String utf8FileName = URLEncoder.encode(attachmentFilename, StandardCharsets.UTF_8) + .replace("+", "%20"); + String sanitizedFileName = FileNameCleaning.cleanFileName(attachmentFilename); response - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + attachmentFilename + "\""); + .header( + HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + sanitizedFileName + + "\"; filename*=UTF-8''" + utf8FileName + ); } return response.build(); }); diff --git a/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/validator/FileUploadValidator.java b/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/validator/FileUploadValidator.java index 3fb8468..c219a01 100644 --- a/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/validator/FileUploadValidator.java +++ b/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/validator/FileUploadValidator.java @@ -1,9 +1,9 @@ package com.coreoz.plume.file.validator; -import com.coreoz.plume.file.cleaning.FileNameCleaning; +import com.coreoz.plume.file.utils.FileNameCleaning; import com.coreoz.plume.file.services.mimetype.FileMimeTypeDetector; import com.coreoz.plume.file.services.mimetype.PeekingInputStream; -import com.coreoz.plume.file.utils.FileNames; +import com.coreoz.plume.file.utils.FileExtensionCleaning; import com.coreoz.plume.jersey.errors.Validators; import com.coreoz.plume.jersey.errors.WsError; import com.coreoz.plume.jersey.errors.WsException; @@ -57,7 +57,7 @@ private FileUploadValidator( this.data = new FileUploadData( filePeekingStream.peekedStream(), fileName, - FileNames.parseFileNameExtension(fileName), + FileExtensionCleaning.parseFileNameExtension(fileName), mimeType, fileSize ); From 008e9b89eaa3cca8df48d1a587cd1f53d62ee200 Mon Sep 17 00:00:00 2001 From: Lucas Amiaud Date: Thu, 11 Jan 2024 16:50:14 +0100 Subject: [PATCH 2/3] mark FileNames as deprecated --- .../FileExtensionCleaning.java | 2 +- .../{utils => cleaning}/FileNameCleaning.java | 2 +- .../plume/file/services/FileService.java | 2 +- .../coreoz/plume/file/utils/FileNames.java | 44 +++++++++++++++++++ .../file/utils/FileExtensionCleaningTest.java | 1 + .../file/utils/FileNameCleaningTest.java | 1 + .../coreoz/plume/file/webservices/FileWs.java | 4 +- .../file/validator/FileUploadValidator.java | 4 +- 8 files changed, 53 insertions(+), 7 deletions(-) rename plume-file-core/src/main/java/com/coreoz/plume/file/{utils => cleaning}/FileExtensionCleaning.java (96%) rename plume-file-core/src/main/java/com/coreoz/plume/file/{utils => cleaning}/FileNameCleaning.java (98%) create mode 100644 plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNames.java diff --git a/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileExtensionCleaning.java b/plume-file-core/src/main/java/com/coreoz/plume/file/cleaning/FileExtensionCleaning.java similarity index 96% rename from plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileExtensionCleaning.java rename to plume-file-core/src/main/java/com/coreoz/plume/file/cleaning/FileExtensionCleaning.java index 4e01013..4f2b768 100644 --- a/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileExtensionCleaning.java +++ b/plume-file-core/src/main/java/com/coreoz/plume/file/cleaning/FileExtensionCleaning.java @@ -1,4 +1,4 @@ -package com.coreoz.plume.file.utils; +package com.coreoz.plume.file.cleaning; import javax.annotation.Nullable; import java.util.regex.Pattern; diff --git a/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNameCleaning.java b/plume-file-core/src/main/java/com/coreoz/plume/file/cleaning/FileNameCleaning.java similarity index 98% rename from plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNameCleaning.java rename to plume-file-core/src/main/java/com/coreoz/plume/file/cleaning/FileNameCleaning.java index e0eb1e3..1727765 100644 --- a/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNameCleaning.java +++ b/plume-file-core/src/main/java/com/coreoz/plume/file/cleaning/FileNameCleaning.java @@ -1,4 +1,4 @@ -package com.coreoz.plume.file.utils; +package com.coreoz.plume.file.cleaning; import javax.annotation.Nullable; import java.text.Normalizer; diff --git a/plume-file-core/src/main/java/com/coreoz/plume/file/services/FileService.java b/plume-file-core/src/main/java/com/coreoz/plume/file/services/FileService.java index 8ad25b1..368556f 100644 --- a/plume-file-core/src/main/java/com/coreoz/plume/file/services/FileService.java +++ b/plume-file-core/src/main/java/com/coreoz/plume/file/services/FileService.java @@ -26,7 +26,7 @@ import com.coreoz.plume.file.services.metadata.FileMetadata; import com.coreoz.plume.file.services.metadata.FileMetadataService; import com.coreoz.plume.file.services.storage.FileStorageService; -import com.coreoz.plume.file.utils.FileExtensionCleaning; +import com.coreoz.plume.file.cleaning.FileExtensionCleaning; import lombok.SneakyThrows; diff --git a/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNames.java b/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNames.java new file mode 100644 index 0000000..6ed8cc7 --- /dev/null +++ b/plume-file-core/src/main/java/com/coreoz/plume/file/utils/FileNames.java @@ -0,0 +1,44 @@ +package com.coreoz.plume.file.utils; + +import javax.annotation.Nullable; +import java.util.regex.Pattern; + +/** + * For removal in V4 + * Call {@link com.coreoz.plume.file.cleaning.FileExtensionCleaning} instead + */ +@Deprecated(forRemoval = true) +public class FileNames { + private static final Pattern fileExtensionExcludePattern = Pattern.compile("[^a-zA-Z0-9]"); + + private FileNames() { + // empty constructor + } + + /** + * Transform to extension to lower case and + * remove all characters that are not numbers or between 'a' and 'z' + * + * @param fileExtension the file name extension, e.g. jpg + * @return the clean file extension, null if fileExtension is null + */ + @Nullable + public static String cleanExtensionName(String fileExtension) { + if (fileExtension == null) { + return null; + } + return fileExtensionExcludePattern.matcher(fileExtension).replaceAll("").toLowerCase(); + } + + @Nullable + public static String parseFileNameExtension(String fileName) { + if (fileName == null) { + return null; + } + int dotIndex = fileName.lastIndexOf("."); + if (dotIndex == -1) { + return null; + } + return fileName.substring(dotIndex + 1); + } +} diff --git a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java b/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java index b8e8bcd..0b97f10 100644 --- a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java +++ b/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java @@ -1,5 +1,6 @@ package com.coreoz.plume.file.utils; +import com.coreoz.plume.file.cleaning.FileExtensionCleaning; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java b/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java index 4012563..1879c18 100644 --- a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java +++ b/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java @@ -1,5 +1,6 @@ package com.coreoz.plume.file.utils; +import com.coreoz.plume.file.cleaning.FileNameCleaning; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/plume-file-web-download-jersey/src/main/java/com/coreoz/plume/file/webservices/FileWs.java b/plume-file-web-download-jersey/src/main/java/com/coreoz/plume/file/webservices/FileWs.java index f7b4e25..5d8a725 100644 --- a/plume-file-web-download-jersey/src/main/java/com/coreoz/plume/file/webservices/FileWs.java +++ b/plume-file-web-download-jersey/src/main/java/com/coreoz/plume/file/webservices/FileWs.java @@ -2,8 +2,8 @@ import com.coreoz.plume.file.service.FileDownloadJerseyService; import com.coreoz.plume.file.service.configuration.FileDownloadConfigurationService; -import com.coreoz.plume.file.utils.FileExtensionCleaning; -import com.coreoz.plume.file.utils.FileNameCleaning; +import com.coreoz.plume.file.cleaning.FileExtensionCleaning; +import com.coreoz.plume.file.cleaning.FileNameCleaning; import com.coreoz.plume.jersey.security.permission.PublicApi; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; diff --git a/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/validator/FileUploadValidator.java b/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/validator/FileUploadValidator.java index c219a01..bec9a15 100644 --- a/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/validator/FileUploadValidator.java +++ b/plume-file-web-upload-jersey/src/main/java/com/coreoz/plume/file/validator/FileUploadValidator.java @@ -1,9 +1,9 @@ package com.coreoz.plume.file.validator; -import com.coreoz.plume.file.utils.FileNameCleaning; +import com.coreoz.plume.file.cleaning.FileNameCleaning; import com.coreoz.plume.file.services.mimetype.FileMimeTypeDetector; import com.coreoz.plume.file.services.mimetype.PeekingInputStream; -import com.coreoz.plume.file.utils.FileExtensionCleaning; +import com.coreoz.plume.file.cleaning.FileExtensionCleaning; import com.coreoz.plume.jersey.errors.Validators; import com.coreoz.plume.jersey.errors.WsError; import com.coreoz.plume.jersey.errors.WsException; From 6a6c7b774529318fd6d49f3a77075a199d3175ac Mon Sep 17 00:00:00 2001 From: Lucas Amiaud Date: Thu, 11 Jan 2024 16:52:28 +0100 Subject: [PATCH 3/3] move test to cleaning package --- .../file/{utils => cleaning}/FileExtensionCleaningTest.java | 3 +-- .../plume/file/{utils => cleaning}/FileNameCleaningTest.java | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) rename plume-file-core/src/test/java/com/coreoz/plume/file/{utils => cleaning}/FileExtensionCleaningTest.java (89%) rename plume-file-core/src/test/java/com/coreoz/plume/file/{utils => cleaning}/FileNameCleaningTest.java (91%) diff --git a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java b/plume-file-core/src/test/java/com/coreoz/plume/file/cleaning/FileExtensionCleaningTest.java similarity index 89% rename from plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java rename to plume-file-core/src/test/java/com/coreoz/plume/file/cleaning/FileExtensionCleaningTest.java index 0b97f10..0e47c60 100644 --- a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileExtensionCleaningTest.java +++ b/plume-file-core/src/test/java/com/coreoz/plume/file/cleaning/FileExtensionCleaningTest.java @@ -1,6 +1,5 @@ -package com.coreoz.plume.file.utils; +package com.coreoz.plume.file.cleaning; -import com.coreoz.plume.file.cleaning.FileExtensionCleaning; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java b/plume-file-core/src/test/java/com/coreoz/plume/file/cleaning/FileNameCleaningTest.java similarity index 91% rename from plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java rename to plume-file-core/src/test/java/com/coreoz/plume/file/cleaning/FileNameCleaningTest.java index 1879c18..854c75c 100644 --- a/plume-file-core/src/test/java/com/coreoz/plume/file/utils/FileNameCleaningTest.java +++ b/plume-file-core/src/test/java/com/coreoz/plume/file/cleaning/FileNameCleaningTest.java @@ -1,6 +1,5 @@ -package com.coreoz.plume.file.utils; +package com.coreoz.plume.file.cleaning; -import com.coreoz.plume.file.cleaning.FileNameCleaning; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat;