wake-up-neo.com

Wie wird die Benutzereingabe ungültiger UTF-8-Zeichen behandelt?

Ich bin auf der Suche nach einer allgemeinen Strategie/einem Ratgeber zum Umgang mit ungültigen UTF-8-Eingaben von Benutzern. 

Obwohl meine Webapp-Anwendung UTF-8 verwendet, geben manche Benutzer irgendwie ungültige Zeichen ein. Dies führt zu Fehlern in PHP json_encode () und scheint insgesamt eine schlechte Idee zu sein.

W3C I18N FAQ: Mehrsprachige Formulare sagt "Wenn Nicht-UTF-8-Daten empfangen werden, sollte eine Fehlernachricht zurückgesendet werden.".

  • Wie genau soll dies praktisch geschehen, an einem Ort mit Dutzenden verschiedener Orte, an denen Daten eingegeben werden können?
  • Wie präsentieren Sie den Fehler auf hilfreiche Weise dem Benutzer?
  • Wie speichern und zeigen Sie vorübergehend fehlerhafte Formulardaten an, damit der Benutzer nicht seinen gesamten Text verliert? Schlechte Charaktere entfernen? Verwenden Sie ein Ersatzzeichen und wie?
  • Wenn für vorhandene Daten in der Datenbank ungültige UTF-8-Daten gefunden werden, sollte ich versuchen, sie zu konvertieren und zurückzuspeichern (wie? utf8_encode ()? mb_convert_encoding () ?) Oder als -ist in der Datenbank aber etwas (was?) vor json_encode ()?

EDIT: Ich bin mit der mbstring-Erweiterung sehr vertraut und frage nicht, "wie funktioniert UTF-8 in PHP".) Ich hätte gerne Rat von Leuten, die Erfahrung in realen Situationen haben, wie sie damit umgehen. 

EDIT2: Als Teil der Lösung möchte ich wirklich eine fast -Methode sehen, um ungültige Zeichen in U + FFFD umzuwandeln.

37
philfreo

Das accept-charset="UTF-8"-Attribut ist nur eine Richtlinie für Browser, die nicht befolgt werden muss. Auf diese Weise sind sie nicht dazu verpflichtet.

Normalerweise ignoriere ich schlechte Zeichen entweder über iconv() oder mit den weniger zuverlässigen utf8_encode() / - utf8_decode() Funktionen. Wenn Sie iconv verwenden, haben Sie auch die Möglichkeit, schlechte Zeichen zu transliterieren.

Hier ist ein Beispiel mit iconv():

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

Wenn Sie Ihren Benutzern eine Fehlermeldung anzeigen möchten, würde ich dies wahrscheinlich auf eine globale Art und nicht auf eine pro Wert erhaltene Basis tun. Etwas wie dieses würde wahrscheinlich gut funktionieren:

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!

Möglicherweise möchten Sie auch neue Zeilen normalisieren und (nicht-) sichtbare Steuerzeichen wie folgt entfernen:

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 zum Konvertieren von UTF-8 in Unicode-Codepunkte:

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

Wahrscheinlich schneller als jede andere Alternative, jedoch nicht ausgiebig getestet.


Beispiel:

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

Ist es das, wonach Sie gesucht haben?

58
Alix Axel

Der Empfang ungültiger Zeichen aus Ihrer Web-App hat möglicherweise mit den für HTML-Formulare angenommenen Zeichensätzen zu tun. Sie können angeben, welcher Zeichensatz für Formulare mit dem Attribut accept-charset verwendet werden soll:

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

Sie können auch ähnliche Fragen in StackOverflow mit Zeigern zum Umgang mit ungültigen Zeichen betrachten, z. die in der rechten Spalte, aber ich denke, dass das Signalisieren eines Fehlers für den Benutzer besser ist als der Versuch, die ungültigen Zeichen zu bereinigen, die einen unerwarteten Verlust wichtiger Daten oder eine unerwartete Änderung der Eingaben des Benutzers verursachen.

