2010-09-15 23 views
32

Estoy buscando una estrategia/consejo general sobre cómo manejar la entrada UTF-8 no válida de los usuarios.¿Cómo manejar la entrada del usuario de caracteres UTF-8 no válidos?

Aunque mi aplicación web usa UTF-8, de alguna manera algunos usuarios introducen caracteres no válidos. Esto causa errores en PHP json_encode() y en general parece una mala idea tener alrededor.

W3C I18N FAQ: Multilingual Forms dice "Si se reciben datos que no son UTF-8, se debe enviar un mensaje de error nuevamente".

  • ¿Cómo exactamente esto debería hacerse prácticamente, en un sitio con docenas de lugares diferentes donde se pueden ingresar datos?
  • ¿Cómo se presenta el error de una manera útil para el usuario?
  • ¿Cómo almacena y muestra temporalmente datos de formularios incorrectos para que el usuario no pierda todo su texto? ¿Despojar a los malos personajes? Use un personaje de reemplazo, y ¿cómo?
  • Para los datos existentes en la base de datos, cuando se detectan datos UTF-8 no válidos, debo tratar de convertirlos y guardarlos de nuevo (¿cómo? utf8_encode()? mb_convert_encoding()?), O dejar como está en la base de datos pero haciendo algo (¿qué?) antes de json_encode()?

EDIT: Estoy muy familiarizado con la extensión mbstring y no estoy preguntando "cómo funciona UTF-8 en PHP". Me gustaría recibir consejos de personas con experiencia en situaciones del mundo real sobre cómo manejaron esto.

Edit2: Como parte de la solución, Realmente me gustaría ver a un rápido método para convertir caracteres no válidos a U + FFFD

+0

no sigue las directrices se ha vinculado, pero acabo de reemplazar secuencias de bytes no válidos con [U + FFFD] (http://www.fileformat.info/info/unicode/char/ fffd/index.htm) así que puedo terminar con esto. – zildjohn01

+0

@ zildjohn01, ¿cuál es la mejor manera de hacer esto (qué funciones de PHP?). ¿Podría dejar una respuesta detallada con su enfoque? – philfreo

+0

Para ser sincero, no es muy emocionante. Acabo de traducir un analizador UTF-8 de C a PHP. Escanea el byte de la cadena por byte, y si se encuentra una secuencia de byte no válida, reescribe la cadena manualmente. Lento, pero portátil. – zildjohn01

Respuesta

55

El atributo accept-charset="UTF-8" es sólo una guía para los navegadores a seguir, no están obligados a presentar que de esa forma, los robots de envío de formularios de mierda son un buen ejemplo ...

Lo que suelo hacer es ignorar bad chars, ya sea a través de iconv() o con las funciones menos confiables utf8_encode()/utf8_decode(), si usa iconv, también tiene la opción de transcribir los caracteres incorrectos.

Aquí hay un ejemplo usando iconv():

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

Si desea mostrar un mensaje de error a los usuarios que probablemente haría esto de una manera global en vez de una base por valor recibido, algo como esto probablemente haría muy 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! 

también puede querer normalizar las nuevas líneas y tira de caracteres de control (no) visibles, así:

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

Código convertir de UTF-8 a puntos de código 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 

Probablemente más rápido que cualquier otra alternativa, no se han probado extensivamente sin embargo.


Ejemplo:

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

Es esto lo que estabas buscando?

+1

¿Este método le permite reemplazar caracteres no válidos con U + FFFD en lugar de simplemente pelar? Parece que eso sería más útil para que el usuario vea exactamente qué caracteres tienen un problema. – philfreo

+0

@philfreo: No es que yo sepa, no con iconv. Pero puede salirse con las expresiones regulares, algo así como: 'preg_replace ('/ ([^ \ p {L} \ p {M} \ p {Z} \ p {N} \ p {P} \ p {S} \ p {C}])/u ',' convert_to_unicode_notation ("\\ 1")) ', string); '- esto es solo desde lo alto de mi cabeza soñolienta, seguramente existen mejores expresiones regulares. Sin embargo, ¡ten en cuenta que esto será considerablemente más lento que el enfoque iconv! –

+2

@philfreo: Ok, esta es una lectura obligada: http://webcollab.sourceforge.net/unicode.html. –

1

Hay una extensión de varios bytes para PHP, comprobar que funciona : http://www.php.net/manual/en/book.mbstring.php

Debe probar la función mb_check_encoding().

¡Buena suerte!

+0

Estoy muy familiarizado con la extensión mb, ya que me uní a ella en mi propia pregunta. Los comentarios en esta página indican que este mb_check_encoding() realmente no verifica las secuencias de bytes incorrectos, además, realmente estoy preguntando sobre una estrategia general, no sobre cómo hacer una parte específica. – philfreo

+0

