PHP: converti qualsiasi stringa in UTF-8 senza conoscere il set di caratteri originale, o almeno prova


146

Ho un'applicazione che si occupa di client da tutto il mondo e, naturalmente, voglio che tutto ciò che va nei miei database sia codificato UTF-8.

Il problema principale per me è che non so quale sarà la codifica dell'origine di qualsiasi stringa: potrebbe provenire da una casella di testo (l'utilizzo <form accept-charset="utf-8">è utile solo se l'utente ha effettivamente inviato il modulo), oppure potrebbe essere da un file di testo caricato, quindi non ho alcun controllo sull'input.

Ciò di cui ho bisogno è una funzione o classe che assicuri che il contenuto del mio database sia, per quanto possibile, codificato UTF-8. Ci ho provato iconv(mb_detect_encoding($text), "UTF-8", $text); ma questo ha dei problemi (se l'input è "fidanzata" restituisce "fidanzata"). Ho provato molte cose = /

Per i caricamenti di file, mi piace l'idea di chiedere all'utente finale di specificare la codifica che usano e mostrare loro le anteprime di come sarà l'output, ma questo non aiuta contro gli hacker cattivi (in effetti, potrebbe rendere la loro vita un po 'più facile).

Ho letto le altre domande SO sull'argomento, ma sembrano avere tutte sottili differenze come "Ho bisogno di analizzare i feed RSS" o "Scrivo i dati dai siti Web" (o, in effetti, "Non puoi").

Ma ci deve essere qualcosa che almeno abbia una buona prova !


5
Fondamentalmente non è possibile per definizione essere assolutamente corretti, in realtà il tasso di successo nell'indovinare una codifica sconosciuta non è eccezionale. È possibile utilizzare l'euristica, ma sarà corretta meno del 100% delle volte, a seconda del materiale molto meno del 100%. Devi esserne consapevole. Forse qualcuno qui può almeno suggerire una biblioteca con una buona euristica.
Inganno

Certo, so che non esiste una soluzione perfetta, quindi il desiderio di qualcosa che almeno avrà un buon risultato.
Grim ...

Questo potrebbe aiutare: stackoverflow.com/q/505562/642173
Melsi

Hai provato a usare UTF-8//IGNOREcome secondo parametro in iconv?
spara il

Sì, è quello che ho finito per fare. Non perfetto, ovviamente, dato che allora la "fidanzata" diventa "fidanzata", ma è certamente migliore. Come mai TRANSLIT non funziona?
Grim ...

Risposte:


255

Quello che stai chiedendo è estremamente difficile. Se possibile, convincere l'utente a specificare la codifica è il migliore. Prevenire un attacco non dovrebbe essere molto più facile o più difficile in quel modo.

Tuttavia, potresti provare a fare questo:

iconv(mb_detect_encoding($text, mb_detect_order(), true), "UTF-8", $text);

Impostarlo su rigoroso potrebbe aiutarti a ottenere un risultato migliore.


5
Per favore, dai un'occhiata al mb_detect_encodingcodice sorgente nella tua distro php (da qualche parte qui: ext / mbstring / libmbfl / mbfl / mbfl_ident.c). Questa funzione non funziona affatto correttamente. Per alcune codifiche ha anche "return true", lol. Altri sono nelle funzioni Ctrl + c Ctrl + v. Questo perché non è possibile rilevare la codifica senza un qualche tipo di dizionario o approccio statistico (come il mio).
Oroboros102,

1
Il modo in cui lo capisco, mb_detect_encodingpassa attraverso l'elenco delle codifiche fornite e accetta il primo che non ha sequenze di byte non valide nella stringa ... Per le codifiche che non hanno sequenze di byte non valide come ISO-8859-1, è sempre vero . Nessuna euristica "intelligente" e i risultati variano notevolmente con l'elenco (e l'ordine) delle codifiche che passi.
wutz,

Questo sembra funzionare per me. I miei utenti stavano inviando del testo su una pagina utf8 con tinymce, ma per qualche ragione sconosciuta a volte i caratteri non utf8 finivano nel database. Questo è stato risolto, quindi grazie mille.
giorgio79,

@Jeff Day - Grazie per questo. Perdonate la mia ignoranza, cosa intendete con "Impostarlo su Rigoroso"?
Ash501,

[Jeff Day] sta inviando mb_detect_order()anche se è il valore predefinito per questo parametro, perché voleva impostare il rilevamento della codifica rigorosa su true (il 3 ° parametro) :)
jave.web

28

