稲沢市よりお届けしてます。

Perlをはじめとしたプログラミング周りのあれこれについて。Perl界のモブキャラとして暗躍します(謎)。

Windows10コマンドプロンプトでTest::Mojo したときの日本語文字化け対処

テスト大事だよねって今さら改心した。


昨日『モダンPerl入門』を読んでて、「ナマケモノになるためにテスト書く」っていう感じのところが腑に落ちた。

確かにリファクタリングとか機能追加が億劫になるのって、動いてるものが同じように動くって確信持てずに進めて泥沼化するイメージがあるから。

自動テスト書いて、こまめにチェックして進めれば大胆なリファクタリングも恐れずに進められるしね。ま、Gitでバージョン管理しろとかの話も取り入れていかなきゃだけど、まずはテストを書くことから。

ということで、早速Mojolicious::Liteのアプリのテストを書いてみた。myapp.plのあるディレクトリにtディレクトリを掘って、そこに 01.auth.t っていう名前のテストスクリプトを配置。


Windowsコマンドプロンプトで日本語はダメなのか?

書いたテストは

(1)ログイン画面をGETしたときにHTTPステータス200が返ってきて「ログイン」という文字があるかどうか。

(2)ログイン画面をGETしたときにHTTPステータス200が返ってきて指定したinput タグの要素が存在するかどうか。

テストコードはこんな感じ↓

#!/usr/bin/env perl
use Test::More;
use Test::Mojo;

use FindBin;
$ENV{MOJO_HOME}="$FindBin::Bin/../";
require "$ENV{MOJO_HOME}/myapp.pl";

my $t = Test::Mojo->new;
$t->get_ok('/login')->status_is(200)->content_like(qr/ログイン/);
$t->get_ok('/login')->status_is(200)
    ->element_exists('input[name=csrf_token][type=hidden]')
    ->element_exists('input[name=login_id][type=text]')
    ->element_exists('input[name=password][type=password]');

done_testing;


しかし、文字コード問題に起因するっぽいエラーが出現。「Wide character in print at C:/usr/lib/Test/Builder.pm line 1826, line 399」みたいなエラーメッセージとともに下の画像のようなテスト結果に。

f:id:perl48:20160812003527p:plain

このあたりは、マルチバイト文字の国に生まれた宿命ですな。

6年前にもこんな記事書いてた↓

perl48.hatenablog.com



use utf8; するだけ。

解決は意外とあっさり。さっきのテストコードに「use utf8;」ディレクティブを追加するだけ。

さっきのとウォーリーを探せレベルで変更の少ないテストコード

#!/usr/bin/env perl
use Test::More;
use Test::Mojo;
use utf8;
use FindBin;
$ENV{MOJO_HOME}="$FindBin::Bin/../";
require "$ENV{MOJO_HOME}/myapp.pl";

my $t = Test::Mojo->new;
$t->get_ok('/login')->status_is(200)->content_like(qr/ログイン/);
$t->get_ok('/login')->status_is(200)
    ->element_exists('input[name=csrf_token][type=hidden]')
    ->element_exists('input[name=login_id][type=text]')
    ->element_exists('input[name=password][type=password]');

done_testing;

コマンドプロンプトでも問題なくテストパスしました。

f:id:perl48:20160812003542p:plain


今回のスクリプトの場合は、スクリプト内部に書いた「ログイン」って文字が内部文字コードになってるので、「use utf8」で文字コードをUTF8にしてあげるという感じですね。

Windows XP時代のコマンドプロンプトだと、さらに出力を「cp932」にエンコードしないと文字化けしてたけど、Windows10のコマンドプロンプトは大丈夫みたい。ありがたい。



さ、真の「怠惰」を目指してテストをモリモリ書いていきますよー。





5年前頃、本屋さんで立ち読みしてもチンプンカンプンだった「モダンPerl入門」。やっと内容が理解でき、生かせるレベルになりました。続けることで、僕のPerl力とかその他のパラメータも地味に上がってるんでしょうねー。なんか嬉しい。


PerlワンライナーでSAY!!!してみる

いい加減 say 使ってもいいかも。



