Как определить, содержит ли строка строку с неверными кодированными символами
Сценарий использования
Мы реализовали веб-сервис, который наши разработчики веб-интерфейса используют (через php api) для отображения данных продукта. На веб-сайте пользователь вводит что-то (т.е. Строку запроса). Внутри веб-сайт совершает звонок к службе через api.
Примечание. Мы используем restlet, а не tomcat
Исходная проблема
Firefox 3.0.10, похоже, уважает выбранную кодировку в браузере и кодирует URL-адрес в соответствии с выбранной кодировкой. Это приводит к различным строкам запроса для ISO-8859-1 и UTF-8.
Наш веб-сайт перенаправляет входные данные от пользователя и не конвертирует его (что ему нужно), поэтому он может позвонить в службу через api, вызывая веб-сервис, используя строку запроса, содержащую немецкие умлауты.
т.е. для части запроса, похожей на
...v=abcädef
если выбрано "ISO-8859-1", часть отправленного запроса выглядит как
...v=abc%E4def
но если выбрано "UTF-8", часть отправленного запроса выглядит как
...v=abc%C3%A4def
Желаемое решение
Поскольку мы управляем службой, потому что мы ее внедрили, мы хотим проверить серверную сторону, чтобы вызов содержал символы не utf-8, если это так, ответьте с помощью http:/p >
Текущее решение в деталях
Проверить для каждого символа (== string.substring(i, я + 1))
- если character.getBytes() [0] равно 63 для '?'
- если Character.getType(character.charAt(0)) возвращает OTHER_SYMBOL
код
protected List< String > getNonUnicodeCharacters( String s ) {
final List< String > result = new ArrayList< String >();
for ( int i = 0 , n = s.length() ; i < n ; i++ ) {
final String character = s.substring( i , i + 1 );
final boolean isOtherSymbol =
( int ) Character.OTHER_SYMBOL
== Character.getType( character.charAt( 0 ) );
final boolean isNonUnicode = isOtherSymbol
&& character.getBytes()[ 0 ] == ( byte ) 63;
if ( isNonUnicode )
result.add( character );
}
return result;
}
Вопрос
Будет ли это улавливать все недопустимые символы (не utf-закодированные)?
У любого из вас есть лучшее (более простое) решение?
Примечание. Я проверил URLDecoder со следующим кодом
final String[] test = new String[]{
"v=abc%E4def",
"v=abc%C3%A4def"
};
for ( int i = 0 , n = test.length ; i < n ; i++ ) {
System.out.println( java.net.URLDecoder.decode(test[i],"UTF-8") );
System.out.println( java.net.URLDecoder.decode(test[i],"ISO-8859-1") );
}
Отпечатки:
v=abc?def
v=abcädef
v=abcädef
v=abcädef
и он не выдает смещение IllegalArgumentException
Ответы
Ответ 1
Я задал тот же вопрос,
Обработка кодировки символов в URI на Tomcat
Недавно я нашел решение, и он работает очень хорошо для меня. Возможно, вы захотите попробовать. Вот что вам нужно сделать,
- Оставьте свою кодировку URI как Latin-1. В Tomcat добавьте URIEncoding = "ISO-8859-1" в Connector в server.xml.
- Если вам нужно вручную декодировать URL, используйте Latin1 как charset.
- Используйте функцию fixEncoding() для исправления кодировок.
Например, чтобы получить параметр из строки запроса,
String name = fixEncoding(request.getParameter("name"));
Вы можете делать это всегда. Строка с правильным кодированием не изменяется.
Код прилагается. Удачи!
public static String fixEncoding(String latin1) {
try {
byte[] bytes = latin1.getBytes("ISO-8859-1");
if (!validUTF8(bytes))
return latin1;
return new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Impossible, throw unchecked
throw new IllegalStateException("No Latin1 or UTF-8: " + e.getMessage());
}
}
public static boolean validUTF8(byte[] input) {
int i = 0;
// Check for BOM
if (input.length >= 3 && (input[0] & 0xFF) == 0xEF
&& (input[1] & 0xFF) == 0xBB & (input[2] & 0xFF) == 0xBF) {
i = 3;
}
int end;
for (int j = input.length; i < j; ++i) {
int octet = input[i];
if ((octet & 0x80) == 0) {
continue; // ASCII
}
// Check for UTF-8 leading byte
if ((octet & 0xE0) == 0xC0) {
end = i + 1;
} else if ((octet & 0xF0) == 0xE0) {
end = i + 2;
} else if ((octet & 0xF8) == 0xF0) {
end = i + 3;
} else {
// Java only supports BMP so 3 is max
return false;
}
while (i < end) {
i++;
octet = input[i];
if ((octet & 0xC0) != 0x80) {
// Not a valid trailing byte
return false;
}
}
}
return true;
}
EDIT: Ваш подход не работает по разным причинам. Когда есть ошибки в кодировке, вы не можете рассчитывать на то, что получаете от Tomcat. Иногда вы получаете или?. В других случаях вы ничего не получите, getParameter() возвращает null. Скажем, вы можете проверить "?", Что происходит в строке запроса содержит действительные "?"
Кроме того, вы не должны отклонять запрос. Это не ваша ошибка пользователя. Как я упоминал в своем исходном вопросе, браузер может кодировать URL-адрес в UTF-8 или Latin-1. Пользователь не имеет никакого контроля. Вы должны принять оба. Изменение сервлета на латинский-1 сохранит все символы, даже если они ошибаются, чтобы дать нам возможность исправить это или выбросить.
Решение, которое я разместил здесь, не является совершенным, но оно лучшее, что мы нашли до сих пор.
Ответ 2
Вы можете использовать CharsetDecoder, настроенный для исключения исключения, если найдены недопустимые символы:
CharsetDecoder UTF8Decoder =
Charset.forName("UTF8").newDecoder().onMalformedInput(CodingErrorAction.REPORT);
См. CodingErrorAction.REPORT
Ответ 3
Заменить все контрольные символы на пустую строку
value = value.replaceAll("\\p{Cntrl}", "");
Ответ 4
Это то, что я использовал для проверки кодировки:
CharsetDecoder ebcdicDecoder = Charset.forName("IBM1047").newDecoder();
ebcdicDecoder.onMalformedInput(CodingErrorAction.REPORT);
ebcdicDecoder.onUnmappableCharacter(CodingErrorAction.REPORT);
CharBuffer out = CharBuffer.wrap(new char[3200]);
CoderResult result = ebcdicDecoder.decode(ByteBuffer.wrap(bytes), out, true);
if (result.isError() || result.isOverflow() ||
result.isUnderflow() || result.isMalformed() ||
result.isUnmappable())
{
System.out.println("Cannot decode EBCDIC");
}
else
{
CoderResult result = ebcdicDecoder.flush(out);
if (result.isOverflow())
System.out.println("Cannot decode EBCDIC");
if (result.isUnderflow())
System.out.println("Ebcdic decoded succefully ");
}
Изменить: обновлено с предложением Vouze
Ответ 5
URLDecoder будет декодировать данную кодировку. Это должно правильно указывать ошибки. Однако в документации указано:
Есть два возможных способа, которыми этот декодер может иметь дело с незаконными строками. Он может либо оставить незаконные символы в одиночку, либо он может вызвать исключение IllegalArgumentException. Какой подход требует декодер, остается реализовать.
Итак, вы должны попробовать. Обратите внимание также (из документации метода decode()):
Рекомендация Консорциума World Wide Web утверждает, что UTF-8 следует использовать. Не делать этого может привести к несовместимости
так что еще о чем подумать!
EDIT: Apache Commons URLDecode утверждает, что выбрасывает соответствующие исключения для неправильных кодировок.
Ответ 6
Я работаю над аналогичной проблемой "угадай кодировку". Лучшее решение подразумевает знание кодировки. Если вы не согласны с этим, вы можете сделать обоснованные догадки, чтобы различать UTF-8 и ISO-8859-1.
Чтобы ответить на общий вопрос о том, как определить правильную кодировку строки UTF-8, вы можете проверить следующие вещи:
- Нет байта 0x00, 0xC0, 0xC1 или в диапазоне 0xF5-0xFF.
- Хвост байтам (0x80-0xBF) всегда предшествует старший байт 0xC2-0xF4 или другой хвостовой байт.
- Головные байты должны правильно предсказать количество хвостовых байтов (например, в каждом байте в 0xC2-0xDF должен следовать ровно один байт в диапазоне 0x80-0xBF).
Если строка передает все те тесты, то она интерпретируется как действительная UTF-8. Это не гарантирует, что это UTF-8, но это хороший предиктор.
Правовой ввод в ISO-8859-1, скорее всего, не будет содержать управляющих символов (0x00-0x1F и 0x80-0x9F), кроме разделителей строк. Похож, 0x7F также не определен в ISO-8859-1.
(Я основываю это на страницах Википедии для UTF-8 и ISO-8859-1.)
Ответ 7
Возможно, вы захотите включить известный параметр в свои запросы, например. "... & encTest = ä €", чтобы безопасно различать различные кодировки.
Ответ 8
Вам нужно настроить кодировку символов с самого начала. Попробуйте отправить соответствующий заголовок Content-Type, например Content-Type: text/html; charset = utf-8, чтобы исправить правильную кодировку. Стандартное соответствие относится к utf-8 и utf-16 как правильное кодирование для веб-служб. Изучите заголовки ответов.
Кроме того, на стороне сервера — в случае, когда браузер неправильно обрабатывает кодировку, отправленную сервером; принудительное кодирование путем выделения новой строки. Также вы можете проверить каждый байт в кодированной строке utf-8, выполнив одиночный each_byte и 0x80, проверив результат как ненулевой.
boolean utfEncoded = true;
byte[] strBytes = queryString.getBytes();
for (int i = 0; i < strBytes.length(); i++) {
if ((strBytes[i] & 0x80) != 0) {
continue;
} else {
/* treat the string as non utf encoded */
utfEncoded = false;
break;
}
}
String realQueryString = utfEncoded ?
queryString : new String(queryString.getBytes(), "iso-8859-1");
Кроме того, возьмите в этой статье, я надеюсь, что это вам поможет.
Ответ 9
для вас может быть интересно следующее регулярное выражение:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/185624
Я использую его в рубине следующим образом:
module Encoding
UTF8RGX = /\A(
[\x09\x0A\x0D\x20-\x7E] # ASCII
| [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
| \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
| \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
| \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
| [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
| \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)*\z/x unless defined? UTF8RGX
def self.utf8_file?(fileName)
count = 0
File.open("#{fileName}").each do |l|
count += 1
unless utf8_string?(l)
puts count.to_s + ": " + l
end
end
return true
end
def self.utf8_string?(a_string)
UTF8RGX === a_string
end
end
Ответ 10
Попробуйте использовать UTF-8 по умолчанию, как всегда, в любом месте, где вы можете коснуться. (База данных, память и пользовательский интерфейс)
Одно и одно кодирование кодировки могут уменьшить множество проблем, и на самом деле это может ускорить работу вашего веб-сервера. Существует так много вычислительной мощности и памяти, затраченных на кодирование/декодирование.