Nella madrepatria Russia abbiamo 4 codifiche popolari, quindi la tua domanda è molto richiesta qui.

Solo con i codici char dei simboli non puoi rilevare la codifica, perché le code page si intersecano. Alcune tabelle codici in diverse lingue hanno persino un'intersezione completa. Quindi, abbiamo bisogno di un altro approccio .

L'unico modo per lavorare con codifiche sconosciute è lavorare con le probabilità. Quindi, non vogliamo rispondere alla domanda "che cos'è la codifica di questo testo?", Stiamo cercando di capire " qual è probabilmente la codifica di questo testo? ".

Un ragazzo qui nel famoso blog tecnologico russo ha inventato questo approccio:

Crea l'intervallo di probabilità dei codici char in ogni codifica che desideri supportare. Puoi costruirlo usando alcuni grandi testi nella tua lingua (ad es. Un po 'di narrativa, usa Shakespeare per l'inglese e Tolstoy per il russo, lol). Otterrai smth in questo modo:

    encoding_1:
    190 => 0.095249209893009,
    222 => 0.095249209893009,
    ...
    encoding_2:
    239 => 0.095249209893009,
    207 => 0.095249209893009,
    ...
    encoding_N:
    charcode => probabilty

Il prossimo. Prendi il testo con codifica sconosciuta e per ogni codifica nel tuo "dizionario di probabilità" cerchi la frequenza di ogni simbolo nel testo con codifica sconosciuta. Somma delle probabilità dei simboli. La codifica con un punteggio più alto è probabilmente il vincitore. Risultati migliori per testi più grandi.

Se sei interessato , posso aiutarti con piacere in questo compito. Possiamo aumentare notevolmente l'accuratezza costruendo un elenco di probabilità a due caratteri.

Btw. mb_detect_encoding di certo non funziona. Sì a tutti. Dai un'occhiata al codice sorgente di mb_detect_encoding in "ext / mbstring / libmbfl / mbfl / mbfl_ident.c".


11

Probabilmente hai provato a farlo, ma perché non usare semplicemente la funzione mb_convert_encoding? Tenterà di rilevare automaticamente il set di caratteri del testo fornito oppure è possibile passargli un elenco.

Inoltre, ho provato a eseguire:

$text = "fiancée";
echo mb_convert_encoding($text, "UTF-8");
echo "<br/><br/>";
echo iconv(mb_detect_encoding($text), "UTF-8", $text);

e i risultati sono gli stessi per entrambi. Come vedi che il tuo testo viene troncato in "fidanzato"? è nel DB o in un browser?


Nel database, a quanto pare, ho appena provato con il tuo codice e sono d'accordo.
Grim ...

1
Verificare che anche le regole di confronto definite nella tabella / colonna siano UTF-8.
Alexey Gerasimov,

@AlexeyGerasimov Immagino di dover davvero indagare iconv. Ho provato a fare un modo quasi puro mb_ *. Cosa ne pensi?
Anthony Rutledge,

5

Non è possibile identificare il set di caratteri di una stringa che è completamente accurato. Ci sono modi per provare a indovinare il set di caratteri. Uno di questi modi, e probabilmente / attualmente il migliore in PHP, è mb_detect_encoding (). Questo eseguirà la scansione della stringa e cercherà occorrenze di cose uniche per determinati set di caratteri. A seconda della stringa, potrebbero non esserci tali eventi distinguibili.

Prendi il set di caratteri ISO-8859-1 vs ISO-8859-15 ( http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1 )

C'è solo una manciata di personaggi diversi e, a peggiorare le cose, sono rappresentati dagli stessi byte. Non c'è modo di rilevare, dato una stringa senza sapere la sua codifica, se il byte 0xA4 dovrebbe significare ¤ o € nella tua stringa, quindi non c'è modo di sapere che è il set di caratteri esatto.

(Nota: potresti aggiungere un fattore umano o una tecnica di scansione ancora più avanzata (es. Cosa suggerisce Oroboros102), per cercare di capire in base al contesto circostante, se il personaggio dovrebbe essere ¤ o €, anche se questo sembra un ponte troppo lontano)

Esistono differenze più distinguibili tra ad esempio UTF-8 e ISO-8859-1, quindi vale comunque la pena provare a capirlo quando non sei sicuro, anche se puoi e non dovresti mai fare affidamento sul fatto che sia corretto.

Lettura interessante: http://kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-string

