Do You PHP?    
Search Engine Optimization  php5 powerd  Valid XHTML 1.0!  Valid CSS!  このサイトのはてなブックマーク数 



last updated
2006/11/18

counter hits
since 1999/11/06


PECL::filterを試してみる

caution[2006/10/31] filter拡張モジュールはPHP5.2.0からデフォルトで組み込まれます(バージョン0.11.0相当)。また、本記事で使用したfilter 0.9.4とPHP5.2.0やfilter 0.11.0ではAPIがかなり変わっていますので、注意が必要です。APIの詳細はPHPマニュアルにもありますので、参照してください。

入力値のチェックは通常PHPスクリプト側で行うと思います。また、汎用ライブラリとしてPEARみたいにライブラリ化されているものもありますが、PECLでもフィルターモジュールが最近(と言っても2005年10月頃ですが)用意されています。以前から知っていたのですが、PHPマニュアルにもドキュメント化された事もあり、使い方を知っておくためにざっくりと触ってまとめてみました。

なお、2006/05/05時点の最新版は0.9.4です。

使用した環境

まずは試した環境ですが、PHPは以下のようなconfigureオプションを付けたPHP5.1.3です。

●PHP5.1.3のconfigureオプション
--with-apxs2=/usr/local/apache2/bin/apxs \
--prefix=/usr/local/lib/php51 \
--with-pear=/usr/local/lib/php51/pear \
--with-config-file-path=/usr/local/lib/php51/ini/5.1.3 \
--with-config-file-scan-dir=/usr/local/lib/php51/ini.d \
--enable-zend-multibyte \
--enable-mbstring \
--enable-mbregex \
--with-dom \
--with-gd=shared \
--with-jpeg-dir \
--with-png-dir \
--with-zlib-dir \
--with-ttf \
--with-freetype-dir \
--enable-gd-jis-conv \
--with-java=shared,/usr/local/jdk \
--with-xsl \
--with-oci8=shared,/u01/app/oracle/product/10.1.0 \
--with-pdo-oci=shared,/u01/app/oracle/product/10.1.0 \
--with-mysql=shared \
--with-pgsql=shared \
--enable-soap=shared

「--prefix」オプションを付けているため、phpコマンドのパスがデフォルトとは変わっていますが適宜読み替えてください。

インストール

インストール自体は、peclコマンドで行います。というか、これ一発でインストールできるので、あれこれインストールしてます(^^;

●filter拡張モジュールのインストール
# /usr/local/lib/php51/bin/pear config-set preferred_state beta
# /usr/local/lib/php51/bin/pecl install filter
# 

インストールが終わったら、拡張モジュールをロードするようphp.iniに追記します。

●php.iniの編集
            :
extension=filter.so
            :

最後にApacheを再起動し、phpinfoの画面で正しくロードされている事を確認します。

phpinfo画面

動作サンプル

今回のサンプルはプルダウンでフィルタを選択し、入力値をバリデート/サニタイズするものです。動作がややこしいものは「論より証拠」ということで実際に見てもらった方が早いです。EXPERIENCEのページに用意しましたので、色々と試してみてください。

filter_data関数でオプションが使用できる

  • FILTER_VALIDATE_INT
  • FILTER_VALIDATE_REGEXP
  • FILTER_VALIDATE_URL

については、それぞれ

  • 0~10の正数
  • 「スキーム+ホスト名」のパターンにマッチ
  • ハイフン付き郵便番号にマッチ([0-9]{3}-[0-9]{4})

といった制限を設けています。また、「FILTER_UNSAFE_RAW」については、強制終了させていますので悪しからず。

●filter.php
  
<?php
interface Filter
{
    /**
     * 検証を実行する
     * @param string パラメータ
     * @return 検証後のパラメータ
     */
    public function execute($param);
}
class ValidateFilterInt implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_VALIDATE_INT,
                           array('min_range' => 0, 'max_range' => 10));
    }
}
class ValidateFilterBoolean implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_VALIDATE_BOOLEAN);
    }
}
class ValidateFilterFloat implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_VALIDATE_FLOAT);
    }
}
class ValidateFilterRegexp implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_VALIDATE_REGEXP,
                           array('regexp' => '[0-9]{3}-[0-9]{4}'));
    }
}
class ValidateFilterUrl implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_VALIDATE_URL,
                           FILTER_FLAG_SCHEME_REQUIRED + FILTER_FLAG_HOST_REQUIRED);
    }
}
class ValidateFilterEmail implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_VALIDATE_EMAIL);
    }
}
class ValidateFilterIp implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
    }
}
class SanitizeFilterString implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_SANITIZE_STRING);
    }
}
class SanitizeFilterEncoded implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_SANITIZE_ENCODED);
    }
}
class SanitizeFilterSpecialChars implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_SANITIZE_SPECIAL_CHARS);
    }
}
class SanitizeFilterRaw implements Filter
{
    public function execute($param)
    {
        die('sorry, now disabled');
//        return filter_data($param, FILTER_UNSAFE_RAW);
    }
}
class SanitizeFilterEmail implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_SANITIZE_EMAIL);
    }
}
class SanitizeFilterUrl implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_SANITIZE_URL);
    }
}
class SanitizeFilterInt implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_SANITIZE_NUMBER_INT);
    }
}
class SanitizeFilterFloat implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_SANITIZE_NUMBER_FLOAT);
    }
}
class SanitizeFilterQuotes implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_SANITIZE_MAGIC_QUOTES);
    }
}
class FilterCallback implements Filter
{
    public function execute($param)
    {
        return filter_data($param, FILTER_CALLBACK,
                           array($this, 'callback'));
    }