say関数 といえば、Perl 5.10 から導入された「改行付き print」。Ruby書いてて「puts」とか超便利じゃん!って妬ましい気持ちになったものですが、Perlでもできちゃうんですねー。


ただ、僕の主戦場の格安レンタルサーバーではPerl5.8時代が長く続いていたので、登場してからsayする機会はなかなか訪れませんでした。


・・・そして時は経ち2016年。


Perlの最新バージョンが5.24になり、さくらインターネットのスタンダードプランでもPerl5.14まで選べるようになりました。「いつsayするか?今でしょ!!」(古い)ということで、コマンドラインでsayしてみました。




ワンライナーでsayするには -Eオプション

早速sayしようと思い、コマンドラインを叩くとエラーが・・・。


f:id:perl48:20160807141335p:plain

飛鳥があんなことになってしまったから「say yes」といえないのか、そんな妄想がよぎったけどググッたら解決。コマンドラインオプションを大文字の「E」、「E-girls」の「E」にすれば動きました。




f:id:perl48:20160807141408p:plain


迷わずに、say yes。






print と say の実行結果を比較


せっかくなので、printとの出力の違いをリトグリ(Little Glee Monsters)の「SAY!!!」で比較。



print で出力


f:id:perl48:20160807141418p:plain



出力が一行に並んじゃいますねー。改行させたかったら「. \"\n\"」を追加せなかん。




f:id:perl48:20160807141426p:plain

こんな感じ。泥臭さが隠し切れないですね。


Windowsコマンドプロンプトってなんでダブルクォートでスクリプトを囲ませるんだろ。評価したい「\n」を囲むダブルクォートをエスケープしなきゃいけないってマンドクサイ。



say で出力


f:id:perl48:20160807141437p:plain


イキってsay関数を使えば「\n」無しでもスマートに改行。うん、便利だ。







これで、「."\n"」って打鍵するのダルいなぁという気分ともオサラバです:)



Let's say!!!

Mojolicious::Lite のcsrf_fieldタグヘルパーでCSRF対策を体感してみた

クロスサイト・リクエスト・フォージェリ(CSRF)対策としてのトークン発行

Webアプリケーションを狙った攻撃としてポピュラーなCSRF。MojoliciousにそんなCSRF対策のためのタグヘルパーがあったので、試してみました。

CSRFだけでなくWebアプリケーションのセキュリティに関しては徳丸本を読んでおくとよいと思います。


>> Webアプリのセキュリティに関するデファクトスタンダード的な徳丸本




Mojolicious::Lite に標準装備されてるCSRF対策を触る

理屈だけじゃなくって、実際に動かしてみるのが一番学習効率が高いと思う。ということで、業務での実運用に使おうと目論んでいるMojolicious でCSRF対策をやってみた。

実行環境は、

OS: Windows10
Perl バージョン: 5.20.2
Mojolicious バージョン: 7.01

です。

ソースは以下の通り。




CSRF対策をした入力フォーム(csrf_protect.pl)

#!"C:\xampp\perl\bin\perl.exe"

use FindBin;
use lib "$FindBin::Bin/../../perl/site/lib/";
use Mojolicious::Lite;

get '/' => {template => 'target'};

post '/' => sub {
    my $c = shift;

    my $validation = $c->validation;
    return $c->render(text => 'BAD CSRF token! No more cracking!', status => 403)
        if $validation->csrf_protect->has_error('csrf_token');

    my $city = $validation->required('city')->param('city');
    $c->render(text => "あなたが住みたい町は $city ですね!")
        unless $validation->has_error;
} => 'target';

app->start;
__DATA__
@@ target.html.ep
<!DOCTYPE html>
<html>
<body>
%= form_for target => begin
    %= csrf_field
    %= label_for city => 'あなたが住みたいと思う町はどこですか?'
    <br /><br />
    %= text_field 'city'
    %= submit_button
% end
</body>
</html>



入力フォームへCSRFトークン無しでPOST送信を試みるスクリプト(csrf_protect_post.pl)

#!"C:\xampp\perl\bin\perl.exe"

use strict;
use warnings;
use LWP::UserAgent;

my $url = 'http://localhost:3000';
my %postdata = ('city' => '一宮市');

my $ua = LWP::UserAgent->new;
my $res = $ua->post($url, \%postdata)->as_string;

