Skip to content

Commit

Permalink
Merge pull request #20 from Coreoz/fix/content-disposition-utf8
Browse files Browse the repository at this point in the history
Fix Content-Disposition header to handle UTF8 file names
  • Loading branch information
amanteaux authored Jan 11, 2024
2 parents 5283925 + 6a6c7b7 commit 197b219
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.coreoz.plume.file.cleaning;

import javax.annotation.Nullable;
import java.util.regex.Pattern;

public class FileExtensionCleaning {
private static final Pattern fileExtensionExcludePattern = Pattern.compile("[^a-zA-Z0-9]");

private FileExtensionCleaning() {
// 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. <code>jpg</code>
* @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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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:<br>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.cleaning.FileExtensionCleaning;

import lombok.SneakyThrows;

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
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]");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
package com.coreoz.plume.file.utils;
package com.coreoz.plume.file.cleaning;

import org.junit.Test;

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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.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;
Expand All @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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.FileNames;
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;
Expand Down Expand Up @@ -57,7 +57,7 @@ private FileUploadValidator(
this.data = new FileUploadData(
filePeekingStream.peekedStream(),
fileName,
FileNames.parseFileNameExtension(fileName),
FileExtensionCleaning.parseFileNameExtension(fileName),
mimeType,
fileSize
);
Expand Down

0 comments on commit 197b219

Please sign in to comment.