Ruby Earrings
eRuby で遊ぶ

第3回 外部からの入力

一度直してみましょう

前回では Just another Ruby Hacker, のスクリプトを表示する eRuby HTML を書きました。いかがでしたか、print 文まみれの CGI より作り易いでしょ。しかーし。eRuby が最も威力を発揮するのは作る時ではなく直す時。例えば eRuby の授業があって、宿題が出たとします。すぐできたので嬉しくなって、前回のスクリプトをまえだせんせい(仮名)に見せに行ったとしましょう。

ぼく まえだせんせい(仮名)〜。こういうの作りました。
先生 ほほう。なかなかよくできたね。よしじゃあごほうびをあげよう。
ぼく えーなんだろう(わくわく)。
先生 君だけ特別に、テーブルじゃなくて、リストで書いてみる宿題。あしたまでね。
ぼく げげっ。

うーむ、まえだせんせい(仮名)ってば、イジワル。…じゃあみなさんもこの宿題、やってみてくださいね。(←イジワルなのは、誰?)


ファイルからデータを読むということ

前回のスクリプトは、hackers_data がスクリプトに埋め込んでありました。でも、そんなんじゃ動的なページとは言えませんね。データをファイルに書いておいて、ファイルから読むことにしましょう。作者名とスクリプトをタブで区切った、1データ1行のデータファイル jarh.dat を用意します。CGI としての eruby と同じディレクトリに置いてください。

わたなべ氏	print(eval %-%: Just another ruby hacker, :-)
青山氏	(eval %|eval %:print %/Just another ruby hacker, /:|)
前田氏	'tsuJona rehtbuR ah yrekc,'.unpack('N6c').pack('V6c').display
わたなべ氏	STDOUT << "4a75737420616e6f746865722052756279206861636b65722c".gsub(/../, '=\&').unpack("M")
前田氏	print "Just another Ruby hacker,".unpack("M")
前田氏	$><<"jUST ANOTHER rUBY HACKER,".swapcase
あおき氏	[74,117,115,116,32,97,110,111,116,104,101,114,32,82,117,98,121,32,104,97,99,107,101,114,44,10 ].each do |i| print i.chr end
ふなば氏	'abcdefghijklmnopqrstuvwxyz'.tr('gqtkmevuxndyfohljsrwzciabp','nyae  kcrRt,auorhh e stJub').display

ではこれを読んで表示する eRuby HTML を書いてみましょう。クラス定義は同じで、インスタンス生成のためのデータをどこから持ってくるかだけが違います。

<html><head><title>eRuby test</title>
<meta http-equiv="Content-Type" content="text/html; charset=x-euc-jp"></head>
<%
require "cgi"
class Hacker
  attr_reader :name, :scriptstr
  def initialize(name, scriptstr)
    @name, @scriptstr = name, scriptstr
  end
  def result; eval @scriptstr; end
end
%>
<body><h1>various 'Just another Ruby Hacker,'</h1>
<table border="1">
  <tr>
    <th>HackerName</th>
    <th>Script</th>
    <th>Result</th>
  </tr>
<%File.readlines("jarh.dat").each do |line|
    hacker = Hacker.new(*line.chomp.split(/\t+/))%>
  <tr>
    <td><%=hacker.name%></td>
    <td><%=CGI::escapeHTML(hacker.scriptstr)%></td>
    <td><%hacker.result%></td>
  </tr>
<%end%>
</table>
</body></html>

配列を直接持っていた前のバージョンよりちょっとスッキリしましたね。では実行結果

…って、エラーになるじゃんさー! どして? なにも変わってないのに? わたくしが何かしたとでもおっしゃるの?

はい。しました。変わってなくないです。すごく変えました。ただし、「すごく変わった」のは、Ruby スクリプトとしてどうかという話ではなく、セキュリティの観点から見ての話です。ヒント1。いままでデータはスクリプトの中にありましたが、今回はスクリプトの外から取ってきました。 ヒント2。そのデータを eval しています。

おわかりになりましたか? つまり、外から取ってきた信用ならないデータを eval してしまったんですね。そしてセキュリティチェックにひっかかった、というわけです。

はい? セキュリティチェックって何だっけ何だっけ。Ruby 本をお持ちのかたは p.111 を開いて下さい。お持ちでないかたは今すぐ本屋に走って買ってきて下さい、ってウソウソ。肝心なところだけ引用します。

セキュリティチェック機能の中心は「汚染されたデータ」による危険な操作を許可しないことです。「汚染されたデータ」とは外部から与えられた信頼できないデータのことです。コマンドライン引数、環境変数、外部からの入力は全て汚染されています。

(「オブジェクト指向スクリプト言語Ruby」 p.111 より)

eruby はデフォルトでセキュリティレベル 1 で起動されるので、外部からのデータを eval したりすると、ブブー。SecurityError 例外が発生してしまいます。この例ではあんまり危なくないように思えますが、 たとえばフォームからの入力を受け取ってそのまま eval したりとかは「ダメ。ゼッタイ。」ってカンジでしょ? セキュリティチェック機構は、そういうコワイことをウッカリやってしまうのを防いでくれます。

防いでくれると言っても、CGI でこの類のことを一切やらない、というわけにもいかないことも多いです。そういうとき、セキュリティレベル 1 なら対策があります。「汚れたデータ」を「汚れてないデータだと思うことにする」というワザ(Object#untaint)があるのです。上の例に適用するとすれば、

    hacker = Hacker.new(*line.chomp.split(/\t+/))

を、

    hacker = Hacker.new(*line.untaint.chomp.split(/\t+/))

としてやればエラーになりません。前回のと同じ結果が出ます(ここにはその eRuby は置きません、御自分で確かめてみてください)。でも、普通はこんなにキケンなところに安易に untaint を使っちゃダメですよ。untaint を使う時には、次に掲げるまつもとさんのありがた〜いお言葉を必ず思い出してください。Ruby 本 p.112 から引用します。

Ruby はユーザーが明示的に「汚れ」を取り除いて、自分の首をくくるのを止めたりはしません。

(「オブジェクト指向スクリプト言語Ruby」 p.112 より。 テキスト及び背景の色は引用者による演出です)

ひー、おそろしや。自分の手でキューッとやっちゃうことにならないかどうか、よーく考えてから使って下さいね。


フォームからデータを取得する

さて、背筋を寒くしたところで、CGI の基本、「フォームからの入力を受け取る」をやっておきます。普通の Ruby スクリプトで CGI 書く時と同じです。特別なことはなにもありませんので、例をひとつ作って、そのソースを示すだけにしておきますね。

この例では、誕生日を入力すると、干支とトシ、生まれた曜日を表示してくれます。(*1) epoch 以前にご出生のかたも大丈夫ですからご心配なく。トシをとるのは実は誕生日の1日前だっていうの、ご存知でした? その事実は 4/1 生まれの人には常識で、なぜそうなってるかというのは 2/29 生まれの人が知っていることなんだそうです。:)

(*1) ああもうカンタンそうに見えて難しいんですの、トシの計算って!! こんなに難しいんですもの、トシ訊かれても「イヤー、トシの計算は難しいからネッ」でやり過ごしてもいいと思います。ふんとにもー。某掲示板の奥田綾乃ファンちゃん、おつきあいいただきましてありがとうございました。スクリプトいただいてしまいました。てへ。ありがとね。

| prev || Ruby Earrings || next |

TAKEUCHi Kahori <takeuchi@kahori.com>