530 5.7.0 Must issue a STARTTLS command first.の対処

redmineをWindowsマシンにインストールして、通知メールをGmail(TLS)経由で送ろうとしたときにはまったエラーの対処法です。

手順は以下サイトを参考に。
Setup Redmine to send email using GMail – Redmine Blog – The Official Redmine blog –

redmineの管理設定画面上から、「テストメールを送信」をしたあと、

メール送信中にエラーが発生しました (530 5.7.0 Must issue a STARTTLS command first. ***************** )

というエラーが発生していました。

問題はaction_mailer_optional_tlsのインストールの失敗。

C:\>cd Rails\redmine-1.0.1
C:\Rails\redmine-1.0.1>ruby script/plugin install git://github.com/collectiveidea/action_mailer_optional_tls.git

何もエラーも表示されずに実行されるのですが、vendor\plugins\action_mailer_optional_tlsの中身が空の状態。

ということで、
collectiveidea’s action_mailer_optional_tls at master – GitHub
から、action_mailer_optional_tls以下のファイルをディレクトリ含めて一式ダウンロードして、手動で設置すればOKです。

あとはRedmineを再起動して再度送信テストをしてみてください。Apps経由でも使えるので便利ですね。

参考までに環境メモ。Windows XP SP3、Ruby 1.8.7  P248、gem 1.3.5でした。


Androidエミュレータの通信をFiddlerで見る

Androidのアプリ開発をするときに、サーバとの通信を確認したいということはよくあります。

通信の確認にFiddlerを組み合わせて使うことで、XMLを整形された状態で見たり、詳細なHTTPヘッダを確認したり、リクエストを改ざんしたり、アプリプログラム側を変更せずに開発サーバを見に行くようにしたりできるようになります。自分のローカルマシンだけでサーバ・クライアントモデルの開発が完了できる環境を作っておくと楽ですね。

Fiddlerについてはこちら。
実はFiddlerがすごすぎたので、機能まとめ紹介 : blog.loadlimit – digital matter –

AndroidエミュレータのHTTP Proxy設定方法について参考にしたのはこちら。
[Android] エミュレーターのプロキシ設定(Using the Emulator with a Proxy) – adakoda

AndroidのブラウザでGoogleニュースを見たときのリクエストはこんな感じで覗けます。

emurator

fiddler_http_debug

具体的なやり方は以下の通り。
あと、開発はEclipseで行っていると仮定しますが、別にエミュレータそのまま起動する場合でもOKです。

Fiddlerインストール

FillderのメニューからTools→Fiddler Optionsを選択
menu_options

Connectionsタブから、Allow remote computers to connectを選択して、ローカルマシン以外からのアクセスを受け付ける
fiddler_allow_remote
併せて、その上のFldder listens on portを覚えておく。ここでは8888を使用。

Fiddlerを一度終了して、起動する

EclipseのメニューのWindows→Preferencesを開いて、左側のツリーからAndroid→Launchを選択、Default emulator optionsに-http-proxy http://[ローカルマシンのIP]:[先ほど調べたFiddlerのポート]を入力。
preferences

すでにプロジェクトを作ってしまっている場合には、メニューのRun→Run configurationsからAdditional Emulator Command Line Optionsを設定する。
run_configurations

これで、ApplyしてRunすれば、ProxyにFiddlerを経由して開発することができるようになります。

あとは、必要に応じて、UserAgentにAndroidが含まれているかどうかなどをFiddlerのフィルタにセットすればOKです。

FiddlerのHOSTS機能でサーバのドメインを開発用のVirtualPCなどに割り当てれば、一台で完結できます。

FiddlerのメニューからTools→HOSTSを選択
menu_hosts

開発マシンのIPとドメインを設定してやればOKです。
hosts 

ただし、Androidのブラウザの場合、どうやら存在するドメインでないと、リクエストできないみたいです。おそらくHTTPリクエスト前にDNS名前解決を一度しているのではないかと思います。ドメイン存在していれば大丈夫なので、たいした問題ではないかと思います。


phpMyAdminのYAMLエクスポートとsymfonyのfixture

Symfony 1.4+Doctrineにて。テストを実行するために、fixtureを用意するのですが、

