Я правильно поддерживаю UTF-8 в своих PHP-приложениях?

Я хотел бы убедиться, что все, что я знаю о 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; charset = utf-8');

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

<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, во всяком случае) строки – это просто последовательности байтов. С ними не связано подразумеваемый или явный набор символов; это то, о чем программист должен отслеживать.

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

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

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

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

функции

Вот пара пользовательских функций PHP, которые я нашел, но у меня нет никакого способа проверить, что они действительно работают. Возможно, у кого-то есть пример, который я могу использовать. Сначала выполняется преобразованиеToUTF8 (), а затем seem_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 .

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

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

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

Я слышал, что вы также должны указывать свои формы как 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 в последовательности байтов, вы можете пропустить один, и пусть элемент сценария в том, что вы считаете безопасным текстом.

Overlong последовательности были запрещены в первые дни Unicode, но Microsoft потребовала очень долгое время, чтобы получить свое дерьмо: IE будет интерпретировать байтовую последовательность «\ xC0 \ xBC» как «<» вплоть до IE6 с пакетом обновления 1 (SP1). Opera также ошибалась до (примерно, я думаю) версии 7. К счастью, эти старые браузеры вымирают, но по-прежнему стоит фильтровать чересстрочные последовательности, если эти браузеры все еще существуют (или новые браузеры-идиоторы совершают ту же ошибку в будущем ). Вы можете сделать это и исправить другие неудачные последовательности, с регулярным выражением, которое позволяет использовать только правильный UTF-8, например, этот W3.

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

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

Был ли UTF-16 написан для ограничения лимита в UTF-8?

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

seems_utf8

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

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

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

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

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

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

Вы можете сказать, что Apache и PHP выдает правильные заголовки AddDefaultCharset utf-8 устанавливая 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, он просто служит другой цели.

database / 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-документ, содержащий элемент формы. Это может помочь, и нет причин, по которым вы не должны устанавливать этот атрибут. Но это не «надежный».

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

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