2012-04-06

coffeescriptのいいところ(個人の感想です)

文字列の連結が楽

"#{user.name}さんが路上で肉を食べている"

みたいに書ける。一般的にjqueryをつかってクライアントに近いjsを書いてる場合、セレクタを動的に記述するケースはとても多いと思う。そういう時に非常に強力な武器になる。 

$.find("p[data-id=#{user.id}][data-name=#{user.name}]")

 

ヒアドキュメントが書ける

"""
#{user.name}さんがいきなり踊りだした。
春なので仕方ないが、さすがにズボンは履いてほしい。
ああ、遠くからサイレンの音が聞こえる。
"""

ちょっとした部分テンプレートを書いたりするときに便利で、終了時にインデントしてても大丈夫。

html = """
     <p data-name data-id>
          <span></span>
     </p>
     """ 

 

 

で、こういうシンタックスあれこれは公式サイトにちゃんと書いてあるので全部目を通してからつかいましょう。大した量はないので学習コスト云々は全然気にしなくていいし、結果的にコード量を削減できるからすぐ元が取れる。

まあしかし最終的に本番環境だったらjavascriptをuglifyすると思うので、サイトをつかう人間からしたらその元がcoffeeだろうが生jsだろうがどっちでもいいわ、難読化読みにくいわ、安全な実装かどうか判断しにくいわ、っていう結論になるしメンテする人間が使いやすいほうを選択すればいいんじゃないすかね、という話だと思っている。

今後ブラウザにcoffeeの変換エンジンが標準で積まれるようになって、text/coffeescript が主流になるようなことがあれば変わってくるのかな?インデントが意味を持つから難読化(によるファイルサイズ削減)に現状ほど価値がなくなるし、他人のサイトのjsを読むという趣味のある人間がその内容を把握しやすくなる。結果的に危険な実装は減りそうだと思う。

 

 

個人的に期待していたのはこういう展開だったんだけど、なんか closure templatesが既にそういうことをやってるみたいで全然未来の話ではなかった。あとで試そうと思う。トランスレータひとつ挟むだけで簡単な記述ができて、かつセキュリティ的にもより安全になるっていうのはいい話ですね。

2012-04-06

続・js.erbを使わない場合のrails ajax

js.erbを使わないようにするにはjson+クライアントテンプレート+コールバックがソリューションですね、みたいなことを一つ前に書いたけど、書いたあとにだいたいhtml+コールバックですむしパフォーマンスを気にしないならそれで十分だよなあと思った。ふつうのWEBアプリケーションでjs.erbが登場するケース、いくつかパターンがあるので思いつく範囲で簡単に代替手段を書いておこうと思う。 

 

1. htmlは不要でjavascriptだけを実行したい

レスポンスが返ってきたら特定の要素を非表示にしたりする場合。javascriptで完結する。

ajax:successなどのコールバックを使うようにする。

 

2. 部分的なhtmlが必要で、javascriptも実行したい

特定のノードを差し替えて、そのあとアニメーション処理をしたりする場合。

text/htmlを返してコールバックで拾う。条件によってjavascriptの処理を変えたい場合はHTMLの内容で判断する。

  

---ちょっと面倒な壁---

  

3. 部分的なhtmlが複数必要

複数のノードを書き換える必要がある場合。

必要な要素が含まれたtext/htmlを返して、コールバック時に分割して処理する。

 

番外. そもそもviewのレンダリングが遅いのが気になる

railsにかぎらずviewのレンダリングがボトルネックになるのはよくある話。

 json返してクライアント側でテンプレート処理をする。テンプレートはviewと冗長にならないように工夫をする必要がある。(前回書いたパターン)

もしくはサーバサイドでレンダリング済みのhtmlをキャッシュするようにする。アプリケーションの仕様に合わせてキャッシュのライフサイクルをmodelのフックなどに実装する必要がある。

 

そんなわけで

js.erb使わなくても結構どうにかなるので、ほんとうに必要がどうかはちゃんと考えるようにしたほうがいいと思う。仮にjs.erbをつかわなくても、viewの中に動的なscriptを書いて変数を埋めたりしてる場合はコンテキストに応じたエスケープが必要になることには変わりないので、js.erbをつかわないということはセキュリティリスクを考慮してjavascriptを動的に生成しないというポリシーに基づいているということを常に認識しなければならない。まあ認識してない人なんかいないか。