    private function callback() {
        return 'コールバックされました';
    }
}
?>
<?php
    $input_value = '';
    $validate_type = 0;

    $validate_types = array(
                          '検証(int)' => 'ValidateFilterInt',
                          '検証(boolean)' => 'ValidateFilterBoolean',
                          '検証(float)' => 'ValidateFilterFloat',
                          '検証(regexp)' => 'ValidateFilterRegexp',
                          '検証(url)' => 'ValidateFilterUrl',
                          '検証(email)' => 'ValidateFilterEmail',
                          '検証(ip)' => 'ValidateFilterIp',
                          'サニタイズ(string)' => 'SanitizeFilterString',
                          'サニタイズ(encoded)' => 'SanitizeFilterEncoded',
                          'サニタイズ(special chars)' => 'SanitizeFilterSpecialChars',
                          'サニタイズ(raw)' => 'SanitizeFilterRaw',
                          'サニタイズ(email)' => 'SanitizeFilterEmail',
                          'サニタイズ(url)' => 'SanitizeFilterUrl',
                          'サニタイズ(int)' => 'SanitizeFilterInt',
                          'サニタイズ(float)' => 'SanitizeFilterFloat',
                          'サニタイズ(quotes)' => 'SanitizeFilterQuotes',
                          'サニタイズ(callback)' => 'FilterCallback'
                      );

    if (input_has_variable(INPUT_POST, 'input_value') &&
        input_has_variable(INPUT_POST, 'validate_type')) {
        $filter_name = $_POST['validate_type'];
        if (!class_exists($filter_name)) {
            die('<p><font color="red">class' . $filter_name. ' not exist !</font></p>');
        }
        $filter = new $filter_name;
        $filtered_value = $filter->execute($_POST['input_value']);
        if (is_null($filtered_value)) {
            echo '<p><font color="red">入力されていない、もしくは入力された値が不正です。</font></p>';
        }
        else {
            $input_value = $filtered_value;
            echo '<p><font color="green">OK! [' . $input_value . ']</font></p>';
        }
        $validate_type = $_POST['validate_type'];
        echo '<hr>';
    }
    else {
        echo '<p><font color="red">no input !</font></p>';
    }

    echo 'PHP version:' . phpversion();
