Filter reserved characters in file names (#2143)

* Issue-2121 bugfix 过滤文件名中的非法字符

* Issue-2121 todo: 需要增加一些单元测试

* Issue-2121 add UT

* Issue-2121 add UT

* Issue-2121 move filter method to FileUtils

* Issue-2121 move filter method to FilenameUtils

* Issue-2121 checkstyle formatted

* Issue-2121
1. add regex to filter more reversed info
2. limit filename length in 200
2. add UT

* Issue-2121 remove tail dots

* Issue-2121 throw  FileOperationException if filename is empty

* Issue-2121 trim() string after substring it

* Issue-2121 change pattern name

* Issue-2121 revert irrelevant code

* Issue-2121 bug, '+' should not be filtered
pull/2149/head
ezio 2022-06-09 15:48:13 +08:00 committed by GitHub
parent 36b99d2476
commit 127a8b71c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 1 deletions

View File

@ -106,6 +106,7 @@ import run.halo.app.service.UserService;
import run.halo.app.utils.DateTimeUtils;
import run.halo.app.utils.DateUtils;
import run.halo.app.utils.FileUtils;
import run.halo.app.utils.FilenameUtils;
import run.halo.app.utils.HaloUtils;
import run.halo.app.utils.JsonUtils;
import run.halo.app.utils.VersionUtil;
@ -630,8 +631,9 @@ public class BackupServiceImpl implements BackupService {
}
content.append(postMarkdownVo.getOriginalContent());
try {
String filename = postMarkdownVo.getTitle() + "-" + postMarkdownVo.getSlug();
String markdownFileName =
postMarkdownVo.getTitle() + "-" + postMarkdownVo.getSlug() + ".md";
FilenameUtils.sanitizeFilename(filename) + ".md";
Path markdownFilePath = Paths.get(markdownFileTempPathName, markdownFileName);
if (!Files.exists(markdownFilePath.getParent())) {
Files.createDirectories(markdownFilePath.getParent());

View File

@ -3,9 +3,12 @@ package run.halo.app.utils;
import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import run.halo.app.exception.FileOperationException;
/**
* Filename utilities.
@ -15,6 +18,14 @@ import org.springframework.util.Assert;
*/
public class FilenameUtils {
private static final Pattern FILENAME_RESERVED_CHARS_PATTERN =
Pattern.compile("[\\\\/:*?\"<>|.]");
private static final Pattern FILENAME_WIN_RESERVED_NAMES_PATTERN =
Pattern.compile("^(CON|PRM|AUX|NUL|COM[0-9]|LPT[0-9])$");
private static final int FILENAME_MAX_LENGTH = 200;
private FilenameUtils() {
}
@ -104,4 +115,21 @@ public class FilenameUtils {
return filename.substring(dotLastIndex + 1);
}
/**
* @param filename filename
* @return sanitized filename, without any reserved character
*/
public static String sanitizeFilename(String filename) {
String sanitizedFilename =
FILENAME_RESERVED_CHARS_PATTERN.matcher(filename.trim()).replaceAll("");
if (StringUtils.isEmpty(sanitizedFilename)) {
throw new FileOperationException("文件名不合法: " + filename);
}
Matcher matcher = FILENAME_WIN_RESERVED_NAMES_PATTERN.matcher(sanitizedFilename);
if (matcher.matches()) {
sanitizedFilename = sanitizedFilename + "_file";
}
return sanitizedFilename.length() < FILENAME_MAX_LENGTH ? sanitizedFilename :
sanitizedFilename.substring(0, FILENAME_MAX_LENGTH).trim();
}
}

View File

@ -1,8 +1,10 @@
package run.halo.app.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.Test;
import run.halo.app.exception.FileOperationException;
/**
* Filename utilities test.
@ -45,4 +47,67 @@ class FilenameUtilsTest {
assertEquals("tar.gz", FilenameUtils.getExtension("he/ll/o.tar.gz"));
assertEquals("tar.bz2", FilenameUtils.getExtension("he/ll/o.tar.bz2"));
}
@Test
public void fileNameWithReservedCharsWillBeReplaced() {
String filename1 = "abcde";
String filteredFilename1 = FilenameUtils.sanitizeFilename(filename1);
assertEquals("abcde", filteredFilename1);
String filename2 = "abcde|字符替换\\星号*大于>小于<slash/中文字符、";
String filteredFilename2 = FilenameUtils.sanitizeFilename(filename2);
assertEquals("abcde字符替换星号大于小于slash中文字符、", filteredFilename2);
}
@Test
public void fileNameWithReversedNameWillBeReplaced() {
String filename1 = "CON ";
String sanitizedName = FilenameUtils.sanitizeFilename(filename1);
assertEquals("CON_file", sanitizedName);
String filename2 = "LPT19";
sanitizedName = FilenameUtils.sanitizeFilename(filename2);
assertEquals("LPT19", sanitizedName);
String filename3 = "CON 12345";
sanitizedName = FilenameUtils.sanitizeFilename(filename3);
assertEquals("CON 12345", sanitizedName);
String filename4 = "COM3";
sanitizedName = FilenameUtils.sanitizeFilename(filename4);
assertEquals("COM3_file", sanitizedName);
}
@Test
public void filenameLengthLimit() {
StringBuilder filename = new StringBuilder("haloe");
while (filename.length() < 300) {
filename.append("haloe");
}
assertEquals(300, filename.length());
String sanitizedName = FilenameUtils.sanitizeFilename(filename.toString());
assertEquals(200, sanitizedName.length());
}
@Test
public void filenameMixTest() {
String filename = "halo |I'd like.to $be a: CONtributor。。...";
String sanitizedName = FilenameUtils.sanitizeFilename(filename);
assertEquals("halo I'd liketo $be a CONtributor。。", sanitizedName);
}
@Test
public void filenameWithDotsWillBeSanitized() {
String filename = "pls.approve...my..PR..... ";
String sanitizedName = FilenameUtils.sanitizeFilename(filename);
assertEquals("plsapprovemyPR", sanitizedName);
}
@Test
public void fileNameExtremelyInvalid() {
String filename = "?<>\\:*|...";
FileOperationException exception = assertThrows(FileOperationException.class,
() -> FilenameUtils.sanitizeFilename(filename));
assertEquals("文件名不合法: ?<>\\:*|...", exception.getMessage());
}
}