いくつかパターンを書いたけど同一アプリケーションなのにリクエスト先によってjsonが返ってくるのかhtmlが返ってくるかわからないっていうのは混乱を招くと思うし、URIで判断できるようにするか、もしくはどれかに統一されてるほうが一貫性があるしメンテする人間に優しいよなあとか考えてる。

2012-03-19

js.erbを使わない場合のrails ajax

js.erbはセキュリティリスクあるから使うなという話をよく見るし、それはおおむね正しいと思うんですが、じゃあ代替としてどうするのがいいのっていうところまで示している記事とかあんまり見ないよなあと思ったので書いておきます。主に設計のお話なのでコピペに役立つコードみたいなのは出てきません。

テンプレート処理が不要なケース、たとえばレスポンスを受けてDOM内における特定の数値を書き換えるだけとかの場合は特に細かいことを気にしないでもいいはずなので明記はしません。js.erbで書いた場合にrenderによるテンプレート処理を行うであろうケースを違う手段で代替した場合、ものすごくざっくり書くとこういう感じになると思います。

  1. 必要な文字列テンプレートは出力されたHTMLにあらかじめ埋め込んでおく
  2. jquery_ujsでイベントハンドラが生えている(ajax:*)ので、それぞれコールバック処理を書いておく
  3. ajaxリクエストを受けたコントローラはrespond_toするなどしてjsonを返すようにする
  4. 1で埋め込んだ文字列テンプレートのコピーを取得し、jsonの値を埋めこんで任意の箇所に差し込む

まずjsonに含まれている値がHTMLエスケープ済みかどうかは、確実に認識しておいたほうがいいでしょう。サーバ側でエスケープするのか、javascript側でやるのか、このへんは開発時のポリシーなどに依存しますが一貫性を持って臨んだほうがいいです。値の埋め込み処理をjavascriptのライブラリに依存する場合もあるかもしれませんが、べつにそういうのを使うのは構わないので、内部の実装をあらかじめ読むなどして確認し、このライブラリはエスケープするのかしないのか、そのあたりは確実に判断できるようにしておいてください。そういった心がけで世界からくだらないXSSがひとつなくなります。

html.erbも返すけど、場合によってはjson+文字列テンプレートでも対応してね、というようなアクションの場合(ページング処理がある画面とか)、html.erbと文字列テンプレートが同じviewを参照するように設計するべきでしょう。セキュリティリスクを気にかけた結果DRYでない実装をして余計なバグを生むみたいなのはマヌケだし、避けるのが賢明です。

POSTした結果あたらしく生成されたレコードをリスト表示に追加する、といった場面でも同じようなことがいえると思います。リスト表示しているHTMLと、jsonをうけて構築される新規レコードのHTML、それぞれが同じviewを参照できるようにしておきましょう。

javascriptにおけるテンプレート処理のために空のHTML構造を出力するケースと、値が埋めこまれてhtml.erbとして出力されるケース、それぞれを兼ねた一つのpartial viewを用意しておくというのがスマートで良いと思います。

こういった設計思想を無駄だとか面倒だとおもう人あたりがpartial viewをrenderしたものをjs.erbを使ってinnerHTMLしているのでしょう。たしかに値を埋めこむという処理の実装はviewとjavascriptの2箇所に散るケースもあるし、そう思う気持ちも理解できなくはないので、別にjs.erbつかってもいいが正しくjavascriptエスケープしろや、といったほうが個人的にはシンプルだとおもいますね。

2012-03-12

rails3以降のWEBアプリケーションにありがちなXSS

 
  • 明示しなくてもviewでの出力をHTMLエスケープしてくれるので無警戒になってる
  • html_escapeがシングルクォートをエスケープしないという認識がない

 

このあたりの要素が組み合わさるとXSSがうまれやすいという話を書きます。わかってる人にとってはクソみたいな内容なので読む時間がもったいないかもしれません。たとえばjs.erbでこんなふうに書いてたとして

var article_id = '<%= @article_id %>';

 @article_idが信用できる値だという前提だと問題ないのですが、controllerで

@article_id = params[:article][:id]

 実はこんなことしてるだけであとは素通しっていう人も、もしかしたらいるかもしれません。要は外から渡された値をそのまま出力しているというノーガードな感じです。それでもrails3以降なら明示しなくてもHTMLエスケープしてくれるし安心だね!もし </script> って渡されてもscriptタグ途切れないからね!かくしてエスケープ漏れによるXSSは死んだッ!第三部完!

 

 というように考えていると危険だなあという話です。簡単な例ですが params[:article][:id] にシングルクォートを含んでjavascriptのコンテキストを破壊することができます。

