Perl楽しいから好き

Perlをはじめとしたプログラミング周りのあれこれについて。モダーンなPerlを楽しんでいます。

Perlで、とある配列を指定した要素数ごとに別の配列リファレンスに分割する処理

my @arr = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);

↑ こういう感じの配列を、

my $expected = [
    [ 1, 2, 3, 4 ],
    [ 5, 6, 7, 8 ],
    [ 9, 10, 11, 12 ]
];

↑ こういう感じに分割したい欲望に駆られました。この場合は、13個の要素を持つ配列を4つの要素ずつの配列リファレンスに分割(端数は切り捨て)

で、テストから書きました。

#!/usr/bin/env perl
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use Test::More tests => 2;

BEGIN {use_ok('MyArrayUtility')}

# MyArrayUtility::divide_arrayのテスト
my @arr = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
my $arr_ref = \@arr;
my $item_number_in_list = 4;
my $divided_arr_ref = MyArrayUtility::divide_array($arr_ref, $item_number_in_list);
my $expected = [
    [ 1, 2, 3, 4 ],
    [ 5, 6, 7, 8 ],
    [ 9, 10, 11, 12 ]
];
is_deeply($divided_arr_ref, $expected, 'divide_array');


実処理部分はこんな感じで。

package MyArrayUtility;

use strict;
use warnings;
use utf8;

# 配列を指定した要素数ごとに別の配列に分割する処理
sub divide_array {
    my ($arr_ref, $item_number_in_list) = @_;
    my $count = @$arr_ref;

    # スライスしてできる配列の数(端数は切り捨て)
    my $array_num = int($count / $item_number_in_list);

    # 結果を格納する配列リファレンス
    my $divided_arr_ref = [];

    my $i = 0;
    my $j = 0;
    while ($i < $array_num) {
        while ($j < $item_number_in_list) {
            push(@{$divided_arr_ref->[$i]}, shift(@$arr_ref));
            $j++;
        }
        $i++;
        $j = 0;
    }

    return $divided_arr_ref;
}


1;

ディレクトリ構成はこんな感じで慣習通りに。

C:.
+---lib
|       MyArrayUtility.pm
|
\---t
        my_array_utility.t

なんか、もう少しスマートに書ける気がするけど、とりあえず要件は満たしているのでOKかな。

リファレンスがやっとわかり始めてきました。

Perl初心者な私でも、CPANテスターとしてオープンソースに貢献できた!

きっかけ

今まで気になりつつも、やったことなかったんですが、

moznionさんの2013年のスライド↓

YAPC::Asia 2013 - CPAN Testers Reports の情報を上手に使う

https://www.slideshare.net/moznion/yapc2013-26371522

www.slideshare.net


↑ の88枚目を見て、簡単そうなのでCPANモジュールのテストやってみました。


普段からApp::cpanminusでモジュールのインストールしてるなら超簡単にできます。

手順は簡単4ステップ。

ちなみにWindows10でやってみました。

手順

(1)まずは、App::cpanminus::reporterというモジュールをインストール。
C:\Users\kamioka>cpanm App::cpanminus::reporter
(2)報告者の情報をセットアップします。といっても名前とメールアドレスを指定する程度です。
C:\Users\kamioka>cpanm-reporter --setup
(3)Mojoliciousをテストします。
C:\Users\kamioka>cpanm --test-only Mojolicious

私の環境には既にMojoliciousがインストール済なので、オプションの「--test-only」を付けました。テストと合わせてモジュールのインストールも行う場合は「--test-only」無しでOKです。

(4)インストール結果を送ります。自分のパソコンの実行環境と合わせてインストールがうまくいったかどうかがCPANテスターズに送信されます。
C:\Users\kamioka>cpanm-reporter

CPANテスターになった日

送信してから数時間すると、

f:id:perl48:20180719010431j:plain

http://matrix.cpantesters.org/?dist=Mojolicious;os=mswin32;reports=1http://matrix.cpantesters.org/?dist=Mojolicious;os=mswin32;reports=1



↑こんな感じでテスト結果が反映されます。自分の名前が載ると、貢献できたなって気がします。


そして、Linux環境に比べてWindows環境のテスターが少ない感じ。

なので、WindowsPerlを使ってるならモジュールインストールがてらCPANテストもすると貢献度が高い気分に浸れます。



これで、私もCPANテスターの仲間入りっ!


ノースキルな私でもモブキャラ的な立ち位置から、Perl界隈に貢献できちゃいました。




この仕組み、いいねっ!

Perlでuse utf8したまま明示的に半角スペースを全角スペースに置き換える

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use 5.010;
use Encode qw(encode);

# 半角スペースを明示的に全角スペースにする
my $str = "ニューヨーク シティボーイ";
say encode('utf8', $str); # ニューヨーク シティボーイ
$str =~ s/\s/\x{3000}/g;
say encode('utf8', $str); # ニューヨーク シティボーイ

データベースに登録された氏名の苗字と名前の間が全角スペースのものがある。で、検索フォームから苗字と名前の間を半角スペースで入力した時にマッチさせたい、という要望に応えるために書きました。

use utf8プラグマを書くと \s が半角スペースにも全角スペースにもマッチする、というのは単純にスペース除去するときは便利。なんだけど、半角スペース←→全角スペースを置き換えたいとき、ちょっとつまづいちゃいました。

my $full_space = "\x{3000}";

↑このコードポイント表現で全角スペースを示せるので、それを使いました。

2018/7/21 追記

jitojitoさん、コメントありがとうございます。確かに半角スペースのみを厳密に指定するなら、そっちもコードポイントで記述するのがいいですね。

Perlでuse utf8したまま明示的に半角スペースを全角スペースに置き換える - Perl楽しいから好き

\s はタブ文字なども含まれますので、半角空白のみなら /\x{0020}/\x{3000}/ とするほうが良いかもしれません。

2018/07/21 10:07

ということで書き直してみました。
 ↓

#!/usr/bin/env perl
use strict;
use warnings;
use utf8;
use 5.010;
use Encode qw(encode);

# 半角スペースを明示的に全角スペースにする
my $str = "ニューヨーク シティボーイ";
say encode('utf8', $str); # ニューヨーク シティボーイ
$str =~ s/\x{0020}/\x{3000}/g;
say encode('utf8', $str); # ニューヨーク シティボーイ


イイ感じになりました。ありがとうございます!

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