Как правильно декодировать параметры Unicode, передаваемые сервлету - PullRequest
35 голосов
/ 22 января 2009

Предположим, у меня есть:

<a href="http://www.yahoo.com/" target="_yahoo" 
    title="Yahoo!&#8482;" onclick="return gateway(this);">Yahoo!</a>
<script type="text/javascript">
function gateway(lnk) {
    window.open(SERVLET +
        '?external_link=' + encodeURIComponent(lnk.href) +
        '&external_target=' + encodeURIComponent(lnk.target) +
        '&external_title=' + encodeURIComponent(lnk.title));
    return false;
}
</script>

Я подтвердил, что external_title кодируется как Yahoo!%E2%84%A2 и передается SERVLET. Если в SERVLET я делаю:

Writer writer = response.getWriter();
writer.write(request.getParameter("external_title"));

Я получаю Yahoo! ™ 1012 * в браузере. Если я вручную переключу кодировку символов браузера на UTF-8, она изменится на Yahoo! TM (что я и хочу).

Итак, я решил, что кодировка, которую я отправлял в браузер, была неправильной (это была Content-type: text/html; charset=ISO-8859-1). Я изменил SERVLET на:

response.setContentType("text/html; charset=utf-8");
Writer writer = response.getWriter();
writer.write(request.getParameter("external_title"));

Теперь кодировка символов браузера UTF-8, но она выдает Yahoo! Â , и я не могу заставить браузер отображать правильный символ вообще.

У меня вопрос: есть ли комбинация Content-type и / или new String(request.getParameter("external_title").getBytes(), "UTF-8"); и / или чего-то еще, что приведет к Yahoo! TM , появившемуся в SERVLET вывод?

Ответы [ 8 ]

44 голосов
/ 22 января 2009

Вы почти у цели. EncodeURIComponent правильно кодирует в UTF-8, что вы всегда должны использовать в URL сегодня.

Проблема в том, что отправленная строка запроса искажается на пути к вашему серверному скрипту, потому что getParameter () использует ISO-8559-1 вместо UTF-8. Это связано с «Древними временами», еще до того, как Интернет остановился на UTF-8 для URI / IRI, но довольно прискорбно, что спецификация сервлета не была обновлена ​​для соответствия реальности или, по крайней мере, не предоставила для нее надежную, поддерживаемую опцию.

(В Servlet 2.3 есть request.setCharacterEncoding, но он не влияет на синтаксический анализ строки запроса, и если ранее был прочитан один параметр, возможно, с помощью какого-либо другого элемента инфраструктуры, он вообще не будет работать.)

Таким образом, вам нужно использовать специфичные для контейнера методы, чтобы получить правильный UTF-8, часто включающий материал в server.xml. Это совершенно отстой для распространения веб-приложений, которые должны работать где угодно. Для Tomcat см. http://wiki.apache.org/tomcat/FAQ/CharacterEncoding, а также В чем разница между "URIEncoding" Tomcat, Encoding Filter и request.setCharacterEncoding .

18 голосов
/ 31 марта 2010

Я получил ту же проблему и решил ее, расшифровав Request.getQueryString() с помощью URLDecoder () и после извлечения моих параметров.

String[] Parameters = URLDecoder.decode(Request.getQueryString(), 'UTF-8')
                       .splitat('&');
16 голосов
/ 07 октября 2012

Есть способ сделать это в Java (без возни с server.xml)

Не работают:

protected static final String CHARSET_FOR_URL_ENCODING = "UTF-8";

String uname = request.getParameter("name");
System.out.println(uname);
// ÏηγÏÏÏÏη
uname = request.getQueryString();
System.out.println(uname);
// name=%CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7
uname = URLDecoder.decode(request.getParameter("name"),
        CHARSET_FOR_URL_ENCODING);
System.out.println(uname);
// ÏηγÏÏÏÏη // !!!!!!!!!!!!!!!!!!!!!!!!!!!
uname = URLDecoder.decode(
        "name=%CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7",
        CHARSET_FOR_URL_ENCODING);
System.out.println("query string decoded : " + uname);
// query string decoded : name=τηγρτσςη
uname = URLDecoder.decode(new String(request.getParameter("name")
        .getBytes()), CHARSET_FOR_URL_ENCODING);
System.out.println(uname);
// ÏηγÏÏÏÏη // !!!!!!!!!!!!!!!!!!!!!!!!!!!

Работы :

final String name = URLDecoder
        .decode(new String(request.getParameter("name").getBytes(
                "iso-8859-1")), CHARSET_FOR_URL_ENCODING);
System.out.println(name);
// τηγρτσςη

Сработало, но сломается, если кодировка по умолчанию! = Utf-8 - попробуйте это вместо этого (пропустите вызов decode (), в котором нет необходимости):

final String name = new String(request.getParameter("name").getBytes("iso-8859-1"),
        CHARSET_FOR_URL_ENCODING);

Как я сказал выше, если server.xml испорчен как в:

<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1"
                     redirectPort="8443"  URIEncoding="UTF-8"/> 

(обратите внимание на URIEncoding="UTF-8") приведенный выше код будет нарушен (потому что getBytes("iso-8859-1") должно читаться как getBytes("UTF-8")). Так что для пуленепробиваемого решения вы должны получить значение атрибута URIEncoding. К сожалению, это, похоже, зависит от контейнера - еще хуже для конкретной версии контейнера. Для Tomcat 7 вам понадобится что-то вроде:

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;

import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.connector.Connector;

public class Controller extends HttpServlet {

