web-dev-qa-db-fra.com

Comment gérer les entrées utilisateur de caractères UTF-8 non valides?

Je cherche une stratégie générale/un conseil sur la façon de gérer les entrées UTF-8 non valides des utilisateurs. 

Même si mon application Web utilise UTF-8, certains utilisateurs entrent des caractères non valides. Cela provoque des erreurs dans json_encode () de PHP et semble globalement une mauvaise idée.

FAQ W3C I18N: Formulaires multilingues indique "Si des données non-UTF-8 sont reçues, un message d'erreur doit être renvoyé.".

  • Comment cela devrait-il être fait concrètement sur un site comportant des dizaines d'endroits où des données peuvent être saisies?
  • Comment présentez-vous l'erreur de manière utile à l'utilisateur?
  • Comment stockez-vous et affichez-vous temporairement les mauvaises données de formulaire afin que l'utilisateur ne perde pas tout son texte? Enlevez les mauvais personnages? Utilisez un personnage de remplacement, et comment?
  • Pour les données existantes dans la base de données, si des données UTF-8 non valides sont détectées, dois-je essayer de les convertir et les sauvegarder (comment? utf8_encode ()? mb_convert_encoding () ?), Ou laisser -est dans la base de données mais faire quelque chose (quoi?) avant json_encode ()?

EDIT: Je connais très bien l'extension mbstring et je ne demande pas "comment fonctionne UTF-8 en PHP". J'aimerais avoir des conseils de personnes expérimentées dans des situations réelles sur la façon dont elles ont géré cette situation. 

EDIT2: dans le cadre de la solution, j'aimerais vraiment voir une méthode rapide pour convertir les caractères invalides en U + FFFD

37
philfreo

L'attribut accept-charset="UTF-8" n'est qu'un guide à suivre pour les navigateurs. Ils ne sont pas obligés de soumettre cela. De cette manière, les robots de soumission de formulaires pourris sont un bon exemple ...

Ce que je fais habituellement, c’est ignorer les caractères incorrects, via iconv() ou avec les fonctions moins fiables utf8_encode() / utf8_decode() , si vous utilisez iconv, vous avez également la possibilité de translittérer les caractères incorrects.

Voici un exemple utilisant iconv():

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

Si vous souhaitez afficher un message d'erreur à vos utilisateurs, je le ferais probablement de manière globale plutôt que sur la base des valeurs reçues, ce qui serait probablement très bien:

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!

Vous pouvez également vouloir normaliser les nouvelles lignes et éliminer les caractères de contrôle visibles (non), comme ceci:

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

Code pour convertir les points de code UTF-8 en 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

Probablement plus rapide que toute autre alternative, cependant, je ne l'ai pas testée de manière approfondie.


Exemple:

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

Est ce que c'est ce que vous recherchiez?

58
Alix Axel

La réception de caractères non valides de votre application Web peut avoir à voir avec les jeux de caractères supposés pour les formulaires HTML. Vous pouvez spécifier le jeu de caractères à utiliser pour les formulaires avec l'attribut accept-charset :

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

Vous pouvez également vous pencher sur des questions similaires dans StackOverflow pour savoir comment gérer les caractères non valides, par exemple. ceux de la colonne de droite, mais je pense que signaler une erreur à l'utilisateur est préférable à la suppression des caractères non valides entraînant une perte inattendue de données importantes ou un changement inattendu des entrées de l'utilisateur.

4
Archimedix

J'ai mis en place une classe assez simple pour vérifier si l'entrée est en UTF-8 et pour exécuter utf8_encode() selon les besoins:

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);
2
Nev Stokes

Je recommande simplement de ne pas laisser de déchets entrer. Ne vous fiez pas aux fonctions personnalisées, qui pourraient gâcher votre système. Il vous suffit de parcourir les données soumises par rapport à un alphabet que vous avez créé. Créez une chaîne d'alphabet acceptable et parcourez les données soumises, octet par octet, comme s'il s'agissait d'un tableau. Poussez les caractères acceptables vers une nouvelle chaîne et omettez les caractères inacceptables. Les données que vous stockez dans votre base de données sont alors des données déclenchées par l'utilisateur, mais pas réellement des données fournies par l'utilisateur.

EDIT # 4: Remplacer un mauvais personnage par entiy:

EDIT # 3: Mise à jour: 22 septembre 2010 @ 13:32 Raison: maintenant la chaîne renvoyée est UTF-8, plus j'ai utilisé le fichier de test que vous avez fourni comme preuve.

<?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
Geekster

Pour être complet à cette question (pas nécessairement la meilleure réponse) ...

function as_utf8($s) {
    return mb_convert_encoding($s, "UTF-8", mb_detect_encoding($s));
}
1
philfreo

Il existe une extension multi-octets pour PHP, consultez-la: http://www.php.net/manual/fr/book.mbstring.php

Vous devriez essayer mb_check_encoding () function.

Bonne chance!

1
Otar

Essayez de faire ce que Rails fait pour forcer tous les navigateurs à toujours publier des données 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>

Voir railssnowman.info ou le correctif initial pour une explication.

  1. Pour que le navigateur envoie les données de soumission de formulaire dans le codage UTF-8, restituez simplement la page avec un en-tête Content-Type de "text/html; charset = utf-8" (ou utilisez une balise meta http-equiv).
  2. Pour que le navigateur envoie les données de soumission de formulaire dans le codage UTF-8, même si l'utilisateur manipule le codage de page (les navigateurs le permettent), utilisez accept-charset="UTF-8" dans le formulaire.
  3. Pour que le navigateur envoie les données de soumission de formulaire dans le codage UTF-8, même si l'utilisateur tripote le codage de la page (les navigateurs le permettent), et même si le navigateur est IE et que l'utilisateur a changé de page En codant en coréen et en saisissant les caractères coréens dans les champs de formulaire, ajoutez une entrée masquée au formulaire avec une valeur telle que &#x2713; qui ne peut provenir que du jeu de caractères Unicode (et, dans cet exemple, pas du jeu de caractères Coréen).
0
yfeldblum

Que diriez-vous de supprimer tous les caractères en dehors de votre sous-ensemble donné. Au moins dans certaines parties de mon application, je ne permettrais pas l'utilisation de caractères en dehors des [a-Z] [0-9 jeux], par exemple les noms d'utilisateurs. Vous pouvez créer une fonction de filtre qui supprime silencieusement tous les caractères en dehors de cette plage ou renvoie une erreur si elle les détecte et envoie la décision à l'utilisateur.

0
Elzo Valugi

Définissez UTF-8 comme jeu de caractères pour tous les en-têtes générés par votre code PHP

Dans chaque en-tête de sortie PHP, spécifiez UTF-8 comme encodage:

header('Content-Type: text/html; charset=utf-8');
0
Mr. Nobody