Ste*_*eod 108 java string encoding file
我从外部进程收到一个字符串.我想使用该String来创建文件名,然后写入该文件.这是我的代码片段:
String s = ... // comes from external source
File currentFile = new File(System.getProperty("user.home"), s);
PrintWriter currentWriter = new PrintWriter(currentFile);
Run Code Online (Sandbox Code Playgroud)
如果s包含无效字符,例如基于Unix的OS中的"/",则会(正确地)抛出java.io.FileNotFoundException.
如何安全地编码String以便它可以用作文件名?
编辑:我希望的是一个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);
Run Code Online (Sandbox Code Playgroud)
但我不确定URLEncoder是否可靠用于此目的.
cle*_*tus 98
我的建议是采用"白名单"方法,这意味着不要尝试过滤掉不良角色.而是定义什么是好的.您可以拒绝文件名或过滤它.如果要过滤它:
String name = s.replaceAll("\\W+", "");
Run Code Online (Sandbox Code Playgroud)
这样做是替换任何不是数字,字母或下划线的字符.或者,您可以用另一个字符(如下划线)替换它们.
问题是如果这是一个共享目录,那么你不希望文件名冲突.即使用户隔离了用户存储区域,也可能只是通过过滤掉不良字符而导致文件冲突.如果用户想要下载它,那么用户输入的名称通常很有用.
出于这个原因,我倾向于允许用户输入他们想要的内容,根据我自己选择的方案存储文件名(例如userId_fileId),然后将用户的文件名存储在数据库表中.这样,您可以将其显示回用户,存储您想要的内容,并且不会危及安全性或消除其他文件.
您也可以对文件进行哈希处理(例如MD5哈希),但是您无法列出用户输入的文件(无论如何都没有有意义的名称).
编辑:修复了java的正则表达式
vog*_*vog 34
这取决于编码是否应该是可逆的.
可逆
使用URL encoding(java.net.URLEncoder
)替换特殊字符%xx
.请注意,您要处理字符串等于,等于或为空的特殊情况!¹许多程序使用URL编码来创建文件名,因此这是每个人都能理解的标准技术..
..
不可逆
使用给定字符串的哈希值(例如SHA-1).现代哈希算法(不是 MD5)可以被认为是无冲突的.事实上,如果发现碰撞,您将在密码学方面取得突破.
"myApp-"
.如果直接放入文件$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!?");
}
}
Jon*_*ica 21
这是我使用的:
public String sanitizeFilename(String inputName) {
return inputName.replaceAll("[^a-zA-Z0-9-_\\.]", "_");
}
Run Code Online (Sandbox Code Playgroud)
它的作用是使用正则表达式替换每个不是字母,数字,下划线或带下划线的点的字符.
这意味着"如何将£转换为$"之类的内容将变为"How_to_convert___to__".不可否认,这个结果不是非常用户友好,但它是安全的,并且保证生成的目录/文件名在任何地方都可以使用.在我的情况下,结果不会显示给用户,因此不是问题,但您可能希望将正则表达式更改为更宽松.
值得注意的是,我遇到的另一个问题是我有时会得到相同的名称(因为它基于用户输入),所以你应该知道这一点,因为你不能在一个目录中有多个同名的目录/文件.此外,您可能需要截断或缩短生成的字符串,因为它可能超过某些系统具有的255个字符限制.
Ste*_*n C 14
如果您希望结果与原始文件类似,则SHA-1或任何其他哈希方案不是答案.如果必须避免碰撞,那么简单地替换或删除"坏"字符也不是答案.
相反,你想要这样的东西.
char fileSep = '/'; // ... or do this portably.
char escape = '%'; // ... or some other legal char.
String s = ...
int len = s.length();
StringBuilder sb = new StringBuilder(len);
for (int i = 0; i < len; i++) {
char ch = s.charAt(i);
if (ch < ' ' || ch >= 0x7F || ch == fileSep || ... // add other illegal chars
|| (ch == '.' && i == 0) // we don't want to collide with "." or ".."!
|| ch == escape) {
sb.append(escape);
if (ch < 0x10) {
sb.append('0');
}
sb.append(Integer.toHexString(ch));
} else {
sb.append(ch);
}
}
File currentFile = new File(System.getProperty("user.home"), sb.toString());
PrintWriter currentWriter = new PrintWriter(currentFile);
Run Code Online (Sandbox Code Playgroud)
该解决方案提供可逆编码(没有冲突),其中编码的字符串在大多数情况下类似于原始字符串.我假设您使用的是8位字符.
URLEncoder
有效,但它的缺点是它编码了大量合法的文件名字符.
如果您想要一个不保证可逆的解决方案,那么只需删除"坏"字符,而不是用转义序列替换它们.
Sha*_*ley 14
对于那些寻求通用解决方案的人来说,这些可能是常见的标准:
为此,我们可以使用正则表达式匹配非法字符,对它们进行百分比编码,然后约束编码字符串的长度.
private static final Pattern PATTERN = Pattern.compile("[^A-Za-z0-9_\\-]");
private static final int MAX_LENGTH = 127;
public static String escapeStringAsFilename(String in){
StringBuffer sb = new StringBuffer();
// Apply the regex.
Matcher m = PATTERN.matcher(in);
while (m.find()) {
// Convert matched character to percent-encoded.
String replacement = "%"+Integer.toHexString(m.group().charAt(0)).toUpperCase();
m.appendReplacement(sb,replacement);
}
m.appendTail(sb);
String encoded = sb.toString();
// Truncate the string.
int end = Math.min(encoded.length(),MAX_LENGTH);
return encoded.substring(0,end);
}
Run Code Online (Sandbox Code Playgroud)
模式
上面的模式基于POSIX规范中允许字符的保守子集.
如果要允许点字符,请使用:
private static final Pattern PATTERN = Pattern.compile("[^A-Za-z0-9_\\-\\.]");
Run Code Online (Sandbox Code Playgroud)
只要警惕像"."这样的字符串.和"......"
如果要避免在不区分大小写的文件系统上发生冲突,则需要转义大写:
private static final Pattern PATTERN = Pattern.compile("[^a-z0-9_\\-]");
Run Code Online (Sandbox Code Playgroud)
或者逃避小写字母:
private static final Pattern PATTERN = Pattern.compile("[^A-Z0-9_\\-]");
Run Code Online (Sandbox Code Playgroud)
您可以选择将特定文件系统的保留字符列入黑名单,而不是使用白名单.EG此正则表达式适合FAT32文件系统:
private static final Pattern PATTERN = Pattern.compile("[%\\.\"\\*/:<>\\?\\\\\\|\\+,\\.;=\\[\\]]");
Run Code Online (Sandbox Code Playgroud)
长度
在Android上,127个字符是安全限制.许多文件系统允许255个字符.
如果您更喜欢保留尾部,而不是字符串的头部,请使用:
// Truncate the string.
int start = Math.max(0,encoded.length()-MAX_LENGTH);
return encoded.substring(start,encoded.length());
Run Code Online (Sandbox Code Playgroud)
解码
要将文件名转换回原始字符串,请使用:
URLDecoder.decode(filename, "UTF-8");
Run Code Online (Sandbox Code Playgroud)
限制
由于较长的字符串被截断,因此编码时可能会发生名称冲突,或者解码时可能会出现损坏.