PHP:元の文字セットを認識せずに任意の文字列をUTF-8に変換するか、少なくとも試してください


146

私は世界中のクライアントを扱うアプリケーションを持っており、当然、データベースに送られるすべてのものをUTF-8でエンコードしたいと思っています。

私にとっての主な問題は、文字列のソースがどのエンコーディングになるかわからないということです-テキストボックスからのものである可能性があります(これ<form accept-charset="utf-8">は、ユーザーが実際にフォームを送信した場合にのみ役立ちます)。アップロードされたテキストファイルからなので、実際には入力を制御できません。

私が必要としているのは、データベースに入るものが可能な限りUTF-8でエンコードされていることを確認する関数またはクラスです。私は試しましたiconv(mb_detect_encoding($text), "UTF-8", $text); が、問題があります(入力が「fiancée」の場合、「fianc」を返します)。私はたくさんのことを試しました= /

ファイルのアップロードについては、エンドユーザーに使用するエンコードを指定して、出力がどのようになるかをプレビュー表示するように依頼するのが好きですが、これは厄介なハッカーには役立ちません(実際、ハッカーに命を吹き込む可能性があります)少し簡単です)。

この件に関して他のSOの質問を読みましたが、「RSSフィードを解析する必要がある」または「Webサイトからデータをスクレイピングする」などの微妙な違いがあるようです(または、実際には「できません」)。

しかし、少なくとも良い試みがあるはずです!


5
基本的に、完全に正しいことを定義することは基本的に不可能です。実際、未知のエンコーディングを推測する成功率はそれほど高くありません。ヒューリスティックを使用することは可能ですが、100%未満の素材によっては、100%未満の時間で正確になります。あなたはそれに気づく必要があります。たぶん、ここの誰かが少なくともヒューリスティックなライブラリを提案できるかもしれません。
だます

確かに、私は完璧な解決策はないことを知っています-したがって、少なくともうまくいくものに対する欲求。
グリム...

これは役立つかもしれません:stackoverflow.com/q/505562/642173
Melsi

UTF-8//IGNOREの2番目のパラメータとして使用してみましたiconvか?
発射

ええ、それは私がやったことです。「fiancée」が「fiance」になるので、明らかに完璧ではありませんが、それは確かに優れています。TRANSLITが機能しないのはなぜですか?
グリム...

回答:


255

あなたが求めていることは非常に難しいです。可能であれば、ユーザーにエンコードを指定させるのが最善です。攻撃を防ぐことは、それほど簡単で難しいものではありません。

ただし、これを試すことができます。

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

strictに設定すると、より良い結果が得られる可能性があります。


5
mb_detect_encodingphpディストリビューション(ここのどこか:ext / mbstring / libmbfl / mbfl / mbfl_ident.c)のソースコードを見てください。この機能はまったく正しく動作しません。エンコーディングによっては、「trueを返す」こともあります(笑)。その他はCtrl + c Ctrl + v関数にあります。これは、なんらかの辞書や統計手法(私のものなど)がないとエンコーディングを検出できないためです。
Oroboros102

1
私が理解する方法mb_detect_encoding、提供されたエンコーディングのリストを調べ、文字列に無効なバイトシーケンスがない最初のエンコーディングを受け入れます。ISO-8859-1などの無効なバイトシーケンスがないエンコーディングは常にtrueです。 。「スマート」なヒューリスティックはなく、結果は、渡すエンコードのリスト(および順序)によって大きく異なります。
wutz

これは私のために働いているようです。ユーザーがtinymceを使用してutf8ページにテキストを送信していましたが、何らかの不明な理由により、utf8以外の文字がデータベースに表示されることがありました。これで直ったので、どうもありがとうございました。
giorgio79

@Jeff Day-これをありがとう。私の無知を許してください、「それを厳格に設定する」とはどういう意味ですか?
Ash501 2014年

[Jeff Day]はmb_detect_order()、このパラメータのデフォルト値であるにもかかわらず送信しています。厳密なエンコーディング検出をtrueに設定したかったためです(3番目のパラメータ):)
jave.web

28

ロシア祖国では4つの一般的なエンコーディングがあるため、あなたの質問はここで大きな需要があります。

シンボルの文字コードによってのみ、コードページが交差するため、エンコーディングを検出できません。異なる言語の一部のコードページには完全な共通部分さえあります。したがって、別のアプローチが必要です。

未知のエンコーディングを処理する唯一の方法は、確率を処理することです。したがって、「このテキストのエンコーディングは何ですか?」という質問には答えたくありません。「このテキストのエンコーディングが最もありそうなものは何か」を理解しようとしています

人気のあるロシアの技術ブログの1人がこのアプローチを発明しました。

サポートするすべてのエンコーディングで、charコードの確率範囲を構築します。あなたの言語でいくつかの大きなテキストを使用してそれを構築することができます(例えば、いくつかのフィクション、英語にはシェイクスピアーを、ロシア語にはトルストイを使用してください、笑)。あなたはこのようなsmthを取得します:

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

