我知道不同的平台有不同的文件名要求,所以我想知道是否有一种跨平台的方法可以做到这一点?
编辑:在Windows平台上,文件名中不能包含问号“?” ,而在Linux上可以。文件名可能包含这些字符,我希望支持这些字符的平台能够保留它们,但其他平台则会将它们剥离掉。
此外,我更喜欢使用标准的Java解决方案,而不需要第三方库。
如其他地方建议的那样,这通常不是您想要做的。最好使用安全方法(例如File.createTempFile())创建临时文件。
您不应该使用白名单仅保留“良好”的字符。如果文件只由汉字组成,则会将其全部剥离。出于这个原因,我们不能使用包含列表,而必须使用排除列表。
Linux几乎允许任何东西,这可能真的很麻烦。我只会将Linux限制为与Windows相同的列表,以便将来自己省心。
在Windows上使用此C#代码片段,我生成了一个无效的Windows字符列表。这个列表中的字符比您想象的要多得多(41个),因此我不建议尝试创建自己的列表。
foreach (char c in new string(Path.GetInvalidFileNameChars()))
{
Console.Write((int)c);
Console.Write(",");
}
这是一个简单的Java类,用于“清洁”文件名。
public class FileNameCleaner {
final static int[] illegalChars = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47};
static {
Arrays.sort(illegalChars);
}
public static String cleanFileName(String badFileName) {
StringBuilder cleanName = new StringBuilder();
for (int i = 0; i < badFileName.length(); i++) {
int c = (int)badFileName.charAt(i);
if (Arrays.binarySearch(illegalChars, c) < 0) {
cleanName.append((char)c);
}
}
return cleanName.toString();
}
}
编辑:
正如Stephen建议的那样,您可能还应该验证这些文件访问仅在您允许的目录中发生。
以下答案提供了Java中建立自定义安全上下文并在该“沙盒”中执行代码的示例代码。
binarySearch
正常工作,必须对 illegalChars
数组进行排序。请添加 Arrays.sort(illegalChars)
或将该数组更改为 "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 34, 42, 47, 58, 60, 62, 63, 92, 124}"。 - Franz KafkacharAt()
... 基本上你不应该使用 charAt
。原因是 charAt
无法处理位于 基本多文种平面 之外的 Unicode 代码点,因为它是一个 16 位值。相反,使用 codePointAt() 返回一个整数。此外,这消除了您当前正在执行的强制转换为 int 的需要。 - Stijn de Wittlength()
返回字符数,因此如果您使用codePointAt
,则需要使用codePointCount():badFileName.codePointCount(0, badFileName.length());
- Stijn de WittString filename = "A20/B22b#öA\\BC#Ä$%ld_ma.la.xps";
String sane = filename.replaceAll("[^a-zA-Z0-9\\._]+", "_");
结果:A20_B22b_A_BC_ld_ma.la.xps
解释:
[a-zA-Z0-9\\._]
匹配a-z大小写字母、数字、点和下划线
[^a-zA-Z0-9\\._]
是反向匹配,即所有不符合第一个表达式的字符
[^a-zA-Z0-9\\._]+
是一串不符合第一个表达式的字符序列
所以,每一串不包含a-z、0-9或. _字符的字符序列都将被替换。
String sane = filename.replaceAll("(?U)[^\\w\\._]+", "_") ;
。该代码将替换文件名中非单词字符、下划线和点之外的所有字符为下划线。 - Ariepublic class FileNameCleaner {
final static int[] illegalChars = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47};
static {
Arrays.sort(illegalChars);
}
public static String cleanFileName(String badFileName) {
StringBuilder cleanName = new StringBuilder();
int len = badFileName.codePointCount(0, badFileName.length());
for (int i=0; i<len; i++) {
int c = badFileName.codePointAt(i);
if (Arrays.binarySearch(illegalChars, c) < 0) {
cleanName.appendCodePoint(c);
}
}
return cleanName.toString();
}
}
这里的关键更改:
length
结合使用,而不仅仅是使用length
charAt
append
char
转换为int
。实际上,您不应该处理char
,因为它们基本上对BMP之外的任何内容都无用。public static String sanitizeName( String name ) {
if( null == name ) {
return "";
}
if( SystemUtils.IS_OS_LINUX ) {
return name.replaceAll( "[\u0000/]+", "" ).trim();
}
return name.replaceAll( "[\u0000-\u001f<>:\"/\\\\|?*\u007f]+", "" ).trim();
}
SystemUtils
is from Apache commons-lang3
有一个非常好的内置Java解决方案-Character.isXxx()。
尝试使用Character.isJavaIdentifierPart(c)
:
String name = "name.é+!@#$%^&*(){}][/=?+-_\\|;:`~!'\",<>";
StringBuilder filename = new StringBuilder();
for (char c : name.toCharArray()) {
if (c=='.' || Character.isJavaIdentifierPart(c)) {
filename.append(c);
}
}
Result is "name.é$_".
从您的问题中并不清楚,但由于您打算从Web表单接受路径名(?), 您可能应该阻止尝试重命名某些内容;例如"C:\Program Files"。这意味着在进行访问检查之前,您需要将路径名规范化以消除"."和".."。
鉴于此,我不会尝试删除非法字符。相反,我会使用"new File(str).getCanonicalFile()"来产生规范路径,然后检查它们是否符合您的沙箱限制,并最终使用"File.exists()", "File.isFile()"等方法检查源和目标文件是否符合要求,且它们不是同一个文件系统对象。我会通过尝试执行操作并捕获异常来处理非法字符。
Paths.get(...)
抛出一个详细的异常,其中包含非法字符的位置。
public static String removeInvalidChars(final String fileName)
{
try
{
Paths.get(fileName);
return fileName;
}
catch (final InvalidPathException e)
{
if (e.getInput() != null && e.getInput().length() > 0 && e.getIndex() >= 0)
{
final StringBuilder stringBuilder = new StringBuilder(e.getInput());
stringBuilder.deleteCharAt(e.getIndex());
return removeInvalidChars(stringBuilder.toString());
}
throw e;
}
}
如果您想使用更多的字符,如 [A-Za-z0-9],请查看MS Naming Conventions,并且不要忘记过滤掉“...整数表示在1到31之间的字符...”,就像Aaron Digulla的例子一样。对于这些字符,David Carboni的代码将不足以满足需求。
保留字符列表摘录:
可以使用当前代码页中的任何字符作为名称,包括 Unicode 字符和扩展字符集(128-255)中的字符,但不能使用以下字符: 以下保留字符: - < (小于号) - > (大于号) - : (冒号) - " (双引号) - / (正斜杠) - \ (反斜杠) - | (竖线或管道符) - ? (问号) - * (星号) 整数值零,有时也称为 ASCII NUL 字符。 其整数表示在 1 到 31 范围内的字符,除了备用数据流允许使用这些字符。有关文件流的更多信息,请参阅文件流。 目标文件系统不允许的任何其他字符也不能使用。