雑記

2000|01|
2003|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|
2007|01|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|09|11|
2009|02|03|05|06|07|08|10|11|12|
2010|01|03|04|05|06|07|08|09|10|
2011|05|06|09|10|
2012|03|07|09|12|
2013|01|02|04|05|07|08|10|11|
2014|04|05|08|10|12|
2015|01|05|
2016|09|
2024|06|
2025|01|

2010-09-20 [長年日記]

[Hiki][tDiary][Ruby] HikiDoc 改造版ふたたび

HikiDocの改造版をgithubで公開しました。HikiやtDiaryでhikidoc.rbを差し替えるだけで機能すると言う点ではかわりませんが、今回は開き直ってオリジナル記法との互換性を捨てているので注意が必要です。

目玉はブロック要素の記述の強化でプラグインを使ったやっつけ仕事とは違い、

  • ブロック要素の入れ子に対応
  • li, dd, td 要素内へのブロックの記述に対応

といったことがちゃんとできます。

その他の主な変更は以下のとおりです。

  • 段落やリストで「改行+行頭スペース」と書くと<br />になる
  • title属性に対応
  • 文字飾り系の充実
  • mimeTeX 対応

詳しくはTextFormattingRules.jaまたはテストを参照してください。 ----[green]

----[green]

以下に[[TextFormattingRules.ja|http://github.com/hs/hikidoc/raw/master/TextFormattingRules.ja]]のコピペを貼っておきます。tDiaryはhikidocに渡す前に"!"1個の見出しだけなんか前処理をしていてうまく変換できないのでその部分だけちょっと誤魔化してます。Hikiの場合は[[こちら|http://www.on-sky.net/~hs/misc/?TextFormattingRules.ja]]

基本

段落と改行

  • テキスト内の改行は通常無視され、連続した複数の行は連結されて1つのパラグラフになります。
  • 改行した次の行の行頭にスペースを入れると、改行(<br>)タグが挿入され、表示的にも改行されます。
  • 空行(改行のみ、またはスペース、タブだけの行)はパラグラフの区切りになります。

[border center]

記述例出力例
((((((
((((((
((((((
単に
折り返した行の表示は
ブラウザによりこのように整形されます。

))) 単に 折り返した行の表示は ブラウザによりこのように整形されます。 )))

段落内で改行したい場合は、
 ←ここにスペースを入れます。

))) 段落内で改行したい場合は、

←ここにスペースを入れます。

)))

空行を入れます。

すると、段落が変わります。

))) 空行を入れます。

すると、段落が変わります。 )))

class属性とtitle属性

  • この後に解説する要素の共通オプションとして、各要素の開始記号の直後に'['と']'で挟んだ文字列を記述すると、class属性とtitle属性を付けることができます。
  • '['と']'の間の文字列が英字で始まる場合、class属性となります。CSSと組み合わせることにより、要素の見た目を変えることができます。
  • ["説明"]'['と']'の間の文字列がダブルクォート'"'で始まる場合、title属性となります。多くのブラウザでは、title属性が指定された要素にマウスカーソルを合わせると、その内容が小さな枠で表示されます。
  • class属性とtitle属性の両方を指定したい場合、class属性とtitle属性の間にカンマ記号','を書きます。

[border center]

((((((
((((((
[bgOrange,"マウスカーソルをあわせるとタイトルが表示されます"]
段落にclass属性を付けてCSSで見栄えを変えられます。

))) [bgOrange,"マウスカーソルをあわせるとタイトルが表示されます"] 段落にclass属性を付けてCSSで見栄えを変えられます。 )))

 !![foo,"マウスカーソルをあわせるとタイトルが表示されます"]見出し

)))

[foo,"マウスカーソルをあわせるとタイトルが表示されます"]見出し

)))

見出し

  • 「!」を行の先頭に書くと見出しになります。
  • 「!」は1つから6つまで記述することが可能で、それぞれ<H1>〜<H6>に変換されます。

[border center]

記述例出力例
((((((
 !! 見出し2
 !!! 見出し3
 !!!! 見出し4
 !!!!! 見出し5

)))

[notoc] 見出し2

[notoc] 見出し3
[notoc] 見出し4
[notoc]! 見出し5

)))

リンク

一般的なリンク

  • ページ名を2つのカギカッコで囲むと、リンクになります。
  • カギカッコの中に「単語|URL」と書くと、「単語」を見出しとしたURLへのリンクになります。
  • URLの末尾がjpg,jpeg,png,gifの場合、IMGタグに展開されます。(単語を指定するとALT属性に設定されます)
  • パラグラフ中にURLっぽいものがあると勝手にリンクがはられます。

[border center]

記述例出力例
((((((
((((((
((((((
((((((
[[http://www.hikiwiki.org/en/]]

))) http://www.hikiwiki.org/en/ )))

[[Hiki|http://www.hikiwiki.org/en/]]

))) Hiki )))

[[Clover|http://jp.rubyist.net/theme/clover/clover_h1.png]]

))) Clover )))

http://www.hikiwiki.org/en/

))) http://www.hikiwiki.org/en/ )))

WikiName

  • 「大文字の英字で始まり、小文字の英字または数字が1文字以上続く」
という条件が2回以上繰り返される単語はWikiNameになり自動的にリンクがはられます。

[border center]

記述例出力例コメント
(((((((((
WikiName
HogeRule1
NOTWIKINAME
WikiNAME
fooWikiName

)))

WikiName
HogeRule1
NOTWIKINAME
WikiNAME
fooWikiName

)))

WikiNameになる
WikiNameになる
全て大文字なのでWikiNameではない
NAMEが全て大文字なのでWikiNameではない
先頭に全て小文字の英字fooがあるためWikiNameではない

)))

InterWiki

を参考にInterWikiもサポートしています。サーバの追加は InterWikiNameというページを編集します。

[border center]

記述例出力例
((((((
 [[Hiki:逆引きRuby]]
 [[ruby-list:1]]
 [[GoogleJ:ruby wiki]]

)))

[[Hiki:逆引きRuby]]
[[ruby-list:1]]
[[GoogleJ:ruby wiki]]

))) InterWikiはouter aliasと呼ばれる簡単に外部とリンクする機能にも対応しています。outer aliasを利用するにはInterWikiNameへaliasキーワードを付けてサーバを追加します。

本文中ではaliasに設定した単語を2つのカギカッコで囲むとリンクとなります。

[border center]

記述例出力例
((((((

;InterWikiName

{![[Ruby|http
//www.ruby-lang.org/]] alias!}

;本文

{!!}

)))

[[Ruby]]

)))

コメント行

「//」が行頭にある行はコメント行になり、出力されなくなります。

[border center]

記述例出力例
{!// ここはコメントです。!}(((

(表示されません) )))

水平線

マイナス記号「-」を行の先頭から4つ書くと水平線になります。

[border center]

記述例出力例
{!----!}(((

)))

箇条書き

  • 「*」を行の先頭に書くと箇条書きになります。
  • 「#」を行の先頭に書くと番号付きの箇条書きになります。
  • 「*」、「#」は5つまで記述することが可能で入れ子にすることもできます。

[border center]

記述例表示例
((((((
((((((
*レベル1
**レベル2
***レベル3
****レベル4
*****レベル5
******6個目以降は本文になる
* *スペースを入れれば先頭に「*」を記述可能

)))

  • レベル1
    • レベル2
      • レベル3
        • レベル4
          • レベル5
            • 6個目以降は文章として扱われる
  • *スペースを入れれば先頭に「*」を記述可能

)))

#レベル1-1
##レベル2-1
##レベル2-2
#レベル1-2

)))

  1. レベル1-1
    1. レベル2-1
    2. レベル2-2
  2. レベル1-2

)))

用語解説

  • セミコロン「;」を行の先頭に書くと用語(<dt>)になります。
  • コロン「:」を行の先頭に書くと解説(<dd>)になります。
  • 用語の行では、「;:」(セミコロン+コロン)の後に解説を書いて1行で済ますこともできます。
  • 直後のの行を字下げすることにより、用語や解説内に改行(<br>)を入れられます。

[border center]

記述例表示例
((((((
;用語;:解説
 改行
----
;用語だけ
----
:解説だけ

))) ;用語;:解説

改行

;用語だけ


解説だけ

)))

表(テーブル)は「||」で始めます。 セルの項目の頭に「!」をつけることにより見出しセルになります。 行の連結には「^」を列の連結には「>」を、連結したい数だけセルの項目頭につけてください。

[border center]

記述例(((
表示例(((
 ||!行見出し\列見出し||!列-A||!列-B||!列-C||!>列-D-E(横連結)
 ||!行-1||A1||B1||^C1-C2(縦連結)||D1||E1
 ||!行-2||A2||B2||^>D2-E2-D3-E3(縦横連結)
 ||!行-3||>>A3-C3(横3連結)

)))

行見出し\列見出し列-A列-B列-C列-D-E(横連結)
行-1A1B1C1-C2(縦連結)D1E1
行-2A2B2D2-E2-D3-E3(縦横連結)
行-3A3-C3(横3連結)

)))

インライン要素

行内の任意の場所に記述できる要素です。

文字飾り

波括弧「{」1つと記号の組み合わせで、文字飾りを表示することができます。

[border center]

記号記述例表示例タグ
色付き~色:{{~red:}色付き{~}}{~red:色付き~}<font color="指定色">
強調'{{'}強調{}{'強調'}<em>
強調2つの '{{}強調{'{!'}!}''}{強調}<strong>
下線 _{{_}下線{_}}{_下線_}<u>
斜体/{{/}斜体{/}}{/斜体/}<i>
取消線={{=}取消線{=}}{=取消線=}<del>
拡大文字+拡大{{+}大{+}}大文字拡大{+大+}大文字<big>
縮小文字-縮小{{-}小{-}}小文字縮小{-小-}小文字<small>
上付き2つの^上{{^^}付き{^^}}上{^^付き^^}<sup>
下付き2つの_下{{__}付き{__}}下{__付き__}<sub>
  • 異なる文字飾りを重ねて指定することもできます。
  • 拡大文字と縮小文字は入れ子で指定することができます。
  • それ以外の文字飾りは入れ子にはできません。

[border center]

記述例表示例
{!{'強調の中で{=取消線を指定すると=}'}効果が重なります!}{'強調の中で{=取消線を指定すると=}'}効果が重なります
{!1{+2{+3{+4{+5+}4+}3+}2+}1!}1{+2{+3{+4{+5+}4+}3+}2+}1
{!1{-2{-3{-4{-5-}4-}3-}2-}1!}1{-2{-3{-4{-5-}4-}3-}2-}1
{!その他の{'文字飾りの{'入れ子'}はできません'}!}その他の{'文字飾りの{'入れ子'}はできません'}

数式

文字列を「{{$}」と「{$}}」で囲むとmimeTeXによって整形された数式が表示されます。

[border center]

記述例表示例
数式を {!{$f(x) = \frac{1}{3}$}!} インライン表示数式を {$f(x) = \frac{1}{3}$}インライン表示

その他のインライン要素

その他、以下のインライン要素が記述できます。

[border center]

記号記述例表示例備考
引用元{@{!{@参考文献@}!}{@参考文献@}<cite>タグ
無変換{!{!{!{@!}参考文献{!@}!}!}{!{@参考文献@}!}間の文字列を変換せずに表示
名前付き文字列{!{"!}{!{"[foo]テキスト"}!}{"[foo]テキスト"}class/title属性付きの<span>タグで囲まれます

ブロック要素

「<<<」という行と「>>>」という行で挟まれた部分はブロック要素になります。

引用

  • 「blockquote(b)」オプションを使うと引用(<blockquote>)になります。
  • 他のブロック要素を入れ子にすることができます。

[border center]

((((((
((((((

記述例 ))) 表示例(class/title属性はソースで確認できます) )))

 <<<b[foo]
 引用
 <<<b["タイトル"]
 さらに引用
 >>>
 >>>

)))

引用
<<<b["タイトル"]
さらに引用

>>> )))

中央/右/左寄せ

  • 「center(c)」オプションを使うと中央寄せになります。
  • 「right(r)」オプションを使うと右寄せになります。
  • 「left(l)」オプションを使うと左寄せになります。
  • 他のブロック要素を入れ子にすることができます。

[border center]

((((((
((((((

記述例 ))) 表示例 )))

 <<<c
 中央寄せ
 <<<b
 入れ子で引用
 <<<l
 引用内で左寄せに戻す
 >>>
 >>>
 >>>
 <<<r
 右寄せ
 >>>

)))

中央寄せ
<<<b
入れ子で引用
<<<l
引用内で左寄せに戻す

>>> >>>

右寄せ

)))

整形済みテキスト

  • 「pre(p)」オプションを使うと整形済みテキストになります。
  • 他のブロック要素は入れ子にできず、最初に現れる「>>>」行までを整形済みテキストとして表示します。
  • 一部のインライン要素(強調、太字、取り消し先、下線、イタリック)は変換します。
  • 「pre_asis(pa)」オプションを指定すると、インライン要素の変換が抑制されます。

[border center]

((((((
((((((
((((((

記述例 ))) 表示例 )))

 <<<p
 整形済みテキスト {'強調'} {=取り消し先=}
 >>>

)))

整形済みテキスト {'強調'} {=取り消し先=}

)))

 <<<pa
 整形済みテキスト {'強調'} {=取り消し先=}
 >>>

)))

整形済みテキスト {'強調'} {=取り消し先=}

)))

数式

  • 「math(m)」オプションを使うと、数式をパラグラフ(<p>)タグで囲んだブロックを表示します。

[border center]

記述例表示例
((((((
 <<<m
 f(x) = \frac{1}{3}
 >>>

)))

f(x) = \frac{1}{3}

)))

無変換

  • 「asis(a)」オプションを使うと、改行だけ<br>タグに変換し、他の全ての要素の変換を抑制します。
  • このブロックはパラグラフ(<p>)タグで囲まれます。
  • 他のブロック要素は入れ子にできず、最初に現れる「>>>」行までを表示します。
  • 「>>>」という文字列を表示したい場合は先頭にスペースを入れます。

[border center]

((((((
((((((

記述例 ))) 表示例 )))

 <<<a
 無変換 {'強調'} {=取り消し先=}
 >>>

)))

無変換 {'強調'} {=取り消し先=}

)))

ブロック

  • 「div(d)」オプションを使うと、<div>タグで囲まれたブロックを表示します。
  • 「div(d)」を省略してid/class名だけ指定することもできます。

[border center]

((((((
((((((
((((((

記述例 ))) 表示例 )))

 <<<div[foo]
 <div class="foo">と</div>タグで囲まれます。
 >>>

)))

<div class="foo">と</div>タグで囲まれます。

)))

 <<<[,"bar"]
 <div title="bar">と</div>タグで囲まれます。
 >>>

)))

<div title="bar">と</div>タグで囲まれます。

)))

要素内ブロック

箇条書き、用語解説の解説、および表の中に「(((」と書くと、次の行から「)))」という行までのブロックが要素内に表示されます。

箇条書き・解説

箇条書きや解説では「(((」を記述した要素の直後にブロックを続けます。

[border center]

記述例表示例
((((((
#(((
要素1
 <<<c
 中央寄せ
 >>>
)))
#(((
要素2(改行→)
 パラグラフ
 <<<b
 引用
 >>>
)))

)))

  1. (((

要素1

中央寄せ

)))

  1. (((

要素2(改行→)

パラグラフ
引用

))) )))

表を記述する場合、まずは表全体の構造を記述し、中にブロックを表示したい要素を「(((」とします。その表の後ろに左上から順にブロックを必要な数だけ繰り返し記述し、各ブロックを「)))」で閉じます。

[border center]

記述例表示例
((((((
// まずは表を書きます。
||(((||1行2列
||2行1列||(((
//表はここまで
//続けてブロック要素を順に書きます。
1行1列
 <<<r
 右寄せ
 >>>
)))
2行2列

パラグラフ(改行→)
 2行目
*リスト1
)))

)))

(((1行2列
2行1列(((

1行1列

右寄せ

))) 2行2列

パラグラフ(改行→)

2行目
  • リスト1

))) )))

プラグイン

「{」2つと「}」2つで囲むとプラグインを呼び出すことができます。 パラメータを複数行に分けて書くことも可能です。 プラグインのみを単独行に書いた場合はブロックプラグインになり、前後に<p>と</p>が付かなくなります。

[border center]

記述例
(((
(((
{{recent(3)}}

)))

 {{pre('
  ...
 )}}

)))


2010-09-26 [長年日記]

[Sinatra][Ruby] SinatraでI18nライブラリを使う

Rubyの軽量フレームワークSinatraI18nライブラリを使う方法のメモです。I18nについては別の実装としてR18nライブラリというのもあるようです。

まずはライブラリのデフォルトに従い、最小のコードで最低限の機能を実現することを目標にします。

ライブラリのインストール

# gem install i18n
# gem install sinatra-i18n
# gem install rack-contrib

;i18n

I18nライブラリ

;sinatra-i18n

SinatraからI18nライブラリを使うためのライブラリ

;rack-contrib

Rackのミドルウェアとユーティリティを集めたライブラリ。今回はRack
:Localeを使います

プロジェクトのディレクトリ作成

# mkdir i18n-sample
# cd i18n-sample

ロケールファイルの作成

# mkdir -p config/locales

config/locales/の下にen.ymlというファイルを作る。ファイルはYAMLフォーマットでロケールをトップレベルのキーとしたハッシュの形で記述します。

en:
  message:
    hello: hello!

ja:
  message:
    hello: こんにちは!

このサンプルでは、message.hello というラベルに、:en ロケールでは"hello!"、:ja ロケールでは"こんにちは!"という文字列が対応するようになります。

サンプルコード

i18n-sampleディレクトリにstart.rbを作る。

require 'rubygems'
require 'sinatra'
require 'sinatra/i18n'
require 'rack/contrib'

Sinatra.register Sinatra::I18n
use Rack::Locale

get '/?' do
   t('message.hello')
end

実行

# ruby start.rb
== Sinatra/1.0 has taken the stage on 4567 for development with backup from Mongrel

この状態でブラウザから http://sample-host:4567/ にアクセスすると、ブラウザの言語設定にしたがって"Hello!"または"こんにちは!"と表示されます。

問題点

というわけで、非常に簡単にI18n対応できたのですが、次のような問題もあります。

  • ロケールファイルが1つしか指定できない(のでen.ymlに対応しようとする全てのロケールの設定を書く必要がある)
  • 対応していないロケールに設定されているブラウザからアクセスするとエラーになる

ということで、次のエントリではこの問題に対処します。

参考情報

[Sinatra][Ruby] SinatraでI18nライブラリを使う(続き)

SinatraとI18n、Rack::Localeライブラリを組み合わせると簡単にI18n対応することができますが、以下のような問題がありました。

  • ロケールファイルが1つしか指定できない(のでen.ymlに対応しようとする全てのロケールの設定を書く必要がある)
  • 対応していないロケールに設定されているブラウザからアクセスするとエラーになる

このエントリではこれらの問題の解決を図ります。

ライブラリのコピー

libというディレクトリを作り、その下に変更するライブラリをコピーします。

# mkdir -p lib/sinatra
# mkdir -p lib/rack/contrib

# cp /GEM/LIBRARY/ROOT/sinatra-i18n-0.1.0/lib/sinatra/i18n.rb lib/sinatra/
# cp /GEM/LIBRARY/ROOT/rack-contrib-1.0.1/lib/rack/contrib/locale.rb lib/rack/contrib

このようにディレクトリ階層を再現することにより、元のソースの変更を最小限で済ませることができ、またオリジナルのライブラリで問題が解決した際にはコピーしたファイルを消すだけで元に戻せるようにできます。

ライブラリの修正

コピーしたライブラリファイルを以下のように修正します。 ;lib/sinatra/i18n.rb

(((

Sinatra の :locales 設定に複数のファイルを指定できるようにします。

% diff -U0 i18n.rb.old i18n.rb
--- i18n.rb.old 2010-09-26 18:40:05.000000000 +0900
+++ i18n.rb     2010-09-26 18:40:26.000000000 +0900
@@ -18 +18 @@
-      ::I18n.backend.load_translations(app.locales)
+      ::I18n.backend.load_translations(*app.locales)

))) ;lib/rack/contrib/locale.rb

(((

ブラウザのACCEPT_LANGUAGEを優先順に評価し、アプリ側で有効なロケールが見つかったら設定します。見つからなかった場合はI18n.default_localeに設定します。

% diff -u locale.rb.old locale.rb
--- locale.rb.old       2010-09-26 18:54:43.000000000 +0900
+++ locale.rb   2010-09-26 19:40:04.000000000 +0900
@@ -12,14 +12,14 @@

       # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
       if lang = env["HTTP_ACCEPT_LANGUAGE"]
-        lang = lang.split(",").map { |l|
+        lang.split(",").each { |l|
           l += ';q=1.0' unless l =~ /;q=\d+\.\d+$/
-          l.split(';q=')
-        }.first
-        locale = lang.first.split("-").first
-      else
-        locale = I18n.default_locale
+          locale = l.split(';q=').first.split("-").first.to_sym
+          break if I18n.available_locales.include?(locale)
+          locale = nil
+        }
       end
+      locale ||= I18n.default_locale

       locale = env['rack.locale'] = I18n.locale = locale.to_s
       status, headers, body = @app.call(env)

)))

サンプルコードの修正

サンプルコードを次のように修正します。

$LOAD_PATH.unshift './lib'
require 'rubygems'
require 'sinatra'
require 'sinatra/i18n'
require 'rack/contrib'

set :locales, Dir[ "config/locales/*.yml" ]
I18n.default_locale = :ja

Sinatra.register Sinatra::I18n
use Rack::Locale

get '/?' do
   t('message.hello')
end

;1行目

$LOAD_PATHの先頭に./libを追加して、lib以下のライブラリが優先的にロードされるようにします。

;7行目

ロケールの設定ファイルとして、config/localesディレクトリ以下の*.ymlを読み込むようにします。

;8行目

デフォルトロケールを"ja"にします。

ロケールファイルの分割

config/locales/ の下のen.ymlをja.ymlにコピーし、ロケールの設定を分けます。

;en.yml

(((
en:
  message:
    hello: hello!

))) ;ja.yml

(((
ja:
  message:
    hello: こんにちは!

)))

実行

# ruby start.rb
== Sinatra/1.0 has taken the stage on 4567 for development with backup from Mongrel
本日のツッコミ(全2件) [ツッコミを入れる]

- aparadekto [Hey, I can't view your site properly within Opera, I actua..]

- hs [Hmm... I checked with Opera 10.63 and it looks no problem ..]


2010-09-29 [長年日記]

[Ruby][Sinatra] SinatraでRack::Csrfの例外をハンドリングする

SinatraでのCSRF対策にはRack::Csrfが使えますが、普通に

use Rack::Csrf, :raise => true

とか書いただけでは、errorハンドラで例外を捕捉・処理できません。production環境ではCSRFチェックに引っかかると403応答+真っ白な画面になるのですが、false negativeの可能性が0ではないので、何らかの処理はしておきたいところです。

というわけで、試行錯誤の結果行き着いたのが以下のようなコード。もっと良い方法があったらコメントください。

require 'rubygems'
require 'sinatra'
require 'rack/csrf'
require 'haml'

use Rack::Session::Pool
use Rack::Csrf, :raise => true, :skip => ['POST:.*', 'PUT:.*', 'DELETE:.*']

set :run, true
set :root, File.dirname(__FILE__)
set :show_exceptions, false

def check_csrf
   raise Rack::Csrf::InvalidCsrfToken unless params[Rack::Csrf.csrf_field] == session['csrf.token']
end

before do
   check_csrf if ['POST', 'PUT', 'DELETE'].include?(env['REQUEST_METHOD'])
end

error Rack::Csrf::InvalidCsrfToken do
   "CSRF exception!!"
end

##########

get '/?' do
   haml ROOT_HAML
end

get '/form' do
   haml FORM_HAML
end

get '/form2' do
   haml FORM_HAML_WITHOUT_CSRF_TAG
end

post '/?' do
   "ok"
end

##########

ROOT_HAML = <<EOHAML
%a{:href => "/form"} form
%br
%a{:href => "/form2"} form2
EOHAML

FORM_HAML = <<EOHAML
%h1 フォーム
%form{:method => "POST", :action => '/'}
  = Rack::Csrf.csrf_tag(env)
  %p
    %input{:type => "submit"}
EOHAML

FORM_HAML_WITHOUT_CSRF_TAG = <<EOHAML
%h1 フォーム
%form{:method => "POST", :action => '/'}
  %p
    %input{:type => "submit"}
EOHAML

上記ソースをstart.rbなど適当な名前で保存し、

% ruby start.rb

で起動し、http://sample-host:4567/ にアクセスすると、"form","form2"という2つのリンクが表示されます。"form"に進んでボタンを押すと"ok"、"form2"に進んでボタンを押すと"CSRF exception!!"と表示されます。

以下、ソースの要点を解説します。

読み込みと設定

require 'rack/csrf'
  :
use Rack::Csrf, :raise => true, :skip => ['POST:.*', 'PUT:.*', 'DELETE:.*']

requireでライブラリを読み込み、useで使用を宣言(?)します。この時、:skipにPOST, PUT, DELETEメソッドの任意のページを指定してRack::Csrf.callメソッド内でのチェックを無効にしてしまいます。

表示の切り替え

set :show_exceptions, false

Sinatraの例外表示処理を無効にします。trueにすればいつものトレース画面が表示されます。

チェック用メソッド

def check_csrf
   raise Rack::Csrf::InvalidCsrfToken unless params[Rack::Csrf.csrf_field] == session['csrf.token']
end

CSRFチェック用のメソッドです。

フィルタ

before do
   check_csrf if ['POST', 'PUT', 'DELETE'].include?(env['REQUEST_METHOD'])
end

beforeフィルタ内にこのように書くことで、POST, PUT, DELETEメソッドの全てのページでCSRFのチェックを行います。これで普通にRack::Csrfを利用した場合と同じ動作となり、かつ例外が発生したときにerrorハンドラーで処理できるようになります。

もし、特定のページだけCSRFチェックしたい場合は、beforeブロック内のcheck_csrfを削除して、

post '/foo' do
  check_csrf
   :
end

のように、チェックしたいページでの処理の先頭にcheck_csrfを持ってきます。

エラーハンドラ

error Rack::Csrf::InvalidCsrfToken do
   "CSRF exception!!"
end

例外を捕捉して処理するためのコードです。

Rack::Csrfの場合は:skipオプションがあったので迂回できましたが、あまりエレガントではないですね。

あまりよく分かってないのですが、Rackの方で、

  • callメソッド内ではraise禁止
  • check_csrfのようなメソッドを準備しておいて、lazyにチェックできるような仕組みを作る
  • callメソッド内で発生した例外をアプリに渡すような仕組みを作る

とかの何らかの枠組みが必要な気がします。