PHP: Преобразуйте любую строку в UTF-8, не зная исходного набора символов, или, по крайней мере, попробуйте


У меня есть приложение, которое работает с клиентами со всего мира, и, естественно, я хочу, чтобы все, поступающее в мои базы данных, было закодировано в кодировке UTF-8.

Основная проблема для меня заключается в том, что я не знаю, какая кодировка будет источником какой-либо строки - это может быть из текстового поля (использование <form accept-charset="utf-8"> полезно только в том случае, если пользователь действительно отправил форму), или это может быть из загруженного текстового файла, поэтому я действительно не могу контролировать ввод.

Что мне нужно, так это функция или класс, который гарантирует, что информация, поступающая в мою базу данных, насколько это возможно, закодирована в формате UTF-8. Я пробовал iconv(mb_detect_encoding($text), "UTF-8", $text); , но у этого есть проблемы (если ввод "невеста", он возвращает "невеста"). Я много чего перепробовал =/

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

Я прочитайте другие вопросы SO по этой теме, но, похоже, у всех них есть тонкие различия, такие как "Мне нужно анализировать RSS-каналы" или "Я очищаю данные с веб-сайтов" (или, действительно, "Вы не можете").

Но должно быть что-то, что, по крайней мере, имеет хорошую попытку!

Author: Peter O., 2011-11-02

10 answers

То, о чем вы просите, чрезвычайно сложно. Если возможно, лучше всего попросить пользователя указать кодировку. Предотвращение атаки не должно быть намного проще или сложнее таким образом.

Однако вы можете попробовать сделать это:

iconv(mb_detect_encoding($text, mb_detect_order(), true), "UTF-8", $text);

Установка значения strict может помочь вам получить лучший результат.

 217
Author: Jeff Day, 2011-11-02 12:32:49

На родине, в России, у нас есть 4 популярных кодировки, поэтому ваш вопрос здесь очень востребован.

Только по кодам символов символов вы не можете определить кодировку, потому что кодовые страницы пересекаются. Некоторые кодовые страницы на разных языках имеют даже полное пересечение. Итак, нам нужен другой подход.

Единственный способ работать с неизвестными кодировками - это работать с вероятностями. Итак, мы не хотим отвечать на вопрос "что такое кодировка этого текста?", мы пытаемся понять" какова наиболее вероятная кодировка этого текста?".

Один парень здесь, в популярном российском технологическом блоге, изобрел этот подход:

Постройте диапазон вероятностей кодов символов в каждой кодировке, которую вы хотите поддерживать. Вы можете создать его, используя некоторые большие тексты на вашем языке (например, некоторые художественные произведения, используйте Шекспира для английского и Толстого для русского, lol). Вы получите что-то вроде этого:

    encoding_1:
    190 => 0.095249209893009,
    222 => 0.095249209893009,
    ...
    encoding_2:
    239 => 0.095249209893009,
    207 => 0.095249209893009,
    ...
    encoding_N:
    charcode => probabilty

Далее. Вы берете текст в неизвестной кодировке и для каждой кодировки в вашем "вероятностный словарь" вы ищете частоту каждого символа в тексте с неизвестной кодировкой. Суммируйте вероятности символов. Кодировка с большим рейтингом, скорее всего, станет победителем. Лучшие результаты для больших текстов.

Если вам интересно, я с радостью помогу вам с этой задачей. Мы можем значительно повысить точность, построив список вероятностей с двумя кодами.

Кстати. mb_detect_encoding определенно не работает. Да, вообще. Пожалуйста, взгляните на исходный код mb_detect_encoding в "ext/mbstring/libmbfl/mbfl/mbfl_ident.c".

 27
Author: Oroboros102, 2013-07-10 09:19:02

Вы, вероятно, пробовали это, но почему бы просто не использовать функцию mb_convert_encoding? Он попытается автоматически определить набор символов предоставленного текста или вы можете передать ему список.

Кроме того, я попытался запустить:

$text = "fiancée";
echo mb_convert_encoding($text, "UTF-8");
echo "<br/><br/>";
echo iconv(mb_detect_encoding($text), "UTF-8", $text);

И результаты одинаковы для обоих. Как вы видите, что ваш текст сокращен до "жених"? это в базе данных или в браузере?

 8
Author: Alexey Gerasimov, 2011-11-14 18:52:53

Нет способа определить кодировку строки, которая была бы полностью точной. Есть способы попытаться угадать кодировку. Одним из таких способов, и, вероятно,/в настоящее время лучшим в PHP, является mb_detect_encoding(). Это будет сканировать вашу строку и искать вхождения элементов, уникальных для определенных кодировок. В зависимости от вашей строки таких различимых вхождений может и не быть.

