PHP: Konvertieren Sie eine beliebige Zeichenfolge in UTF-8, ohne den ursprünglichen Zeichensatz zu kennen, oder versuchen Sie es zumindest


146

Ich habe eine Anwendung, die sich mit Kunden aus der ganzen Welt befasst, und natürlich möchte ich, dass alles, was in meine Datenbanken gelangt, UTF-8-codiert wird.

Das Hauptproblem für mich ist, dass ich nicht weiß, wie die Quelle einer Zeichenfolge codiert werden soll - es könnte aus einem Textfeld stammen (die Verwendung <form accept-charset="utf-8">ist nur nützlich, wenn der Benutzer das Formular tatsächlich gesendet hat) oder es könnte sein aus einer hochgeladenen Textdatei, so dass ich wirklich keine Kontrolle über die Eingabe habe.

Was ich brauche, ist eine Funktion oder Klasse, die sicherstellt, dass das Material, das in meine Datenbank gelangt, so weit wie möglich UTF-8-codiert ist. Ich habe es versucht, iconv(mb_detect_encoding($text), "UTF-8", $text); aber das hat Probleme (wenn die Eingabe "Verlobte" ist, wird "Verlobte" zurückgegeben). Ich habe viele Dinge ausprobiert = /

Beim Hochladen von Dateien gefällt mir die Idee, den Endbenutzer zu bitten, die von ihm verwendete Codierung anzugeben und ihm eine Vorschau der Ausgabe anzuzeigen, aber dies hilft nicht gegen böse Hacker (tatsächlich könnte dies ihr Leben kosten etwas einfacher).

Ich habe die anderen SO-Fragen zu diesem Thema gelesen, aber sie scheinen alle subtile Unterschiede zu haben, wie "Ich muss RSS-Feeds analysieren" oder "Ich kratzte Daten von Websites" (oder "Sie können nicht").

Aber es muss etwas geben, das zumindest einen guten Versuch hat !


5
Grundsätzlich ist es per Definition nicht möglich, absolut korrekt zu werden. In Wirklichkeit ist die Erfolgsrate beim Erraten einer unbekannten Codierung nicht großartig. Es ist möglich, Heuristiken zu verwenden, diese sind jedoch in weniger als 100% der Fälle korrekt, je nach Material weit unter 100%. Sie müssen sich dessen bewusst sein. Vielleicht kann jemand hier zumindest eine Bibliothek mit guten Heuristiken vorschlagen.
Täuschung

Klar, ich weiß, dass es keine perfekte Lösung gibt - daher der Wunsch nach etwas, das zumindest gut läuft.
Grimmig ...


Haben Sie versucht, UTF-8//IGNOREals 2. Parameter in zu verwenden iconv?
Feuer

Ja, das habe ich letztendlich getan. Natürlich nicht perfekt, da aus "Verlobte" "Verlobter" wird, aber es ist sicherlich besser. Wie kommt es, dass TRANSLIT nicht funktioniert?
Grimmig ...

Antworten:


254

Was Sie verlangen, ist extrem schwer. Wenn möglich, ist es am besten, den Benutzer dazu zu bringen, die Codierung anzugeben. Das Verhindern eines Angriffs sollte auf diese Weise nicht viel einfacher oder schwieriger sein.

Sie können dies jedoch versuchen:

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

Wenn Sie es auf streng einstellen, erhalten Sie möglicherweise ein besseres Ergebnis.


5
Bitte werfen Sie einen Blick auf den mb_detect_encodingQuellcode in Ihrer PHP-Distribution (irgendwo hier: ext / mbstring / libmbfl / mbfl / mbfl_ident.c). Diese Funktion funktioniert überhaupt nicht richtig. Für einige Codierungen hat es sogar "return true", lol. Andere befinden sich in Strg + C Strg + V-Funktionen. Das liegt daran, dass Sie die Codierung ohne ein Wörterbuch oder einen statistischen Ansatz (wie meinen) nicht erkennen können.
Oroboros102