?>
<form action="" method="post">
<input type="text" name="input_value" value="<?php echo $input_value; ?>">
<select name="validate_type">
<?php
    foreach ($validate_types as $key => $value) {
        $default = '';
        if ($validate_type === $value) {
            $default = ' selected';
        }
        echo '<option value="' .  $value . '"' . $default . '>' . $key;
    }
?>
</select>
<input type="submit" value="おりゃ!"></td>
</form>
<hr>
<?php
    show_source("filter.php5");
?>

なお、PHP5.2.0やfilter0.11.0以降のサンプルは以下のようになります。FILTER_CALLBACKを使う場合に、ちょっと小細工が必要です。filter0.9.4だと素直に動くんですがねぇ。。。

●filter_5.2.0.php
  
<?php
interface Filter
{
    /**
     * 検証を実行する
     * @param string パラメータ
     * @return 検証後のパラメータ
     */
    public function execute($param);
}
class ValidateFilterInt implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_VALIDATE_INT, array('min_range' => 0, 'max_range' => 10));
    }
}
class ValidateFilterBoolean implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_VALIDATE_BOOLEAN);
    }
}
class ValidateFilterFloat implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_VALIDATE_FLOAT);
    }
}
class ValidateFilterRegexp implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_VALIDATE_REGEXP, array('regexp' => '[0-9]{3}-[0-9]{4}'));
    }
}
class ValidateFilterUrl implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_VALIDATE_URL,
                          FILTER_FLAG_SCHEME_REQUIRED + FILTER_FLAG_HOST_REQUIRED);
    }
}
class ValidateFilterEmail implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_VALIDATE_EMAIL);
    }
}
class ValidateFilterIp implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
    }
}
class SanitizeFilterString implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_SANITIZE_STRING);
    }
}
class SanitizeFilterEncoded implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_SANITIZE_ENCODED);
    }
}
class SanitizeFilterSpecialChars implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_SANITIZE_SPECIAL_CHARS);
    }
}
class SanitizeFilterRaw implements Filter
{
    public function execute($param)
    {
        die('sorry, now disabled');
//        return filter_var($param, FILTER_UNSAFE_RAW);
    }
}
class SanitizeFilterEmail implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_SANITIZE_EMAIL);
    }
}
class SanitizeFilterUrl implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_SANITIZE_URL);
    }
}
class SanitizeFilterInt implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_SANITIZE_NUMBER_INT);
    }
}
class SanitizeFilterFloat implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_SANITIZE_NUMBER_FLOAT);
    }
}
class SanitizeFilterQuotes implements Filter
{
    public function execute($param)
    {
        return filter_var($param, FILTER_SANITIZE_MAGIC_QUOTES);
    }
}
class FilterCallback implements Filter
{
    public function execute($param)
    {
        /**
         * filter_var($param, FILTER_CALLBACK, array($this, 'callback'))
         * の場合、
         *
         *  First argument is expected to be a valid callback in ...
         *
         * が発生するため、ちょっと小細工
         */
        return filter_var($param, FILTER_CALLBACK,
                          array('options' => array($this, 'callback')));
    }

