Как обработать пользовательский ввод недопустимых символов UTF-8? - PullRequest
37 голосов
/ 15 сентября 2010

Я ищу общую стратегию / совет о том, как обрабатывать неверный ввод UTF-8 от пользователей.

Хотя мое веб-приложение использует UTF-8, некоторые пользователи почему-то вводят недопустимые символы.Это вызывает ошибки в PHP json_encode () и в целом кажется плохой идеей иметь дело.

W3C Часто задаваемые вопросы по I18N: многоязычные формы говорит: «Если не UTF-Если данные получены, сообщение об ошибке должно быть отправлено обратно. ".

  • Как именно это должно быть сделано практически на всем сайте с десятками различных мест, где можно вводить данные?
  • Как вы представляете ошибку полезным способом для пользователя?
  • Как вы временно сохраняете и отображаете неверные данные, чтобы пользователь не потерял весь свой текст?Убрать плохих персонажей?Использовать символ замены и как?
  • Для существующих данных в базе данных, когда обнаруживаются недействительные данные UTF-8, следует ли мне попытаться преобразовать их и сохранить обратно (как? utf8_encode ()? mb_convert_encoding () ?) Или оставить в базе данных как есть, но что-то делать (что?) Перед json_encode ()?

РЕДАКТИРОВАТЬ: IЯ очень знаком с расширением mbstring и не спрашиваю "как работает UTF-8 в PHP".Я хотел бы получить совет от людей с опытом работы в реальных ситуациях, как они справились с этим.

РЕДАКТИРОВАТЬ 2: Как часть решения, я действительно хотел бы видеть быстрый метод для преобразования недопустимых символов в U + FFFD

Ответы [ 9 ]

59 голосов
/ 18 сентября 2010

Атрибут accept-charset="UTF-8" является лишь руководством для браузеров, которым не нужно следовать, они не обязаны сообщать, что таким образом, дрянные боты отправки форм являются хорошим примером ...

Обычно я игнорирую плохие символы с помощью iconv() или с менее надежными функциями utf8_encode() / utf8_decode(), если вы используете iconv у вас также есть возможность транслитерировать плохие символы.

Вот пример использования iconv():

$str_ignore = iconv('UTF-8', 'UTF-8//IGNORE', $str);
$str_translit = iconv('UTF-8', 'UTF-8//TRANSLIT', $str);

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

function utf8_clean($str)
{
    return iconv('UTF-8', 'UTF-8//IGNORE', $str);
}

$clean_GET = array_map('utf8_clean', $_GET);

if (serialize($_GET) != serialize($clean_GET))
{
    $_GET = $clean_GET;
    $error_msg = 'Your data is not valid UTF-8 and has been stripped.';
}

// $_GET is clean!

Вы также можете нормализовать новые строки и удалить (не) видимые контрольные символы, например:

function Clean($string, $control = true)
{
    $string = iconv('UTF-8', 'UTF-8//IGNORE', $string);

    if ($control === true)
    {
            return preg_replace('~\p{C}+~u', '', $string);
    }

    return preg_replace(array('~\r\n?~', '~[^\P{C}\t\n]+~u'), array("\n", ''), $string);
}

Код для преобразования из UTF-8 в кодовые точки Unicode:

function Codepoint($char)
{
    $result = null;
    $codepoint = unpack('N', iconv('UTF-8', 'UCS-4BE', $char));

    if (is_array($codepoint) && array_key_exists(1, $codepoint))
    {
        $result = sprintf('U+%04X', $codepoint[1]);
    }

    return $result;
}

echo Codepoint('à'); // U+00E0
echo Codepoint('ひ'); // U+3072

Вероятно, быстрее, чем любая другая альтернатива, хотя и не тестировали ее всесторонне.


Пример:

$string = 'hello world�';

// U+FFFEhello worldU+FFFD
echo preg_replace_callback('/[\p{So}\p{Cf}\p{Co}\p{Cs}\p{Cn}]/u', 'Bad_Codepoint', $string);

function Bad_Codepoint($string)
{
    $result = array();

    foreach ((array) $string as $char)
    {
        $codepoint = unpack('N', iconv('UTF-8', 'UCS-4BE', $char));

        if (is_array($codepoint) && array_key_exists(1, $codepoint))
        {
            $result[] = sprintf('U+%04X', $codepoint[1]);
        }
    }

    return implode('', $result);
}

Это то, что вы искали?

4 голосов
/ 15 сентября 2010

Получение недопустимых символов из вашего веб-приложения может иметь отношение к наборам символов, предполагаемым для форм HTML.Вы можете указать, какой набор символов использовать для форм с атрибутом accept-charset :

<form action="..." accept-charset="UTF-8">

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

2 голосов
/ 21 сентября 2010

Я собрал довольно простой класс, чтобы проверить, есть ли ввод в UTF-8, и пройти через utf8_encode(), как нужно:

class utf8
{

    /**
     * @param array $data
     * @param int $options
     * @return array
     */
    public static function encode(array $data)
    {
        foreach ($data as $key=>$val) {
            if (is_array($val)) {
                $data[$key] = self::encode($val, $options);
            } else {
                if (false === self::check($val)) {
                    $data[$key] = utf8_encode($val);
                }
            }
        }

        return $data;
    }

    /**
     * Regular expression to test a string is UTF8 encoded
     * 
     * RFC3629
     * 
     * @param string $string The string to be tested
     * @return bool
     * 
     * @link http://www.w3.org/International/questions/qa-forms-utf-8.en.php
     */
    public static function check($string)
    {
        return preg_match('%^(?:
            [\x09\x0A\x0D\x20-\x7E]              # ASCII
            | [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
            |  \xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
            | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}  # straight 3-byte
            |  \xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
            |  \xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
            | [\xF1-\xF3][\x80-\xBF]{3}          # planes 4-15
            |  \xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
            )*$%xs',
            $string);
    }
}