$ symfony doctrine:data-dump dump.yml

でやろうとしたらデータが重すぎて途中で落ちる。

ので、phpMyAdminにYAMLエクスポート機能がいつの間にか実装されていたので、それを使って必要な部分だけをエクスポートします。

ところがそれをそのまま読み込むといくつか問題が…

ということで、気になった3点。

  • 改行があると読み込めない
  • 日付形式が読み込めない
  • fixtureのymlファイルよりインデントひとつ分浅い

改行のある行はダブルクォートで囲ってから、改行文字を\nにすれば読み込めるようになります。

日付はダブルクォートで囲めばOK。

インデントは置換するなり何なりしましょう。

テスト周りに関してはチートシートが欲しいなぁ…


Symfonyで外部のプログラムを実行する

Symfony 1.4でシェルのプログラムを呼び出すときに、PHPのexec関数に代わる便利なメソッドがあったのでメモ。

sfFilesystem::execute()がそれです。
symfony API » sfFilesystem Class | symfony | Web PHP Framework

内部的にはproc_openで
戻り値は、標準出力と標準エラー出力が分離されて配列で返ってきます。

タスクから、呼び出すとこんな感じになります。

    protected
        $outputBuffer = '';

    protected function execute($arguments = array(), $options = array())
    {
        // initialize the database connection
        $databaseManager = new sfDatabaseManager($this->configuration);
        $connection = $databaseManager->getDatabase($options['connection'] ? $options['connection'] : null)->getConnection();

        // 処理待ちリストから一行取得
        $process = Doctrine::getTable('Processes')->findOneByStatus('WAIT');
        $command = $process['command'];

        // バッファクリア
        $this->outputBuffer = '';
        try {
            // 実行。第二引数は標準出力、第三引数は標準エラー出力を受け取るコールバック関数
            list($output, $err) = $this->getFilesystem()->execute($command, array($this, 'setBuffer'), array($this, 'setBuffer'));
            $process['status'] = 'SUCCESS';
            $process['message'] = $output;
            $process->save();
        } catch (RuntimeException $e) {
            // コマンドが実行できなかった場合と、終了時エラーの場合はRuntimeExceptionがthrowされる
            $process['status'] = 'FAILED';
            // エラー時はexecuteの戻り値を取得できないのでコールバックの結果からメッセージを取得
            $process['message'] = $this->outputBuffer;
            // 必要なら$e->getCode();でエラーコード取得できます
            $process->save();
        }
    }

    public function setBuffer($output)
    {
        $this->outputBuffer .= $output;
    }

/vendor/pear/php/symfony/task/project/sfProjectDeployTask.class.phpがサンプルとして使えます。

もちろん、PHPのexec関数を使って

exec($command, $output = array(), $return_var = 0);

とかしてもいいと思います。

多分プラグインのインストール段階でmakeとかするときに使うんじゃないですかね。

標準出力を段階的に画面に表示できるので。

ただし、出力バッファの読み取り単位ごとに、0.1秒のディレイをかけているようなので、速度重視なところでは注意が必要です。


Symfonyのクライアントサイドバリデータ

SymfonyでjQueryを使ったクライアントサイドのバリデーションを簡単に実現できるjnAjaxFormValitatorというプラグインがあったので、試してみました。

このプラグインはフォームが変更された際に、Ajaxでサーバにバリデーションルールと値を送ってサーバサイドでバリデーションをかけ、その結果を表示します。

結論から言うと、簡単に導入できて便利ですが、一部セキュリティ的に難あり、

1.2用ですが、ちょっとの修正で1.4でも使えます。

まずはダウンロード。

Plugins | jnAjaxFormValidatorPlugin | 1.0.2 | symfony | Web PHP Framework

20100617223542

ダウンロードしなくてもサーバから直接インストールできるらしいのですが、今回はtgzファイルをダウンロードしてきてインストールしました。

$ ./symfony plugin:install ./jnAjaxFormValidatorPlugin-1.0.2.tgz
>> plugin    installing plugin "./jnAjaxFormValidatorPlugin-1.0.2.tgz"
>> sfSymfonyPluginManager Installation successful for plugin "./jnAjaxFormValidatorPlugin-1.0.2.tgz"

