Поставщик файловой системы zip-сервера Java 7, похоже, не принимает пробелы в URI
Я тестировал все возможные варианты и перестановки, но я не могу построить FileSystemProvider с помощью схемы zip/jar для пути (URI), который содержит пробелы. Существует очень упрощенный тест, доступный в 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)) {}
}
}
}
Когда я выполняю этот код (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)
Если я использую + вместо %20 в качестве символа escape пространства, выдается другое исключение:
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)
Я мог бы пропустить что-то очень очевидное, но будет ли это указывать на проблему с предоставленным поставщиком файловой системы ZIP/JAR?
EDIT:
Другой вариант использования, основанный на объекте File, по запросу в комментариях:
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();
}
}
}
Исключение повторяется как:
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)
Ответы
Ответ 1
На самом деле дальнейший анализ, похоже, указывает на проблему с 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();
Из JavaDocs URI.getSchemeSpecificPart() мы можем видеть следующее:
Строка, возвращаемая этим методом, равна возвращаемой getRawSchemeSpecificPart, за исключением того, что все последовательности экранированных октеты декодируются.
Эта же строка затем передается обратно в качестве аргумента в новый конструктор URI(). Поскольку любые экранированные октеты удаляются с помощью getSchemeSpecificPart(), если исходный URI содержит любые escape-символы, они не будут распространяться на новый URI - следовательно, исключение.
Потенциальное обходное решение - обходит всех доступных поставщиков файловой системы и получает ссылку на тех, кто специфицирует "jar". Затем используйте это, чтобы создать новую файловую систему, основанную только на пути.
Ответ 2
Это ошибка в Java 7, и она была отмечена как фиксированная в Java 8 (см. Идентификатор ошибки 7156873). Исправление также должно быть обращено к Java 7, но на данный момент он не определил, какое обновление будет иметь его (см. Идентификатор ошибки 8001178).
Ответ 3
jar: URI должны иметь escaped zip-URI в своей специфичной для схемы части, поэтому ваш jar: URI просто ошибочен - он должен быть справедливо двойным с экранированием, поскольку схема jar: состоит из URI хоста,!/и локальный путь.
Однако это экранирование подразумевается и не выражается минимальной "спецификацией URL" в JarURLConnection. Я согласен, однако, с повышенной ошибкой в JRE, что он все равно должен принимать одиночные экраны, хотя это может привести к тому, что некоторые странные краевые случаи не поддерживаются.
Как отмеченный tornike и evermean в другом ответе, проще всего сделать FileSystems.newFileSystem(путь, null) - но это не работает, когда вы хотите для перехода и env с помощью say "create" = true.
Вместо этого создайте jar: URI с помощью конструктора на основе компонентов:
URI jar = new URI("jar", path.toUri().toString(), null);
Это правильно закодирует часть, относящуюся к схеме.
Как тест JUnit, который также подтверждает, что это экранирование, используемое при открытии с пути:
@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"));
}
}
(Я сделал аналогичный тест с "с\u2301unicode\u263bhere", чтобы проверить, что мне не нужно использовать .toASCIIString())
Ответ 4
Существует два способа создания файловой системы:
FileSystem fs = FileSystems.newFileSystem(uri, env);
FileSystem fs = FileSystems.newFileSystem(zipfile, null);
Если в имени файла есть место вместе с указанным выше решением для создания uri. Он также работает, если вы используете другой метод, который не принимает uri в качестве аргумента.