2009年6月24日追記
この日記の内容は「当時こうした」というもので、
is_int の動作や使い方に関しては
他によりよいページがあると思います。

なお、「End_Of_SQL」で探してきた人は
http://diarynote.jp/d/10395/20071001.html
を見てください。
========================================
 僕は基本的に型の強い言語の方が好きである。
(でも、コンパイラ言語よりインタプリタ言語が好き。)

 PHP から PostgerSQL を使っているが、
ヒアドキュメントに変数を埋め込む形で
SQL 文を作って渡すようにしている。
 で、SQL インジェクション を防止するため、
サニタイズしてから埋め込む。
文字列なら

$foo = pg_escape_string($foo);
$SQL=<<End_Of_SQL
SELECT ’$foo’;
End_Of_SQL

 NULL を考慮するときは

if(is_null()){
    $foo = ’NULL’;
} else {
    $foo = "’" . pg_escape_string($foo) . "’";
}
$SQL=<<End_Of_SQL
SELECT $foo;
End_Of_SQL

みたいにして。
で、問題は数値。特に整数値。

 PHP は 型の弱い言語で通常は型を強く意識する必要はない。
最初にその変数に代入したときの値で内部に型を持ち、
必要なときに暗黙に型変換を行う。

    $foo = 3;
    echo $foo . ’ +1 は?’;
    echo $foo + 1;
    $foo = ’3’;
    echo $foo . ’ +1 は?’;
    echo $foo + 1;
結果:
    3 +1 は?
    4
    3 +1 は?
    4


 is_numeric という関数はその変数が数値か数値文字列なら
TRUE を返す。

    $foo = 3;
    echo is_numeric($foo);
    $foo = 3.0;
    echo is_numeric($foo);
    $foo = ’3’;
    echo is_numeric($foo);
結果:
    true
    true
    true

しかし、PHP の組み込み関数の is_int は
変数の内部に持つ型が int型かどうかを判定する。

    $foo = 3;
    echo is_int($foo);
    $foo = 3.0;
    echo is_int($foo);
    $foo = ’3’;
    echo is_int($foo);
結果:
    true
    false
    false

 SQL文に渡す前の前チェックで、
変数に is_numeric を噛ますことで
SQL インジェクションは防いでいた。
しかし、整数でなければならないところで
整数値かどうかのチェックをしていなかった。
 で、実験してみた。

CREATE TABLE boo(int_field INTEGER UNIQUE);

みたいなテーブルがある状態で

$foo = 3.1
$SQL=<<End_Of_SQL
SELECT int_field FORM boo
WHERE int_field = $foo
End_Of_SQL

ってのはまぁいい。
int_field を numeric型にキャストして比較されるので。
問題は関数を使ったとき。

CREATE FUNCTION woo(INTEGER) RETURNS INTEGER AS
’SELECT $1’ LANGUAGE SQL;

としてあるときに

$foo = 3.1
$SQL=<<End_Of_SQL
SELECT woo($foo)
End_Of_SQL

とすると エラーが発生する。
 実際、html の Form で
数値を直接入力させるところは無かったが、
SELECT で数値を選ぶところがあり、
Form を改竄して直接値を放り込むテストをしたところ、
PostgreSQL のエラーが出てしまった。

 で、数値を入れて query を投げるときに
数値が整数かどうかをチェックすることにした。
でも、is_int ではだめ。
Form から送られてくるのを $_REQUEST から取り出して
変数に代入するので
内部型は整数ではない。
全部明示的に整数型にキャストすることも検討したが、
意図しない値に変わるのを嫌った。

 で、

function is_int_numeric($var){
    returns is_numeric(var) && ((int)$var == $var)
}

という関数を作った。
但し、これでは

$foo = 3.0
if(is_int_numeric($foo)){
    exit();
}
$SQL=<<End_Of_SQL
SELECT woo($foo)
End_Of_SQL

の場合にエラーとなるので
チェック後に明示的に int型にキャストするようにした。

$foo = 3.0
if(is_int_numeric($foo)){
    exit();
}
$foo = (int)$foo;
$SQL=

コメント