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é.".
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
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?
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.
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);
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 .= '�';
}
}
}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 = '�';
}
}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.')';
}
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));
}
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!
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="✓" />
</div>
<!-- form fields -->
</form>
Voir railssnowman.info ou le correctif initial pour une explication.
meta http-equiv
).accept-charset="UTF-8"
dans le formulaire.✓
qui ne peut provenir que du jeu de caractères Unicode (et, dans cet exemple, pas du jeu de caractères Coréen).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.
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');