1
So wie ich es verstehe, mb_detect_encoding gehe die Liste der bereitgestellten Codierungen durch und akzeptiere die erste, die keine ungültigen Byte-Sequenzen in der Zeichenfolge enthält ... Bei Codierungen, die keine ungültigen Byte-Sequenzen wie ISO-8859-1 enthalten, ist dies immer der Fall . Keine "intelligenten" Heuristiken, und die Ergebnisse variieren stark mit der Liste (und Reihenfolge) der von Ihnen übergebenen Codierungen.
Wutz

Das scheint für mich zu funktionieren. Meine Benutzer haben Text auf einer utf8-Seite mit tinymce eingereicht, aber aus einem unbekannten Grund sind manchmal nicht utf8-Zeichen in der Datenbank gelandet. Dies hat das Problem behoben. Vielen Dank.
Giorgio79

@ Jeff Day - Danke dafür. Verzeihen Sie meine Unwissenheit, was meinen Sie mit "Streng setzen"?
Ash501

[Jeff Day] sendet mb_detect_order() , obwohl dies der Standardwert für diesen Parameter ist, da er die strikte Codierungserkennung auf true setzen wollte (der 3. Parameter) :)
jave.web

28

Im Mutterland Russland haben wir 4 beliebte Kodierungen, daher ist Ihre Frage hier sehr gefragt.

Nur durch Zeichencodes von Symbolen können Sie keine Codierung erkennen, da sich Codeseiten überschneiden. Einige Codepages in verschiedenen Sprachen haben sogar eine vollständige Überschneidung. Also brauchen wir einen anderen Ansatz .

Die einzige Möglichkeit, mit unbekannten Codierungen zu arbeiten, besteht darin, mit Wahrscheinlichkeiten zu arbeiten. Wir möchten also nicht die Frage "Was ist die Kodierung dieses Textes?" Beantworten, sondern versuchen zu verstehen, " Was ist die wahrscheinlichste Kodierung dieses Textes? ".

Ein Typ hier im beliebten russischen Tech-Blog hat diesen Ansatz erfunden:

Erstellen Sie den Wahrscheinlichkeitsbereich von Zeichencodes in jeder Codierung, die Sie unterstützen möchten. Sie können es mit einigen großen Texten in Ihrer Sprache erstellen (z. B. Fiktion, Shakespeare für Englisch und Tolstoi für Russisch, lol). Sie werden so etwas bekommen:

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

Nächster. Sie nehmen Text in unbekannter Codierung und suchen für jede Codierung in Ihrem "Wahrscheinlichkeitswörterbuch" nach der Häufigkeit jedes Symbols in unbekannt codiertem Text. Summenwahrscheinlichkeiten von Symbolen. Codierung mit höherer Bewertung ist wahrscheinlich der Gewinner. Bessere Ergebnisse für größere Texte.

Wenn Sie interessiert sind kann ich Ihnen bei dieser Aufgabe gerne weiterhelfen. Wir können die Genauigkeit erheblich erhöhen, indem wir eine Wahrscheinlichkeitsliste mit zwei Zeichen erstellen.

Übrigens. mb_detect_encoding funktioniert sicher nicht. Ja überhaupt. Bitte werfen Sie einen Blick auf den Quellcode von mb_detect_encoding in "ext / mbstring / libmbfl / mbfl / mbfl_ident.c".


11

Sie haben es wahrscheinlich versucht, aber warum nicht einfach die Funktion mb_convert_encoding verwenden? Es wird versucht, den Zeichensatz des bereitgestellten Textes automatisch zu erkennen, oder Sie können ihm eine Liste übergeben.

Außerdem habe ich versucht zu rennen:

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

und die Ergebnisse sind für beide gleich. Wie sehen Sie, dass Ihr Text auf "Verlobter" abgeschnitten ist? ist es in der DB oder in einem Browser?


