Нормализация возможно закодированных строк URI в Java - PullRequest
7 голосов
/ 23 февраля 2012

Используя Java, я хочу удалить идентификатор фрагмента и выполнить простую нормализацию (например, схемы в нижнем регистре, хосты) разнообразного набора URI. URI ввода и вывода должны быть эквивалентны в общем смысле HTTP.

Как правило, это должно быть просто. Однако для таких URI, как http://blah.org/A_%28Secret%29.xml#blah, процент которых кодирует (Secret), поведение java.util.URI усложняет жизнь.

Метод нормализации должен возвращать http://blah.org/A_%28Secret%29.xml, поскольку URI http://blah.org/A_%28Secret%29.xml и http://blah.org/A_(Secret).xml не эквивалентны в интерпретации [§2.2; RFC3968 ]

Итак, у нас есть два следующих метода нормализации:

URI u = new URI("http://blah.org/A_%28Secret%29.xml#blah");
System.out.println(u);
        // prints "http://blah.org/A_%28Secret%29.xml#blah"

String path1 = u.getPath();      //gives "A_(Secret).xml"
String path2 = u.getRawPath();   //gives "A_%28Secret%29.xml"


//NORMALISE METHOD 1
URI norm1 = new URI(u.getScheme().toLowerCase(), u.getUserInfo(), 
                      u.getHost().toLowerCase(), u.getPort(), path1, 
                      u.getQuery(), null);
System.out.println(norm1);
// prints "http://blah.org/A_(Secret).xml"

//NORMALISE METHOD 2
URI norm2 = new URI(u.getScheme().toLowerCase(), u.getUserInfo(),
                      u.getHost().toLowerCase(), u.getPort(), path2, 
                      u.getQuery(), null);
System.out.println(norm2);
// prints "http://blah.org/A_%2528Secret%2529.xml"

Как мы видим, URI анализируется и перестраивается без идентификатора фрагмента.

Однако для метода 1 u.getPath() возвращает незашифрованный URI, который изменяет окончательный URI.

Для метода 2 u.getRawPath() возвращает исходный путь, но при передаче конструктору URI Java решает добавить двойное кодирование.

Это похоже на ловушку китайского пальца.

Итак, два основных вопроса:

  • Почему java.util.URI чувствует необходимость играть с кодировкой?
  • Как этот метод нормализации может быть реализован без использования оригинальной процентной кодировки?

(Я бы предпочел не реализовывать методы разбора / конкатенации java.util.URI, которые нетривиальны.)


РЕДАКТИРОВАТЬ: Вот некоторая дополнительная информация от URI Javadoc .

  • Конструктор с одним аргументом требует, чтобы все недопустимые символы в его аргументе были заключены в кавычки, а сохраняет все экранированные октеты и другие присутствующие символы.

  • Многобаргетные конструкторы заключают в кавычки недопустимые символы, как того требуют компоненты, в которых они появляются. Символ процента ('%') всегда указывается этими конструкторами. Любые другие символы сохраняются.

  • Методы getRawUserInfo, getRawPath , getRawQuery, getRawFragment, getRawAuthority и getRawSchemeSpecificPart возвращают значения соответствующих компонентов в необработанном виде без интерпретации любых экранированных октетов . Строки, возвращаемые этими методами, могут содержать как экранированные октеты, так и другие символы и не содержать недопустимых символов.

  • Методы getUserInfo, getPath , getQuery, getFragment, getAuthority и getSchemeSpecificPart декодируют любые экранированные октеты в соответствующих компонентах. Строки, возвращаемые этими методами, могут содержать как другие символы, так и недопустимые символы и не должны содержать никаких экранированных октетов.

  • Метод toString возвращает строку URI со всеми необходимыми кавычками, но которая может содержать другие символы.

  • Метод toASCIIString возвращает полностью заключенную в кавычки и закодированную строку URI, которая не содержит никаких других символов.

Таким образом, я не могу использовать конструктор с несколькими аргументами, если внутренняя кодировка класса URI не запутана в кодировке URL. Тьфу!

Ответы [ 2 ]

10 голосов
/ 03 марта 2012

Поскольку java.net.URI введен в Java 1.4 (который выходит в 2002 году) и основан на RFC2396, который обрабатывает '(' и ')' как символы, которым не требуется escape и семантика не меняется, даже если он сбежал, более того, он даже говорит, что не следует избегать его, если в этом нет необходимости (§2.3, RFC2396).

Но RFC3986 (выпущенный в 2005 году) изменил это, и я думаю, разработчики JDK решили не изменять поведение java.net.URI для совместимости существующего кода.

Случайным поиском в Интернете я обнаружил, что Jena IRI выглядит хорошо.

public class IRITest {
public static void main(String[] args) {
    IRIFactory factory = IRIFactory.uriImplementation();
    IRI iri = factory.construct("http://blah.org/A_%28Secret%29.xml#blah");
    ArrayList<String> a = new ArrayList<String>();
    a.add(iri.getScheme());
    a.add(iri.getRawUserinfo());
    a.add(iri.getRawHost());
    a.add(iri.getRawPath());
    a.add(iri.getRawQuery());
    a.add(iri.getRawFragment());
    IRI iri2 = factory.construct("http://blah.org/A_(Secret).xml#blah");
    ArrayList<String> b = new ArrayList<String>();
    b.add(iri2.getScheme());
    b.add(iri2.getRawUserinfo());
    b.add(iri2.getRawHost());
    b.add(iri2.getRawPath());
    b.add(iri2.getRawQuery());
    b.add(iri2.getRawFragment());

    System.out.println(a);
    //[http, null, blah.org, /A_%28Secret%29.xml, null, blah]
    System.out.println(b);
    //[http, null, blah.org, /A_(Secret).xml, null, blah]
}
}
4 голосов
/ 23 февраля 2012

Обратите внимание на этот отрывок в конце [§2.2; RFC3968]

Приложения, производящие URI, должны кодировать октеты данных в процентах, которые соответствуют символам в зарезервированном наборе, если только эти символы разрешены схемой URI для представления данных в составная часть. Если зарезервированный символ найден в компоненте URI и для этого персонажа роль разграничения не известна, тогда она должна быть интерпретируется как представляющий октет данных, соответствующий этому кодировка символов в US-ASCII.

Таким образом, если для схемы задано http или https, кодирование будет правильным.

Попробуйте использовать метод toASCIIString вместо toString для печати URI. E.g.:

System.put.println(norm1.toASCIIString());
...