¿Qué comentario es eso? Nadie menciona esa función que puedo ver. El propósito de la función es exactamente verificar las secuencias de bytes incorrectos. Hay [un error abierto para la función] (https://bugs.php.net/bug.php?id=47990), pero un comentario en esa página dice que debe cerrarse. – itpastorn

4

La recepción de caracteres no válidos desde su aplicación web podría tener que ver con los conjuntos de caracteres asumidos para los formularios HTML. Puede especificar qué conjunto de caracteres a utilizar para las formas con el accept-charset attribute:

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

También puede ser que desee echar un vistazo a preguntas similares en StackOverflow para consejos sobre cómo manejar caracteres no válidos, por ejemplo los de la columna de la derecha, pero creo que es mejor señalar un error al usuario que tratar de limpiar esos caracteres no válidos que causan la pérdida inesperada de datos importantes o cambios inesperados de las entradas de los usuarios.

+0

Especifica los conjuntos de caracteres aceptados por el servidor. No estoy seguro de si basta con especificar la codificación UTF-8 para la página: el navegador podría mostrar UTF-8 al enviar datos de formulario en ISO-8859-1 u otra cosa. – Archimedix

+0

¿Qué significa 'accept-charset' realmente? ¿Es imposible para un usuario enviar caracteres inválidos o solo una sugerencia? ¿Cómo debo manejar los datos incorrectos si aún los recibo en el servidor? – philfreo

+0

De acuerdo con http://stackoverflow.com/questions/3719974/is-here-any-benefit-to-adding-accept-charsetutf-8-to-html-forms-if-the-page esto sería innecesario – philfreo

0

¿Qué le parece quitar todos los chars fuera de su subconjunto dado. Al menos en algunas partes de mi aplicación, no permitiría el uso de caracteres fuera de [a-Z] [0-9 conjuntos], por ejemplo, nombres de usuario. Puede crear una función de filtro que elimine silenciosamente todos los caracteres fuera de este rango, o que devuelva un error si los detecta y envía la decisión al usuario.

+0

"ignorar secuencias mal formadas o caracteres no disponibles no cumple con ISO 10646, hará que la depuración de sea más difícil y puede generar confusión entre los usuarios". http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt – philfreo

+0

@philifreo: ¿es a lo que ha vinculado su tarea o solo como referencia? Si se trata solo de una referencia, eso se debe a que el profesor asigna una tarea a los alumnos y los está desafiando, no porque exista una relevancia filosófica para detectar la codificación incorrecta. ¿Conoces la expresión "el espectáculo debe continuar"? Eso se aplica también a la programación y es por eso que mi respuesta te da la capacidad de eliminar los caracteres incorrectos o devolver un error si se detectan. – Geekster

0

trate de hacer lo que hace rieles para obligar a todos los navegadores siempre para publicar los datos 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> 

Ver railssnowman.info o the initial patch una explicación.

  1. Para que el navegador envía datos de forma de presentación en la codificación UTF-8, simplemente representar la página con una cabecera Content-Type "text/html; charset = UTF-8" (o use una etiqueta meta http-equiv)
  2. Para que el navegador envíe datos de envío de formularios en la codificación UTF-8, incluso si el usuario juega con la codificación de la página (los navegadores permiten que los usuarios lo hagan), use accept-charset="UTF-8" en el formulario.
  3. Para que el navegador envíe datos de formulario en la codificación UTF-8, incluso si el usuario juega con la codificación de página (los navegadores permiten que los usuarios lo hagan), e incluso si el navegador es IE y el usuario cambió la codificación de la página a coreano e ingresó caracteres coreanos en los campos del formulario, agregue una entrada oculta al formulario con un valor como &#x2713; que solo puede ser del juego de caracteres Unicode (y, en este ejemplo, no es el juego de caracteres coreano).
+1

¿'accept-charset' realmente obliga a los navegadores a no enviar datos que no sean UTF8? ¿Qué pasa si lo intentan? ¿Cómo debo manejarlo en el servidor si se omite esta validación del lado del cliente? – philfreo

+0

¿Puedes explicar el campo oculto también? ¿Es eso necesario? – philfreo

+0

Según http://stackoverflow.com/questions/3719974/is-there-any-benefit-to-adding-accept-charsetutf-8-to-html-forms-if-the-page todo esto sería innecesario – philfreo

1

Recomiendo simplemente no permitir que entre la basura. No confíe en funciones personalizadas, que pueden atascar su sistema. Simplemente recorra los datos enviados con un alfabeto que diseñe. Cree una cadena de alfabeto aceptable y recorra los datos enviados, byte a byte, como si fuera una matriz. Empuje los caracteres aceptables a una nueva cadena y omita los caracteres inaceptables. Los datos que almacena en su base de datos son datos activados por el usuario, pero no datos proporcionados por el usuario.

EDIT # 4: Reemplazando el caracter malo con entiy: & # 65533;

EDIT # 3: Actualizado: 22 de septiembre de 2010 a las 1:32 pm Motivo: Ahora la cadena devuelta es UTF-8, además utilicé el archivo de prueba que proporcionó como comprobante.

<?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.')'; 
} 
+0

¿Cómo propone hacer eso, cuando el "alfabeto" es cualquier carácter UTF-8 válido? – philfreo

+0

Okay EDIT # 1 se ha actualizado y debe purificar todo lo que desee poner en JSON. Por supuesto, puede ajustar los caracteres en su alfabeto si JSON todavía se ahoga. Si pudieras publicar un archivo de datos de muestra que se está ahogando en JSON, eso me ayudaría a ajustarlo. – Geekster

+0

Eso no parece que sea compatible con UTF-8 ... – philfreo

2

que arme una clase bastante sencillo para comprobar si la entrada está en UTF-8 y para ejecutar a través utf8_encode() como las necesidades de ser:

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); 
+0

Gracias por w3 regex. – rjha94

1

Para completar a esta pregunta (no necesariamente la mejor respuesta) ...

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

Esto es bueno, pero ten cuidado; mb_detect_encoding() no siempre es 100% exacto si no especifica qué codificaciones debe verificar. Además, algunas codificaciones se comportan de manera casi idéntica (por ejemplo, ISO-8859-1/Latin-1 y CP-1252/Windows-1252; de hecho, cualquier codificación de un solo byte, como KOI8-R, ** cualquier ** sabor de ISO -8859- *, etc. es prácticamente imposible de detectar a menos que emplee algunas heurísticas muy inteligentes [y probablemente computacionalmente costosas]). –

Cuestiones relacionadas