web-dev-qa-db-ja.com

無効なUTF-8文字のユーザー入力を処理する方法は?

ユーザーからの無効なUTF-8入力を処理する方法に関する一般的な戦略/アドバイスを探しています。

私のwebappはUTF-8を使用していますが、どういうわけか一部のユーザーは無効な文字を入力します。これにより、PHPの json_encode() でエラーが発生します。全体としては、お勧めできません。

W3C I18N FAQ:Multilingual Forms は「UTF-8以外のデータを受信した場合、エラーメッセージを送り返す必要があります。」と言います。

  • データを入力できるさまざまな場所が多数あるサイト全体で、これを実際にどの程度正確に行う必要がありますか?
  • ユーザーに役立つ方法でエラーをどのように提示しますか?
  • ユーザーがすべてのテキストを失うことがないように、不良フォームデータを一時的に保存および表示するにはどうすればよいですか?悪いキャラクターを取り除く?置換文字を使用し、どのように?
  • データベース内の既存のデータについて、無効なUTF-8データが検出された場合、それを変換して保存し直さなければなりませんか(方法? tf8_encode ()? mb_convert_encoding() ?)、またはデータベースにそのまま残しますが、json_encode()の前に何か(何?)を行いますか?

編集:私はmbstring拡張に非常に精通しており、「PHPでUTF-8がどのように機能するか」を尋ねていません。現実世界の状況でこれをどのように扱ったか経験のある人からのアドバイスが欲しいです。

EDIT2:解決策の一部として、無効な文字をU + FFFD に変換するfastメソッドを見てみたい

37
philfreo

_accept-charset="UTF-8"_属性は、ブラウザが従うべきガイドラインにすぎず、そのように送信することを強制されていません。くだらないフォーム送信ボットが良い例です...

私が通常行うことは、 iconv() を使用するか、信頼性の低い utf8_encode() / utf8_decode() functions、iconvを使用する場合、不良文字を音訳するオプションもあります。

iconv()を使用した例を次に示します。

_$str_ignore = iconv('UTF-8', 'UTF-8//IGNORE', $str);
$str_translit = iconv('UTF-8', 'UTF-8//TRANSLIT', $str);
_

エラーメッセージをユーザーに表示したい場合は、値ごとの受信ではなくグローバルな方法でこれを行うと思いますが、このようなことはおそらくうまくいくでしょう:

_function utf8_clean($str)
{
    return iconv('UTF-8', 'UTF-8//IGNORE', $str);
}

$clean_GET = array_map('utf8_clean', $_GET);

if (serialize($_GET) != serialize($clean_GET))
{
    $_GET = $clean_GET;
    $error_msg = 'Your data is not valid UTF-8 and has been stripped.';
}

// $_GET is clean!
_

次のように、新しい行を正規化し、(非)可視の制御文字を削除することもできます。

_function Clean($string, $control = true)
{
    $string = iconv('UTF-8', 'UTF-8//IGNORE', $string);

    if ($control === true)
    {
            return preg_replace('~\p{C}+~u', '', $string);
    }

    return preg_replace(array('~\r\n?~', '~[^\P{C}\t\n]+~u'), array("\n", ''), $string);
}
_

UTF-8からUnicodeコードポイントに変換するコード:

_function Codepoint($char)
{
    $result = null;
    $codepoint = unpack('N', iconv('UTF-8', 'UCS-4BE', $char));

    if (is_array($codepoint) && array_key_exists(1, $codepoint))
    {
        $result = sprintf('U+%04X', $codepoint[1]);
    }

    return $result;
}

echo Codepoint('à'); // U+00E0
echo Codepoint('ひ'); // U+3072
_

おそらく他のどの選択肢よりも高速です、しかし広範囲にテストしていません。


例:

_$string = 'hello world�';

// U+FFFEhello worldU+FFFD
echo preg_replace_callback('/[\p{So}\p{Cf}\p{Co}\p{Cs}\p{Cn}]/u', 'Bad_Codepoint', $string);