4
Archimedix

Ich habe eine ziemlich einfache Klasse zusammengestellt, um zu überprüfen, ob die Eingabe in UTF-8 erfolgt und wie es notwendig ist, utf8_encode() auszuführen:

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

Ich empfehle, nur den Müll nicht zuzulassen. Verlassen Sie sich nicht auf benutzerdefinierte Funktionen, die Ihr System zum Stillstand bringen können. Vergleichen Sie einfach die übermittelten Daten mit einem von Ihnen entworfenen Alphabet. Erstellen Sie eine akzeptable Alphabetzeichenfolge, und bewegen Sie die übermittelten Daten Byte für Byte wie ein Array. Übertragen Sie akzeptable Zeichen in eine neue Zeichenfolge und lassen Sie inakzeptable Zeichen aus. Die Daten, die Sie in Ihrer Datenbank speichern, sind Daten, die vom Benutzer ausgelöst werden, nicht jedoch die vom Benutzer angegebenen Daten.

EDIT # 4: Ersetzen eines schlechten Zeichens durch ein Zeichen: ent

BEARBEITEN # 3: Aktualisiert: 22. September 2010 @ 13:32 Uhr Grund: Die zurückgegebene Zeichenfolge ist UTF-8, und ich habe die Testdatei verwendet, die Sie als Beweis bereitgestellt haben.

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

Für die Vollständigkeit dieser Frage (nicht unbedingt die beste Antwort) ...

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

Es gibt eine Multibyte-Erweiterung für PHP. Schauen Sie sich das an: http://www.php.net/manual/de/book.mbstring.php

Sie sollten die Funktion mb_check_encoding () ausprobieren.

Viel Glück!

1
Otar

Versuchen Sie, das zu tun, was Rails tut, damit alle Browser immer UTF-8-Daten bereitstellen:

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

Siehe railssnowman.info oder den ursprünglichen Patch für eine Erklärung.

  1. Damit der Browser Formular-Übermittlungsdaten in der UTF-8-Codierung sendet, rendern Sie die Seite einfach mit einem Content-Type-Header mit "text/html; charset = utf-8" (oder verwenden Sie ein meta http-equiv-Tag).
  2. Damit der Browser die Daten zum Senden von Formularen in der UTF-8-Codierung sendet, verwenden Sie accept-charset="UTF-8" im Formular, auch wenn der Benutzer mit der Seitencodierung herumfummelt (Browser lassen dies zu).
  3. Damit der Browser Daten zum Senden von Formularen in der UTF-8-Codierung sendet, auch wenn der Benutzer mit der Seitencodierung herumfummelt (bei Browsern ist dies möglich) und auch wenn der Browser IE ist und der Benutzer die Seite gewechselt hat Wenn Sie für die Codierung in Koreanisch und in die Formularfelder eingegebene koreanische Zeichen eingegeben haben, fügen Sie dem Formular eine ausgeblendete Eingabe hinzu, beispielsweise &#x2713;, die nur aus dem Unicode-Zeichensatz stammen kann (und in diesem Beispiel nicht aus dem koreanischen Zeichensatz).
0
yfeldblum

Wie wäre es, wenn Sie alle Zeichen außerhalb Ihrer angegebenen Untermenge entfernen. Zumindest in einigen Teilen meiner Anwendung würde ich keine Zeichen außerhalb der [a-Z] [0-9-Sets] zulassen, z. B. Benutzernamen. Sie können eine Filterfunktion erstellen, die alle Zeichen außerhalb dieses Bereichs unbemerkt entfernt oder einen Fehler zurückgibt, wenn sie diese erkennt und die Entscheidung an den Benutzer weiterleitet.

0
Elzo Valugi

Legen Sie UTF-8 als Zeichensatz für alle Header fest, die von Ihrem PHP -Code ausgegeben werden

Geben Sie in jedem PHP - Ausgabeheader UTF-8 als Kodierung an:

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