2009-08-15 11 views
5

Devo convalidare qualche input utente codificato in UTF-8. Molti hanno raccomandato di utilizzare il seguente codice:Convalida UTF-8 in PHP senza utilizzare preg_match()

preg_match('/\A(
    [\x09\x0A\x0D\x20-\x7E] 
    | [\xC2-\xDF][\x80-\xBF] 
    | \xE0[\xA0-\xBF][\x80-\xBF] 
    | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} 
    | \xED[\x80-\x9F][\x80-\xBF] 
    | \xF0[\x90-\xBF][\x80-\xBF]{2} 
    | [\xF1-\xF3][\x80-\xBF]{3} 
    | \xF4[\x80-\x8F][\x80-\xBF]{2} 
)*\z/x', $string); 

Si tratta di un'espressione regolare preso da http://www.w3.org/International/questions/qa-forms-utf-8. Tutto è andato bene fino a quando ho scoperto un bug in PHP che sembra essere stato in circolazione almeno dal 2006. Preg_match() causa un errore di seg se la stringa $ è troppo lunga. Non sembra esserci alcuna soluzione. È possibile visualizzare l'invio del bug qui: http://bugs.php.net/bug.php?id=36463

Ora, per evitare l'uso di preg_match, ho creato una funzione che fa esattamente la stessa cosa dell'espressione regolare precedente. Non so se questa domanda sia appropriata qui a Stack Overflow, ma vorrei sapere se la funzione che ho realizzato è corretta. Eccolo:

EDIT [13.01.2010]: Se qualcuno è interessato, ci sono stati diversi bug nella versione precedente che ho postato. Di seguito è la versione finale della mia funzione.