インストール成功したら、アプリケーション以下のconfig/setting.ymlを開いてモジュールを有効にする設定を書きます。

all:
  .settings:
    enabled_modules:        [default, jnAjaxFormValidator]

READMEは全部スペルミスでjnAjaxFormValitatorになっているのでコピペすると間違えます。

次はアプリケーション以下のconfig/routing.ymlに以下を追加します。

# —– jnAjaxFormValidator —–
jnAjaxFormValidator_validateJSON:
  url:   /jnAjaxFormValidator/validateJSON
  param: { module: jnAjaxFormValidator, action: validateJSON }

その後、キャッシュをクリアします。

$ ./symfony cc

バリデーションを使用するテンプレートの先頭にでも、

<?php use_helper(‘ajaxFormValidator’) ?>
<?php echo ajaxAllFieldsValidators($form) ?>

と書いておけばOKです。

インストール手順はこれで完了ですが、このままだとSymfony 1.4で動かないので、actions.class.phpを修正します。

/plugins/jnAjaxFormValidatorPlugin/modules/jnAjaxFormValidator/actions/actions.class.php 29行目
sfLoader::loadHelpers(‘I18N’);

sfContext::getInstance()->getConfiguration()->loadHelpers(array(‘I18N’));

以上で完了です。

20100617225540

あと、書き出されるエラーが
<ul class="error_list"><li>エラーメッセージ</li></ul>
の構造に固定されているので、必要であればヘルパー側を修正する必要があります。

難点は、バリデーションのルールをブラウザ側に一度保持することです。単純にバリデータクラス名とバリデーションルール、値をJSからサーバに送信してバリデータを実行しているだけなので。

20100617225648

上の図はリクエスト時のパラメータです。GETで送られます。何が問題かというと、サーバ内のバリデーションルールが表に流れるので、攻撃者が穴を見つけやすくなるということですね。

バリデーションは完璧で、特にクリティカルな要件でないなら使えると思います。

あと、返されるエラーメッセージはformで定義したものを使えないので、デフォルトのエラーメッセージになります。

デフォルトのエラーメッセージは、アプリケーションのConfigurationなどで

sfValidatorBase::setDefaultMessage(‘max_length’, ‘%max_length%文字までにしてください’);

などとしておくか、カタログファイルでも変更できます。

ちなみにソースコードはびっくりするくらいシンプル。チェコ人が作ったらしく、I18Nも意識されてます。


sfWidgetFormInputCheckboxの正負を反転させる その2

前回のエントリで、モデルを拡張して読み出し時と保存時に正負反転させる方法を紹介しましたが、カラム名と実際の動作が逆になってしまって紛らわしいので、widgetとvalidatorで反転させることにしました。Symfony 1.4+Doctrineです。

既存のsfWidgetFormInputCheckboxとsfValidatorBooleanを継承して実装します。

lib/widget/sfWidgetFormInputCheckboxInverse.class.php

<?php

class sfWidgetFormInputCheckboxInverse extends sfWidgetFormInputCheckbox
{
    public function render($name, $value = null, $attributes = array(), $errors = array())
    {
        
        if (!(null !== $value && $value !== false))
        {
            $attributes['checked'] = 'checked';
        }

        if (!isset($attributes['value']) && null !== $this->getOption('value_attribute_value'))
        {
            $attributes['value'] = $this->getOption('value_attribute_value');
        }

        return parent::render($name, null, $attributes, $errors);
    }
}

lib/validator/sfValidatorBooleanInverse.class.php

<?php

class sfValidatorBooleanInverse extends sfValidatorBoolean
{

    protected function configure($options = array(), $messages = array())
    {
        parent::configure($options, $messages);
        $this->setOption('empty_value', true);
    }

    protected function doClean($value)
    {
        if (in_array($value, $this->getOption('true_values')))
        {
          return false;
        }

        if (in_array($value, $this->getOption('false_values')))
        {
          return true;
        }

        throw new sfValidatorError($this, 'invalid', array('value' => $value));
    }

}

