Правильно ли я поддерживаю UTF-8 в моих приложениях PHP? - PullRequest
40 голосов
/ 23 августа 2009

Я хотел бы убедиться, что все, что я знаю о UTF-8, правильно. Я уже некоторое время пытаюсь использовать UTF-8, но продолжаю сталкиваться со все большим количеством ошибок и других странных вещей, из-за которых кажется почти невозможным иметь сайт с 100% UTF-8. Где-то всегда есть что-то, чего мне не хватает. Возможно, кто-то здесь может исправить мой список или ОК, чтобы я не пропустил ничего важного.

База данных

Каждый сайт должен где-то хранить данные. Независимо от ваших настроек PHP, вы также должны сконфигурировать БД. Если вы не можете получить доступ к файлам конфигурации, убедитесь, что « SET NAMES 'utf8' », как только вы подключитесь. Кроме того, обязательно используйте utf8_ unicode_ ci на всех ваших столах. Это предполагает MySQL для базы данных, вам придется изменить для других.

Regex

Я делаю ОЧЕНЬ много регулярных выражений, которые на более сложны , чем ваш обычный поиск-замена. Я должен не забыть использовать модификатор "/ u", чтобы PCRE не повредил мои строки . Тем не менее, даже тогда существуют проблемы, по-видимому, .

Строковые функции

Все строковые функции по умолчанию (strlen (), strpos () и т. Д.) Следует заменить на Многобайтовые строковые функции , которые смотрят символ вместо байта.

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

header ('Content-Type: text / html; кодировка = UTF-8' ); * +1038 *

Также стоит добавить правильный тег в заголовок страницы. Хотя фактический заголовок переопределит это, если они будут отличаться.

<meta http-equiv="Content-Type" content="text/html;charset=utf-8">

Вопросы

Нужно ли преобразовывать все, что я получаю от пользовательского агента (HTML-формы и URI) в UTF-8, когда страница загружается, или если я могу просто оставить строки / значения такими, какие они есть, и по-прежнему выполнять их через эти функции без проблем?

Если мне нужно конвертировать все в UTF-8 - какие шаги мне следует предпринять? mb_detect_encoding , кажется, построен для этого, но я продолжаю видеть, как люди жалуются, что это не всегда работает. mb_check_encoding также, похоже, имеет проблему с отличием хорошей строки UTF-8 от искаженной.

Хранит ли PHP строки в памяти по-разному, в зависимости от того, какую кодировку он использует (например, типы файлов), или он все еще хранится как обычный фрагмент, причем некоторые символы интерпретируются по-разному (например, & amp; vs & in HTML). chazomaticus отвечает на этот вопрос:

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

Если передать функцию не-UTF-8 функции mb_ *, это когда-нибудь вызовет проблему?

Если строка UTF неправильно закодирована, что-то пойдет не так (например, ошибка синтаксического анализа в регулярном выражении?) Или просто пометит сущность как плохую (html)? Есть ли вероятность, что неправильно закодированные строки приведут к тому, что функция вернет FALSE, потому что строка плохая?

Я слышал, что вы должны также пометить ваши формы как UTF-8 (accept-charset = "UTF-8"), но я не уверен, в чем выгода ..?

Был ли UTF-16 записан для ограничения в UTF-8? Как в UTF-8 не хватило места для персонажей? (У2 (UTF) к?)

Функция

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

function seems_utf8($str) {
    $length = strlen($str);
    for ($i=0; $i < $length; $i++) {
        $c = ord($str[$i]);
        if ($c < 0x80) $n = 0; # 0bbbbbbb
        elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
        elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
        elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
        elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb
        elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b
        else return false; # Does not match any model
        for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
            if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
                return false;
        }
    }
    return true;
}

function is_utf8($str) {
    $c=0; $b=0;
    $bits=0;
    $len=strlen($str);
    for($i=0; $i<$len; $i++){
        $c=ord($str[$i]);
        if($c > 128){
            if(($c >= 254)) return false;
            elseif($c >= 252) $bits=6;
            elseif($c >= 248) $bits=5;
            elseif($c >= 240) $bits=4;
            elseif($c >= 224) $bits=3;
            elseif($c >= 192) $bits=2;
            else return false;
            if(($i+$bits) > $len) return false;
            while($bits > 1){
                $i++;
                $b=ord($str[$i]);
                if($b < 128 || $b > 191) return false;
                $bits--;
            }
        }
    }
    return true;
}

Если кому-то интересно, я нашел отличный пример страницы для использования при тестировании UTf-8 .