    // ...
    static String CHARSET_FOR_URI_ENCODING; // the `URIEncoding` attribute
    static {
        MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(
            0);
        ObjectName name = null;
        try {
            name = new ObjectName("Catalina", "type", "Server");
        } catch (MalformedObjectNameException e1) {
            e1.printStackTrace();
        }
        Server server = null;
        try {
            server = (Server) mBeanServer.getAttribute(name, "managedResource");
        } catch (AttributeNotFoundException | InstanceNotFoundException
                | MBeanException | ReflectionException e) {
            e.printStackTrace();
        }
        Service[] services = server.findServices();
        for (Service service : services) {
            for (Connector connector : service.findConnectors()) {
                System.out.println(connector);
                String uriEncoding = connector.getURIEncoding();
                System.out.println("URIEncoding : " + uriEncoding);
                boolean use = connector.getUseBodyEncodingForURI();
                // TODO : if(use && connector.get uri enc...)
                CHARSET_FOR_URI_ENCODING = uriEncoding;
                // ProtocolHandler protocolHandler = connector
                // .getProtocolHandler();
                // if (protocolHandler instanceof Http11Protocol
                // || protocolHandler instanceof Http11AprProtocol
                // || protocolHandler instanceof Http11NioProtocol) {
                // int serverPort = connector.getPort();
                // System.out.println("HTTP Port: " + connector.getPort());
                // }
            }
        }
    }
}

И все же вам нужно настроить это для нескольких разъемов (проверьте закомментированные части). Тогда вы бы использовали что-то вроде:

new String(parameter.getBytes(CHARSET_FOR_URI_ENCODING), CHARSET_FOR_URL_ENCODING);

Тем не менее это может завершиться ошибкой ( IIUC ), если parameter = request.getParameter("name");, декодированный с помощью CHARSET_FOR_URI_ENCODING, был поврежден, поэтому байты, которые я получаю с помощью getBytes (), не были оригинальными (вот почему iso-8859-1) используется по умолчанию - , это сохранит байты ). Вы можете избавиться от всего этого, вручную проанализировав строку запроса в строках:

URLDecoder.decode(request.getQueryString().split("=")[1],
        CHARSET_FOR_URL_ENCODING);

Я все еще ищу место в документации, где упоминается, что request.getParameter("name") вызывает URLDecoder.decode() вместо возврата строки %CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7? Ссылка в источнике будет высоко ценится.
Также, как я могу передать в качестве значения параметра строку, скажем, %CE? => см. Комментарий: parameter=%25CE

2 голосов
/ 22 января 2009

Я подозреваю, что искажение данных происходит в запросе, то есть заявленная кодировка запроса не соответствует той, которая фактически используется для данных.

Что возвращает request.getCharacterEncoding()?

Я действительно не знаю, как JavaScript обрабатывает кодировки или как заставить его использовать конкретную.

Вам необходимо убедиться, что кодировки используются правильно на всех этапах - НЕ пытайтесь «исправить» данные, используя new String() и getBytes() в точке, где они уже были неправильно закодированы.

Редактировать: Может быть полезно иметь страницу происхождения (страницу с Javascript), также закодированную в UTF-8 и объявленную как таковую в ее Content-Type. Тогда я считаю, что Javascript может по умолчанию использовать UTF-8 для своего запроса - но это не определенное знание, просто догадки.

0 голосов
/ 15 мая 2019

Спасибо за все, что я узнал о декодировании кодировки набора символов по умолчанию, который используется в tomcat, jetty Я использую этот метод для решения своих проблем с помощью Google Guava

        String str = URLDecoder.decode(request.getQueryString(), StandardCharsets.UTF_8.name());
        final Map<String, String> map = Splitter.on('&').trimResults().withKeyValueSeparator("=").split(str);
        System.out.println(map);
        System.out.println(map.get("aung"));
        System.out.println(map.get("aa"));
0 голосов
/ 18 ноября 2015

В некоторых версиях Jetty существует ошибка, из-за которой неправильно обрабатывается большее количество символов UTF-8. Если ваш сервер принимает арабские буквы правильно, но не смайлики, это признак того, что у вас есть версия с этой проблемой, поскольку арабский язык не соответствует ISO-8859-1, но находится в нижнем диапазоне символов UTF-8 («нижний» означает java будет представлять его в одном символе).

Я обновил версию 7.2.0.v20101020 до версии 7.5.4.v20111024, и это решило проблему; Теперь я могу использовать метод getParameter (String) вместо того, чтобы анализировать его сам.

Если вам действительно любопытно, вы можете покопаться в своей версии org.eclipse.jetty.util.Utf8StringBuilder.append (byte) и посмотреть, правильно ли он добавляет несколько символов в строку, когда код utf-8 высок достаточно или если, как в 7.2.0, он просто приводит int к типу char и добавляет.

0 голосов
/ 22 января 2009

Я думаю, что смогу заставить работать следующее:

encodeURIComponent(escape(lnk.title))

Это дает мне %25u2122 (для & # 8482) или %25AE (для & # 174), что в сервлете расшифрует до %u2122 и %AE соответственно.

После этого я смогу относительно легко преобразовать% u2122 в '\u2122' и% AE в '\u00AE', используя (char) (base-10 integer value of %uXXXX or %XX) в совпадении, и заменить цикл с помощью регулярных выражений.

т.е. - сопоставить /%u([0-9a-f]{4})/i, извлечь соответствующее подвыражение, преобразовать его в base-10, превратить в символ и добавить к выходу, затем сделать то же самое с /%([0-9a-f]{2})/i

0 голосов
/ 22 января 2009

Вы всегда можете использовать JavaScript для дальнейшей работы с текстом.

<div id="test">a</div>
<script>
var a = document.getElementById('test');
alert(a.innerHTML);
a.innerHTML = decodeURI("Yahoo!%E2%84%A2");
alert(a.innerHTML);
</script>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...