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


146

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

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

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

Для завантаження файлів мені подобається ідея попросити кінцевого користувача вказати кодування, яке він використовує, і показати їм попередній перегляд того, як буде виглядати вихід, але це не допомагає проти неприємних хакерів (насправді це може зробити їх життя трохи легше).

Я читав інші запитання щодо цієї тематики на цю тему, але, здається, всі вони мають тонкі відмінності, такі як "Мені потрібно розібрати RSS-канали" або "Я скреблю дані з веб-сайтів" (або, справді, "Ви не можете").

Але має бути щось, що, принаймні, добре спробувати !


5
По суті, за визначенням неможливо отримати абсолютно коректне, насправді рівень успішності відгадування невідомого кодування не є надзвичайним. Можна використовувати евристику, але вона буде правильною менше ніж 100% часу, залежно від матеріалу набагато менше 100%. Вам це потрібно знати. Можливо, хтось тут може принаймні запропонувати бібліотеку з хорошою евристикою.
деле

Звичайно, я знаю, що ідеального рішення не існує - звідси бажання чогось, що принаймні буде добре.
Похмурий ...

це може допомогти: stackoverflow.com/q/505562/642173
Мелсі

Ви намагалися використовувати UTF-8//IGNOREяк 2-й парам в 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). Ця функція взагалі не працює належним чином. Для деяких кодувань це навіть "повернення справжнього", хаха. Інші знаходяться у функціях Ctrl + c Ctrl + v. Це тому, що ви не можете виявити кодування без якогось словника чи статистичного підходу (як у мене).
Oroboros102

1
Як я це розумію, mb_detect_encodingпроходить список наданих кодувань і приймає перше, яке не має недійсних послідовностей байтів у рядку ... Для кодувань, у яких немає недійсних послідовностей байтів, таких як ISO-8859-1, це завжди вірно . Ніякої "розумної" евристики, і результати сильно різняться залежно від переліку (та порядку) кодувань, які ви передаєте.
wutz

Це, здається, працює для мене. Мої користувачі надсилали текст на сторінці utf8 з tinymce, але з незрозумілої причини символи non utf8 іноді потрапляли в базу даних. Це виправлено, тому дуже дякую.
giorgio79

@Jeff Day - за це дякую. Вибачте моє невігластво, що ви маєте на увазі "Налаштувати його на суворе"?
Ash501

[Джефф День] надсилає, mb_detect_order()хоча це параметр за замовчуванням для цього параму, тому що він хотів встановити строге виявлення кодування на істинне (3-й парам) :)
jave.web

28

У батьківщині Росії у нас є 4 популярні кодування, тому ваше запитання тут дуже затребуване.

Тільки за допомогою кодових символів ви не можете виявити кодування, оскільки кодові сторінки перетинаються. Деякі кодові сторінки різними мовами мають навіть повний перетин. Отже, нам потрібен інший підхід .

Єдиний спосіб роботи з невідомими кодуваннями - це робота з ймовірностями. Отже, ми не хочемо відповідати на запитання "що таке кодування цього тексту?", Ми намагаємось зрозуміти " що найімовірніше кодування цього тексту? ".

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

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

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

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

Якщо ви зацікавлені , я з радістю можу вам допомогти з цим завданням. Ми можемо значно підвищити точність, побудувавши ймовірний список з двома кодами.

Btw. mb_detect_encoding Certanly не працює. Так, зовсім. Перегляньте вихідний код mb_detect_encoding у "ext / mbstring / libmbfl / mbfl / mbfl_ident.c".


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

і результати однакові для обох. Як ви бачите, що ваш текст усічений на "наречений"? це в БД чи в браузері?


У базі даних здається - я щойно спробував ваш код і я згоден.
Похмурий ...

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 - Снідаки ) Принаймні ви можете бути впевнені, що кожен текст, поданий через ваші форми, є utf_8. Щодо завантажених файлів, спробуйте запустити на ньому команду unix 'file -i', наприклад, exec () (якщо можливо на вашому сервері), щоб допомогти виявленню (використовуючи BOM документа). Щодо скреблінгу даних, ви можете прочитати заголовки HTTP, які зазвичай вказують діаграму. Під час розбору файлів XML дивіться, чи містять метадані XML визначення діаграми.

Замість того, щоб намагатися автоматично відгадати діаграму, спершу слід спробувати забезпечити певну шаблону самостійно, де це можливо, або намагатися схопити визначення з джерела, з якого ви отримуєте його (якщо це можливо), перш ніж вдаватися до виявлення.


Форми та посилання на реєстрацію електронної пошти із зашифрованими даними. Саме там я намагаюся зробити свій внесок 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 (зачекайте, не закінчено)

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

    c. Якщо повідомлення про кодування та перетворене значення є обома 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, закодованого в базі даних, але у двійковому вигляді. Коли ви знову виводите файл, використовуйте також двійковий вихід, тоді це повністю прозоро.

Ваша ідея приємна, що користувач може сказати кодування, будь він / вона зможе сказати все-таки після завантаження файлу, як це бінарне.

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


Ви б побачили і опублікували мою відповідь? Конструктивні коментарі цінуються. Дякую.
Ентоні Рутлідж

1

Ви можете встановити набір метрик, щоб спробувати відгадати, яке кодування використовується. Знову ж таки, не ідеально, але міг би вловити деякі недоліки з mb_detect_encoding ().


Так, добре кажучи про mb_detect_encoding()промахи, чи вважаєте ви, що моя відповідь має шанс снігового снігу влітку в Сахарі?
Ентоні Рутлідж

1

Якщо ви готові "взяти це до консолі", я рекомендую enca. На відміну від досить спрощеного mb_detect_encoding, він використовує "суміш розбору, статистичного аналізу, здогадів та чорної магії для визначення їх кодування" (lol - див. Сторінку людини ). Однак зазвичай вам потрібно передати мову вхідного файлу, якщо ви хочете виявити такі кодування, характерні для кожної країни. (Однак, mb_detect_encodingпо суті, є така ж вимога, як кодування повинно з'являтися "в потрібному місці" у списку переданих кодувань, щоб його взагалі можна було виявити.)

encaтакож придумали тут: Як знайти кодування файлу в Unix за допомогою скриптів


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

Я спробував щось подібне. Це мені допомогло. Якщо знайдено в інформації про мета-шаблони, я перетворюю, інакше нічого не роблю.


Помилка, чи можете ви перевірити свою функцію та виправити змінні?
Мартін

Що таке $ url? Що таке $ html?
Мартін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.