In der Datenbank scheint es - ich habe gerade einen Versuch mit Ihrem Code gemacht und ich stimme zu.
Grimmig ...

1
Stellen Sie sicher, dass die in der Tabelle / Spalte definierte Sortierung auch UTF-8 ist.
Alexey Gerasimov

@AlexeyGerasimov Ich denke, ich muss wirklich nachforschen iconv. Ich habe versucht, einen fast reinen mb_ * Weg zu machen. Was denkst du?
Anthony Rutledge

5

Es gibt keine Möglichkeit, den Zeichensatz einer Zeichenfolge zu identifizieren, der vollständig korrekt ist. Es gibt Möglichkeiten, den Zeichensatz zu erraten. Eine dieser Möglichkeiten und wahrscheinlich / derzeit die beste in PHP ist mb_detect_encoding (). Dadurch wird Ihre Zeichenfolge gescannt und nach Vorkommen von Dingen gesucht, die für bestimmte Zeichensätze einzigartig sind. Abhängig von Ihrer Zeichenfolge gibt es möglicherweise keine solchen unterscheidbaren Vorkommen.

Nehmen Sie den Zeichensatz ISO-8859-1 gegen ISO-8859-15 ( http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1 ).

Es gibt nur eine Handvoll verschiedener Zeichen, und um es noch schlimmer zu machen, werden sie durch dieselben Bytes dargestellt. Es gibt keine Möglichkeit zu erkennen, ob ein Byte 0xA4 ¤ oder € in Ihrer Zeichenfolge bedeuten soll, wenn eine Zeichenfolge angegeben wird, ohne zu wissen, dass sie codiert ist. Daher gibt es keine Möglichkeit, den genauen Zeichensatz zu ermitteln.

(Hinweis: Sie können einen menschlichen Faktor oder eine noch weiter fortgeschrittene Scan-Technik hinzufügen (z. B. was Oroboros102 vorschlägt), um anhand des umgebenden Kontexts herauszufinden, ob das Zeichen ¤ oder € sein sollte, obwohl dies wie eine Brücke erscheint zu weit)

Es gibt deutlichere Unterschiede zwischen z. B. UTF-8 und ISO-8859-1. Es lohnt sich also immer noch, dies herauszufinden, wenn Sie sich nicht sicher sind, obwohl Sie sich niemals darauf verlassen können und sollten, dass es korrekt ist.

Interessante Lektüre: http://kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-string

