Как правильно декодировать параметры юникода, переданные в сервлет
Предположим, что у меня есть:
<a href="#" onclick="location.href='http://www.yahoo.com/'; return false;" target="_yahoo"
title="Yahoo!™" 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! â "¢ в браузере. Если я вручную переключу кодировку символов браузера на 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
?
Ответы
Ответ 1
Ты почти там. EncodeURIComponent правильно кодирует UTF-8, что вы всегда должны использовать в URL-адресе сегодня.
Проблема заключается в том, что представленная строка запроса искажается на пути к серверной стороне script, поскольку getParameter() использует ISO-8559-1 вместо UTF-8. Это происходит из Ancient Times до того, как веб-сайт установил UTF-8 для URI/IRI, но довольно жалко, что спецификация Servlet не обновлена в соответствии с реальностью или, по крайней мере, обеспечивает надежный, поддерживаемый вариант для нее.
(В Servlet 2.3 есть request.setCharacterEncoding, но это не влияет на синтаксический анализ строки запроса, и если один параметр был прочитан раньше, возможно, каким-то другим элементом структуры, он вообще не работает.)
Итак, вам нужно работать с определенными контейнерами методами, чтобы получить правильный UTF-8, часто включающий материал в server.xml. Это полностью засасывает распространение веб-приложений, которые должны работать в любом месте. Для Tomcat см. http://wiki.apache.org/tomcat/FAQ/CharacterEncoding, а также В чем разница между "URIEncoding" от Tomcat, Encoding Filter и request.setCharacterEncoding.
Ответ 2
У меня возникла такая же проблема и решена ее путем декодирования Request.getQueryString()
с помощью URLDecoder() и после извлечения моих параметров.
String[] Parameters = URLDecoder.decode(Request.getQueryString(), 'UTF-8')
.splitat('&');
Ответ 3
Есть способ сделать это в 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 - попробуйте это вместо этого (оставьте вызов для декодирования() он не нужен):
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
Ответ 4
Я подозреваю, что искажение данных происходит в запросе, то есть заявленная кодировка запроса не соответствует той, которая фактически используется для данных.
Что возвращает request.getCharacterEncoding()
?
Я действительно не знаю, как JavaScript обрабатывает кодировки или как использовать конкретную.
Вам нужно убедиться, что кодировки используются правильно на всех этапах - НЕ пытайтесь "исправить" данные с помощью new String()
a getBytes()
в том месте, где оно уже было закодировано неправильно.
Изменить: Это может помочь получить исходную страницу (ту, что с Javascript), также закодированную в UTF-8 и объявленную как таковой в своем Content-Type. Тогда я считаю, что Javascript может по умолчанию использовать UTF-8 для своего запроса - но это не определенные знания, просто догадки.
Ответ 5
Вы всегда можете использовать 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>
Ответ 6
Думаю, я могу заставить следующее работать:
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, превратить его в char и добавить его к выходу, а затем сделать то же самое с /%([0-9a-f]{2})/i
Ответ 7
В некоторых версиях Jetty есть ошибка, из-за которой он неправильно анализирует символы UTF-8 большего числа. Если ваш сервер правильно принимает арабские буквы, но не emoji, это знак у вас есть версия с этой проблемой, так как арабский не находится в ISO-8859-1, но находится в нижнем диапазоне символов UTF-8 ( "нижнее" значение java будет представлять его в одном char).
Я обновил версию 7.2.0.v20101020 до версии 7.5.4.v20111024, и это устранило проблему; Теперь я могу использовать метод getParameter (String) вместо того, чтобы самостоятельно его анализировать.
Если вам действительно интересно, вы можете вставить свою версию org.eclipse.jetty.util.Utf8StringBuilder.append(byte) и посмотреть, правильно ли она добавляет несколько символов в строку, когда код utf-8 высок достаточно или если, как и в 7.2.0, он просто переводит int в char и добавляет.
Ответ 8
Спасибо за все, что я узнаю о кодировке декодирования набора символов по умолчанию, который используется в 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"));