// For example
$data = utf8::encode($_POST);
1 голос
/ 25 сентября 2010

Для полноты на этот вопрос (не обязательно лучший ответ) ...

function as_utf8($s) {
    return mb_convert_encoding($s, "UTF-8", mb_detect_encoding($s));
}
1 голос
/ 20 сентября 2010

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

РЕДАКТИРОВАТЬ # 4: Замена неверного символа на entiy: �

РЕДАКТИРОВАНИЕ # 3:Обновлено: 22 сентября 2010 г., 13:32. Причина: теперь возвращаемая строка - UTF-8, плюс я использовал предоставленный вами тестовый файл в качестве доказательства.

<?php
// build alphabet
// optionally you can remove characters from this array

$alpha[]= chr(0); // null
$alpha[]= chr(9); // tab
$alpha[]= chr(10); // new line
$alpha[]= chr(11); // tab
$alpha[]= chr(13); // carriage return

for ($i = 32; $i <= 126; $i++) {
$alpha[]= chr($i);
}

/* remove comment to check ascii ordinals */

// /*
// foreach ($alpha as $key=>$val){
//  print ord($val);
//  print '<br/>';
// }
// print '<hr/>';
//*/
// 
// //test case #1
// 
// $str = 'afsjdfhasjhdgljhasdlfy42we875y342q8957y2wkjrgSAHKDJgfcv kzXnxbnSXbcv   '.chr(160).chr(127).chr(126);
// 
// $string = teststr($alpha,$str);
// print $string;
// print '<hr/>';
// 
// //test case #2
// 
// $str = ''.'©?™???';
// $string = teststr($alpha,$str);
// print $string;
// print '<hr/>';
// 
// $str = '©';
// $string = teststr($alpha,$str);
// print $string;
// print '<hr/>';

$file = 'http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt';
$testfile = implode(chr(10),file($file));

$string = teststr($alpha,$testfile);
print $string;
print '<hr/>';


function teststr(&$alpha, &$str){
    $strlen = strlen($str);
    $newstr = chr(0); //null
    $x = 0;
    if($strlen >= 2){

        for ($i = 0; $i < $strlen; $i++) {
            $x++;
            if(in_array($str[$i],$alpha)){
                // passed
                $newstr .= $str[$i];
            }else{
                // failed
                print 'Found out of scope character. (ASCII: '.ord($str[$i]).')';
                print '<br/>';
                $newstr .= '&#65533;';
            }
        }
    }elseif($strlen <= 0){
        // failed to qualify for test
        print 'Non-existent.';

    }elseif($strlen === 1){
        $x++;
        if(in_array($str,$alpha)){
            // passed

            $newstr = $str;
        }else{
            // failed
            print 'Total character failed to qualify.';
            $newstr = '&#65533;';
        }
    }else{
        print 'Non-existent (scope).';
        }

if(mb_detect_encoding($newstr, "UTF-8") == "UTF-8"){
// skip
}else{
    $newstr = utf8_encode($newstr);
}


// test encoding:
if(mb_detect_encoding($newstr, "UTF-8")=="UTF-8"){
    print 'UTF-8 :D<br/>';
    }else{
        print 'ENCODED: '.mb_detect_encoding($newstr, "UTF-8").'<br/>';
        }




return $newstr.' (scope: '.$x.', '.$strlen.')';
}
1 голос
/ 15 сентября 2010

Существует многобайтовое расширение для PHP, посмотрите его: http://www.php.net/manual/en/book.mbstring.php

Вы должны попробовать mb_check_encoding () function.

Good luck!

0 голосов
/ 03 июля 2018

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

В каждом выходном заголовке PHP укажите UTF-8 в качестве кодировки:

header('Content-Type: text/html; charset=utf-8');
0 голосов
/ 15 сентября 2010

Попробуйте сделать то, что делает Rails, чтобы заставить все браузеры всегда публиковать данные UTF-8:

<form accept-charset="UTF-8" action="#{action}" method="post"><div
    style="margin:0;padding:0;display:inline">
    <input name="utf8" type="hidden" value="&#x2713;" />
  </div>
  <!-- form fields -->
</form>

См. railssnowman.info или начальный патч для объяснения.

  1. Чтобы браузер отправлял данные отправки формы в кодировке UTF-8, просто отобразите страницу с заголовком Content-Type «text / html; charset = utf-8» (или используйте тег meta http-equiv) .
  2. Чтобы браузер отправлял данные отправки формы в кодировке UTF-8, даже если пользователь возится с кодировкой страницы (браузеры позволяют пользователям это делать), используйте accept-charset="UTF-8" в форме.
  3. Чтобы браузер отправлял данные отправки формы в кодировке UTF-8, даже если пользователь играет с кодировкой страницы (браузеры позволяют пользователям делать это), и даже если браузер IE, а пользователь переключил кодировку страницы для корейских и введенных корейских символов в полях формы добавьте скрытый ввод в форму со значением, например &#x2713;, которое может быть только из кодировки Unicode (и, в данном примере, не из кодировки корейского языка).
0 голосов
/ 15 сентября 2010

Как насчет удаления всех символов за пределами вашего подмножества.По крайней мере, в некоторых частях моего приложения я бы не позволил использовать символы вне [aZ] [0-9 наборов], например, имена пользователей.Вы можете создать функцию фильтра, которая автоматически удаляет все символы вне этого диапазона или возвращает ошибку, если обнаруживает их и передает решение пользователю.

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