function Bad_Codepoint($string)
{
    $result = array();

    foreach ((array) $string as $char)
    {
        $codepoint = unpack('N', iconv('UTF-8', 'UCS-4BE', $char));

        if (is_array($codepoint) && array_key_exists(1, $codepoint))
        {
            $result[] = sprintf('U+%04X', $codepoint[1]);
        }
    }

    return implode('', $result);
}
_

これはあなたが探していたものですか?

60
Alix Axel

Webアプリから無効な文字を受け取るには、HTMLフォームで想定されている文字セットが関係している可能性があります。 accept-charset属性 を使用して、フォームに使用する文字セットを指定できます。

<form action="..." accept-charset="UTF-8">

また、無効な文字の処理方法に関するポインターについては、StackOverflowの同様の質問をご覧ください。右側の列にありますが、重要なデータの予期しない損失やユーザーの入力の予期しない変更を引き起こす無効な文字をクリーンアップするよりも、ユーザーにエラーを通知する方が良いと思います。

4
Archimedix

入力がUTF-8かどうかを確認し、必要に応じてutf8_encode()を実行するために、かなり単純なクラスを作成しました。

class utf8
{

    /**
     * @param array $data
     * @param int $options
     * @return array
     */
    public static function encode(array $data)
    {
        foreach ($data as $key=>$val) {
            if (is_array($val)) {
                $data[$key] = self::encode($val, $options);
            } else {
                if (false === self::check($val)) {
                    $data[$key] = utf8_encode($val);
                }
            }
        }

        return $data;
    }

    /**
     * Regular expression to test a string is UTF8 encoded
     * 
     * RFC3629
     * 
     * @param string $string The string to be tested
     * @return bool
     * 
     * @link http://www.w3.org/International/questions/qa-forms-utf-8.en.php
     */
    public static function check($string)
    {
        return preg_match('%^(?:
            [\x09\x0A\x0D\x20-\x7E]              # 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
            )*$%xs',
            $string);
    }
}

// For example
$data = utf8::encode($_POST);
2
Nev Stokes

ガベージが入らないようにすることをお勧めします。システムが動かなくなる可能性があるカスタム関数に依存しないでください。提出したデータを、設計したアルファベットに対して単純に調べます。許容可能なアルファベット文字列を作成し、送信されたデータをバイトごとに、配列のように調べます。許容可能な文字を新しい文字列にプッシュし、許容できない文字を省略します。データベースに保存するデータは、ユーザーがトリガーしたデータですが、実際にユーザーが提供したデータではありません。

編集#4:悪いキャラクターを全体に置き換える:�

編集#3:更新日:2010年9月22日@ 1:32 pm理由:返される文字列はUTF-8であり、加えて、証拠として提供したテストファイルを使用しました。

<?php
// build alphabet
// optionally you can remove characters from this array

$alpha[]= chr(0); // null
$alpha[]= chr(9); // tab
$alpha[]= chr(10); // new line
$alpha[]= chr(11); // tab
$alpha[]= chr(13); // carriage return

for ($i = 32; $i <= 126; $i++) {
$alpha[]= chr($i);
}

/* remove comment to check ascii ordinals */

// /*
// foreach ($alpha as $key=>$val){
//  print ord($val);
//  print '<br/>';
// }
// print '<hr/>';
//*/
// 
// //test case #1
// 
// $str = 'afsjdfhasjhdgljhasdlfy42we875y342q8957y2wkjrgSAHKDJgfcv kzXnxbnSXbcv   '.chr(160).chr(127).chr(126);
// 
// $string = teststr($alpha,$str);
// print $string;
// print '<hr/>';
// 
// //test case #2
// 
// $str = ''.'©?™???';
// $string = teststr($alpha,$str);
// print $string;
// print '<hr/>';
// 
// $str = '©';
// $string = teststr($alpha,$str);
// print $string;
// print '<hr/>';