Esistono altri modi per garantire il set di caratteri corretto. Per quanto riguarda i moduli, prova ad applicare UTF-8 il più possibile (controlla il pupazzo di neve per assicurarti che il tuo invio sia UTF-8 in ogni browser: http://intertwingly.net/blog/2010/07/29/Rails-and -Snowmen ) Fatto ciò, almeno puoi essere sicuro che ogni testo inviato tramite i tuoi moduli è utf_8. Per quanto riguarda i file caricati, prova a eseguire il comando unix 'file -i' tramite ad esempio exec () (se possibile sul tuo server) per facilitare il rilevamento (usando la distinta base del documento.) Per quanto riguarda i dati di scraping, puoi leggere le intestazioni HTTP, che di solito specifica il set di caratteri. Durante l'analisi dei file XML, vedere se i metadati XML contengono una definizione di set di caratteri.

Piuttosto che provare a indovinare automagicamente il set di caratteri, dovresti prima cercare di assicurarti un certo set di caratteri, ove possibile, o cercare di ottenere una definizione dalla fonte da cui stai ricevendo (se applicabile) prima di ricorrere al rilevamento.


Moduli e collegamenti di registrazione e-mail con dati crittografati. È qui che sto cercando di rendere il mio input UTF-8 o niente. Cosa ne pensi della mia risposta? Sono utili i commenti utili. Grazie.
Anthony Rutledge,

3

Ci sono alcune risposte davvero valide e tentativi di rispondere alla tua domanda qui. Non sono un master di codifica, ma capisco il tuo desiderio di avere un puro stack UTF-8 fino al tuo database. Ho usato la utf8mb4codifica di MySQL per tabelle, campi e connessioni.

La mia situazione si riduce a "Voglio solo i miei disinfettanti, validatori, logica aziendale e dichiarazioni preparate per gestire UTF-8 quando i dati provengono da moduli HTML o collegamenti di registrazione e-mail". Quindi, nel mio modo semplice, ho iniziato con questa idea:

  1. Tentativo di rilevare la codifica: $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
  2. Se la codifica non può essere rilevata, throw new RuntimeException
  3. Se l'ingresso è UTF-8, continua.
  4. Altrimenti, se lo è ISO-8859-1oASCII

    un. Tentativo di conversione in UTF-8 (attendere, non terminato)

    b. Rileva la codifica del valore convertito

    c. Se la codifica segnalata e il valore convertito sono entrambi UTF-8, continua.

    d. Altro,throw new RuntimeException

Dalla mia classe astratta Sanitizer

Disinfettante

    private function isUTF8($encoding, $value)
    {
        return (($encoding === 'UTF-8') && (utf8_encode(utf8_decode($value)) === $value));
    }

    private function utf8tify(&$value)
    {
        $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];

        mb_internal_encoding('UTF-8');
        mb_substitute_character(0xfffd); //REPLACEMENT CHARACTER
        mb_detect_order($encodings);

        $stringEncoding = mb_detect_encoding($value, $encodings, true);

        if (!$stringEncoding) {
            $value = null;
            throw new \RuntimeException("Unable to identify character encoding in sanitizer.");
        }

        if ($this->isUTF8($stringEncoding, $value)) {
            return;
        } else {
            $value = mb_convert_encoding($value, 'UTF-8', $stringEncoding);
            $stringEncoding = mb_detect_encoding($value, $encodings, true);

            if ($this->isUTF8($stringEncoding, $value)) {
                return;
            } else {
                $value = null;
                throw new \RuntimeException("Unable to convert character encoding from ISO-8859-1, or ASCII, to UTF-8 in Sanitizer.");
            }
        }

        return;
    }

Si potrebbe sostenere che dovrei separare le preoccupazioni di codifica dalla mia Sanitizerclasse astratta e semplicemente iniettare un Encoderoggetto in un'istanza figlio concreta di Sanitizer. Tuttavia, il problema principale con il mio approccio è che, senza ulteriori conoscenze, rifiuto semplicemente i tipi di codifica che non desidero (e mi affido alle funzioni PHP mb_ *). Senza ulteriori studi, non posso sapere se ciò danneggia alcune popolazioni o meno (o, se sto perdendo informazioni importanti). Quindi, ho bisogno di saperne di più. Ho trovato questo articolo.

Ciò che ogni programmatore deve assolutamente e positivamente sapere sulle codifiche e sui set di caratteri per lavorare con il testo

Inoltre, cosa succede quando i dati crittografati vengono aggiunti ai miei collegamenti di registrazione e-mail (utilizzando OpenSSLo mcrypt)? Questo potrebbe interferire con la decodifica? Che dire di Windows-1252? E le implicazioni sulla sicurezza? L'uso di utf8_decode()e utf8_encode()in Sanitizer::isUTF8sono dubbi.

