2008年06月11日

Zend FrameworkでDocTest  このエントリをはてなブックマークに登録 

Zend Frameworkをベースに開発している新しいカートのユニットテストを効率的に行おうと色々考えていて、前から気になっていたDocTestを試してみることにしました。

ということで、Mapleのkunitさん作のDocTestをZend Frameworkのプロジェクトで使うための覚書。

といっても、割とすんなりいきました。

Zend Frameworkにて、MVCの形のプロジェクトを既に用意できている段階であれば、パスの通ったところに、DocTestのalpha2を解凍して、srcディレクトリの中のファイルをコピー。

TestControllerを作成して、その中で、DocTestを呼び出すようにする。

<?php
class TestController extends My_Controller_Action
{
    public function init()
    {
        parent::init();
    }

public function preDispatch() { parent::preDispatch(); }
public function indexAction() { $this->_helper->viewRenderer->setNoRender();
$params = array('compileDir' => './var/tests_c'); $testDir = './lib/My'; $options = array('prefix' => 'My');
echo '<pre>'; Maple_DocTest::singleton($params)->run($testDir, $options); echo '</pre>'; } }

こんな感じ。

テスト対象を指定するところで、当初ライブラリ関係を収めているディレクトリ(./lib)を指定していたため、Zend Framework自体や、Smartyなんかもテスト対象とされてしまって美しくなかったんですが、ソースを読んでprefixの指定でうまく解決できました。

あとは、テストから除外したいディレクトリを設定するために、DocTestにちょっと手を加えたのと、テストに関する情報を抜き出す正規表現をちょっと変更しました。

DocTest.phpのgetTestDataメソッドの853行目

