<y>

Splatoonプレイヤー兼Webエンジニア

日報 2016/08/16

今日ちょっと正規表現の沼に足を取られてしまったのでそれを書いていきたい。

やろうとしたこと

Ruby on Railsでアプリケーションを作っていて、「お知らせ」(notification)というモデルを新規に登録する際に起きた話。
「お知らせ」にはタイトルや登録日といったプロパティがあるのだが、今回問題になったのはURLというプロパティ。URLにはwebページのリンクが入るのだが、このURLリンクをユーザーが入力する際のバリデーションをどうするかという話になった。
rails url バリデーション」といった言葉でググると色々出てくるのだが、結論として「こちらがどんなに精密にURLのバリデーションチェックをしようが、ユーザーが存在しないURLを入力したら意味がなくなる。形式的なバリデーションは出来ても、ユーザーの入力によっては無力だ」という話になった。これにはまあ色々な意見があると思うのだが、今回作成しているアプリケーションは、一つの企業の組織内の人間だけが利用するものなので、精緻なバリデーションは不要だろうということになった。

本題

今回のバリデーションの内容は、「全角と半角カタカナのみを禁止にする」というものになった。早速ググッて色々調べたらなにやらよさげな正規表現が。

 /^[ -~。-゜]*$/

正規表現において、^は行頭、$は行末を表す。つまり上記の正規表現は、行頭〜行末の間に、半角の文字が一文字以上入っていれば大丈夫という旨だということ。
なるほど、これなら良さそうだと思い、モデルクラスに

validates :url, format: { with:  /^[ -~。-゜]*$/ }

と記述してサーバーを再起動し、お知らせの新規登録画面に遷移しようとした。 そして表示された画面がこちら。 f:id:yklitter:20160818000454p:plain

エラーなのかと萎えつつエラー内容を読んだら、
「その正規表現は複数行向けのアンカー(^と$)を使っているけど、脆弱性があるよ。\Aと\zって知ってる?それとも:multilineオプションをtrueにするの忘れた?」という内容だった。(英語が苦手なので誤訳があったら大変申し訳ない)
脆弱性?と思い調べてみたら、Ruby正規表現機能はデフォルトで複数行モードになっているとのことだった。
例を挙げると、/^[a-z]*$/という、「半角小文字アルファベットだけで構成された文字列ならおk」という正規表現バリデーションに、

ほげふが全角
hogefuga

という複数行の文字列を通そうとすると、通ってしまう。
これは、半角小文字アルファベットだけで構成された文字列「の行がある」という理由でバリデーションを通過する。「^」はあくまでも行頭であり、行の頭であれば問題無いと判断されるようだ。
これではバリデーションが意味をなさなくなってしまい、セキュリティリスクがある。そのため、^と$の代わりに「\A」と「\z」を使う。「\A」は文字列の先頭、「\z」は文字列の末尾を表すとのことで、なるほどこれなら複数行でも問題なさそうだ。
railsはこのようにセキュリティリスクがある正規表現をArgumentErrorとして表示してくれるのか…と感動してしまうワンシーンだった。

その後

\Aと\zを使って、/\A[ -~。-゜]*\z/に書きなおしたぜ!と意気揚々と「あいう」という全角文字列をURLの入力欄にぶち込んだら、あっさり通過してしまった。あれ…。
全角アルファベットが含まれているとしっかりとエラーメッセージを吐くのだが、日本語のみだとすんなり通る。
マジか…とげんなりしていたところ、どうやらShift_JISの場合のやり方らしい。正規表現沼と文字コード沼はつながっていた…。
で、まだどうするかを模索中。
沼は深い。

参考にしたサイト

Ruby正規表現で全角と半角のチェック(バリデーション)する

正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう | 徳丸浩の日記

qiita.com