如何安全地用Java对字符串进行编码以用作文件名?

2022-08-31 08:10:33

我从外部进程接收字符串。我想使用该字符串创建文件名,然后写入该文件。以下是我的代码片段:

    String s = ... // comes from external source
    File currentFile = new File(System.getProperty("user.home"), s);
    PrintWriter currentWriter = new PrintWriter(currentFile);

如果 s 包含无效字符,例如基于 Unix 的操作系统中的 “/”,则 (正确地) 抛出 java.io.FileNotFoundException。

如何安全地对字符串进行编码,以便将其用作文件名?

编辑:我希望的是一个API调用,它可以为我做到这一点。

我可以这样做:

    String s = ... // comes from external source
    File currentFile = new File(System.getProperty("user.home"), URLEncoder.encode(s, "UTF-8"));
    PrintWriter currentWriter = new PrintWriter(currentFile);

但我不确定URLEncoder是否可靠。


答案 1

我的建议是采取“白名单”方法,这意味着不要试图过滤掉坏字符。相反,定义什么是可以的。您可以拒绝文件名或对其进行筛选。如果要过滤它:

String name = s.replaceAll("\\W+", "");

这样做的作用是将任何不是数字,字母或下划线字符替换为无。或者,您可以用另一个字符(如下划线)替换它们。

问题是,如果这是一个共享目录,那么你不希望文件名冲突。即使用户存储区域是按用户隔离的,您也可能通过过滤掉坏字符而最终得到一个冲突的文件名。如果用户也想下载它,则输入的名称通常很有用。

出于这个原因,我倾向于允许用户输入他们想要的内容,根据我自己选择的方案(例如userId_fileId)存储文件名,然后将用户的文件名存储在数据库表中。这样,您就可以将其显示给用户,以您想要的方式存储内容,并且不会损害安全性或清除其他文件。

您还可以对文件进行哈希处理(例如MD5哈希),但这样您就无法列出用户放入的文件(无论如何都不能使用有意义的名称)。

编辑:修复了java的正则表达式


答案 2

这取决于编码是否应该可逆。

可逆

使用 URL 编码 () 将特殊字符替换为 。请注意,您需要注意字符串等于 、等于或为空的特殊情况!¹ 许多程序使用 URL 编码来创建文件名,因此这是每个人都能理解的标准技术。java.net.URLEncoder%xx...

不可逆

使用给定字符串的哈希(例如 SHA-1)。现代哈希算法(不是MD5)可以被认为是无冲突的。事实上,如果你发现碰撞,你会在密码学上有一个突破口。


¹ 您可以使用前缀(如“myApp-”)优雅地处理所有 3 种特殊情况。如果您将文件直接放入$HOME,则必须这样做以避免与现有文件(如“.bashrc”)发生冲突。
public static String encodeFilename(String s)
{
    try
    {
        return "myApp-" + java.net.URLEncoder.encode(s, "UTF-8");
    }
    catch (java.io.UnsupportedEncodingException e)
    {
        throw new RuntimeException("UTF-8 is an unknown encoding!?");
    }
}