PHP: конвертировать любую строку в UTF-8, не зная исходного набора символов, или хотя бы попытаться - PullRequest
137 голосов
/ 02 ноября 2011

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

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

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

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

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

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

Ответы [ 10 ]

235 голосов
/ 02 ноября 2011

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

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

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

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

28 голосов
/ 18 ноября 2011

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

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

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

OneПарень из популярного российского технического блога изобрел такой подход:

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

    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".

10 голосов
/ 14 ноября 2011

Вы, наверное, пытались это сделать, но почему бы просто не использовать функцию 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);

и результаты одинаковы для обоих. Как вы видите, что ваш текст урезан до 'fianc'? это в БД или в браузере?

5 голосов
/ 20 ноября 2011

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

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

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

2 голосов
/ 15 марта 2017

Здесь есть несколько действительно хороших ответов и попыток ответить на ваш вопрос. Я не мастер кодирования, но я понимаю ваше желание иметь стек pure 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_ *, дайте мне знать.

2 голосов
/ 20 ноября 2011

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

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

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

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

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

1 голос
/ 20 ноября 2011

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

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

1 голос
/ 14 ноября 2011

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

0 голосов
/ 11 января 2018

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

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

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

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

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

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

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

0 голосов
/ 20 марта 2014
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 по умолчанию:

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...