// params[:article][:id] が ';alert('xss');' という値だった場合
var article_id = '';alert('xss');'';

じゃあシングルクォートも含めてHTMLエスケープされていればいいのかいうとそういう話ではありません。あとダブルクォートでくくればいいという話でもない(末尾にバックスラッシュ入れられたらjsエラーになる)です。あと上の例でarticle_idをdocument.writeする場合なんかだと、タグの数値参照(\x3cscript\x3e とか)がきたらアウトなので、そもそもHTMLエスケープされてればOKという問題ではないのです。この状況ではjavascriptを書いているので、javascript用のエスケープを行う必要があるし、それはいまのところrailsが勝手にはやってくれません。

helperにescape_javascriptという対策があり、jというエイリアスがあります。これをつかうことで信用出来ない値を安全にjavascript内に出力することができます。

var article_id = '<%= j @article_id %>';

escape_javascriptの実装は簡単に言ってしまうとバックスラッシュ、閉じタグの開始、シングルクォートとダブルクォートをそれぞれエスケープして、改行を\nに統一するというものです。

renderでhtmlを生成してjqueryでつっこむ、といったようなことをやってる時は当然のようにつかってると思いますが、それ以外でも値をjavascript中に出力するケースについては記述しておくのが安全だと思います。

なお一番安全なのがサーバサイドでjavascriptを動的に生成しまくるのを避けることですが、ことrailsajaxにおいてはviewまわりの生産性に大きく差がでることが多いと思うので、受け入れることが出来る人はそんなにいないのかもしれないなあと感じています。このへんについては後日書きます。

2012-03-01

〜が〜について「いいね!」と言っています。が、それは誤クリックによるものです。

誰も見ていないゴミのようなブログなので意味があるかは不明ですが、facebookページに関する注意喚起を書きます。真面目な話です。

  1. iframe形式で外部ドメインに設置されたfacebookページのいいねボタン(like boxというらしい)が押されると、ページの管理人にほぼリアルタイムでメールが飛ぶ
  2. そのメールには押した人のfacebook上の登録名、つまり実名の可能性が高い文字列が記述されている
  3. いいねボタンは透明にして上にコンテンツをかぶせるなどして誤クリックを誘うこと(クリックジャッキング)ができる

という挙動を2012/3/1現在で確認しています。半年ぐらい前から改善されているかのチェックを定期的に行なっていますが、本日に至るまで状況は同じです。本当にメールがリアルタイムかどうかについては、色々検証したところ押してから数分後に配信されるようなケースもあり、クリック頻度によってある程度変動するのではないかと見ています。試験的にfacebookページを作成し、作成したのとは別のユーザで外部ドメインに設置したいいねを押したらすぐにメールが配送されました。ページの編集 => 基本設定 にメール設定らしき項目があるため、人によっては既に受け取らないという設定をしている可能性もあります。試験的に作ったページの設定はデフォルトで受け取ることになっていました。

なお上記の流れで作成したfacebookページは3/31に置き換わるという話のタイムライン形式になっていたので、仕様変更後も状況は特に変わらないのではという推測をしています。

リアルタイムに近い間隔でメールが飛ぶというのがちょっとひどいなと思っていて、誤って押したと気付いたときに急いで訂正するという手段が対策として意味を成さないし、メールという形式で通知が行われた場合いったん配送されてしまうとfacebookが内容をコントロールすることができない。たとえばfacebook内の通知機能(画面の上の方で赤くなるやつ)だったら、誤クリックに気づいて訂正したタイミングでその通知データを消して通知自体もなかったことにする、というのが(実際にどうかは知らないが)技術的には期待されるのだろうけど、配信が完了したメールでそれができるのかといわれると不可能で、しかもそのメールには誤って押してしまった人のアカウントを特定する情報が記載されている。そしてfacebookのアカウントは、その運営ポリシーにより現実の属性と非常に近い情報が紐付けられている事が多い。これをどうみるのかはプライバシーの感覚により個人差があるのかもしれないけど、個人的には前述のとおりやりすぎで、メール通知機能自体を提供しないようにするか、もしくは通知まで長期のタイムラグが必要になるという認識でいる。

この状況にクリックジャッキングの問題を組み合わせると、たとえばエロサイト上において、あなたが性的な興奮を抱くような表現をかぶせられたいいねボタンを誤って押してしまったとき、そういった性的嗜好があるという属性をそのfacebookページの管理者に指定された人物がメールで受け取る、といった悪用をすることができる。XSSがあるドメインだと外部から悪意のあるfacebookページのいいねボタンを突っ込めるだろうし、エロサイトの管理者を信頼しているからOKという問題でもない。非常に厄介だと思う。