    private function callback() {
        echo 'コールバックされました';
        return true;
    }
}
?>
<?php
    $input_value = '';
    $validate_type = 0;

    $validate_types = array(
                          '検証(int)' => 'ValidateFilterInt',
                          '検証(boolean)' => 'ValidateFilterBoolean',
                          '検証(float)' => 'ValidateFilterFloat',
                          '検証(regexp)' => 'ValidateFilterRegexp',
                          '検証(url)' => 'ValidateFilterUrl',
                          '検証(email)' => 'ValidateFilterEmail',
                          '検証(ip)' => 'ValidateFilterIp',
                          'サニタイズ(string)' => 'SanitizeFilterString',
                          'サニタイズ(encoded)' => 'SanitizeFilterEncoded',
                          'サニタイズ(special chars)' => 'SanitizeFilterSpecialChars',
                          'サニタイズ(raw)' => 'SanitizeFilterRaw',
                          'サニタイズ(email)' => 'SanitizeFilterEmail',
                          'サニタイズ(url)' => 'SanitizeFilterUrl',
                          'サニタイズ(int)' => 'SanitizeFilterInt',
                          'サニタイズ(float)' => 'SanitizeFilterFloat',
                          'サニタイズ(quotes)' => 'SanitizeFilterQuotes',
                          'サニタイズ(callback)' => 'FilterCallback'
                      );

    if (filter_has_var(INPUT_POST, 'input_value') &&
        filter_has_var(INPUT_POST, 'validate_type')) {
        $filter_name = $_POST['validate_type'];
        if (!class_exists($filter_name)) {
            die('<p><font color="red">class' . $filter_name. ' not exist !</font></p>');
        }
        $filter = new $filter_name;
        $filtered_value = $filter->execute($_POST['input_value']);
        if (!$filtered_value) {
            echo '<p><font color="red">入力されていない、もしくは入力された値が不正です。</font></p>';
        }
        else {
            $input_value = $filtered_value;
            echo '<p><font color="green">OK! [' . $input_value . ']</font></p>';
        }
        $validate_type = $_POST['validate_type'];
        echo '<hr>';
    }
    else {
        echo '<p><font color="red">no input !</font></p>';
    }

    echo 'PHP version:' . phpversion();
?>
<form action="" method="post">
<input type="text" name="input_value" value="<?php echo $input_value; ?>">
<select name="validate_type">
<?php
    foreach ($validate_types as $key => $value) {
        $default = '';
        if ($validate_type === $value) {
            $default = ' selected';
        }
        echo '<option value="' .  $value . '"' . $default . '>' . $key;
    }
?>
</select>
<input type="submit" value="おりゃ!">
</form>
<a href="filter03.php?a[]=a&a[]=1&b=2">link</a>
<form action="filter03.php" method="post">
<input type="hidden" name="a[]" value="a">
<input type="hidden" name="a[]" value="1">
<input type="hidden" name="b" value="2">
<input type="submit" value="うりゃ!">
</form>
<hr>
<?php
    show_source("filter.php");
?>

いきなりフィルタリングされる?

PHPマニュアルにも書いてありますが、filter拡張モジュールはextensionディレクティブに指定しただけ(要Apache再起動)で、php.iniのfilter.defaultディレクティブで指定されたフィルタが$_GET/$_POST/$_COOKIE/$_REQUESTの各データをフィルタリングします。デフォルトは「stringフィルタ(FILTER_SANITIZE_STRING)」です。つまり、インストール直後でもstringフィルタがかかった状態になります。なお、他のフィルタを指定する場合は、PHPマニュアルの「表1. 存在するフィルタ」の列「名前」にある名称を指定してやります。正直これが分かるまで、結構はまりました。。。

また、$_POST['変数名']とやってもフィルタリング後の値しか取得できません。これもPHPマニュアルに書いてありますが、フィルタリング前の値を取得するにはinput_get関数を使用します。この時、フィルタとしてFILTER_UNSAFE_RAWを指定します。

なお、先のfilter.defaultディレクティブに「unsafe_raw」(FILTER_UNSAFE_RAWに相当)を指定することで、$_GET/$_POST/$_COOKIE/$_REQUESTの各データをフィルタリングしないようにすることもできます。

●php.ini
            :
extension=filter.so
filter.default=unsafe_raw

まとめ

filter拡張モジュールを一通り試してみました。個人的な印象としてですが、バリデータについては、冒頭にもあるとおり、すでに様々なバリデータがライブラリ化されている(公私ともに)と思われますので、若干「時すでに遅し」な感じがします。一方、「いきなりフィルタリング」が非常に有効に機能する場面がありそうな感じがします。クライアントからリクエストしたタイミングでほぼ「強制的に」フィルタリングが行えますので、うまく使うことでセキュリティの強化を図ることができると思います。イメージ的にはFILTER_CALLBACKと組み合わせて、カスタム処理と組み合わせた形になるんじゃないかなぁ、という感じです。



About This Site |  Privacy Policy |  Contact
Copyright © 1999 - 2006 by Hideyuki SHIMOOKA all rights reserved.