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

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

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

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

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

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

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

10 ответов


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

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

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

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


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

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

единственный способ работы с неизвестными кодировками-это работа с вероятностями. Поэтому мы не хотим ответить на вопрос "Что такое кодировка текста?"мы пытаясь понять"какова наиболее вероятная кодировка этого текста?".

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

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

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

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

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

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


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


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

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

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

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


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

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

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

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

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


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


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

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


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

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

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

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

    b. Обнаружьте кодировку преобразованного значения

    c. Если сообщенный кодирование и преобразованное значение оба UTF-8, продолжай.

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


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);

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


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

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

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

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

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

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

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