Возьмите кодировку ISO-8859-1 против ISO-8859-15( http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1 )

Существует всего несколько разных символов, и, что еще хуже, они представлены одними и теми же байтами. Невозможно определить, если вам дана строка, не зная ее кодировки, должен ли байт 0xA4 обозначать ¤ или € в вашей строке, поэтому нет способа узнать, что это точная кодировка.

(Примечание: вы могли бы добавить человеческий фактор или еще более продвинутый метод сканирования (например, что Oroboros102 предлагает), чтобы попытаться выяснить, основываясь на окружающем контексте, должен ли персонаж быть ¤ или €, хотя это кажется слишком далеким мостом)

Существуют более заметные различия, например, между UTF-8 и ISO-8859-1, поэтому все равно стоит попытаться выяснить это, когда вы не уверены, хотя вы можете и никогда не должны полагаться на то, что это правильно.

Интересное чтение: http://kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-string

Однако существуют и другие способы обеспечения правильной кодировки. Что касается форм, постарайтесь как можно больше применять UTF-8 (проверьте snowman, чтобы убедиться, что ваша отправка будет UTF-8 в каждом браузере: http://intertwingly.net/blog/2010/07/29/Rails-and-Snowmen ) После этого, по крайней мере, вы можете быть уверены, что каждый текст, отправленный через ваши формы, utf_8. Что касается загруженных файлов, попробуйте запустить на нем команду unix "файл -i", например, через exec() (если возможно на вашем сервере), чтобы помочь обнаружению (используя спецификацию документа.) Что касается очистки данных, вы можете прочитать заголовки HTTP, в которых обычно указывается кодировка. При анализе XML-файлов проверьте, содержат ли метаданные XML определение кодировки.

Вместо того, чтобы пытаться автоматически угадать кодировку, вы должны сначала попытаться обеспечить определенную кодировку самостоятельно, где это возможно, или попытаться чтобы получить определение из источника, из которого вы его получаете (если применимо), прежде чем прибегать к обнаружению.

 5
Author: matthiasmullie, 2011-11-20 17:15:43

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

Я не думаю, что это проблема. Приложение знает источник входных данных. Если это из формы, используйте кодировку UTF-8 в вашем случае. Это работает. Просто убедитесь, что предоставленные данные являются правильно закодированный (проверка). Имейте в виду, что не все базы данных поддерживают UTF-8 в полном диапазоне.

Если это файл, вы сохраните его не в кодировке UTF-8 в базе данных, а в двоичном виде. Когда вы снова выводите файл, также используйте двоичный вывод, тогда это полностью прозрачно.

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

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

 2
Author: hakre, 2011-11-20 19:00:58

Вы можете настроить набор показателей, чтобы попытаться угадать, какая кодировка используется. Опять же, не идеально, но может уловить некоторые промахи в mb_detect_encoding().

 1
Author: Parris Varney, 2011-11-14 15:29:01

Если вы готовы "перенести это на консоль", я бы рекомендовал enca. В отличие от довольно упрощенного mb_detect_encoding, он использует "смесь синтаксического анализа, статистического анализа, угадывания и черной магии для определения их кодировок" (lol - см. справочную страницу ). Однако обычно вам приходится передавать язык входного файла, если вы хотите обнаружить такие кодировки для конкретной страны. (Однако, mb_detect_encoding по существу имеет то же требование, что и кодировка, которая должна была бы отображаться "в нужном месте" в список переданных кодировок, чтобы его вообще можно было обнаружить.)

enca также подошел сюда: Как найти кодировку файла в Unix с помощью скрипта(ов)

 1
Author: wutz, 2017-05-23 12:03:08

Здесь есть несколько действительно хороших ответов и попыток ответить на ваш вопрос. Я не мастер кодирования, но я понимаю ваше желание иметь чистыйстек UTF-8 вплоть до вашей базы данных. Я использовал кодировку MySQL utf8mb4 для таблиц, полей и соединений.

Моя ситуация сводилась к следующему: "Я просто хочу, чтобы мои дезинфицирующие средства, валидаторы, бизнес-логика и подготовленные инструкции работали с UTF-8, когда данные поступают из HTML-форм или электронной почты ссылки для регистрации". Итак, по-своему просто, я начал с этой идеи:

  1. Попытка определить кодировку: $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
  2. Если кодировка не может быть обнаружена, throw new RuntimeException
  3. Если ввод UTF-8, продолжайте.
  4. В противном случае, если это ISO-8859-1 или ASCII

    А. Попытка преобразования в UTF-8 (подождите, не закончено)

    Б. Определите кодировку преобразованного значения

    С. Если сообщенная кодировка и преобразованное значение являются одновременно UTF-8, продолжайте.

    Д. Остальное, throw new RuntimeException

Из моего абстрактного класса Sanitizer

Sanitizer

    private function isUTF8($encoding, $value)
    {
        return (($encoding === 'UTF-8') && (utf8_encode(utf8_decode($value)) === $value));
    }

    private function utf8tify(&$value)
    {
        $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];

        mb_internal_encoding('UTF-8');
        mb_substitute_character(0xfffd); //REPLACEMENT CHARACTER
        mb_detect_order($encodings);

        $stringEncoding = mb_detect_encoding($value, $encodings, true);

        if (!$stringEncoding) {
            $value = null;
            throw new \RuntimeException("Unable to identify character encoding in sanitizer.");
        }

        if ($this->isUTF8($stringEncoding, $value)) {
            return;
        } else {
            $value = mb_convert_encoding($value, 'UTF-8', $stringEncoding);
            $stringEncoding = mb_detect_encoding($value, $encodings, true);

            if ($this->isUTF8($stringEncoding, $value)) {
                return;
            } else {
                $value = null;
                throw new \RuntimeException("Unable to convert character encoding from ISO-8859-1, or ASCII, to UTF-8 in Sanitizer.");
            }
        }

        return;
    }

