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

146

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

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

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

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

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

Но должно быть что-то, что, по крайней мере, стоит попробовать !

Грим ...
источник
5
В принципе, по определению невозможно получить абсолютно правильные результаты, в действительности вероятность угадывания неизвестной кодировки не является потрясающей. Можно использовать эвристику, но она будет правильной менее чем в 100% случаев, в зависимости от материала, намного меньшего, чем 100%. Вы должны знать об этом. Может быть, кто-то здесь может хотя бы предложить библиотеку с хорошей эвристикой, хотя.
deceze
Конечно, я знаю, что нет идеального решения - отсюда и желание чего-то, что, по крайней мере, принесет пользу.
Мрачно ...
это может помочь: stackoverflow.com/q/505562/642173
Melsi
Вы пытались использовать UTF-8//IGNOREв качестве второго параметра в iconv?
стрельба
Да, я так и сделал. Очевидно, не идеально, потому что тогда «невеста» становится «женихом», но, безусловно, лучше. Почему TRANSLIT не работает?
Мрачно ...

Ответы:

255

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

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

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

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

Джефф Дэй
источник
5
Пожалуйста, взгляните на mb_detect_encodingисходный код в вашем php-дистрибутиве (где-то здесь: ext / mbstring / libmbfl / mbfl / mbfl_ident.c). Эта функция вообще не работает должным образом. Для некоторых кодировок у него даже есть «return true», лол. Другие находятся в функциях Ctrl + c Ctrl + v. Это потому, что вы не можете обнаружить кодирование без какого-либо словарного или статистического подхода (как у меня).
Oroboros102
1
Насколько я понимаю, он mb_detect_encodingпросматривает список предоставленных кодировок и принимает первый, в котором нет недопустимых байтовых последовательностей в строке ... Для кодировок, в которых нет недопустимых байтовых последовательностей, таких как ISO-8859-1, это всегда верно , Никакой «умной» эвристики, и результаты сильно различаются в зависимости от списка (и порядка) кодировок, которые вы передаете.
wutz
Кажется, это работает для меня. Мои пользователи отправляли текст на странице utf8 с помощью tinymce, но по неизвестной причине символы не utf8 иногда оказывались в базе данных. Это исправило это, так что большое спасибо.
giorgio79
@Jeff Day - Спасибо за это. Прошу прощения за мое невежество, что вы подразумеваете под «Установкой строгости»?
Ash501
[Jeff Day] отправляет, mb_detect_order()хотя это значение по умолчанию для этого параметра, потому что он хотел установить для строгого обнаружения кодирования значение true (3-й параметр) :)
jave.web
28

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

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

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

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

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

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

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

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

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

Oroboros102
источник
11

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

Алексей Герасимов
источник
Похоже, в базе данных - я только что попробовал ваш код, и я согласен.
Мрачно ...
1
Убедитесь, что определенное в таблице / столбце сопоставление также соответствует UTF-8.
Алексей Герасимов
@ AlexeyGerasimov Думаю, мне действительно нужно провести расследование iconv. Я пытался сделать почти чистый способ mb_ *. Что ты думаешь?
Энтони Ратледж
5

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

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

matthiasmullie
источник
Формы и ссылки для регистрации по электронной почте с зашифрованными данными. Именно здесь я пытаюсь сделать свой ввод UTF-8 или ничего. Что вы думаете о моем ответе? Полезные комментарии приветствуются. Спасибо.
Энтони Ратледж
3

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

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

дезинфицирующее средство

    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_ *, дайте мне знать.

Энтони Ратледж
источник
Я нашел это, stackoverflow.com/a/3521396/1429677 отличный ответ на этот вопрос, вот lib github.com/neitanod/forceutf8
Llewellyn
2

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

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

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

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

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

hakre
источник
Вы бы увидели мой вопрос? Конструктивные комментарии приветствуются. Спасибо.
Энтони Ратледж
1

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

Пэррис Варни
источник
Да, если говорить о mb_detect_encoding()промахах, как вы думаете, мой ответ имеет шансы на снежный ком летом в Сахаре?
Энтони Ратледж
1

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

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

Wutz
источник
1

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

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

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

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

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

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

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

Кель Пино
источник
-2
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);

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

littlealien
источник
э-э-э, вы можете проверить свою функцию и исправить переменные?
Мартин
Что такое $ url? Что такое $ html?
Мартин