で、facebookの利用者は具体的にどう自衛するべきなのか。方法は色々考えられるけど、難しいことを考えるのが嫌ならfacebookの使用を終えたら必ずログアウトするというのを徹底しておくのがシンプルだと思う。ログアウトしておけば誤っていいねボタンを押してしまった場合にログインを要求するダイアログが出てくるので、facebookのドメイン外ではそれなりに意味がある。facebookの場合、ログアウトしても破棄されないcookieがあるので、あなたが ( 誰かに騙されて | うっかり ) 誤クリックしてしまったということをfacebook自体が認識できる可能性は残るが、少なくともページ管理者のような第三者にあなたのfacebookアカウントを知られるということはない。

他にもfacebookのドメインを参照するときに専用のブラウザを使うとか、facebookで戸籍上の名前を使用せずアイデンティティの分離を正確に管理するとか、3rd-party cookieを無効にするとか、www.facebook.com 0.0.0.0とか、なにも対策せずガンガン押してガンガン通報するノーガード戦法とか、いろいろな手段が考えられる。個人のプライバシー感覚、技術力などにより取る手段は変わってくると思う。

あと、これだけ世の中にいいねボタンがあふれていると、そもそも自分がなにを押したのか正しく認識できていないという根本的な問題もある。ほいほいといいねを押して歩いているような人たちで、こういったリスクを正しく認識している人は極めて少数なのではないだろうか。本当にいいねと思ってそのボタンを押しているのか、自分がいいねを押すことはそういったリスクに勝る価値があるのか、冷静になって考える姿勢も大切だと思う。

以上、真面目な話でした。 

2012-01-25

pjaxを導入して僕にも彼女ができました

なんかrails3.2でpjaxを公式にサポート云々みたいな話をきいてたけど、ふたを開けてみたらそれっぽい差分がちょっと見当たらなくてついカッとなってしまったので、既存の資産で試してみることにしました。pjaxを。

rails向けのpjax-railsというgemがありますが、半年ぐらいメンテされてなくて不安なので、pjax-railsのなかで使われてるjquery-pjaxを普通につかうことにします。jquery-pjaxも4ヶ月ぐらい動きがないみたいですが、カッとなっているのでそういう細かいことは気にしません。

https://github.com/defunkt/jquery-pjax

さっそく読み込んで、aタグにイベントをバインドしましょう。差し替える要素に"data-pjax-container"という属性が記述されてるものとします。

$("a:not([data-remote])").pjax("[data-pjax-container]")

data-remote以外も無視する必要がある場合はセレクタに追記するといいです。

これだけだとX-PJAX trueというヘッダを付けてリクエストしてるだけになるのでコントローラ側でも対応します。renderをオーバライドしてリクエストヘッダにX-PJAXがセットされていればpjax用のレイアウトにする、みたいな感じでいいんじゃないですかね。

遷移時にこじゃれたアニメーションをしたい場合は

  • start.pjax
  • end.pjax

あたりのイベント内で実装するといい感じにできるはずです。こじゃれるあまりにうざくならないように気をつけましょう。

$(document).bind("start.pjax", function(){});
$(document).bind("end.pjax", function(){});

aタグにイベントをつける際に$.ajaxのオプションを渡すことができます。jquey-pjaxのもってるデフォルトのタイムアウトがやや早いかなという感があるので、サイトの応答速度に応じて明示しておくのがいいでしょう。サーバが200を返していてもタイムアウトまでレスポンスがないとlocation.hrefでふつうにリダイレクト処理が行われるようになってます。

$("a:not([data-remote])").pjax("[data-pjax-container]", {
  timeout:1000, 
  success: function(){}
});

あとブラウザで戻るの処理をしたときにもpopstateが発火するのでこっちもバインドしておきましょう。

$(window).bind("popstate", function(){});

以上でpjaxを導入することができます。簡単ですね。

 

で、こんな導入手順みたいな話はわりとどうでもよいので、実際にやってみて思ったことなどを書いておこうと思います。

すっごくあたりまえのことを書きますが、サイト全体にpjaxでの画面遷移を提供した場合、pjaxレスポンスのコンテナ外で書いてるjsの読み込み処理は初回しか行われません。一般的にheadの中でjsやcssを読んだりすると思いますが、多くの場合にそれらは初回のリクエストで取得したものが使いまわされます。

