Похоже, поставщик файловой системы zip Java 7 не принимает пробелы в URI - PullRequest
20 голосов
/ 26 марта 2012

Я тестировал все возможные варианты и перестановки, но я не могу создать 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 в качестве символа пробела, выдается другое исключение:

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?

РЕДАКТИРОВАТЬ:

Другой вариант использования, основанный на объекте 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)

Ответы [ 4 ]

6 голосов
/ 26 марта 2012

На самом деле дальнейший анализ показывает, что существует проблема с 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". Затем используйте это для создания новой файловой системы, основанной только на пути.

5 голосов
/ 26 декабря 2012

Это ошибка в Java 7, и она была помечена как исправленная в Java 8 (см. Идентификатор ошибки 7156873 ). Исправление также следует перенести в Java 7, но на данный момент не определено, какое обновление будет иметь его (см. Идентификатор ошибки 8001178 ).

4 голосов
/ 16 мая 2013

У jar: URI должен быть экранированный zip-URI в его специфичной для схемы части, поэтому ваш jar: URI просто неверен - его следует правильно экранировать дважды, так как схема jar: состоит из URI хоста,! / и местный путь.

Однако это экранирование подразумевается и не выражается минимальной «спецификацией» URL в JarURLConnection .Однако я согласен с поднятой ошибкой в ​​JRE, что она все еще должна принимать одиночные экранированные символы, хотя это может привести к тому, что некоторые странные крайние случаи не будут поддерживаться.* в другом ответе проще всего сделать FileSystems.newFileSystem (path, null) - но это не работает, когда вы хотите передать и env с произнесением «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"));
    }
}

(я провел аналогичный тест с «with \ u2301unicode \ u263bhere», чтобы проверить, что мне не нужно использовать .toASCIIString ())

3 голосов
/ 06 марта 2013

Существует два способа создания файловой системы:

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

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

Когда в имени файла есть пробел вместе с вышеуказанным решением для создания URI. Это также работает, если вы используете другой метод, который не принимает uri в качестве аргумента.

...