print $res;




実行



実際に実行してみましょう。サーバー側とクライアント側両方でスクリプトを実行するのでコマンドプロンプトを2つ立上げます。

f:id:perl48:20160805222446p:plain

まずは、「perl csrf_protect.pl daemon」でMojoliciousを立ち上げます。ポート番号3000でリッスン状態になります。


で、ブラウザからPOSTします。





f:id:perl48:20160805222457p:plain

ブラウザで「localhost:3000」にアクセスすると、簡素なフォームが現れます。





f:id:perl48:20160805222507p:plain

「愛知県稲沢市」と入力して「OK」をクリックすると、





f:id:perl48:20160805222514p:plain

入力した情報が表示されます。いたって普通です。





f:id:perl48:20160805222528p:plain

サーバー側の通信履歴は上の画像の通りです。ちゃんと「200 OK」が2回出てますね。







では、CSRFトークンなしでPOSTしてみます。



f:id:perl48:20160805222536p:plain

2つめのコマンドプロンプト画面で「perl csrf_protect_post.pl」と打ちます。





f:id:perl48:20160805222547p:plain

見事に「BAD CSRF token! ,,,」のメッセージが返されました。送信した「一宮市」は住みたい町になれない結果に。





f:id:perl48:20160805222555p:plain

サーバー側の通信履歴を見ると、最後の行が「403 Forbidden」になってます。CSRFトークンが無いから非表示されたことがわかります。





実験成功です!



f:id:perl48:20160805222604p:plain

入力フォームのソースコードを見てみると、


<input name="csrf_token" type="hidden" value="58054b73ccfe687d02d51bc6a3e2200d7be31fcb">

という部分があります。



ここに入ってるトークン(アクセスごとにランダム生成)を一緒にPOST送信して、サーバー側のバリデーションOKなら正常なレスポンス。NGなら、403エラーになるよ、という流れですね。



ロジックとしてはシンプルですが、実装して動かして体感すると理解が腹に落ちる感じがします。少なくとも僕にとって「Shut the fuck up and write some code」は有効みたいです。





■参考にさせてもらったURL

日本語版 Mojoliciousドキュメント(木本さん)
https://github.com/yuki-kimoto/mojolicious-guides-japanese/wiki/Mojolicious%3A%3AGuides%3A%3ARendering#user-content-クロスサイトリクエストフォージェリcsrfgithub.com



英語版 Mojolicious ドキュメント
http://mojolicious.org/perldoc/Mojolicious/Guides/Rendering#Cross-site%20request%20forgeryMojolicious::Guides::Rendering - Rendering content

Perlでエスケープシーケンスした16進数の置き換えにハマったけど、quotemeta()で解決した件。

難読化されてるPerlスクリプトを読みたくて、置き換え処理をチョコチョコっと書いたけどうまく動かず。perl -d hoge.pl みたいな感じでデバッガ使って動作確認しても、一見おかしなところは無いっぽかった。

 

原因は、置き換えに使ってる変数に入ってるバックスラッシュだったみたい。

 

うまくいったコードはコチラ

 ↓ ↓ ↓

use strict;
use warnings;

