[groonga-dev,02204] Re: PHPなどによる検索文字列エスケープの基準

Back to archive index

HAYASHI Kentaro hayas****@clear*****
2014年 3月 31日 (月) 13:25:26 JST


林です。

On Sun, 30 Mar 2014 18:46:30 +0900
Kimura A <a.kim****@live*****> wrote:

> お世話になっています。素人プログラマーの木村です。
> 以下の記事を参考にmroonga_escape()の導入を検討していたのですが、ちょっと判断に迷ってしまったのでご相談してみることにしました。
> http://qiita.com/groonga/items/8393520a7eff5a71dd57
> 
> 
> たとえば以下のようなクエリをDBに送りたい場合、DB側で処理されるmroonga_escape()は使い方が難しいように思います。
> 
> SELECT * FROM `table_name` 
> WHERE MATCH(`target_column`) 
> AGAINST(
>     '+Word1_()_withBrackets +(Word2_"_withQuote Word3_+_withPlus) -Word4_-_withMinus' IN BOOLEAN MODE
> );
> 
> このクエリの目的は、以下の条件に合ったレコードを取得することです。
> ・「Word1_()_withBrackets」を含む
> ・「Word2_"_withQuote」または「Word3_+_withPlus」を含む
> ・「Word4_-_withMinus」を含まない
> 
> これに単純にmroonga_escape()を加筆して、
> 
> SELECT * FROM `table_name` 
> WHERE MATCH(`target_column`) 
> AGAINST(
>     mroonga_escape(
>         '+Word1_()_withBrackets +(Word2_"_withQuote Word3_+_withPlus) -Word4_-_withMinus'
>     ) IN BOOLEAN MODE
> );
> 
> のようにしてしまうと、結果的には
> 
> SELECT * FROM `table_name` 
> WHERE MATCH(`target_column`) 
> AGAINST(
>     '\\+Word1_\\(\\)_withBrackets \\+\\(Word2_\\"_withQuote Word3_\\+_withPlus) \\-Word4_\\-_withMinus' IN BOOLEAN MODE
> );
> 
> というクエリを送ったのと同じことになってしまって、所期の目的を果たすことができません(詳しい仕様は理解していませんが、実験してみた範囲ではそのような挙動に見えます)。
> 

そうですね。mroonga_escapeは渡された文字列に対して一律にエスケープ処理を適用するので、上記の例だと不要なところまで
エスケープしてしまいますね。本来ほしいのは以下の文字列なので。

 '+Word1_\\(\\)_withBrackets +(Word2_"_withQuote Word3_+_withPlus) -Word4_-_withMinus'

> 
> なので、できればプログラム(PHP)側でmroonga_escape()近似の処理を行いたいと思っています。
> たとえば以下のように、あらかじめ準備したPHP関数mroongaEscapeで問題のある記号をエスケープして、
> 
> $word1 = mroongaEscape('Word1_()_withBrackets');
> 
> その上でこの変数$word1をクエリに組み込めば、安全に複合条件での検索を行える、という具合にしたいわけです。
> 
> そこで質問なのですが、この場合、PHP関数mroongaEscapeはどのような挙動を満たすべきでしょうか?
> ガイドライン的なものがぜひ欲しいです。今のところ、
> ・「'"()~+><-*」に「\\」を前置する
> ・ただし、それらの記号にあらかじめ「\」が前置されている場合は処理対象としない
> というような感じで考えているんですが(いざ実装しようとすると、なかなか難しくて苦戦中です…)。
> 

mroonga_escapeがデフォルトでエスケープするのは以下の文字なので、

SELECT mroonga_escape("+-<>~*()\"\:");
'\\+\\-\\<\\>\\~\\*\\(\\)\\"\\:

* +-<>~*()": と'\0' (mroonga_escapeがデフォルトでエスケープする)
* ' \r \n (mroonga_escapeがデフォルトではエスケープしない)
  (http://www.php.net/manual/ja/mysqli.real-query.phpより)

上記を対象となる語句に対してエスケープ(\\を前置)する、というようにすれば同じことができそうです。

http://mroonga.org/ja/docs/reference/udf/mroonga_escape.html にデフォルトで
エスケープされる文字の説明がまだなかったんですね。。。追加しとかないと。

> またこれを適用する場合、素のMySQL仕様に準拠したエスケープ処理(PHPにおけるmysqli::real_escape_string()など)は特にしなくて大丈夫ですよね?
> 盲点になりやすいポイントなどあればご示唆いただけると助かります。
> 

準拠していればしなくて大丈夫かと思いますが、PHPですでにあるものについて敢えて自分で再実装するのは行儀がよろしくない
(のとうっかりミスで余計な穴を作らないようにしたほうがいい)ので、

* mroongaEscapeでは +-<>~*()をエスケープ
* ' "などについてはreal_escape_stringにまかせる

  サーバーレベルで設定するなり API 関数 mysqli_set_charset() を使うなりして、 文字セットを明示しておく必要があります。
  この文字セットがmysqli_real_escape_string()に影響を及ぼします。詳細は文字セットの概念を参照ください。

  mysqli_real_escape_string だと↑なことが書いてあるので

などと役割分担するのが良いのではと思いました。

-- 
HAYASHI Kentaro <hayas****@clear*****>




groonga-dev メーリングリストの案内
Back to archive index