Ответы [ 5 ]

20 голосов
/ 23 августа 2009

Нужно ли преобразовывать все, что я получаю от пользовательского агента (HTML-формы и URI) в UTF-8 при загрузке страницы

Нет. Пользовательский агент должен предоставлять данные в формате UTF-8; если нет, то вы теряете преимущество Unicode.

Способ обеспечения того, что пользовательский агент отправляет в формате UTF-8, заключается в обслуживании страницы, содержащей форму, которую он отправляет в кодировке UTF-8. Используйте заголовок Content-Type (и мета-http-эквивалент, если вы хотите сохранить форму и работать автономно).

Я слышал, что вы должны также пометить ваши формы как UTF-8 (accept-charset = "UTF-8")

Не надо. Это была хорошая идея в стандарте HTML, но IE так и не понял ее правильно. Предполагалось, что он должен содержать эксклюзивный список допустимых кодировок, но IE рассматривает его как список дополнительных кодировок, которые нужно попробовать, для каждого поля. Так что, если у вас есть страница ISO-8859-1 и форма «accept-charset =" UTF-8 »», IE сначала попытается закодировать поле как ISO-8859-1, и если есть не-8859-1 там символ, , затем , он прибегнет к UTF-8.

Но поскольку IE не сообщает вам, использовал ли он ISO-8859-1 или UTF-8, это абсолютно бесполезно для вас. Вы должны были бы угадать, для каждого поля отдельно, какая кодировка использовалась! Не полезно. Опустите атрибут и обслуживайте ваши страницы как UTF-8; это лучшее, что вы можете сделать на данный момент.

Если строка UTF неправильно закодирована, что-то пойдет не так

Если вы позволите такой последовательности пройти через браузер, у вас могут возникнуть проблемы. Существуют «сверхдлинные последовательности», которые кодируют кодовую точку с низким номером в более длинной последовательности байтов, чем это необходимо. Это означает, что если вы фильтруете ‘<’, ища этот символ ASCII в последовательности байтов, вы можете пропустить один и пустить элемент скрипта в то, что вы считаете безопасным текстом. </p>

Слишком длинные последовательности были запрещены еще в первые дни Unicode, но Microsoft потребовалось очень много времени, чтобы собрать их дерьмо: IE интерпретировал бы последовательность байтов '\ xC0 \ xBC' как '<' до IE6 Service Пакет 1. Opera также ошиблась до (примерно, я думаю) версии 7. К счастью, эти старые браузеры вымирают, но все же стоит отфильтровывать слишком длинные последовательности в случае, если эти браузеры все еще работают (или новые идиотские браузеры делают то же самое ошибка в будущем). Вы можете сделать это и исправить другие неверные последовательности с помощью регулярного выражения, которое пропускает только надлежащий UTF-8, например <a href="http://www.w3.org/International/questions/qa-forms-utf-8" rel="noreferrer"> этот из W3.

Если вы используете функции mb_ в PHP, вы могли бы быть изолированы от этих проблем. Я не могу сказать наверняка, так как mb_ * был непригодным хрупким, когда я все еще писал PHP.

В любом случае, это также хорошее время для удаления управляющих символов, которые являются крупным и, как правило, недооцененным источником ошибок. Я бы удалил символы 9 и 13 из представленной строки в дополнение к другим, которые вынимает регулярное выражение W3; также стоит удалить простые новые строки для строк, которые, как вы знаете, не должны быть многострочными.

Была ли UTF-16 записана для адресации предела в UTF-8?

Нет, UTF-16 - это кодирование с двумя байтами на кодовую точку, которое используется для упрощения индексации строк Юникода в памяти (со времен, когда весь Юникод помещался в два байта; системы, подобные Windows и Java, все еще это так). В отличие от UTF-8 он не совместим с ASCII и практически не используется в Интернете. Но вы иногда встречаете его в сохраненных файлах, обычно тех, которые были сохранены пользователями Windows, которые были введены в заблуждение описанием Windows UTF-16LE как «Unicode» в меню «Сохранить как».

seems_utf8

Это очень неэффективно по сравнению с регулярным выражением!

Кроме того, обязательно используйте utf8_unicode_ci на всех ваших столах.

Вы действительно можетеИначе обойдемся без этого, рассматривая MySQL как хранилище только для байтов и интерпретируя их как UTF-8 в вашем скрипте. Преимущество использования utf8_unicode_ci заключается в том, что он сопоставляет (сортирует и выполняет сравнение без учета регистра) сведения о символах, отличных от ASCII, например ‘Ŕ’ и ‘Ŕ’ - это одинаковые символы. Если вы используете сопоставление не-UTF8, вам следует придерживаться двоичного (чувствительного к регистру) сопоставления.

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