Le persone hanno sottolineato carenze nelle funzioni PHP mb_ *. Non ho mai avuto il tempo di indagare iconv, ma se funziona meglio delle funzioni mb_ *, fammi sapere.


Ho trovato questo, stackoverflow.com/a/3521396/1429677 ottima risposta a questo problema, ecco la lib github.com/neitanod/forceutf8
Llewellyn

2

Il problema principale per me è che non so quale sarà la codifica dell'origine di qualsiasi stringa: potrebbe provenire da una casella di testo (l'utilizzo è utile solo se l'utente ha effettivamente inviato il modulo), oppure potrebbe essere da un file di testo caricato, quindi non ho alcun controllo sull'input.

Non penso sia un problema. Un'applicazione conosce l'origine dell'input. Se proviene da un modulo, usa la codifica UTF-8 nel tuo caso. Che funzioni. Basta verificare che i dati forniti siano codificati correttamente (validazione). Tieni presente che non tutti i database supportano UTF-8 nella sua gamma completa.

Se è un file non lo salverai codificato UTF-8 nel database ma in forma binaria. Quando si esegue nuovamente l'output del file, utilizzare anche l'output binario, quindi questo è totalmente trasparente.

La tua idea è piacevole che un utente possa dire la codifica, che sia comunque in grado di dirlo dopo aver scaricato il file, poiché è binario.

Quindi devo ammettere che non vedo un problema specifico che sollevi con la tua domanda. Ma forse puoi aggiungere qualche dettaglio in più sul tuo problema.


Vuoi vedere e pubblicare la mia risposta? I commenti costruttivi sono apprezzati. Grazie.
Anthony Rutledge,

1

È possibile impostare un set di metriche per provare a indovinare quale codifica viene utilizzata. Ancora una volta, non perfetto, ma potrebbe cogliere alcune delle mancate da mb_detect_encoding ().


Sì, a proposito di mb_detect_encoding()mancanze, pensi che la mia risposta abbia una possibilità di una palla di neve in estate nel Sahara?
Anthony Rutledge,

1

Se sei disposto a "portarlo alla console", lo consiglierei enca. A differenza del piuttosto semplicistico mb_detect_encoding, usa "una miscela di analisi, analisi statistica, ipotesi e magia nera per determinare le loro codifiche" (lol - vedi la pagina man ). Tuttavia, in genere è necessario passare la lingua del file di input se si desidera rilevare tali codifiche specifiche per Paese. (Tuttavia, mb_detect_encodingha essenzialmente lo stesso requisito, in quanto la codifica dovrebbe apparire "nel posto giusto" nell'elenco delle codifiche passate per poter essere rilevata affatto.)

encaè venuto anche qui: come trovare la codifica di un file in Unix tramite script


1

Sembra che la tua domanda abbia abbastanza risposta, ma ho un approccio che potrebbe semplificarti il ​​caso:

Ho avuto un problema simile nel tentativo di restituire i dati delle stringhe da mysql, anche configurando sia database che php per restituire stringhe formattate su utf-8. L'unico modo per ottenere l'errore era in realtà restituirli dal database.

Infine, navigando attraverso il web ho trovato un modo davvero semplice per gestirlo:

Dando che puoi salvare tutti quei tipi di dati di stringa nel tuo mysql in diversi formati e regole di confronto, quello che devi solo fare è, proprio nel tuo file di connessione php, impostare la raccolta su utf-8, in questo modo:

$connection = new mysqli($server, $user, $pass, $db);
$connection->set_charset("utf8");

Il che significa che prima salvi i dati in qualsiasi formato o confronto e li converti solo al ritorno nel tuo file php.

Spero sia stato utile!



-2
public function convertToUtf8($text) {
    if(!$this->html)
        $this->html = cURL('http://'.$this->url, array('timeout' => 15));

    $html = $this->html;
    preg_match('/<meta.*?charset=(|\")(.*?)("|\")/i', $html, $matches);

    $charset = $matches[2];

    if($charset)
        return mb_convert_encoding($text, 'UTF-8', $charset);
    else
        return $text;
}

Opzioni predefinite cURL:

curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

Ho provato qualcosa del genere. Mi ha aiutato Se si trova nelle informazioni sul meta charset, sto convertendo, altrimenti non faccio nulla.


errr, puoi controllare la tua funzione e correggere le variabili?
Martin,

Che cos'è $ url? Che cos'è $ html?
Martin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.