Подтвердить что ты не робот

Как определить, содержит ли строка строку с неверными кодированными символами

Сценарий использования

Мы реализовали веб-сервис, который наши разработчики веб-интерфейса используют (через 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

4b9b3361

Ответ 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 по умолчанию, как всегда, в любом месте, где вы можете коснуться. (База данных, память и пользовательский интерфейс)

Одно и одно кодирование кодировки могут уменьшить множество проблем, и на самом деле это может ускорить работу вашего веб-сервера. Существует так много вычислительной мощности и памяти, затраченных на кодирование/декодирование.