ほぼ、逆にしたメソッドでオーバーライドしただけです。チェックボックスにチェックを入れなかった場合の挙動がデフォルトではfalseなので、trueに変更しています。

これをformクラスから

        $this->widgetSchema['show_flag'] = new sfWidgetFormInputCheckboxInverse();
        $this->validatorSchema['show_flag'] = new sfValidatorBooleanInverse();

のように定義してやればいいだけです。

例によってカラムのタイプがbooleanになっていることを確認してください。なっていないと0でも1でもtrueと判断されてしまうので。

        $this->hasColumn('show_flag', 'boolean', null, array(
             'type' => 'boolean',
             'notnull' => true,
             'default' => 0,
             ));


sfWidgetFormInputCheckboxの正負を反転させる その1

symfony 1.4にて。データベースの定義は「表示」フラグなのに、フォームでは「非表示」時にチェックを入れさせたい場合などがありまして。

データベースのカラム自体の意味を変えるのは、逆にフォームの意味が今後変わったときにどうするんだという話なので、今回はモデルでの入出力時点で値を変更する方法を試してみました。

actAsで各モデルの好きなカラムを反転できる拡張にしてみました。

lib/InvertBoolean.class.php

<?php

class InvertBoolean extends Doctrine_Template
{
    protected $_options = array(
        'columns' => array()
    );
    
    public function setTableDefinition()
    {
        $this->addListener(new InvertBooleanListener($this->_options));
    }
    
    public function setUp()
    {
    }
}

lib/model/invert_boolean/InvertBooleanListener.class.php

<?php

class InvertBooleanListener extends Doctrine_Record_Listener
{
    protected $_options;
    
    public function __construct(array $options)
    {
        $this->_options = $options;
    }

    public function postHydrate(Doctrine_Event $event)
    {
        $obj = $event->data;
        foreach ($this->_options['columns'] as $column) {
            $obj->$column = !($obj->$column);
        }
        $event->set('data', $obj);
    }

    public function preSave(Doctrine_Event $event)
    {
        foreach ($this->_options['columns'] as $column) {
            $value = !($event->getInvoker()->get($column));
            $event->getInvoker()->set($column, $value);
        }
    }
}

postHydrateでデータをデータベースから読み出してきてハイドレートした後に、preSaveで保存する前にそれぞれデータを反転しています。

カラムのタイプはあらかじめbooleanにしておく必要があります。schema.ymlからデータベース生成した場合は問題ないかと思います。そうでないなら、モデルのsetUpメソッドにでも

        $this->hasColumn('show_flag', 'boolean', null, array(
             'type' => 'boolean',
             'notnull' => true,
             'default' => 0,
             ));

など。

これを使用できるようにするために、同じくモデルのsetUpメソッドに

$this->actAs('InvertBoolean', array('columns'=>array('show_flag')));

とすればOKです。actAsのcolumnsオプションに複数カラム名設定すれば複数カラムを一括で指定できます。

これで、sfWidgetFormInputCheckboxを使ったチェックボックスにチェックを入れたときにデータベースに0が保存され、チェックを外すと1が保存されるカラムができあがりました。

問題点として、カラム名と反対の動きをすることになるので、うっかりする可能性が高まります。

なので、動作は確認しましたが、結局この案は使ってません。次のエントリーで紹介するカスタムウィジェットの方法を使っています。


WSH用の便利関数

整理のために古いディスクを漁っていたら、汎用的に使えそうなWSH(VBScript)の関数群が出てきたのでメモ。

機能はファイルコピー、テンポラリディレクトリの取得、ファイルリストの取得、テキストファイルの中身を配列に読み込む、ファイルとディレクトリの存在確認、正規表現のマッチ。当時のことはよく覚えていないけど、とりあえずPHPライクに便利関数が欲しかったんだと思います。

Function FileCopy(src,dst,overwrite)
	
	Dim objFS, objWshShell
	Set objWshShell = WScript.CreateObject("WScript.Shell")
	Set objFS = WScript.CreateObject("Scripting.FileSystemObject")
	
	objFS.CopyFile src,dst,overwrite
	
End Function