Можно было бы привести аргумент, что я должен отделить проблемы кодирования от моего абстрактного класса Sanitizer и просто ввести объект Encoder в конкретный дочерний экземпляр Sanitizer. Однако основная проблема моего подхода заключается в том, что без дополнительных знаний я просто отвергаю типы кодирования, которые мне не нужны (и я полагаюсь на функции PHP mb_*). Без дальнейшего изучения я не могу знать, вредит ли это некоторым группам населения или нет (или, если я теряю важную информацию). Итак, мне нужно узнать больше. Я нашел эту статью.

Что абсолютно необходимо знать каждому программисту о кодировках и наборах символов для работы с текстом

Кроме того, что происходит, когда зашифрованные данные добавляются в мои ссылки для регистрации по электронной почте (с использованием OpenSSL или mcrypt)? Может ли это помешать декодированию? А как насчет Windows-1252? Что о последствия для безопасности? Использование utf8_decode() и utf8_encode() в Sanitizer::isUTF8 сомнительно.

Люди указали на недостатки в функциях PHP mb_*. Я никогда не тратил время на изучение iconv, но если это работает лучше, чем функции mb_*, дайте мне знать.

 1
Author: Anthony Rutledge, 2018-02-17 02:26:21
public function convertToUtf8($text) {
    if(!$this->html)
        $this->html = cURL('http://'.$this->url, array('timeout' => 15));

    $html = $this->html;
    preg_match('/<meta.*?charset=(|\")(.*?)("|\")/i', $html, $matches);

    $charset = $matches[2];

    if($charset)
        return mb_convert_encoding($text, 'UTF-8', $charset);
    else
        return $text;
}

Параметры ЗАВИТКА по умолчанию:

curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

Я пробовал что-то вроде этого. Это помогло мне. Если найдено в информации о мета-кодировке, я конвертирую, в противном случае ничего не делаю.

 0
Author: littlealien, 2014-12-04 10:07:01

Кажется, что на ваш вопрос вполне дан ответ, но у меня есть подход, который может упростить ваш случай:

У меня была аналогичная проблема при попытке вернуть строковые данные из mysql, даже при настройке базы данных и php для возврата строк, отформатированных в utf-8. Единственный способ, которым я получил ошибку, - это фактически вернуть их из базы данных.

Наконец, путешествуя по Сети, я нашел действительно простой способ справиться с этим:

Учитывая, что вы можете сохранять все эти типы строковых данных в ваш mysql в разных форматах и параметрах сортировки, вам нужно только сделать, прямо в вашем файле подключения php, установить параметры сортировки в utf-8, вот так:

$connection = new mysqli($server, $user, $pass, $db);
$connection->set_charset("utf8");

Что означает, что сначала вы сохраняете данные в любом формате или параметрах сортировки и конвертируете их только при возврате в ваш php-файл.

Надеюсь, это было полезно!

 0
Author: Quel Pino, 2018-01-11 03:04:53