sfFormとエスケープに関する注意点
symfonyで自動エスケープ設定を有効にしている場合、アクションからテンプレートに変数をセットする際にオブジェクトならばsfOutputEscaperクラスでラッピングされ、スカラーであれば直接エスケープされてテンプレートに渡されます。
スカラー値をテンプレートに渡す際にエスケープしない方法 - ゆっくり*ゆっくり
上の記事を読んでいる前提で。
symfonyでフォームを扱う場合はsfFormクラスを利用しますが、自動エスケープが有効になっていても、sfFormはエスケープ対象になりません。なぜならば、sfFormはHTMLの出力を行うクラスなので、これをエスケープ対象にしてしまうと機能が成り立たないからです。
これは上の記事で説明したsfOutputEscaper::markClassesAsSafe()メソッドを利用しています。sfView::initialize()の内部で、次のコードが必ず呼ばれます。
<?php class sfView { public function initialize($context, $moduleName, $actionName, $viewName) { // ... sfOutputEscaper::markClassesAsSafe(array('sfForm', 'sfFormField', 'sfFormFieldSchema', 'sfModelGeneratorHelper')); // ... } }
さて、たとえばsfFormDoctrineを継承したUserFormがあるとします。
<?php class fooActions extends sfActions { public function executeBar(sfWebRequest $request) { $user = new User(); $user->setName("<script>alert('ゆどうふ')</script>"); $this->form = new UserForm($user); } }
さてさて、これをテンプレートで次のようにすると...
<?php echo $form->getObject()->getName() ?>
動かせばわかるんですけど、JavaScriptが実行しちゃいます。仕組みを知っていれば当然のことなのですが、sfOutputEscaperでラッピングされていないオブジェクト($form)の内部に持つオブジェクトを取得($form->getObject())しようとした際、それがエスケープされることはありません。その他にも、sfForm::getValue()の値を出力しようとした場合も当然エスケープされません。
ちなみにフォームがエスケープ対象から外れるといって、render()メソッドを用いて何かHTMLのレンダリングをしようとした際、一切エスケープが行われないわけではありません。sfWidget::escapeOnce()というメソッドがあり、これを用いて必要に応じてエスケープを行います。
ちなみにこのescapeOnce()もまた曲者で、このメソッドに '<' って文字列を渡すと、エスケープしたら '&lt;' になってほしいのに、'<' のままになってしまいます。まあ詳細は中を読んでいただければ。。。
というわけで何が言いたいかというと、sfFormの値をテンプレートで直接出力するのはXSSにつながる危険性があるので気を付けてくださいねってことです。解決策は単純で、sfFormの内部に持つ値をアクション側で変数として受け取り、それをビューに渡してあげるだけです。つまり先ほどの例でいえば、
<?php class fooActions extends sfActions { public function executeBar(sfWebRequest $request) { $user = new User(); $user->setName("<script>alert('ゆどうふ')</script>"); $this->form = new UserForm($user); $this->user = $form->getObject(); // アクションから個別にテンプレートに渡す } }
<?php echo $user->getName() ?>
こうすればちゃんとエスケープされます。