File.toURI不会编码加号

4
我只是想通过这个问题来确认自己的理智。我有一个文件名中包含一个“+”(加号)字符,这在一些操作系统和文件系统上是完全有效的(例如MacOS和HFS +)。
然而,我发现java.io.File#toURI()不正常工作。
例如:
new File("hello+world.txt").toURI().toString()

我的Mac电脑返回:

file:/Users/aretter/code/rocksdb/hello+world.txt

然而在我看来,这是不正确的,因为文件名中的“+”(加号)字符并没有在URI中进行编码。URI根本不代表原始文件名,“+”在URI中具有非常不同的含义,与文件名中的“+”字符截然不同。
因此,如果我们解码URI,加号将被替换为空格字符,并且我们丢失了信息。例如:
URLDecoder.decode(new File("hello+world.txt").toURI().toURL().toString)

这会导致:
file:/Users/aretter/code/rocksdb/hello world.txt

我原本期望的是这样的内容:
new File("hello+world.txt").toURI().toString()

导致:
file:/Users/aretter/code/rocksdb/hello%2Bworld.txt

这样当它稍后被使用和解码时,加号会被保留。

我很难相信Java SE中会存在这样一个明显的漏洞。有人能指出我哪里错了吗?

另外,如果有解决方法,我想听听。请记住,我实际上并没有将静态字符串作为文件名提供给File,而是从磁盘读取文件目录,其中一些文件可能包含“+”(加号)字符。


{btsdaf} - Ravi
5个回答

3

让我尝试澄清一下:

  • '+'加号字符用作编码字符,以编码HTML表单中的空格(也称为application/x-www-form-urlencoded MIME格式)。
  • '%20'字符用作编码字符,以编码URL / URI格式中的空格。

'+'加号字符在URL上下文中被视为普通字符,并且不以任何形式进行编码(例如%20)。

因此,当您调用new File("hello+world.txt").toURI().toString()时,不会对'+'字符执行任何编码(因为不需要)。

现在来看看URLDecoder这个类是用于HTML表单解码的实用程序类。它将'+'加号视为已编码字符,因此将其解码为' '空格字符。在您的示例中,此类将URI的字符串值视为普通html表单字段的值(而不是URI值)。此类不应用于解码完整的URI / URL值,因为它不是为此目的而设计的。

来自URLDecoder#decode(String)的Java文档,

解码x-www-form-urlencoded格式的字符串。平台默认使用编码方式来确定任何连续的 "%xy" 形式的字符所代表的含义。

希望这可以帮助您。

根据评论进行更新 #1:

根据 第2.2节,如果URI组件的数据与保留字符冲突,则必须在形成URI之前将冲突数据进行百分号编码。

还有一个重要点是URI的不同部分具有不同的保留字集,具体取决于它们的上下文。例如,'/'符号仅在URI路径部分中被保留,“+”符号则在查询字符串部分中被保留。因此,在“查询部分”中不需要转义“/”,类似地,在“路径部分”中也不需要转义“+”。

在您的示例中,URI生产者File.toURI不会对URI路径部分中的+符号进行编码(因为在路径部分中+不被视为保留字),因此您可以在URI的字符串表示形式中看到+符号。
您可以参考URI建议了解更多详情。
相关答案:
  1. https://dev59.com/N3NA5IYBdhLWcg3wVcNx#1006074
  2. https://dev59.com/vnE85IYBdhLWcg3wpFKu#2678602
  3. https://dev59.com/S2455IYBdhLWcg3wCfqC#4571518

{btsdaf} - adamretter
@adamretter 但这正是他的观点,+字符不需要被编码。 例如,阅读URI规范BNF。在那里,+有点特殊,但只是因为它在search中有特殊含义,并且只在path中有效。但就是这样,这里的+path的一部分,它是有效的,不需要被编码。(如果你包括?,那就会有所不同) - Cryptjar
{btsdaf} - adamretter
感谢您的全面回答。然而,我仍然不相信+不应该被编码。虽然在URI的ABNF中定义的pchar部分允许使用+,但百分号编码字符也是如此,ABNF表示编码形式。正如规范在第2.4节中所述:“一旦生成,URI始终处于其百分比编码形式。”即URI始终处于编码形式,并且如果我没有弄错的话,在URI路径中的+表示空格字符而不是+字符。ABNF和第2.2和2.4节似乎证实了这一点? - adamretter
1
啊啊啊!!! 好的,我重新阅读了你更新后的答案,并仔细看了几遍 https://dev59.com/S2455IYBdhLWcg3wCfqC#4571518 ,我现在明白了。问题在于 URLDecoder 的设计非常针对特定的 HTML 情况。感谢你的精彩回答。 - adamretter

1
我假设您想将文件名中的加号符号编码为%2B,这样当您解码时就会得到加号符号。如果是这种情况,则需要使用URLEncoder.encode。
System.out.println(URLEncoder.encode(new File("hello+world.txt").toURI().toString()));

它将对所有特殊字符进行编码,包括+符号。输出结果将为:
file%3A%2Fhome%2FT8hvs7%2Fhello%2Bworld.txt

现在,要解码,请使用URLDecoder.decode
System.out.println(URLDecoder.decode("file%3A%2Fhome%2FwQCXni%2Fhello%2Bworld.txt"));

它将显示


file:/home/wQCXni/hello+world.txt

0
如果URI表示一个文件,则让File类解码URI。
假设我们有一个文件的URI,例如获取jar文件的文件路径: URI uri = MyClass.class.getProtectionDomain().getCodeSource().getLocation().toURI();
System.out.println(uri.toString()); => 不好:会显示加号,但对于空格是%20
System.out.println(URLDecoder.decode(uri.toString(), StandardCharsets.UTF_8.toString())); => 不好:将显示空格而不是%20,也将显示加号
System.out.println(new File(uri).getAbsolutePath()); => 好

0

显然这不是一个错误,文档清楚地表明

The plus sign "+" is converted into a space character " " .

你可以这样做:https://ideone.com/JHDkM4
import java.util.*;
import java.lang.*;
import java.io.*;
import static java.lang.System.out;


class Ideone
{
    public static void main (String[] args) throws java.lang.Exception
    {
        out.println(new File("hello+world.txt").toURI().toString());
        out.println(java.net.URLDecoder.decode(new File("hello+world.txt").toURI().toURL().toString()));
        out.println(new File("hello+world.txt").toURI().toString().replaceAll("\\+", "%2B"));
    }
}

{btsdaf} - adamretter

-1
尝试使用反斜杠\来转义加号,像这样做:
new File("hello\+world.txt").toURI().toString()

那甚至都无法编译! - adamretter
如果你正确使用它,它就会生效,只需在字符串中加入一个反斜杠即可编译并运行你的代码。 - Dinh
1
导入java.io.File;public class Test { public static void main(String args[]) { new File("hello+world.txt").toURI().toString(); } }javac Test.javaTest.java:5: 错误:非法的转义字符 - adamretter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接