my %hex_ascii = (
	'\x21' => '!' ,
	'\x22' => '"' ,
	'\x23' => '#' ,
	'\x24' => '$' ,
	'\x25' => '%' ,
	'\x26' => '&' ,
	'\x27' => "'" ,
	'\x28' => '(' ,
	'\x29' => ')' ,
	'\x2a' => '*' ,
	'\x2b' => '+' ,
	'\x2c' => ',' ,
	'\x2d' => '-' ,
	'\x2e' => '.' ,
	'\x2f' => '/' ,
	'\x30' => '0' ,
	'\x31' => '1' ,
	'\x32' => '2' ,
	'\x33' => '3' ,
	'\x34' => '4' ,
	'\x35' => '5' ,
	'\x36' => '6' ,
	'\x37' => '7' ,
	'\x38' => '8' ,
	'\x39' => '9' ,
	'\x3a' => ':' ,
	'\x3b' => ';' ,
	'\x3c' => '<' ,
	'\x3d' => '=' ,
	'\x3e' => '>' ,
	'\x3f' => '?' ,
	'\x40' => '@' ,
	'\x41' => 'A' ,
	'\x42' => 'B' ,
	'\x43' => 'C' ,
	'\x44' => 'D' ,
	'\x45' => 'E' ,
	'\x46' => 'F' ,
	'\x47' => 'G' ,
	'\x48' => 'H' ,
	'\x49' => 'I' ,
	'\x4a' => 'J' ,
	'\x4b' => 'K' ,
	'\x4c' => 'L' ,
	'\x4d' => 'M' ,
	'\x4e' => 'N' ,
	'\x4f' => 'O' ,
	'\x50' => 'P' ,
	'\x51' => 'Q' ,
	'\x52' => 'R' ,
	'\x53' => 'S' ,
	'\x54' => 'T' ,
	'\x55' => 'U' ,
	'\x56' => 'V' ,
	'\x57' => 'W' ,
	'\x58' => 'X' ,
	'\x59' => 'Y' ,
	'\x5a' => 'Z' ,
	'\x5b' => '[' ,
	'\x5c' => '\\' ,
	'\x5d' => ']' ,
	'\x5e' => '^' ,
	'\x5f' => '_' ,
	'\x60' => '`' ,
	'\x61' => 'a' ,
	'\x62' => 'b' ,
	'\x63' => 'c' ,
	'\x64' => 'd' ,
	'\x65' => 'e' ,
	'\x66' => 'f' ,
	'\x67' => 'g' ,
	'\x68' => 'h' ,
	'\x69' => 'i' ,
	'\x6a' => 'j' ,
	'\x6b' => 'k' ,
	'\x6c' => 'l' ,
	'\x6d' => 'm' ,
	'\x6e' => 'n' ,
	'\x6f' => 'o' ,
	'\x70' => 'p' ,
	'\x71' => 'q' ,
	'\x72' => 'r' ,
	'\x73' => 's' ,
	'\x74' => 't' ,
	'\x75' => 'u' ,
	'\x76' => 'v' ,
	'\x77' => 'w' ,
	'\x78' => 'x' ,
	'\x79' => 'y' ,
	'\x7a' => 'z' ,
	'\x7b' => '{' ,
	'\x7c' => '|' ,
	'\x7d' => '}' ,
	'\x7e' => '~' ,
);

my @pattern;
while (my $key = each %hex_ascii) {
	push(@pattern, $key);
}

while(my $line = <DATA>) {
	foreach my $key (@pattern) {
		my $meta_key = quotemeta($key);
		$line =~ s/$meta_key/$hex_ascii{"$key"}/g;
	}
	print $line;
}

__DATA__
"\x70\x6f\x6b\x65\x6d\x6f\x6e" =>  "\x47\x4f",


ハッシュのキーをいちいち配列@patternに突っ込まずに、ダイレクトに

while(my $line = <DATA>) {
	while (my ($key, $value) = each %hex_ascii) {
		my $meta_key = quotemeta($key);
		$line =~ s/$meta_key/$value/g;
	}
	print $line;
}

って書く方がスマートですな。



f:id:perl48:20160803173544p:plain


ちゃんと「"pokemon" => "GO"」って出力されてますね。






↓ ↓参考にさせていただいたブログ

文字列を全置換するには (replace) | hydroculのメモ


d.hatena.ne.jp

PerlでTwitterアプリ開発するときにSSLが必要になったっぽい- Net::Twitter

今さら感満開ですが、Twitterのつぶやきアプリ的なものを作ってみようと思った。もちろん大好物のPerlで。

APIキーの取得から、Access token取得の流れまで解説してくれた下記ブログを参考にさせていただきました。

http://d.hatena.ne.jp/ozuma/20121230/1356801529


あざっす!


とはいえ、若干の仕様変更があったようでその当たりの違いと、 Net::Twitterモジュールをインストールするときにちょっとハマったので、そのあたりをお伝えします。ちなみに、Windows7(64bit)、Perl(v5.16.3)ActivePerl でやりました。


僕が個人的にハマったところ。
(1)「C:\Users\perl48>cpanm Net::Twitter」でNet::Twitterモジュールをインストールできなかった。
(2)Twitterの開発者ページの「Consumer key」と「Consumer secret」の呼び名が「API key」と「API secret」に変わっていた。
(3)アプリの「Permissions」を変更した後に「Access token」を再生成(Regenerate)しなければいけなかった。
(4)SSL接続を要求されるように仕様が変わっていた