function check_UTF8_string(&$string) { 
    $len = mb_strlen($string, "ISO-8859-1"); 
    $ok = 1; 

    for ($i = 0; $i < $len; $i++) { 
     $o = ord(mb_substr($string, $i, 1, "ISO-8859-1")); 

     if ($o == 9 || $o == 10 || $o == 13 || ($o >= 32 && $o <= 126)) { 

     } 
     elseif ($o >= 194 && $o <= 223) { 
      $i++; 
      $o2 = ord(mb_substr($string, $i, 1, "ISO-8859-1")); 
      if (!($o2 >= 128 && $o2 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o == 224) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $i += 2; 
      if (!($o2 >= 160 && $o2 <= 191) || !($o3 >= 128 && $o3 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif (($o >= 225 && $o <= 236) || $o == 238 || $o == 239) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $i += 2; 
      if (!($o2 >= 128 && $o2 <= 191) || !($o3 >= 128 && $o3 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o == 237) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $i += 2; 
      if (!($o2 >= 128 && $o2 <= 159) || !($o3 >= 128 && $o3 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o == 240) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $o4 = ord(mb_substr($string, $i + 3, 1, "ISO-8859-1")); 
      $i += 3; 
      if (!($o2 >= 144 && $o2 <= 191) || 
       !($o3 >= 128 && $o3 <= 191) || 
       !($o4 >= 128 && $o4 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o >= 241 && $o <= 243) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $o4 = ord(mb_substr($string, $i + 3, 1, "ISO-8859-1")); 
      $i += 3; 
      if (!($o2 >= 128 && $o2 <= 191) || 
       !($o3 >= 128 && $o3 <= 191) || 
       !($o4 >= 128 && $o4 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o == 244) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $o4 = ord(mb_substr($string, $i + 3, 1, "ISO-8859-1")); 
      $i += 5; 
      if (!($o2 >= 128 && $o2 <= 143) || 
       !($o3 >= 128 && $o3 <= 191) || 
       !($o4 >= 128 && $o4 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     else { 
      $ok = 0; 
      break; 
     } 
    } 

    return $ok; 
} 

Sì, è molto lungo. Spero di aver capito correttamente come funzioni quell'espressione regolare. Spero anche che sarà di aiuto agli altri.

Grazie in anticipo!

+0

Perché controllate così tanti valori speciali? Può essere molto più semplice. –

+0

Ho provato a verificare esattamente cosa sta verificando l'espressione regolare da W3C. – liviucmg

+0

Cosa pensi di fare nel caso in cui la stringa non sia UTF-8 valida? Meglio avere dati confusi rispetto a nessun dato a destra? –

risposta

7

È sempre possibile utilizzando il Multibyte String Functions:

Se si desidera utilizzare un sacco e cambiarlo in qualche possibilmente :

1) Impostare innanzitutto la codifica che si desidera utilizzare nel file di configurazione

/* Set internal character encoding to UTF-8 */ 
mb_internal_encoding("UTF-8"); 

2) Controllare la stringa

if(mb_check_encoding($string)) 
{ 
    // do something 
} 

Oppure, se non si ha intenzione di cambiarlo, puoi sempre mettere la codifica direttamente in funzione di:

if(mb_check_encoding($string, 'UTF-8')) 
{ 
    // do something 
} 
+0

+1, le funzioni di stringa MB vengono create per tale attività. – Boldewyn

1

Hai provato ereg() invece di preg_match? Forse questo non ha quel bug, e non hai bisogno di una soluzione potenzialmente bacata.

+1

Non ho provato ereg, potrebbe funzionare, ma non voglio usarlo perché: "Questa funzione (ereg) è stata DEPRECATED dal PHP 5.3.0 e RIMOSSA dal PHP 6.0.0. questa funzione è altamente scoraggiata. " – liviucmg

+1

OK, ma poi hai una buona possibilità, che il bug preg_match sia corretto in 6.0. Fai un 'if (function_exists ('ereg'))' e usa preg_match come fallback. – Boldewyn

+0

Tuttavia, utilizzare uno degli altri suggerimenti. Quello di Chacha102 va benissimo, e dato che nel tuo esempio usi mb_substr, immagino, hai abilitato le funzioni di stringa MB. Non dimenticare di accettare la sua risposta (o una qualsiasi delle altre). – Boldewyn

1

Dovresti essere in grado di utilizzare iconv per verificare la validità. Prova a convertirlo in UTF-16 e verifica se ricevi un errore.

0

Ecco una soluzione basata stringa di funzione:

http://www.php.net/manual/en/function.mb-detect-encoding.php#85294

<?php 
function is_utf8($str) { 
    $c=0; $b=0; 
    $bits=0; 
    $len=strlen($str); 
    for($i=0; $i<$len; $i++){ 
     $c=ord($str[$i]); 
     if($c > 128){ 
      if(($c >= 254)) return false; 
      elseif($c >= 252) $bits=6; 
      elseif($c >= 248) $bits=5; 
      elseif($c >= 240) $bits=4; 
      elseif($c >= 224) $bits=3; 
      elseif($c >= 192) $bits=2; 
      else return false; 
      if(($i+$bits) > $len) return false; 
      while($bits > 1){ 
       $i++; 
       $b=ord($str[$i]); 
       if($b < 128 || $b > 191) return false; 
       $bits--; 
      } 
     } 
    } 
    return true; 
} 
?> 
2

Dato che non esiste ancora una funzione isUtf8() esplicita in PHP, ecco come UTF-8 può essere validato con precisione in PHP a seconda della versione di PHP.

modo più facile e più compatibile per convalidare correttamente UTF-8 è ancora tramite espressione regolare utilizzando funzione come:

function isValid($string) 
{ 
    return preg_match(
     '/\A(?> 
      [\x00-\x7F]+      # 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 
     )*\z/x', 
     $string 
    ) === 1; 
} 

Note le due differenze fondamentali per l'espressione regolare offerto dal W3C. Usa una sola subpattern e ha un quantificatore '+' dopo la prima classe di caratteri. Il problema del blocco PCRE persiste ancora, ma la maggior parte è causata dall'utilizzo di subpattern di cattura ripetuta. Ruotando il pattern su un pattern solo una volta e catturando più caratteri a byte singolo in un singolo subpattern, questo dovrebbe impedire a PCRE di esaurire rapidamente lo stack (e causare un segfault). A meno che non si stiano convalidando stringhe con molti caratteri multibyte (nell'intervallo di migliaia), questa espressione regolare dovrebbe essere utile.

Un'altra buona alternativa è l'utilizzo di mb_check_encoding() se l'estensione mbstring è disponibile. Convalida UTF-8 può essere fatto nel modo più semplice:

function isValid($string) 
{ 
    return mb_check_encoding($string, 'UTF-8') === true; 
} 

Si noti, tuttavia, che se si sta utilizzando la versione di PHP prima 5.4.0, questa funzione ha alcuni difetti nella sua validazione:

  • Prima di 5.4.0 la funzione accetta il punto di codice oltre il range Unicode consentito. Ciò significa che consente anche caratteri UTF-8 a 5 e 6 byte.
  • Prima di 5.3.0 la funzione accetta punti di codice surrogato come caratteri UTF-8 validi.
  • Prima di 5.2.5 la funzione è completamente inutilizzabile a causa di non funzionare come previsto.

Come Internet elenca anche numerosi altri modi per convalidare UTF-8, discuterò alcuni di loro qui. Si noti che il deve essere evitato nella maggior parte dei casi.

L'uso di mb_detect_encoding() viene talvolta visto per convalidare UTF-8. Se si dispone di almeno la versione PHP 5.4.0 , lo fa effettivamente lavorare con il parametro stretta via:

function isValid($string) 
{ 
    return mb_detect_encoding($string, 'UTF-8', true) === 'UTF-8'; 
} 

E 'molto importante capire che questo non funziona prima di 5.4.0. È molto difettoso prima di quella versione, poiché controlla solo sequenze non valide, ma consente sequenze eccessive e punti di codice non validi. Inoltre, non si dovrebbe mai usarlo per questo scopo senza il parametro strict impostato su true (in realtà non esegue la convalida senza il parametro strict).

Un modo ingegnoso per convalidare UTF-8 è tramite l'uso del flag 'u' in PCRE. Anche se scarsamente documentato, convalida anche la stringa soggetto. Un esempio potrebbe essere:

function isValid($string) 
{ 
    return preg_match('//u', $string) === 1; 
} 

Ogni stringa deve corrispondere un pattern vuoto, ma l'uso della 'u' bandiera corrisponderà solo su validi stringhe UTF-8. Tuttavia, a meno che non si stia utilizzando almeno lo 5.5.10.La validazione è viziata come segue:

  • Prima 5.5.10, non riconosce 3 e 4 sequenze di byte come valido UTF-8. Poiché esclude la maggior parte dei punti di codice Unicode, questo è un grosso difetto.
  • Prima 5.2.5 permette anche surrogati e punti codice oltre ammessi posto unicode (ad esempio 5 e 6 byte caratteri)

Utilizzando la 'u' comportamento bandiera non hanno un vantaggio se: E 'la il più veloce dei metodi discussi. Se hai bisogno di velocità e stai eseguendo la versione PHP più recente e più grande, questo metodo di convalida potrebbe essere per te.

Un altro modo per convalidare per UTF-8 è tramite json_encode(), che prevede che le stringhe di input siano in UTF-8. Non funziona prima di 5.5.0, ma in seguito le sequenze non valide restituiscono false anziché una stringa. Ad esempio:

function isValid($string) 
{ 
    return json_encode($string) !== false; 
} 

Non consiglierei comunque di fare affidamento su questo comportamento per durare. Le precedenti versioni di PHP hanno semplicemente prodotto un errore su sequenze non valide, quindi non c'è alcuna garanzia che il comportamento corrente sia definitivo.