11 голосов
/ 23 августа 2009

Большая часть того, что вы делаете сейчас, должна быть правильной.

Некоторые примечания: любое сопоставление utf_* в MySQL будет правильно хранить ваши данные как UTF-8, единственное различие между ними - это сопоставление (алфавитный порядок), применяемое при сортировке.

Вы можете указать Apache и PHP выдавать правильные настройки заголовков кодировки AddDefaultCharset utf-8 в httpd.conf / .htaccess и default_charset = "utf-8" в php.ini соответственно.

Вы можете указать расширению mbstring заботиться о строковых функциях. Это работает для меня:

mbstring.internal_encoding=utf-8
mbstring.http_output=UTF-8
mbstring.encoding_translation=On
mbstring.func_overload=6

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

Для преобразования кодировки взгляните на https://sourceforge.net/projects/phputf8/.

PHP совершенно не волнует, что находится в переменной, он просто хранит и извлекает содержимое вслепую.

У вас будут неожиданные результаты, если вы объявите один mbstring.internal_encoding и передадите строки функции mb_ * в другой кодировке. В любом случае вы можете безопасно отправлять ASCII функциям utf-8.

Если вы беспокоитесь о том, что кто-то намеренно публикует неправильно закодированные материалы, я думаю, вы должны HTML Purifie r отфильтровать данные GET / POST перед обработкой.

Accept-charset был в спецификациях с незапамятных времен, но его реальная поддержка в браузерах более или менее равна нулю. Браузер обычно использует кодировку страницы, содержащей форму.

UTF-16 - это не старший брат UTF-8, он просто служит другой цели.

3 голосов
/ 23 августа 2009

база данных / mysql: если вы используете SET NAMES и, например, php / mysql вы оставляете mysql_real_escape_string () в неведении об изменении кодировки символов. Это может привести к неправильным результатам. Итак, если вы полагаетесь на escape-функцию, такую ​​как mysql_real_escape_string (потому что вы не используете подготовленные операторы), SET NAMES является неоптимальным решением. Вот почему было введено mysql_set_charset () или почему gentoo применяет патч, который добавляет параметр конфигурации mysql.connect_charset для php / mysql и php / mysqli.

Клиент обычно не указывает кодировку отправляемых им параметров. Если вы ожидаете данные в кодировке utf-8 и обрабатываете их как таковые , могут быть ошибки кодирования (последовательности байтов, которые недопустимы в utf-8). Таким образом, данные могут отображаться не так, как ожидается, или анализатор может прервать анализ. Но, по крайней мере, пользовательский ввод не может «убежать» и принести больше вреда, например. во встроенном выражении SQL или вывода HTML. Например. взять скрипт (сохраненный как iso-8859-1 или utf-8, не имеет значения)

<?php
$s = 'abcxyz';
var_dump(htmlspecialchars($s, ENT_QUOTES, 'utf-8'));
// adding the byte sequence for äöü in iso-8859-1
$s = 'abc'. chr(0xE4) . chr(0xF6) . chr(0xFC). 'xyz';
var_dump(htmlspecialchars($s, ENT_QUOTES, 'utf-8'));

печать

string(6) "abcxyz"
string(0) ""

E4F6FC не является допустимой последовательностью байтов utf-8, поэтому htmlspecialchars возвращает пустую строку. Другие функции могут вернуться? или другой «особый» персонаж. Но, по крайней мере, они не будут «ошибочно» воспринимать персонажа как злонамеренный управляющий символ - при условии, что все они придерживаются «правильной» кодировки (в данном случае utf-8).

accept-charset не гарантирует, что вы будете получать только данные с этой кодировкой. Насколько вам известно, клиент может даже не «использовать» / проанализировать ваш HTML-документ, содержащий элемент формы. Это может помочь, и нет никаких причин, почему вы не должны устанавливать этот атрибут. Но это не "надежно".

0 голосов
/ 23 августа 2009

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

0 голосов
/ 23 августа 2009

UTF-8 в порядке и не имеет ограничений, которые UTF-16 решает. PHP не меняет способ хранить строки в памяти (в отличие от Python). Если весь поток данных использует UTF-8 (веб-формы получают данные UTF-8, таблицы используют кодировку utf8, а вы используете SET NAMES utf8, и данные хранятся без изменений (без преобразования кодировки), это должно быть хорошо .

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