Java 7 zip file system provider doesn't seem to accept spaces in URI

两盒软妹~` 提交于 2019-11-29 09:08:48

Actually further analysis does seem to indicate there is a problem with the ZipFileSystemProvider. The uriToPath(URI uri) method contained within the class executes the following snippet:

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

From the JavaDocs of URI.getSchemeSpecificPart() we can see the following:

The string returned by this method is equal to that returned by the getRawSchemeSpecificPart method except that all sequences of escaped octets are decoded.

This same string is then passed back as an argument into the new URI() constructor. Since any escaped octets are de-escaped by getSchemeSpecificPart(), if the original URI contained any escape characters, they will not be propagated to the new URI - hence the exception.

A potential workaround - loop through all the available filesystem providers and get the reference to the one who's spec equals "jar". Then use that to create a new filesystem based on path only.

This is a bug in Java 7 and it has been marked as fixed in Java 8 (see Bug ID 7156873). The fix should also be backported to Java 7, but at the moment it's not determined that which update will have it (see Bug ID 8001178).

Stian Soiland-Reyes

The jar: URIs should have the escaped zip-URI in its scheme-specific part, so your jar: URI is simply wrong - it should rightly be double-escaped, as the jar: scheme is composed of the host URI, !/ and the local path.

However, this escaping is only implied and not expressed by the minimal URL "specification" in JarURLConnection. I agree however with the raised bug in JRE that it should still accept single-escaped, although that could lead to some strange edge-cases not being supported.

As pointed out by tornike and evermean in another answer, the easiest is to do FileSystems.newFileSystem(path, null) - but this does not work when you want to pass and env with say "create"=true.

Instead, create the jar: URI using the component-based constructor:

URI jar = new URI("jar", path.toUri().toString(), null);

This would properly encode the scheme-specific part.

As a JUnit test, which also confirms that this is the escaping used when opening from a 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"));
    }
}

(I did a similar test with "with\u2301unicode\u263bhere" to check that I did not need to use .toASCIIString())

tornike

There are two methods to create a filesystem:

FileSystem fs = FileSystems.newFileSystem(uri, env);

FileSystem fs = FileSystems.newFileSystem(zipfile, null);

When there is a space in a filename together with the above solution for creating a uri. It also works if you use a different method that doesn't take a uri as argument.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!