次。未知のエンコーディングのテキストを受け取り、「確率辞書」のすべてのエンコーディングについて、未知のエンコードされたテキストのすべてのシンボルの頻度を検索します。シンボルの確率の合計。大きな評価のエンコーディングがおそらく勝者です。大きなテキストのより良い結果。

ご興味がおありでしたら、喜んでお手伝いさせていただきます。2文字の確率リストを作成することで、精度を大幅に向上させることができます。

ところで mb_detect_encoding certanlyが機能しません。はい、そうです。「ext / mbstring / libmbfl / mbfl / mbfl_ident.c」でmb_detect_encodingソースコードを確認してください。


11

あなたはおそらくこれを試しましたが、なぜmb_convert_encoding関数を使用しないのですか?提供されたテキストの文字セットを自動検出しようとしますが、リストを渡すこともできます。

また、私は実行しようとしました:

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

結果はどちらも同じです。テキストが「fianc」に切り捨てられていることをどのように確認しますか?それはDBにありますか、それともブラウザにありますか?


データベースでは、それは思われます-私はあなたのコードを試したところ、同意します。
グリム...

1
テーブル/列に定義した照合がUTF-8であることを確認してください。
Alexey Gerasimov

@AlexeyGerasimov私は本当に調査する必要があると思いiconvます。ほとんど純粋なmb_ *の方法を試してみました。あなたはどう思いますか?
Anthony Rutledge 2017年

5

完全に正確な文字列の文字セットを識別する方法はありません。文字セットを推測する方法はいくつかあります。これらの方法の1つ、そしておそらく現在のところPHPで最高のものは、mb_detect_encoding()です。これは文字列をスキャンし、特定の文字セットに固有のものの出現を探します。文字列によっては、そのような区別可能な出現がない場合があります。