$file = 'http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt';
$testfile = implode(chr(10),file($file));

$string = teststr($alpha,$testfile);
print $string;
print '<hr/>';


function teststr(&$alpha, &$str){
    $strlen = strlen($str);
    $newstr = chr(0); //null
    $x = 0;
    if($strlen >= 2){

        for ($i = 0; $i < $strlen; $i++) {
            $x++;
            if(in_array($str[$i],$alpha)){
                // passed
                $newstr .= $str[$i];
            }else{
                // failed
                print 'Found out of scope character. (ASCII: '.ord($str[$i]).')';
                print '<br/>';
                $newstr .= '&#65533;';
            }
        }
    }elseif($strlen <= 0){
        // failed to qualify for test
        print 'Non-existent.';

    }elseif($strlen === 1){
        $x++;
        if(in_array($str,$alpha)){
            // passed

            $newstr = $str;
        }else{
            // failed
            print 'Total character failed to qualify.';
            $newstr = '&#65533;';
        }
    }else{
        print 'Non-existent (scope).';
        }

if(mb_detect_encoding($newstr, "UTF-8") == "UTF-8"){
// skip
}else{
    $newstr = utf8_encode($newstr);
}


// test encoding:
if(mb_detect_encoding($newstr, "UTF-8")=="UTF-8"){
    print 'UTF-8 :D<br/>';
    }else{
        print 'ENCODED: '.mb_detect_encoding($newstr, "UTF-8").'<br/>';
        }




return $newstr.' (scope: '.$x.', '.$strlen.')';
}
1
Geekster

この質問の完全性のために(必ずしもベストアンサーとは限りません)...

function as_utf8($s) {
    return mb_convert_encoding($s, "UTF-8", mb_detect_encoding($s));
}
1
philfreo

PHPにはマルチバイトの拡張機能があります。チェックしてみてください: http://www.php.net/manual/en/book.mbstring.php

mb_check_encoding() 関数を試してください。

幸運を!

1
Otar

Railsがすべてのブラウザに常にUTF-8データをポストするように強制するために行うことを試してください:

<form accept-charset="UTF-8" action="#{action}" method="post"><div
    style="margin:0;padding:0;display:inline">
    <input name="utf8" type="hidden" value="&#x2713;" />
  </div>
  <!-- form fields -->
</form>

railssnowman.info または 初期パッチ を参照してください。

  1. ブラウザにフォーム送信データをUTF-8エンコーディングで送信させるには、「text/html; charset = utf-8」のContent-Typeヘッダーでページをレンダリングするだけです(またはmeta http-equivタグを使用します)。
  2. ユーザーがページエンコードをいじっていても(ブラウザーがユーザーに許可している場合でも)、ブラウザーにフォーム送信データをUTF-8エンコードで送信させるには、フォームでaccept-charset="UTF-8"を使用します。
  3. ユーザーがページエンコーディングをいじっていても(ブラウザーがユーザーに許可している場合でも)、ブラウザーがIEおよびユーザーがページエンコードを韓国語に切り替え、フォームフィールドに韓国語の文字を入力し、&#x2713;などの値を使用してフォームに非表示の入力を追加します。韓国語文字セット)。
0
yfeldblum

与えられたサブセット外のすべての文字を削除する方法はどうですか。少なくとも私のアプリケーションの一部では、ユーザー名など、[a-Z] [0-9セット]以外の文字の使用を許可しません。この範囲外のすべての文字をサイレントに除去するフィルター関数を作成するか、それらを検出してユーザーに決定をプッシュするとエラーを返します。

0
Elzo Valugi

PHPコードによって出力されるすべてのヘッダーの文字セットとしてUTF-8を設定します

すべてのPHP出力ヘッダーで、エンコーディングとしてUTF-8を指定します。

header('Content-Type: text/html; charset=utf-8');
0
Mr. Nobody