miau's blog?

LIKE のエスケープと addcslashes()

半年くらい前の話。

DB_DataObject の escape() - miau's blog?

この辺で LIKE のエスケープについて書いたけど、これをブクマしてくれてる人がいて。

はてなブックマーク - TeahousePhantomBookMark - escape

このひとつ上のブクマ

[ThinkIT] 第2回:クエリを使用したSQLインジェクション (2/3)

によると addcslashes() でエスケープできるとのこと。あれー?そんなはずは。




■マニュアルの確認

addcslahes() のマニュアルには当時こんなサンプルコード&説明が書いてまして。

<?php
echo addcslashes('foo[ ]', 'A..z');
// output: \f\o\o\[ \]
// All upper and lower-case letters will be escaped
// ... but so will the [\]^_` and any tabs, line
// feeds, carriage returns, etc.
?>

「A..z」で指定した場合は「[\]^_`」もエスケープされる・・・とここまでは ASCII CODE 順だからいいとして(※これらの記号は Z と a の間に入ってるので)。タブやら LF、CR なんかもエスケープされるって書いてるでしょ。こんな関数でエスケープしちゃダメだってば。

■実際の動作確認

念のため動作検証してみよう、ということで。

<?php
for ($n = 0x00; $n < 0x80; $n++) {
$c = chr($n);
$escaped = addcslashes($c, 'A..z');
if (strlen($escaped) == 2) {
print ($n < 0x20 ? $escaped : $c);
}
}
?>

エスケープされた文字だけを出力するサンプル。2 文字になってたらエスケープされてるとみなす感じ。で、結果は・・・

ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz

\t とか \n とかが入っていないところをみると、タブや改行はエスケープされない様子。マニュアルが間違ってるパターンか・・・。

■バグ報告

ということで、ドキュメントの誤りを指摘してみた。今までの経験から、下手な英語で説明するよりも再現コードを書いたほうが通じる率が高いので、上記のコードを張り付けてドキュメントの動作と矛盾する旨を書いて。

PHP Bugs: #46054: comment of addcslashes 'A..z' is wrong

すると・・・しばらく放置された後に「その動作でいいんだよ」とか言われた。やっぱり通じてない。orz
その後「間違ってるのはドキュメントのほうだよ!」って言ったら通じたらしく、直してくれた↓。やっぱり英作文は苦手だ・・・。

PHP: addcslashes - Manual

<?php
echo addcslashes('foo[ ]', 'A..z');
// output: \f\o\o\[ \]
// All upper and lower-case letters will be escaped
// ... but so will the [\]^_`
?>


■結論というか

なにはともあれ、LIKE のエスケープは addcslashes() でできるという話。ただマルチバイトは考慮しないと思うので、Shift_JIS に対して使うとたぶんアウト。

それにしても LIKE 用のエスケープってずいぶん軽視されてる気がする。RDBMS の差異を吸収するライブラリでちゃんと実装してくれないと、移植性が下がっちゃうんだけどな。Access なんかは LIKE 検索のワイルドカードが「%」じゃなくて「*」だった気がするし。

■本題とは関係ないけど

ついでに。ThinkIT の記事でこの処理がちょっと気になる。

$sub = addcslashes(mysql_real_escape_string("%something_"),"%_");

処理内容は同じなんだけど、個人的にはこう書くべきだと思う。(prepared statement を使えって話は置いといて。)

$sub = mysql_real_escape_string(addcslashes("%something_", "%_"));

なぜかというと【ユーザ入力値中の「%_\」を LIKE のメタキャラクタと区別すべくエスケープしたもの】を【文字列リテラル中に埋め込むために「'\」をエスケープする】必要があるから。

・・・といってもわかりにくいかもしれないので、実例を。たとえばユーザ入力値が $input で、「シングルクォートの後にユーザ入力値が続く」文字列を LIKE 検索したい場合は、

$condition = sprintf(
"hoge LIKE '%s'",
mysql_real_escape_string("'" + addcslashes($input, "%_") + "%")
);

こう書くのが意図どおりの処理順なわけで。最初の処理は「たまたま結果が一緒になっちゃった」みたいですごく気持ち悪いと思うんですけど、いかがでしょう?


(2009-04-07 追記)

英語どうとか以前に「伝えたいことがタイトルにしか書いてないからわかりにくい」「ドキュメント間違ってるよ、って本文にも書いたほうが」との指摘をいただきました。確かにそのとおり。

せめて「Expected result」のところを「Expected result(if document is correct)」とかにしておけばよかったなー、なんて反省中です。
posted at 21:31:11 on 2009-04-06 by miau - Category: PHP No Trackbacks - Permalink

TrackBack

このエントリにトラックバックはありません
現在トラックバックは受け付けていません。

Comments

shunsuke wrote:

$sub = mysql_real_escape_string(addcslashes("%something_"),"%_");
↑の説明の箇所ですが、括弧の対応がズレてる気がします。
↓ではないでしょうか?
$sub = mysql_real_escape_string(addcslashes("%something_","%_"));

最近だと、mysql_real_escape_stringは第二引数の指定も必要ですね。
$sub = mysql_real_escape_string(addcslashes("%something_","%_"),$db);
2009-10-13 20:49:02

miau wrote:

ご指摘ありがとうございます。括弧の対応については修正しました。

第二引数については検証環境がなくてちゃんとしたことが言えないので、ひとまず本文への反映は保留させていただきます。
2009-10-14 16:40:29

Add Comments

現在コメントは受け付けていません。
お手数ですが、 こちら のコメント欄にでも記載していただければと思います。