ISO-8859-1文字セットとISO-8859-15(http://en.wikipedia.org/wiki/ISO/IEC_8859-15#Changes_from_ISO-8859-1)の比較

少数の異なる文字しかなく、さらに悪いことに、それらは同じバイトで表されます。バイト0xA4が文字列の¤または€を表すことになっているのかどうか、文字列がエンコードされていることを知らずに文字列が与えられていることを検出する方法はないため、正確な文字セットを知る方法はありません。

(注:ヒューマンファクターまたはさらに高度なスキャン手法(Oroboros102の提案など)を追加して、文字が¤または€である必要があるかどうか、周囲のコンテキストに基づいて理解しようとすることができますが、これは橋のようです遠すぎる)

たとえばUTF-8とISO-8859-1の間には、より明確な違いがあります。そのため、正確であることに依存することはできますが、絶対に信頼するべきではありません。

興味深い読み物:http : //kore-nordmann.de/blog/php_charset_encoding_FAQ.html#how-do-i-determine-the-charset-encoding-of-a-string

ただし、正しい文字セットを保証する方法は他にもあります。フォームについては、UTF-8を可能な限り強化するようにしてください(snowmanをチェックして、すべてのブラウザーで送信がUTF-8になるようにしてください:http : //intertwingly.net/blog/2010/07/29/Rails-and -スノーマン)これで、少なくともフォームから送信されたすべてのテキストがutf_8であることが確実になります。アップロードされたファイルについては、たとえばexec()を介してunix 'file -i'コマンドを実行して(可能な場合はサーバーで)、検出を支援します(ドキュメントのBOMを使用)。データのスクレイピングに関しては、HTTPヘッダーを読み取ることができます。通常は文字セットを指定します。XMLファイルを解析するときは、XMLメタデータに文字セット定義が含まれているかどうかを確認してください。

文字セットを自動的に推測するのではなく、特定の文字セットを可能な限り自分で確認するか、取得元のソースから定義を取得して(該当する場合)、検出に頼る必要があります。


暗号化されたデータを含むフォームとメール登録リンク。ここで、入力をUTF-8または何もしないようにしています。私の答えをどう思いますか?役立つコメントをお待ちしています。ありがとう。
Anthony Rutledge 2017年

3

本当に良い答えがいくつかあり、ここにあなたの質問に答えようとする試みがあります。私はエンコーディングマスターではありませんが、データベースに至るまで純粋な UTF-8スタックを使用したいという要望を理解しています。utf8mb4テーブル、フィールド、接続にMySQLのエンコーディングを使用しています。

私の状況は、「データがHTMLフォームまたは電子メール登録リンクからのものである場合に、サニタイザー、バリデーター、ビジネスロジック、および準備済みステートメントがUTF-8を処理するようにしたいだけ」に要約されます。だから、私の簡単な方法で、私はこの考えから始めました:

  1. エンコーディングを検出しようとしました: $encodings = ['UTF-8', 'ISO-8859-1', 'ASCII'];
  2. エンコーディングが検出できない場合、 throw new RuntimeException
  3. 入力がの場合UTF-8、続行します。
  4. それ以外の場合、ISO-8859-1またはASCII

    a。UTF-8への変換を試みます(待機し、終了しません)

    b。変換された値のエンコーディングを検出する

    c。報告されたエンコードと変換された値の両方がUTF-8である場合は、続行します。

    d。そうしないと、throw new RuntimeException

私の抽象クラスから Sanitizer

消毒剤

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

エンコーディングの懸念を抽象Sanitizerクラスから分離しEncoderオブジェクトをの具象の子インスタンスに単純に挿入する必要があるという主張をすることができますSanitizer。ただし、私のアプローチの主な問題は、知識がなければ、不要なエンコードタイプを単純に拒否することです(PHPのmb_ *関数に依存しています)。さらなる研究がなければ、それが一部の集団に害を与えるかどうか、または重要な情報を失うかどうかはわかりません。だから、もっと学ぶ必要があります。この記事を見つけました。

すべてのプログラマーがテキストを処理するためにエンコーディングと文字セットについて確実に知っておくべきこと

さらに、暗号化されたデータが(OpenSSLまたはを使用して)メール登録リンクに追加されるとmcryptどうなりますか?これはデコードに干渉する可能性がありますか?Windows-1252はどうですか?セキュリティへの影響はどうですか?使用utf8_decode()utf8_encode()ではSanitizer::isUTF8疑わしいです。

PHPのmb_ *関数の欠点が指摘されています。調査に時間をかけたことはありませんがiconv、mb_ * functionsよりもうまく機能する場合はお知らせください。


私はこれを見つけました、stackoverflow.com / a / 3521396/1429677
Llewellyn

2

私にとっての主な問題は、文字列のソースがどのエンコーディングになるかわからないことです-テキストボックスからのものである可能性があります(これは、ユーザーが実際にフォームを送信した場合にのみ役立ちます)、またはアップロードされたテキストファイルからなので、実際には入力を制御できません。

それは問題ではないと思います。アプリケーションは入力のソースを知っています。フォームからの場合は、UTF-8エンコーディングを使用してください。うまくいきました。提供されたデータが正しくエンコード(検証)されていることを確認してください。すべてのデータベースがUTF-8を完全な範囲でサポートしているわけではないことに注意してください。

ファイルの場合は、UTF-8エンコードでデータベースに保存せず、バイナリ形式で保存します。ファイルを再度出力する場合は、バイナリ出力も使用すると、完全に透過的です。

あなたがファイルをダウンロードした後でバイナリであるため、ユーザーがエンコーディングを伝えることができるというのはいい考えです。

だから私はあなたがあなたの質問で提起する特定の問題を見ないことを認めなければなりません。しかし、多分あなたはあなたの問題が何であるかについていくつかの詳細を追加することができます。


私の答えを見て発行していただけますか?建設的なコメントを歓迎します。ありがとう。
Anthony Rutledge 2017年

1

使用されているエンコーディングを推測するための一連のメトリックを設定できます。繰り返しますが、完璧ではありませんが、mb_detect_encoding()からのいくつかのミスをキャッチできます。


はい、mb_detect_encoding()ミスと言えば、私の答えはサハラで夏に雪だるま式になる可能性があると思いますか?
Anthony Rutledge 2017年

1

「これをコンソールに持っていく」ことをいとわないなら、私はお勧めしencaます。かなり単純化したとは異なり、mb_detect_encoding「解析、統計分析、推測、および黒魔術を組み合わせてエンコーディングを決定する」ことを使用します(笑-man ページを参照)。ただし、そのような国固有のエンコーディングを検出する場合は、通常、入力ファイルの言語を渡す必要があります。(ただし、mb_detect_encodingエンコードが渡されたエンコードのリストで「適切な場所に」表示される必要があるため、本質的に同じ要件があります。

encaまたここに来ました:スクリプトを介してUnixでファイルのエンコーディングを見つける方法


1

あなたの質問はかなり答えられているようですが、私はあなたのケースを簡単にするかもしれないアプローチを持っています:

mysqlから文字列データを返そうとする同様の問題がありました。データベースとphpの両方を構成して、utf-8にフォーマットされた文字列を返すようにすることもできました。エラーが発生した唯一の方法は、実際にデータベースからエラーを返すことでした。

最後に、ウェブを航海して、それに対処するための本当に簡単な方法を見つけました。

これらすべてのタイプの文字列データをさまざまな形式と照合順序でmysqlに保存できることを前提として、php接続ファイルで次のように照合順序をutf-8に設定するだけです。

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

Wichとは、最初にデータを任意の形式または照合順序で保存し、phpファイルに戻ったときにのみ変換することを意味します。

お役に立てば幸いです。



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

cURLのデフォルトオプション:

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

私はこのようなものを試しました。それは私を助けました。メタ文字セット情報で見つかった場合、変換しています。それ以外の場合は何もしません。


エラー、関数を確認して変数を修正できますか?
マーティン

$ urlとは何ですか?$ htmlとは何ですか?
マーティン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.