都度読み込まれないということは当然$.ready()などのイベントも走らないので、前述したpjax関連のイベントなどをつかいpushState/popStateが終わったタイミングでページ初期化時に行われるべき処理の実行を明示する必要があります。

さらに各URLごとにjsやcssを分割している場合は、pjaxのレスポンスにそれらも含めるように書いておかないと、pjax遷移時と通常のGETで遷移した場合に差異が出るような挙動をするでしょう。

新規にサイト作る場合は予め意識すればいいので大した話ではないですけど、既存のサイトをpjax化するみたいな作業は軽い気持ちで始めるとイバラの道になることもあるのではないかと思うのですね。まあ現在どれだけjs使ってるかによるんですが。

初回リクエストのスコープで変数を定義しておけばpjax遷移が続く限り永続化するので、websocketのコネクションを維持しておいて通知処理を行いつつ表示コンテンツを頻繁に切り替える、みたいなケースで利用されることが多くなるんじゃないかなあとか思いました。メリット反面、これまでよりもjsのライフサイクルを意識してサイト全体を構築する必要があるので、メンテナンスの敷居はちょっと上がるんじゃないかなという所感があります。

ヘッダ表示部分やメニュー以外をpjaxで差し替える、といったようなWEB黎明期におけるiframeみたいなつかいかたの場合、レスポンスの削減には劇的に寄与しないし前述のようにjs方面で気を使う必要があって、使い所としては正直なところ微妙だなーとおもっています。まあ大雑把にやってもたしかに体感はよくなるんですけど、もっと局所的な表示を書き換えるようなケースでつかうのが効果的といえるでしょう。それなら別に今までどおりのajaxでいいじゃない、っていう人と、検索エンジンがクロールできるHTMLもついでに作れて素敵やん、っていう人と、まあ人それぞれですかね。

pjaxの使われている箇所としてgithubのツリーui部分が有名ですが、あれ以上のつかいどころ今のところ浮かばないなーと思いました。了

2011-12-15

asset pipeline で必要な javascript だけ読み込むには

Rails3.1 の asset pipeline を production 環境で使うと、マニフェストに書いたルールにしたがって複数の同種リソースを一つにまとめてくれます。これはリクエスト数の削減やレスポンスの圧縮などに寄与しており非常に有益な仕組みなのですが、マニフェストの書き方によっては、そのシーンでは使わないようなファイル、あるいは実行してほしくないファイルもレスポンスで含まれてしまうことがあるため、たとえば javascript においては現状だと下記いずれかのアプローチを取るのがよいのかなと考えています。

 

すべて読み込んで js 側で処理を dispatch する

 特になにも考えず、必要なリソースをマニフェストに書いておきます。そして js 内で location.pathname あたりをもとに実行する処理を振り分けるような薄いフレームワークを用意し、URL ごとに対応するのがいいでしょう。

dispatcher は下記の記事が参考になるかなーと思います。

http://tech.kayac.com/archive/javascript-url-dispatcher.html 

所詮はテキストだし minify されるし gzip すれば大した大きさにはならないし、という前提で納得できる人はこれでいいのではないでしょうか。

 

必要なファイルのみ読み込むようにする

 幸いなことにコントローラ単位でリクエストする方法があって

javascript_include_tag params[:controller]

こう書けば javascripts/[コントローラ名] をまとめられたファイルとは別に呼ぶことができます。リクエストはひとつ増えますが、前述の方法とどっちが効率いいかはリソースの内容などによって異なるでしょう。

ただしこれは実態の有る無しを考慮していないので、すべてのコントローラに対応した js を用意しておくか、あるいは helper など一枚噛ませて実態が存在するかを判断して出力するようにする必要があるでしょう。404 になってもいいぜっていう男気あふれる人は気にせず前進してください。

もちろん production 環境においてそれぞれのリソースは precomplile されている必要があるかと思いますので

config.assets.precompile += %w(*.css *.js)

(ざっくりとですが)こんな感じで config に書いておくといいでしょう。

あと routes.rb でコントローラ名を隠蔽している場合(パスとコントローラ名が一致しない場合)、リクエストしているファイル名からコントローラ名がわかってしまうという点も人によっては嫌かなーと。これもちょっとした工夫が必要ですね。

 

まとめ

 コントローラ単位で必要な js を用意しておいて、それぞれの js の中で location.pathname 単位の dispatch をするのが無難かなという感じです。もっといい方法があれば知りたいところ。