Java 7 zip文件系统提供程序似乎不接受URI中的空格

Kos*_*sta 20 java java-7

我一直在测试所有可能的变体和排列,但我似乎无法使用zip/jar方案为包含空格的路径(URI)构建FileSystemProvider.Oracle Docs提供了一个非常简单的测试用例.我冒昧地修改了这个例子,只是在URI中添加空格,它就停止工作了.下面的代码段:

import java.util.*;
import java.net.URI;
import java.nio.file.*;

public class Test {
    public static void main(String [] args) throws Throwable {
        Map<String, String> env = new HashMap<>(); 
        env.put("create", "true");
        URI uri = new URI("jar:file:/c:/dir%20with%20spaces/zipfstest.zip");
        Path dir = Paths.get("C:\\dir with spaces");
        if(Files.exists(dir) && Files.isDirectory(dir)) {
            try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) {}
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

当我执行此代码(Windows,JDK7u2,x32和x64)时,我得到以下异常:

java.lang.IllegalArgumentException: Illegal character in path at index 12: file:/c:/dir with spaces/zipfstest.zip
    at com.sun.nio.zipfs.ZipFileSystemProvider.uriToPath(ZipFileSystemProvider.java:87)
    at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:107)
    at java.nio.file.FileSystems.newFileSystem(FileSystems.java:322)
    at java.nio.file.FileSystems.newFileSystem(FileSystems.java:272)
Run Code Online (Sandbox Code Playgroud)

如果我使用+而不是%20作为空格转义字符,则抛出另一个异常:

java.nio.file.NoSuchFileException: c:\dir+with+spaces\zipfstest.zip
    at sun.nio.fs.WindowsException.translateToIOException(WindowsException.java:79)
    at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:97)
    at sun.nio.fs.WindowsException.rethrowAsIOException(WindowsException.java:102)
    at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(WindowsFileSystemProvider.java:229)
    at java.nio.file.spi.FileSystemProvider.newOutputStream(FileSystemProvider.java:430)
    at java.nio.file.Files.newOutputStream(Files.java:170)
    at com.sun.nio.zipfs.ZipFileSystem.<init>(ZipFileSystem.java:116)
    at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:117)
    at java.nio.file.FileSystems.newFileSystem(FileSystems.java:322)
    at java.nio.file.FileSystems.newFileSystem(FileSystems.java:272)
Run Code Online (Sandbox Code Playgroud)

我可能会遗漏一些非常明显的东西,但这是否表明提供的ZIP/JAR文件系统提供程序存在问题?

编辑:

另一个基于File对象的用例,如coments中所要求的:

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.nio.file.FileSystems;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Test {
    public static void main(String[] args) throws UnsupportedEncodingException {
        try {
            File zip = new File("C:\\dir with spaces\\file.zip");
            URI uri = URI.create("jar:" + zip.toURI().toURL());
            Map<String, String> env = new HashMap<>();
            env.put("create", "true");
            if(zip.getParentFile().exists() && zip.getParentFile().isDirectory()) {
                FileSystems.newFileSystem(uri, env);
            }
        } catch (Exception ex) {
            Logger.getAnonymousLogger().log(Level.SEVERE, null, ex);
            System.out.println();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

异常再次抛出:

java.lang.IllegalArgumentException: Illegal character in path at index 12: file:/C:/dir with spaces/file.zip
    at com.sun.nio.zipfs.ZipFileSystemProvider.uriToPath(ZipFileSystemProvider.java:87)
    at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:107)
    at java.nio.file.FileSystems.newFileSystem(FileSystems.java:322)
    at java.nio.file.FileSystems.newFileSystem(FileSystems.java:272)
Run Code Online (Sandbox Code Playgroud)

Kos*_*sta 6

实际上,进一步的分析似乎表明ZipFileSystemProvider存在问题.该类中包含的uriToPath(URI uri)方法执行以下代码段:

String spec = uri.getSchemeSpecificPart();
int sep = spec.indexOf("!/");
if (sep != -1)
  spec = spec.substring(0, sep);
return Paths.get(new URI(spec)).toAbsolutePath();
Run Code Online (Sandbox Code Playgroud)

从URI.getSchemeSpecificPart()的JavaDocs中我们可以看到以下内容:

此方法返回的字符串等于getRawSchemeSpecificPart方法返回的字符串,但所有转义的八位字节序列都被解码.

然后将相同的字符串作为参数传递回新的URI()构造函数.由于任何转义的八位字节都被getSchemeSpecificPart()解除转义,如果原始URI包含任何转义字符,它们将不会传播到新URI - 因此是例外.

一个潜在的解决方法 - 遍历所有可用的文件系统提供程序,并获得对spec等于"jar"的引用.然后使用它来创建基于路径的新文件系统.

  • 此错误的解决方法是双重转义空格 - 即%20 - >%2520然后使用空格字符的文件工作.完全没有意义,但它似乎工作 (2认同)

Esk*_*ola 5

这是Java 7中的一个错误,它已在Java 8中标记为已修复(请参阅错误ID 7156873).修复程序也应该向后移植到Java 7,但目前尚未确定哪个更新将具有它(请参阅错误ID 8001178).


Sti*_*yes 5

jar: URI 应该在其特定于方案的部分中包含转义的 zip-URI,因此您的 jar: URI 是完全错误的 - 它应该正确地进行双重转义,因为 jar: 方案由主机 URI、!/ 和本地路径。

然而,这种转义只是隐含的,而不是由JarURLConnection 中的最小 URL“规范”表达的。然而,我同意 JRE 中提出的错误,即它仍然应该接受单转义,尽管这可能导致不支持一些奇怪的边缘情况。

正如tornike 和 evermean在另一个答案中指出的那样,最简单的方法是执行 FileSystems.newFileSystem(path, null) - 但是当您想通过“create”=true 传递和 env 时,这不起作用。

相反,使用基于组件的构造函数创建 jar: URI:

URI jar = new URI("jar", path.toUri().toString(), null);
Run Code Online (Sandbox Code Playgroud)

这将正确编码特定于方案的部分。

作为 JUnit 测试,它也确认这是从 Path 打开时使用的转义:

@Test
public void jarWithSpaces() throws Exception {
    Path path = Files.createTempFile("with several spaces", ".zip");
    Files.delete(path);

    // Will fail with FileSystemNotFoundException without env:
    //FileSystems.newFileSystem(path, null);

    // Neither does this work, as it does not double-escape:
    // URI jar = URI.create("jar:" + path.toUri().toASCIIString());                

    URI jar = new URI("jar", path.toUri().toString(), null);
    assertTrue(jar.toASCIIString().contains("with%2520several%2520spaces"));

    Map<String, Object> env = new HashMap<>();
    env.put("create", "true");

    try (FileSystem fs = FileSystems.newFileSystem(jar, env)) {
        URI root = fs.getPath("/").toUri();    
        assertTrue(root.toString().contains("with%2520several%2520spaces"));
    } 
    // Reopen from now-existing Path to check that the URI is
    // escaped in the same way
    try (FileSystem fs = FileSystems.newFileSystem(path, null)) {
        URI root = fs.getPath("/").toUri();
        //System.out.println(root.toASCIIString());
        assertTrue(root.toString().contains("with%2520several%2520spaces"));
    }
}
Run Code Online (Sandbox Code Playgroud)

(我用“with\u2301unicode\u263bhere”做了一个类似的测试来检查我是否不需要使用 .toASCIIString())