ということで、それぞれについて詳細をば。






(1)「C:\Users\perl48>cpanm Net::Twitter」でNet::Twitterモジュールをインストールできなかった。
 

「cpanm があればどんなモジュールだって一発インストールだぜ!!ワイルドだろ?」
 


と思っていたんですが、Net::Twitter は他のモジュールへの依存が複雑だからかエラーになりました。そのときの失敗画面がコチラ


 ↓ ↓ ↓



【解決策】
冷静にエラーメッセージを読んで、足りないモジュールをcpanコマンドでインストールすることで、無事Net::Twitterモジュールをインストール完了しました。


ちなみに、追加でインストールしたのは、「Sub::Identify」というモジュールと「MooseX::Role::WithOverloading」というモジュールでした。






(2)Twitterの開発者ページの「Consumer key」と「Consumer secret」の呼び名が「API key」と「API secret」に変わっていた。


これは、単純に呼び名が変わっただけで、使い方は同じでした。


今のアプリ管理画面はこんな感じです

 ↓ ↓ ↓






(3)アプリの「Permissions」を変更した後に「Access token」を再生成(Regenerate)しなければいけなかった。


無事、Access token をゲットしてスクリプトを動かしてみたら、「お前には書き込む権限ねぇよ!ROMってろハゲ!」というようなエラーメッセージが返ってきました。


管理画面に「Permission」というタブがあるので、そこで「Read, Write and Access direct messages」という何でもイケちゃいそうなやつを選んで「Update settings」をクリック!

 ↓ ↓ ↓



改めてスクリプトを動かしても、やっぱり、「お前には書き込む権限ねぇよ!ROMってろハゲ!」のエラーメッセージ。「半年間はROMっとかないといけないのかなぁ??」と思ってたときに飛び込んできたのが先ほどの画面の「Note:」って部分。



「Changes to the application permission model will only reflect in access tokens obtained after the permission model change is saved. You will need to re-negotiate existing access tokens to alter the permission level associated with each of your application's users.」


細かいことはよくわからんが、「アクセス権限を変えたら、アクセストークンも作り直せよ」的なことを言ってるんだろうな。で、アクセストークンを再生成したらできました。



【解決策】
管理画面で「Permissions」タブの「Access」を変更後、API keys タブの「Your access token」というところの「Token actions」の「Regenerate my access token」っていうボタンをクリック!






(4)SSL接続を要求されるように仕様が変わっていた
参考にさせてもらったブログのソースのまんまだと、「SSLじゃなきゃ、受け入れてあげないんだから!」っていうガードの固い女子っぽいエラーメッセージが返されました。



実際のエラーメッセージはこんな感じ

 ↓ ↓ ↓




「The Twitter API now requires SSL. Add ( ssl => 1 ) to the options passed to new
to enable it. For backwards compatibility, SSL is disabled by default in this
version. Passing the ssl option to new will disable this warning. If you are
using a Twitter API compatbile service that does not support SSL, add
( ssl => 0 ) to disable this warning and preserve non-SSL connections in future
upgrades.
SSL is required at simple_tweet.pl line 23」


ここまで書いてくれたら、さすがに何すればいいかわかるなぁ。親切なモジュール作者に感謝。


【解決策】
new するときに 「ssl => 1 」を追加。(下にあるソース参照してください)

#!/usr/bin/perl

use strict;
use warnings;
use Net::Twitter;
use utf8;

my $api_key      = 'YOUR_API_KEY';
my $api_secret   = 'YOUR_API_SECRET';
my $token        = 'YOUR_TOKEN',;
my $token_secret = 'YOUR_TOKEN_SECRET',;

my $nt = Net::Twitter->new(
   traits => ['API::RESTv1_1'],
   consumer_key => $api_key,
   consumer_secret => $api_secret,
   access_token => $token,
   access_token_secret => $token_secret,
   ssl => 1,
);

my $text = 'そろそろ寝るか。';
my $result = $nt->update($text);

ということで、ちょいハマりしつつも無事動かせました。



 

