Java URI 中的高 unicode 代码点导致解码时出错

High unicode codepoint in Java URIs cause error when decoded

我编写了一些测试,其中我使用 org.springframework.core.io.ClassPathResource 获取将发送到 API 的文件。要求之一是在文件名中支持 unicode 字符,因此我加载了包含随机混合 unicode 字符的文件,但它无法解码 URI。经过一些诊断后,这个角色“”(猫表情符号,U+1F431)的可爱男孩似乎正在破坏它。我已经使用 unicode 转义 ("\u") 对 Java 文字进行了编码,以防 IDE encoding/view 出现问题。但是什么都没有改变。因此,我使用 Spring Boot initializr 创建了演示,以防某些包产生任何影响并开始调试。从测试来看,似乎可以使用单个“\u”转义序列编码的字符工作得很好(GAMMA 确实有效),但那些需要使用代理对的字符却不行,例如。是“\uD83D\uDC31”。

URL 编码的 emoji/filename 看起来像这样 %ed%a0%bd%ed%b0%b1 来自 sun.net.www.ParseUtil.encodePath(String path, boolean flag),而在同一个 class 但是 decode(String path) 方法。我自己不能直接使用 ParseUtil,因为即使它是 public,也无法访问它。谁能解释这里发生了什么?这只是 ParseUtils/Java 中的错误还是我遗漏了什么?

我在 Windows 10 上使用 Java 11 zulu。

我使用的演示:


@SpringBootApplication
public class DemoApplication {

 ​private static final String GAMMA = "\u03DD"; //ϝ greek small letter digamma
 ​private static final String CAT = "\uD83D\uDC31"; // cat emoji


 ​public static void main(String[] args) throws IOException {
   ​var c = new ClassPathResource(CAT).lastModified();
   ​System.out.println("cat: " + c);
 ​}
}

演示程序异常,它与我的真实测试完全相同,但堆栈更短。

Exception in thread "main" java.lang.IllegalArgumentException: Error decoding percent encoded characters
    at java.base/sun.net.www.ParseUtil.decode(ParseUtil.java:214)
    at java.base/sun.net.www.protocol.file.Handler.openConnection(Handler.java:82)
    at java.base/sun.net.www.protocol.file.Handler.openConnection(Handler.java:72)
    at java.base/java.net.URL.openConnection(URL.java:1074)
    at org.springframework.core.io.AbstractFileResolvingResource.lastModified(AbstractFileResolvingResource.java:272)
    at com.example.demo.DemoApplication.main(DemoApplication.java:34)

PS。 'ϝ' 和 '' 都是合法的 windows 文件名,所以如果 gamma 是可见的,那么 cat 应该是因为它们彼此相邻所以它们是可见的。应该不是文件丢失的问题。

这绝对是 Java 中的错误。 Java17 中仍然存在,与Spring 无关;如果我只使用 URLConnection.getLastModified() 也会出现同样的错误。 我在 Java 漏洞数据库中找不到任何相关信息(目前)。 请参阅 Java bug 8280911

解决方法是创建一个基础 URL,然后创建一个相对于该基础的资源 URL:

URL url = DemoApplication.class.getResource(
    DemoApplication.class.getSimpleName() + ".class");
url = new URL(url, CAT);
var c = url.openConnection().getLastModified();
System.out.println("cat: " + c);