Es gibt jedoch auch andere Möglichkeiten, um den richtigen Zeichensatz sicherzustellen. Versuchen Sie in Bezug auf Formulare, UTF-8 so weit wie möglich durchzusetzen (überprüfen Sie den Schneemann, um sicherzustellen, dass Ihre Übermittlung in jedem Browser UTF-8 ist: http://intertwingly.net/blog/2010/07/29/Rails-and -Schneemänner ) Wenn Sie dies getan haben, können Sie zumindest sicher sein, dass jeder Text, der über Ihre Formulare gesendet wird, utf_8 ist. Versuchen Sie bei hochgeladenen Dateien, den Unix-Befehl 'file -i' über zB exec () (falls möglich auf Ihrem Server) auszuführen, um die Erkennung zu erleichtern (mithilfe der Stückliste des Dokuments). In Bezug auf Scraping-Daten können Sie die HTTP-Header lesen. das gibt normalerweise den Zeichensatz an. Überprüfen Sie beim Parsen von XML-Dateien, ob die XML-Metadaten eine Zeichensatzdefinition enthalten.

Anstatt zu versuchen, den Zeichensatz automatisch zu erraten, sollten Sie zunächst versuchen, einen bestimmten Zeichensatz selbst zu ermitteln, wo dies möglich ist, oder eine Definition aus der Quelle abrufen, von der Sie ihn erhalten (falls zutreffend), bevor Sie auf die Erkennung zurückgreifen.


Formulare und E-Mail-Registrierungslinks mit verschlüsselten Daten. Dort versuche ich, meine Eingabe UTF-8 oder nichts zu machen. Was denkst du über meine Antwort? Hilfreiche Kommentare sind willkommen. Vielen Dank.
Anthony Rutledge

3

Hier gibt es einige wirklich gute Antworten und Versuche, Ihre Frage zu beantworten. Ich bin kein Codierungsmaster, aber ich verstehe Ihren Wunsch nach einem reinen UTF-8-Stack bis hin zu Ihrer Datenbank. Ich habe die MySQL- utf8mb4Codierung für Tabellen, Felder und Verbindungen verwendet.

Meine Situation beschränkte sich auf "Ich möchte nur, dass meine Desinfektionsmittel, Validatoren, Geschäftslogik und vorbereiteten Anweisungen mit UTF-8 umgehen, wenn Daten aus HTML-Formularen oder E-Mail-Registrierungslinks stammen." Auf meine einfache Art begann ich mit dieser Idee:

  1. Versuch, die Codierung zu erkennen: $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
  2. Wenn die Codierung nicht erkannt werden kann, throw new RuntimeException
  3. Wenn die Eingabe erfolgt UTF-8, fahren Sie fort.
  4. Sonst, wenn es ist ISO-8859-1oderASCII

    ein. Konvertierungsversuch auf UTF-8 versuchen (warten, nicht abgeschlossen)

    b. Ermitteln Sie die Codierung des konvertierten Werts

    c. Wenn die gemeldete Codierung und der konvertierte Wert beide UTF-8sind, fahren Sie fort.

    d. Sonst,throw new RuntimeException

Aus meiner abstrakten Klasse Sanitizer

Desinfektionsmittel

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

Man könnte argumentieren, dass ich Codierungsprobleme von meiner abstrakten SanitizerKlasse trennen und einfach ein EncoderObjekt in eine konkrete untergeordnete Instanz von einfügen sollte Sanitizer. Das Hauptproblem bei meinem Ansatz ist jedoch, dass ich ohne weitere Kenntnisse einfach Codierungstypen ablehne, die ich nicht möchte (und ich verlasse mich auf PHP mb_ * -Funktionen). Ohne weitere Studien kann ich nicht wissen, ob dies einigen Bevölkerungsgruppen schadet oder nicht (oder ob ich wichtige Informationen verliere). Also muss ich mehr lernen. Ich habe diesen Artikel gefunden.

Was jeder Programmierer unbedingt über Codierungen und Zeichensätze wissen muss, um mit Text arbeiten zu können

Was passiert außerdem, wenn meinen E-Mail-Registrierungslinks verschlüsselte Daten hinzugefügt werden (mit OpenSSLoder mcrypt)? Könnte dies die Dekodierung stören? Was ist mit Windows-1252? Was ist mit den Auswirkungen auf die Sicherheit? Die Verwendung von utf8_decode()und utf8_encode()in Sanitizer::isUTF8ist zweifelhaft.

Die Leute haben auf Mängel in den PHP-Funktionen mb_ * hingewiesen. Ich habe mir nie Zeit genommen, um Nachforschungen iconvanzustellen, aber wenn es besser funktioniert als die Funktionen von mb_ *, lassen Sie es mich wissen.


Ich fand dies, stackoverflow.com/a/3521396/1429677 ausgezeichnete Antwort auf dieses Problem, hier ist die lib github.com/neitanod/forceutf8
Llewellyn

2

Das Hauptproblem für mich ist, dass ich nicht weiß, wie die Quelle eines Strings codiert werden soll - es könnte aus einem Textfeld stammen (die Verwendung ist nur nützlich, wenn der Benutzer das Formular tatsächlich gesendet hat) oder es könnte sein aus einer hochgeladenen Textdatei, so dass ich wirklich keine Kontrolle über die Eingabe habe.

Ich denke nicht, dass es ein Problem ist. Eine Anwendung kennt die Quelle der Eingabe. Wenn es aus einem Formular stammt, verwenden Sie in Ihrem Fall die UTF-8-Codierung. Das funktioniert. Überprüfen Sie einfach, ob die angegebenen Daten korrekt codiert sind (Validierung). Beachten Sie, dass nicht alle Datenbanken UTF-8 in vollem Umfang unterstützen.

Wenn es sich um eine Datei handelt, wird UTF-8 nicht in der Datenbank, sondern in binärer Form gespeichert. Wenn Sie die Datei erneut ausgeben, verwenden Sie auch die Binärausgabe. Dies ist dann völlig transparent.

Ihre Idee ist schön, dass ein Benutzer die Codierung erkennen kann, sei es aber trotzdem nach dem Herunterladen der Datei, da sie binär ist.

Ich muss also zugeben, dass ich kein bestimmtes Problem sehe, das Sie mit Ihrer Frage ansprechen. Aber vielleicht können Sie weitere Details zu Ihrem Problem hinzufügen.


Würden Sie meine Antwort sehen und herausgeben? Konstruktive Kommentare sind willkommen. Vielen Dank.
Anthony Rutledge

1

Sie können eine Reihe von Metriken einrichten, um zu erraten, welche Codierung verwendet wird. Wieder nicht perfekt, könnte aber einige der Fehler von mb_detect_encoding () abfangen.


Ja, mb_detect_encoding()ich spreche von Fehlschlägen. Glaubst du, meine Antwort hat im Sommer in der Sahara die Chance eines Schneeballs?
Anthony Rutledge

1

Wenn Sie bereit sind, "dies zur Konsole zu bringen", würde ich empfehlen enca. Im Gegensatz zu den eher simplen mb_detect_encodingverwendet es "eine Mischung aus Analyse, statistischer Analyse, Vermutung und schwarzer Magie, um ihre Kodierungen zu bestimmen" (lol - siehe Manpage ). Normalerweise müssen Sie jedoch die Sprache der Eingabedatei übergeben, wenn Sie solche länderspezifischen Codierungen erkennen möchten. (Hat jedoch im mb_detect_encodingWesentlichen die gleiche Anforderung, da die Codierung "an der richtigen Stelle" in der Liste der übergebenen Codierungen erscheinen müsste, damit sie überhaupt erkennbar ist.)

encakam auch hier hoch: So finden Sie die Codierung einer Datei in Unix über Skripte


1

Es scheint, dass Ihre Frage ziemlich beantwortet ist, aber ich habe einen Ansatz, der Ihren Fall vereinfachen kann:

Ich hatte ein ähnliches Problem beim Versuch, Zeichenfolgendaten von MySQL zurückzugeben, und konfigurierte sogar Datenbank und PHP so, dass Zeichenfolgen zurückgegeben wurden, die mit utf-8 formatiert waren. Der einzige Weg, wie ich den Fehler bekam, war, sie tatsächlich aus der Datenbank zurückzugeben.

Als ich schließlich durch das Web segelte, fand ich einen wirklich einfachen Weg, damit umzugehen:

Wenn Sie alle diese Arten von Zeichenfolgendaten in Ihrer MySQL in verschiedenen Formaten und Kollatierungen speichern können, müssen Sie die Kollatierung direkt in Ihrer PHP-Verbindungsdatei wie folgt auf utf-8 setzen:

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

Dies bedeutet, dass Sie zuerst die Daten in einem beliebigen Format oder einer beliebigen Sortierung speichern und sie erst bei der Rückkehr in Ihre PHP-Datei konvertieren.

Hoffe es war hilfreich!



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

Standardoptionen von cURL:

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

Ich habe so etwas versucht. Es hat mir geholfen. Wenn in Meta-Zeichensatz-Informationen gefunden, konvertiere ich, sonst tue ich nichts.


ähm, können Sie bitte Ihre Funktion überprüfen und die Variablen korrigieren?
Martin

Was ist $ url? Was ist $ html?
Martin
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.