Perl で複数のファイルを一気に開いて、Linuxコマンド【paste】みたいにつなげてみた

3つのファイルの1行目同士、2行目同士・・・n行目同士をつなげたい衝動に駆られました。

Linux環境では、

$ paste 1.txt 2.txt 3.txt > res.txt

みたいにすれば一発でしたが、Windows環境なのでそれもできず・・・。


で、Perl でやってみました。


まずは、つなげたい3つのファイルを準備。


1.txt (Shift_JISで保存)
 ↓


2.txt (Shift_JISで保存)
 ↓


3.txt (Shift_JISで保存)
 ↓



で、つなげるスクリプト

paste.pl(UTF-8Nで保存)
 ↓

#!/usr/bin/perl
# 
# Linuxコマンド【paste】っぽいことをする
#
# 2014.09.09_scripted by perl48


use strict;
use warnings;

use Encode;


my $file1 = "1.txt";
open my $fh1, '<', $file1
    or die qq{Can't open file "$file1": $!};

my $file2 = "2.txt";
open my $fh2, '<', $file2
    or die qq{Can't open file "$file2": $!};

my $file3 = "3.txt";
open my $fh3, '<', $file3
    or die qq{Can't open file "$file3": $!};


while(my $line1 = <$fh1>) {
    chomp $line1;
    $line1 = decode('cp932', $line1);

    my $line2 = <$fh2>;
    chomp $line2;
    $line2 = decode('cp932', $line2);

    my $line3 = <$fh3>;
    chomp $line3;
    $line3 = decode('cp932', $line3);

    my $res = $line1 . $line2 . $line3;
    $res = encode('cp932', $res);
    print $res . "\n";
}

close $fh1;
close $fh2;
close $fh3;



実行したら、こんな感じです。
 ↓







思わぬ副産物でしたが、Perlで複数のファイルハンドルを一気に処理することできるんですね。知らんかった。



ここの質疑応答の回答2を参考にさせていただきましたー!

http://okwave.jp/qa/q6529798.html






それにしても、なんだか同じような記述が続いて冗長なスクリプトだ。つなげたいファイルの数を増やしたときに、簡単に利用できるようにしたい。リファクタリングするとしたら、どうやってやろう??ファイル名を引数にしてサブルーチンにする感じ??




このあたりが自分の課題です。




 

Perl でUTF-8(BOM無し)ファイルをUTF-8(BOM付き)に変換する方法

Windows 向けのソフトに読み込ませるファイルをUTF-8(BOM付き)にする必要があったので、やってみた。TeraPadで開いて「文字コード指定保存」すればできるんだけど、スクリプト処理の一連の流れに乗せたかったので、やってみますた。

Windows7(64bit)環境でやってま〜す。


#!/usr/bin/perl
# 
# UTF-8(BOM無し) ⇒ UTF-8(BOM付き)にファイルを変換する
#
# 2014.09.07_scripted by perl48

use strict;
use warnings;
use utf8;

# 読み込むファイル名をセット
my $old = 'before.txt';

# 書き出すファイル名をセット
my $new = 'after.txt';

# ファイルハンドルをオープンして、外部テキストファイルの読み込み準備
open my $read, '<', $old
    or die "Couldn't open : $!";

# ファイルハンドルをオープンして、外部テキストファイルへ書き出し準備
open my $write, '>', $new
    or die "Couldn't open : $!";

print {$write} "\x{feff}" ;  # BOMを先頭につける

# 1行ずつ読みで、ただ書き出す
while (my $line = <$read>) {
    chomp $line;
    print {$write} $line . "\n";
}

# ファイルハンドルを閉じる
close $read;
close $write;


File::BOM っていうモジュールもあるみたいだけど、標準の環境で済ませたかったので文頭に{ EF BB BF } をつけてBOMりました。





TeraPadで開くと下のほうのバーに「UTF-8N」ってなってる。これがBOM無し状態。




で、上記のスクリプトを実行すると・・・





TeraPadで開くと下のほうのバーに「UTF-8」ってなる。これがBOM付き状態。






Ubuntu のときは nkf コマンドで済ませちゃいます。コマンドプロンプトPowerShellにも似たような機能あるのかな??