web-dev-qa-db-ja.com

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

私は世界中からのクライアントを扱うアプリケーションを持っています、そしてもちろん、私は私のデータベースに入るすべてがUTF-8でエンコードされていることを望みます。

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

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

ファイルをアップロードする場合、エンドユーザーに使用するエンコーディングを指定して、出力がどのようになるかをプレビューで表示するように依頼するというアイデアが気に入っています。もう少し簡単)。

この件についての他のSO質問を読んだことがありますが、「RSSフィードを解析する必要がある」や「Webサイトからデータを取得する」など、微妙な違いがあります。できません ")。

しかし、少なくとも善を持っているものがなければなりませんtry

137
Grim...

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

しかし、あなたはこれをやってみることができます:

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

Strictに設定するとより良い結果が得られるでしょう。

233
Jeff Day

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

コードページが交差しているため、シンボルのcharコードだけではエンコーディングを検出できません。さまざまな言語のコードページの中には、完全に交差しているものもあります。だから、私たちは別のアプローチが必要です

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

ロシアの人気技術ブログの1人が、このアプローチを考案しました。

サポートしたいすべてのエンコーディングでcharコードの確率範囲を構築します。あなたは、あなたの言語のいくつかの大きなテキストを使ってそれを作ることができます(例えば、ある小説、英語にはShakespeare、ロシアにはTolstoy、笑)。あなたはこんな風になるでしょう:

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

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

もし興味があるなら、私は喜んでこの仕事を手伝ってくれる。 2文字コードの確率リストを作成することで、精度を大幅に向上させることができます。

ところで。 mb_detect_encodingは確実には機能しません。はい、全然。どうぞ、 "ext/mbstring/libmbfl/mbfl/mbfl_ident.c"にあるmb_detect_encodingソースコードをご覧ください。

27
Oroboros102

おそらくこれを試したことがあるでしょうが、なぜ単に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またはブラウザのどちらにありますか?

9

完全に正確な文字列の文字セットを識別する方法はありません。文字セットを推測する方法があります。これらの方法の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バイトが文字列の¤または€を意味するかどうかを検出する方法はないため、正確な文字セットを知る方法はありません。

(注:文字が¤または€であるべきである場合、周囲の状況に基づいて把握しようとするために、人的要因、またはさらに高度なスキャン技術(Oroboros 102が提案するものなど)を追加できます。遠すぎる)

より明確な違いがあります。 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を強制するようにしてください(すべてのブラウザでyoutの投稿がUTF-8になるように、snowmanをチェックしてください: http://intertwingly.net/blog/2010/07/29/Rails-and-Snowmen )それが済んでいれば、少なくともフォームから送信されたすべてのテキストがutf_8であることは確実です。アップロードされたファイルに関しては、unix 'file -i'コマンドを実行してみてください。 (ドキュメントのBOMを使用して)検出を補助するためにexec()(可能であればサーバー上で)。データの削り取りに関しては、通常は文字セットを指定するHTTPヘッダーを読むことができます。 XMLファイルを解析するときは、XMLメタデータに文字セット定義が含まれているかどうかを確認してください。

自動的に文字セットを推測するのではなく、可能であれば特定の文字セットを自分自身で確認するか、検出に頼る前に入手可能なソースから定義を取得するようにしてください(該当する場合)。

5
matthiasmullie

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

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

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

バイナリだから、ファイルをダウンロードした後にユーザがエンコードを指示できるのであれば、あなたの考えは素晴らしいです。

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

2
hakre

ここにあなたの質問に答えるための本当に良い答えと試みがいくつかあります。私はエンコーディングマスターではありませんが、pure UTF-8スタックをデータベースまで使用することを望んでいます。私はテーブル、フィールド、そしてコネクションにMySQLのutf8mb4エンコーディングを使っています。

私の状況は、「データが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から

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はどうですか?セキュリティへの影響はどうですか? Sanitizer::isUTF8utf8_decode()utf8_encode()を使用することは疑わしいです。

人々はPHP mb_ *関数の欠点を指摘しました。私はiconvを調査するのに時間をかけませんでした、しかし、それがmb_ *関数よりうまく機能するならば、私に知らせてください。

2

「これをコンソールに持っていく」という意志があるのであれば、encaをお勧めします。かなり単純化されたmb_detect_encodingとは異なり、それは "それらのエンコーディングを決定するために解析、統計分析、推測、そしてブラックマジックの混合"を使用します(lol - manページ を参照)。ただし、そのような国固有のエンコードを検出したい場合は、通常、入力ファイルの言語を渡す必要があります。 (しかしながら、mb_detect_encodingは、それがまったく検出可能であるためには、渡されたエンコードのリストの中で「正しい場所に」表示されなければならないので、本質的に同じ要件を持ちます。)

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

1
wutz

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

1
Parris Varney

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

私はmysqlから文字列データを返そうとする同様の問題を抱えていました。さらに、データベースとphpの両方をutf-8にフォーマットされた文字列を返すように設定していました。私がエラーを得た唯一の方法は実際にデータベースからそれらを返すことでした。

最後に、Webを介してセーリングすると私はそれに対処するための本当に簡単な方法を見つけました:

あなたがmysqlに異なるフォーマットと照合順序でそれらすべてのタイプの文字列データを保存できることを与えるために、あなたがする必要があるのはあなたのphp接続ファイルで、次のようにutf-8に照合順序を設定することです

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

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

役に立ったと思います。

0
Quel Pino