Эскейп URI Java, который работает как unescape Javascript

У меня есть строка типа http://google.com/search/q=<%= name %>.

Сторонняя js-библиотека, которую я не могу контролировать, убегает от этого к "http://google.com/search/q=%3C%=%20name%20%%3E"

который Javascript может успешно преобразовать в исходную строку с помощью

unescape("http://google.com/search/q=%3C%=%20name%20%%3E")

Но URLDecode.decode("http://google.com/search/q=%3C%=%20name%20%%3E") в Java выдает IllegalArgumentException из-за неэкранированного литерала % в строке, что, конечно, правильно и соответствует спецификации, но это усложняет обработку на стороне сервера.

Прежде чем я попытаюсь исправить плохой JS-побег на стороне сервера с помощью регулярных выражений (поскольку, как уже упоминалось, я не могу изменить сторону JS), я хотел бы знать, существует ли более разрешительный API декодирования URL/URI Java, который бы работать так же, как unescape в Javascript, то есть игнорировать отдельные символы «%» и декодировать только то, что можно декодировать.


person ccpizza    schedule 06.11.2017    source источник


Ответы (1)


Я быстро просмотрел некоторые библиотеки Apache и столкнулся с той же проблемой. Интересно, что когда я изучал спецификацию языка EMCAScript, я нашел псевдокод для функции unescape(). Вы можете увидеть это на https://tc39.github.io/ecma262/#sec-unescape-string

Достаточно легко собрать упрощенную реализацию этого (см. Ниже), и, по крайней мере, для примера в вашем вопросе выходные данные совпадают.

Теперь этот код никоим образом не оптимизирован, и я не думал о том, имеет ли значение кодировка символов, но это может быть менее болезненным путем, чем пытаться бороться с Regex.

public static String unescape(String s) {
    StringBuilder r = new StringBuilder();
    for (int i = 0; i < s.length();) {
        if (s.charAt(i) == '%') {
            if (looksLikeUnicode(s, i)) {
                r.append((char) fromHex(s, i + 2, i + 5));
                i += 6;
                continue;
            }
            if (looksLikeAscii(s, i)) {
                r.append((char) fromHex(s, i + 1, i + 2));
                i += 3;
                continue;
            }
        }
        r.append(s.charAt(i));
        i += 1;
    }
    return r.toString();
}

private static boolean looksLikeUnicode(String s, int i) {
    return (i + 5 < s.length()) && (s.charAt(i + 1) == 'u') && areHexDigits(s, i + 2, i + 5);
}

private static boolean looksLikeAscii(String s, int i) {
    return (i + 2 < s.length()) && areHexDigits(s, i + 1, i + 2);
}

private static boolean areHexDigits(String s, int from, int to) {
    for (int i = from; i <= to; ++i) {
        if (isNotHexDigit(s.charAt(i))) {
            return false;
        }
    }
    return true;
}

private static boolean isHexDigit(char c) {
    return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}

private static boolean isNotHexDigit(char c) {
    return !isHexDigit(c);
}

private static int fromHex(String s, int from, int to) {
    return Integer.parseInt(s.substring(from, to + 1), 16);
}
person Evan Jones    schedule 07.11.2017
comment
Великолепно! Большое спасибо! Как только вы упомянули ECMAScript, я понял, что на самом деле собственный ScriptingEngine для Java можно использовать для экранирования в стиле JS, то есть new ScriptEngineManager().getEngineByName("nashorn").eval("unescape(\"%3C%=%20name%20%%3E\")"), но это приводит к ненужным накладным расходам на создание экземпляра механизма сценариев, поэтому я отмечаю это как принятый ответ. - person ccpizza; 07.11.2017
comment
Ницца. Я должен был подумать о скриптовом движке. Это более надежное решение, но да, немного тяжелее. - person Evan Jones; 08.11.2017