Function GetTemporaryDirectory
	
	Dim objWshShell
	Set objWshShell = WScript.CreateObject("WScript.Shell")
	
	GetTemporaryDirectory = objWshShell.ExpandEnvironmentStrings("%TEMP%")
	
End Function

Function GetFileList(dir, reg)
	
	Dim objFS, objFolder
	Dim fileCol,tmpFile,fileNames
	Dim i
	Set objFS = WScript.CreateObject("Scripting.FileSystemObject")
	Set objFolder = objFS.GetFolder(dir)
	Set fileCol = objFolder.Files
	
	fileNames = Array(1)
	i = 0
	For Each tmpFile in fileCol
		If RegMatch(reg, tmpFile.name, False) Then
			fileNames(i) = tmpFile.name
			i = i + 1
			Redim Preserve fileNames(i)
		End If
	Next
	
	GetFileList = fileNames
	
End Function

Function FileReadArray(filename)
	
	Const ForReading = 1, ForWriting = 2
	
	Dim objFS, objFileStream
	Dim lines
	Dim i
	Set objFS = WScript.CreateObject("Scripting.FileSystemObject")
	Set objFileStream = objFS.OpenTextFile(filename, ForReading)
	
	lines = Array(1)
	i = 0
	Do While objFileStream.AtEndOfStream <> True
		lines(i) = objFileStream.ReadLine
		i = i + 1
		Redim Preserve lines(i)
	Loop
	
	FileReadArray = lines
	
End Function

Function FileExists(filepath)
	
	Dim objFS
	Set objFS = WScript.CreateObject("Scripting.FileSystemObject")
	
	FileExists = objFS.FileExists(filepath)
	
End Function

Function DirectoryExists(dir)
	
	Dim objFS
	Set objFS = WScript.CreateObject("Scripting.FileSystemObject")
	
	DirectoryExists = objFS.FolderExists(dir)
	
End Function

Function SetRegistry(strName, anyValue ,strType)
	
	Dim objWshShell
	Set objWshShell = WScript.CreateObject("WScript.Shell")
	
	objWshShell.RegWrite strName, anyValue, strType
	
End Function

Function RegMatch(pattern, string, ignoreCase)
	
	Dim regEx, Matches
	Set regEx = New RegExp
	regEx.Pattern = pattern
	regEx.IgnoreCase = ignoreCase
	regEx.Global = True
	Set Matches = regEx.Execute(string)
	
	If Matches.Count = 0 Then
		RegMatch = False
	Else
		RegMatch = True
	End If
	
End Function


Windows Live Essentialsのアップデート

いつものようにブログを書こうと思ってWindows Live Writerを起動してみたら、最新版があるから更新しろというメッセージ。それならばとLive Essentials全体を更新かけてみたのだけど、Windows Live Writerはバージョン番号変わらず…えー。

ちなみにアップデート前のバージョン番号:Build 14.0.8089.726 ja

アップデート後のバージョン番号:Build 14.0.8089.726 ja

The Windows Blog

上記記事見る限り、小さなバグフィックスがあったということだけど…?


VAIOのリカバリー時に、「エラー305: 64」表示

リカバリーを進めていくと、
エラー305: 64
とダイアログが表示されてリカバリーが17%くらいで止まる、というVAIOが持ち込まれてきました。
相談者はCDの焼きミスを疑い、SONYからリカバリディスクを取り寄せて試してみるも現象変わらず。

リカバリのメニューからコマンドプロンプトが起動できるので、中を見てみると、Cドライブは完全に空の状態。

試しにリカバリの推奨設定を無視して、パーティション設定でCドライブを50GBにして、残りをシステム復元用のパーティションにしてみたら、なぜかあっさり通って、CDの2枚目に入れ替えるようにというメッセージが表示されるまで進みました。
ということでメッセージに従うとあっさり復元成功。
何だろうこのバグ…

どうも復元用のパーティションが規定値では確保できないのが問題っぽいです。が、一度上記で成功すると2回目からは規定値でも復元可能になるという不思議な現象。

一応エラーコードの内容に関しては以下に掲載されているので参考までに。
http://www.kb.sony.com/selfservice/microsites/search.do?cmd=displayKC&externalId=C1000995&fes=true