-        if (preg_match_all('|(#test (.*?))?<code>(.*?)</code>|s', $str, $matches, PREG_SET_ORDER)) {
+ if (preg_match_all('|#test((.*?))<code>(.*?)</code>|s', $str, $matches, PREG_SET_ORDER)) {

どうも、元のままだと、#testから始まっていない(DocTest用ではない)Docコメントでも、<code></code>で囲まれた記述があると抜き出してしまう問題があるようでした。
Zend Frameworkのコードの中にそのような箇所があって、当初エラーを吐いていたので修正。

ということで、いざ動いてくれるとすごく楽ちんです。感動ものです。

投稿者 田中@グリニッジ : 17:50 | コメント (0) | トラックバック (0)

2008年06月07日

本日生まれました。  このエントリをはてなブックマークに登録 

私事ですが、本日、正午前に無事第2子が誕生いたしました。
女の子で3560g。元気そうな赤ちゃんでした。

予定日が6月3日だったので、今か今かという感じだったのですが、ようやく生まれて、ほっとしました。

前駆陣痛が続いていたので、ここ2週間くらいで深夜に病院に5回くらいは駆けつけたと思いますが、今晩はぐっすり眠れそうです。

投稿者 田中@グリニッジ : 21:25 | コメント (4) | トラックバック (0)

2008年02月06日

フォーム二重送信防止の考察  このエントリをはてなブックマークに登録 

Webアプリケーションなどにおいて、フォームの送信の際に、入力→確認→完了の手順をとることが多い。CRUD(Create Read Update Delete)のように、Input Confirm Completeで、ICCなんて個人的には呼んでいる。

それはさておき、確認画面から送信ボタンを押して、データベースに登録するなり、メール送信するなりをした場合に、そこでブラウザをリロードしたり、ブラウザの戻るボタンを押して再送信したり、ボタンを2回クリックしたりすると、フォームが二重に送信されてしまう。

これらの対処としては、トランザクショントークンを用いた対策方法が主流である。
確認フォームで、hiddenタグに乱数などで生成されたトークンを埋め込み、同時に同じキーをセッションに持つ。フォームが送信されたらトークンとセッションのキーが一致するか確認し、一致したら正常処理を行い、セッションのキーを破棄。一致しなかったら、エラーとみなし、処理を行わずにリダイレクトなど。

送信後にブラウザのバックなどで戻り、再送信した際には、セッションのキーは既に破棄されてしまっているので、一致することはなくエラーにしかならない。これで二重送信を防止。


とまぁ、これで問題はなさそうなのではあるんですが。

PHPの場合、セッションをスタートすると、デフォルトでヘッダーにno-cacheをセットする。
その状態で、ブラウザでバックし、確認画面などのhiddenタグを埋め込んでいるところに戻ると、no-cacheゆえにページが再読込されて、トークンキーの値が変わってしまい、セッションにもキーが再びセットされ、二重送信が行えるようになってしまう。

no-cacheにしない事がトランザクショントークンを使う場合の条件のようであるが、そのためには、セッションがスタートする前に、
session_cache_limiter('private_no_expire');
なんて書いておけくと良い。これでブラウザでバックしても再読込されないので、二重送信もしっかりと防ぐことが出来るようになる。

一般的なユーザーは、入力内容に間違いがあったと気がつくと、直感的に戻るボタンを押すことが多いと思うので、ブラウザのバック自体は行えたほうが良いが、最終的なフォーム送信後に戻れてしまうと、たとえ二重送信防止となっていて、二回目の送信は実際の処理が行われないとしても、ユーザーは訂正を行ったうえで、再送信を行ったのだと思うかも知れず、そうなってしまうとユーザーの意図と、システムの振る舞いに差異が生じるので、一度送信完了したなら、そのフォームには戻れないようにしたい。


ということで考えていること。

no-cacheが基本
フォーム生成時に、乱数等にてformidを生成。同時にセッションにformidを格納
formidパラメーターを付与したURLにリダイレクト。
ブラウザでバックした場合は、formidに紐づくセッション内容を元にフォームを復元
フォームの送信時にはformidに紐づくセッションを削除
紐づくセッションがない場合は、どこかに遷移
編集用のフォームは、初回の表示もセッションから呼び出し
CSRF対策のトークンキーも盛り込む

みたいな感じ。
こんな感じで、試しに実装してみようと思う。

最後の部分、何を書いているのか自分しか分からないかも。

続きを読む "フォーム二重送信防止の考察"

投稿者 田中@グリニッジ : 23:40 | コメント (0) | トラックバック (0)

2008年01月11日

Zend_Viewでescapeを勝手にやるための試行錯誤  このエントリをはてなブックマークに登録 

Zend_View を使っていて、ビュースクリプトで都度都度 $this->escape($this->hoge) みたいな事をやるのがいやなので、試行錯誤。Zend_View_Abstractを継承したMy_Viewを作ってみました。

<?php

class My_View extends Zend_View_Abstract { public $autoEscape = true;
protected function _run() { $varBackup = null; if($this->autoEscape) { $vars = $this->getVars(); $varBackup = $vars;
foreach($vars as $k => $v) { if (is_string($v)) { $this->assign($k . '_raw', $v); $this->$k = $this->escape($v); } elseif (is_array($v)) { $this->$k = $this->_arrayEscape($v); $this->{$k . '_raw'} = $v; } } }
include func_get_arg(0);
if($varBackup !== null) { $this->clearVars(); $this->assign($varBackup); } }
private function _arrayEscape($vs) { foreach($vs as $k => $v) { if (is_string($v)) { $vs[$k] = $this->escape($v); } elseif (is_array($v)) { $vs[$k] = $this->_arrayEscape($v); } } return $vs; } }

こんな感じです。
使い方は、

// setup view
$view = new My_View();
$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view);
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

みたいな感じで(ViewRendererは使わなくてもいいけど)、あとはアクションの中などで、

// 変数
$this->view->hoge = 'ほげ<br />ほげ';
// 配列
$tmp = array('aaa', 'bbb<br />bbb');
$this->view->hoge_array = $tmp;

とセットする。
ビュースクリプトの中では、エスケープ済みのものと、エスケープしないものがそれぞれ、

<!-- エスケープ済み変数 -->
<?php echo $this->hoge; ?>

<!-- エスケープなし変数 --> <?php echo $this->hoge_raw; ?>
<!-- エスケープ済み配列 --> <?php echo $this->hoge_array[0]; ?> <?php echo $this->hoge_array[1]; ?>
<!-- エスケープなし配列 --> <?php echo $this->hoge_array_raw[0]; ?> <?php echo $this->hoge_array_raw[1]; ?>

みたいな感じ。多重配列も同じような感じ。

単純な用途の場合はこれはこれでいいのだけれど、いろいろとZend Frameworkのソースやドキュメントを追っていくと、自動でエスケープすると、却ってフォーム関係のView_Helperが使いづらくなりそうだったり、毎回別名の変数や配列を生成したりして、どうなのと思うところもあり。何か良いアイデア合ったら教えてください。

ビュースクリプト側でエスケープを書きたくないというのは、もちろん、不注意からXSSを生み出してしまわないためなのですが、そのためにややこしくなるのもいやなので、Viewにセットする段階で、フィルターかましてエスケープしてあげるほうがいいのかなぁとも思っています。

なるべくZend Frameworkの素のままで使ってみようと思ってましたが、やはりSmartyと、Smartyのプラグインを使ってViewまわりを固めたほうが早いかなと思って、現在そっち方面で検討中。

ViewRendererとかは結構便利なので、Zend Frameworkの良い所を生かしつつ。
ということで、Smartyを使ったViewの話は次回にします。

投稿者 田中@グリニッジ : 21:08 | コメント (0) | トラックバック (0)

PHPのソースコードを貼り付けるために  このエントリをはてなブックマークに登録 

PHPのこのソースコードを貼り付けるために、よく他のブログとかでやっているような方法を行おうと調べてみて、ちょっと古いエントリだけれども、

ブログにコードを貼り付ける方法で悩むの巻

を参考にスタイルシートを変更してみた。

pre.code{
	display:block;
	background : #EEE;
	color : #444;
	font-size : 1em;
	margin  : 1em;
	padding : 0.5em;
	border  : 1px solid #CCC;
	width   : 90%;
	overflow : scroll;
}

こんな感じ。

実態参照に変換するために
MovableType用EntityRefButtonプラグイン v0.0.5
も入れました。

エントリーを書くのがかったるくて、覚書みたいなものをあちらこちらに散乱させていたけれど、これでちゃんと書きたいものが書けるようになるかな。あとでソースコードに色も付けちゃおう。

投稿者 田中@グリニッジ : 20:16 | コメント (0) | トラックバック (0)

楽天トラベル 夏の旅行