BrowserMob Proxyを使ってみる

github.com

JavaアプリとかSeleniumとかで使うのがメインみたいだけど、とりあえず挙動を掴むためにstandaloneで動かしてみる。

Windows環境では

$ bin/browsermob-proxy.bat

でBrowserMob Proxyを起動する。UNIX系OS(LinuxとかMacとか)ではbin/browsermob-proxyというシェルスクリプトがあるので、そこから起動すれば良さそう。

プロセスが起動したら、その後はREST API経由で使っていくらしい。

新しいインスタンスを作る

$ curl -X POST http://localhost:8080/proxy
{"port":8081}

新しいHARをオープン

$ curl -X PUT http://localhost:8080/proxy/8081/har

適当なURLにアクセス。 そのままだと証明書のエラーが出るので -k オプションでエラーを無視する。

$ curl https://www.google.co.jp/ -k -x http://localhost:8081

HARを取得

$ curl http://localhost:8080/proxy/8081/har

proxyインスタンスを削除

$ curl -X DELETE http://localhost:8080/proxy/8081

証明書エラーを何とかする

$ curl https://www.google.co.jp/ -x http://localhost:8081
curl: (60) SSL certificate problem: self signed certificate in certificate chain
More details here: https://curl.haxx.se/docs/sslcerts.html

GitHub - lightbody/browsermob-proxy: A free utility to help web developers watch and manipulate network traffic from their AJAX applications.

BrowserMob Proxy 2.1.0+ now supports full MITM: For most users, MITM will work out-of-the-box with default settings. Install the ca-certificate-rsa.cer file in your browser or HTTP client to avoid untrusted certificate warnings. Generally, it is safer to generate your own private key, rather than using the .cer files distributed with BrowserMob Proxy. See the README file in the mitm module for instructions on generating or using your own root certificate and private key with MITM.

ということのようなので、BrowserMob Proxyが提供する ca-certificate-rsa.cer をインポートするとよいらしい。

$ export CURL_CA_BUNDLE=$PWD/ca-certificate-rsa.cer
$ curl https://www.google.co.jp/ -x http://localhost:8081

PerlからIMAPでGmailを受信する

PerlからGmailを送信する方法については前に調べたけど、今回はGmailを受信する方法を調べてみた。

Net::IMAP::Clientでメールの受信、Email::MIMEで本文のParseをする。

まずは動くコードを。GmailのINBOXから最新10件のDate、From、Subject、本文を出力します。

use strict;
use warnings;
use utf8;
use Encode;
use IO::Socket::SSL;
use Net::IMAP::Client;
use Email::MIME;
use HTML::FormatText;

binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";

my $imap = Net::IMAP::Client->new(
   server   => 'imap.gmail.com',
   user     => $USER_ID,
   pass     => $PASSWORD,
   ssl      => 1,
   port     => 993,
) or die "Could not connect to IMAP server" unless $imap;

$imap->login or die('Login failed: ' . $imap->last_error);

$imap->select('INBOX');
my $messages = $imap->search('ALL');

for(my $i=0; $i<10; $i++){
  my $idx = scalar @$messages - $i - 1;
  my $data = $imap->get_rfc822_body($$messages[$idx]);
  my $parsed = Email::MIME->new($data);
  printf("%s %s %s\n", $parsed->header('Date'), $parsed->header('From'), $parsed->header('Subject'));
  
  my $body = '';
  $parsed->walk_parts(sub {
    my $part = shift;
    return if $part->subparts;
    if($part->content_type =~ m{text/html}i){
      $body = HTML::FormatText->format_string($part->body_str);
    }
    elsif($part->content_type =~ m{text/plain}i){
      $body = $part->body_str;
    }
  });
  # CRLF -> LF
  $body =~ s/\r\n/\n/g;
  printf("%s\n", $body);
}
$imap->logout;

短いコードだけど意外とハマったのでまとめておく。

GmailのSEARCH

「最新n件」のメールを表示させたい。

PODを見ると、Net::IMAP::Client->search の第2引数に 'REVERSE DATE' や '^DATE' のようなソート条件を指定できるらしい。
が、なにか指定するとreturnが空になってしまう。
capabilityを調べてみるとSORTが無いようなので、それが原因なのかなあ。

### $capab: [
###           'IMAP4rev1',
###           'UNSELECT',
###           'IDLE',
###           'NAMESPACE',
###           'QUOTA',
###           'ID',
###           'XLIST',
###           'CHILDREN',
###           'X-GM-EXT-1',
###           'UIDPLUS',
###           'COMPRESS=DEFLATE',
###           'ENABLE',
###           'MOVE',
###           'CONDSTORE',
###           'ESEARCH',
###           'UTF8=ACCEPT',
###           'LIST-EXTENDED',
###           'LIST-STATUS',
###           'LITERAL-',
###           'SPECIAL-USE',
###           'APPENDLIMIT=35651584'
###         ]

第1引数も'NEW'とか'RECENT'とか指定してみると空のリストが帰ってくる。よくわからん。

とりあえず'ALL'で全件とって、後ろからn件見ていくという実装にした。

IMAPは難しいな。。時間があるときにちゃんと調べてみよう。

Headerのdecode

ちょっと調べてみると手動でdecodeしている強者もいるみたいですが、Email::MIMEに任せるのが楽。

日本語がエンコードされたSubjectも上手いことUnicode文字列にしてくれます。

Bodyのdecode

これがちょっとややこしい。bodyなんちゃらは若干バリエーションがあって

  • body_raw : そのまま(base64エンコード、7bit ASCIIなど)のbody
  • body : base64 decodeされたbody
  • body_str : base64 decodeされたbodyをさらにcharsetでUnicode文字列にdecodeされたもの

基本的には body_str メソッドを使えばよい。

マルチパートの処理

普通のテキストメールならよいのだけど、HTMLメールにも対応する場合はマルチパートの処理が必要になる。

ということで walk_parts を使う。だいたいサンプルのパクリですが。。

普通に?partsメソッドをforループで回しても良さそうなんだけど、なんか不穏な解説があるので止めておく。

This is a stupid method. Don't use it.

Email::MIME - easy MIME message handling - metacpan.org

まぁ再帰的な構造だからコールバック関数で処理するというのが自然ではあるが、stupid methodとまで言わなくてもねぇ・・・。

CRLF -> LF

メールの改行はCRLFなのでLFに統一する。

PerlのYAML::SyckとJSON::XSでutf-8ファイルを取り扱うポイント

考えてみると当たり前なんだけど、毎回考えるのが面倒なので動くコードをまとめておく。

Perlunicode周りの挙動が面倒なんだよなあ。Python(3)の方がシンプルでよいね。モダンな言語はやっぱりこの辺の取り扱いがスマートだわ。

YAML::Syck

UTF-8 エンコーディングの差異まとめ YAML::XS、YAML::Syck、YAML::Old、YAML、YAML::Tiny - Tociyuki::Diary

普通の用途としてはLoadFile/DumpFileでファイルI/Oをする時に使うだけでしょう。
$YAML::Syck::ImplicitUnicode = 1 を指定すると、Load/Dumpの際にutf-8 decode/encodeをしてくれるようになります。

use strict;
use warnings;
use utf8;
binmode STDOUT, ":utf8";

use YAML::Syck;
$YAML::Syck::ImplicitUnicode = 1;

my $names   = LoadFile(shift);
my $keyword = 'ネコプラ';

foreach my $n (@$names){
  printf("%s %s $keyword\n", $n, (($n =~ /$keyword/) ? 'is' : 'is not'));
}
$ perl a.pl data.yaml
立花りく_//ネコプラ// is ネコプラ
坂下雅_なんキニ! is not ネコプラ
成瀬かおり_//ネコプラ// is ネコプラ
双葉ゆり(ラルムーン) is not ネコプラ
大島遥華_KATA☆CHU is not ネコプラ
高宮さくら_//ネコプラ// is ネコプラ
藍川みり_//ネコプラ// is ネコプラ
佐藤まりん_chuLa is not ネコプラ
もも is not ネコプラ

JSON::XS

[Perl] JSON モジュールの utf8 フラグ周りの仕様 tips 注意点: Kawanet Blog II

ややこしいですね。

JSON::XSは直接ファイルI/Oをしてくれるメソッドが用意されていないので、

  1. バイト列として読み込んでutf8(1)経由でデコード
  2. UTF-8文字列として読み込んでutf8(0)(デフォルト)でデコード

のどちらかで処理する必要があります。

use strict;
use warnings;
use utf8;
binmode STDOUT, ":utf8";

use Path::Class qw/file/;
use JSON::XS;

my $content = file(shift)->slurp;
my $names   = JSON::XS->new->utf8->decode($content);

# あるいはこちらでも可
# my $content = file(shift)->slurp(iomode => '<:encoding(utf-8)');
# my $names   = JSON::XS->new->decode($content);

my $keyword = 'ネコプラ';

foreach my $n (@$names){
  printf("%s %s $keyword\n", $n, (($n =~ /$keyword/) ? 'is' : 'is not'));
}

YAMLと違ってJSONはファイルだけでなくWeb APIでも使われることが多いので、JSON::XSには常にUTF-8 encode済みの文字列を渡す(utf8-flag disabledとして使う)という運用のほうが分かりやすいかもしれません。

まあ、「入口でdecode、出口でencode」の原則さえ分かっていればやりやすい方法でいいかなと思いますが。

imapsyncでモバメをGmailにコピーする その3 - ローカルのmboxをThunderbird + Got Your BackでGmailにコピーする

現在受信しているモバメはimapsyncで問題なくGmailに同期できるようになった。

喜んでいたのもつかの間、昔ローカルにコピーを取っていたモバメのmboxファイルを発見してしまった。ついでにこれもGmailに同期しておきたい。

mboxをimapにコピーするツールは探せばいくらでも出てくるが、Gmailに特化したGot Your Back(GYB)を使うのが確実そう、アップデートもマメだしということで使ってみた。

Home · jay0lee/got-your-back Wiki · GitHub

最近GmailがIFTTTなどのサードパーティーアプリを使用禁止にしたという話題があって、このGYBも影響を受けたんだけど

自分のアカウントでプロジェクトを作ってOAuth認証すればよいという回避策を取ったようです。

Google is stopping acces to gyb - Google Groups
Release GYB 1.20 · jay0lee/got-your-back · GitHub

うーん、それでいいのだろうかという気もする(笑)パスワード忘れそうだから紙に書いて貼っておくわ!に通じるものがあるような・・・。

まあいいけど。

使い方はWikiの方にめっちゃ丁寧に書いてあるので、それを読んでそのままやれば問題ない。Cygwinのコンソール(mintty)だと標準入力でハングするのでコマンドプロンプトを使った。

オプションはこんな感じで。

C:gyb>gyb --email kobayashi01234@gmail.com --action restore-mbox --local-folder local_mbox_hkt --strip-labels --label-restored Mobame/Mobame_HKT48

Using backup folder local_mbox_hkt

Restoring from 1.36gb file local_mbox_hkt\hkt.mbox...
restoring 1 messages (14920)
done!

C:gyb>gyb --email kobayashi01234@gmail.com --action restore-mbox --local-folder local_mbox_mobame --strip-labels --label-restored Mobame

Using backup folder local_mbox_mobame

Restoring from 42.65mb file local_mbox_mobame\mobame.mbox...
 restoring 5 messages (745) - 100.00%
Restoring from 17.45mb file local_mbox_mobame\namamail.mbox...
 restoring 5 messages (630) - 100.00%
done!

ちなみに"gmail mbox インポート"などで検索するとThunderbirdのImportExportToolsというアドオンを使ってローカルにインポートして、IMAP4アカウントのフォルダにコピーするという手順がたくさん出てくる。

仕事がはかどるGmailテクニック(14) アーカイブ機能で作成したバックアップをGmailにインポートするには | マイナビニュース

これでもまぁよい。

ただメールの数が少ない(1000未満くらい)と問題なく動くんだけど、メールの数が多い(1万オーバー)といつの間にか止まっていたりしてうまく動かないので注意。

同期したら Remove Duplicate Messages (Alternate) というアドオンで重複したメールのチェックができるので、同じメールをコピーしていないかチェックする。

GYBの方でも重複メールをコピーしない機能がデフォルトでONになっているから、基本的には問題ないと思う。

imapsyncでモバメをGmailにコピーする その2

特に実用上支障はない(自分は使わないので)けど、日本語のフォルダが文字化けする。
これはデコードにUnicode::Stringを使ってるせいで、Encode::IMAPUTF7を使うとうまくいく。
IMAP4ではUTF-7ではなく修正UTF-7というものを使っているのが原因らしいが、まあ詳しく調べる気はあんまりない。

use strict;
use warnings;

use Unicode::String;
use Encode;
use Encode::IMAPUTF7;

my $s = shift;
print imap_utf7_decode($s);
print "\n";
print encode('utf8', imap_utf7_decode2($s));

sub imap_utf7_decode2 {
  my $s = shift;
  return decode('IMAP-UTF-7', $s);
}

# Copied from http://cpansearch.perl.org/src/FABPOT/Unicode-IMAPUtf7-2.01/lib/Unicode/IMAPUtf7.pm
# and then fixed with
# https://rt.cpan.org/Public/Bug/Display.html?id=11172
sub imap_utf7_decode {
        my ( $s ) = shift ;

        # Algorithm
        # On remplace , par / dans les BASE 64 (, entre & et -)
        # On remplace les &, non suivi d'un - par +
        # On remplace les &- par &
        $s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/xg ;
        $s =~ s/&(?!\-)/\+/xg ;
        $s =~ s/&\-/&/xg ;
        return( Unicode::String::utf7( $s )->utf8 ) ;
}
$ perl u7.pl "&,wM- 90&ZeVO5VJNMG5T10,hMOEw,DDr-"
# 90日以前の受,hMOEw,DDr-
# 90日以前の受信メール

それにしてもimapsync、しょうもないバグが多くないか。重要なバグがないから良いという考え方もあるが。

imap_utf7_encode()も同じように直したほうがいいかもしれない(未確認)

imapsyncでモバメをGmailにコピーする

この話の続き。

ThunderbirdでドコモメールをGmailに自動転送する - kkobayashi_a’s blog

今までThunderbirdのフィルタ機能を使ってキャリアメールからGmailにモバメをコピーしていたけど、そのためだけに常時Thunderbirdを起動させておくのがどうもオーバースペックだなと思っていて。メモリ300~500MBくらい使ってるし。

近々Windowsを再インストールするので、せっかくだからThunderbirdを使わずコマンドラインでcronに登録できるようなものを探してみました。

どうやらimapsyncというツールで目的が達成できそう。

Official imapsync migration tool ( release 1.921 )

有料のソフトっぽいけど、GitHubで古いバージョンが公開されている。

GitHub - imapsync/imapsync: Imapsync is an IMAP transfers tool. The purpose of imapsync is to migrate IMAP accounts or to backup IMAP accounts. IMAP is one of the three current standard protocols to access mailboxes, the two other are POP3 and HTTP with webmails, webmails are often tied to an IMAP server. Upstream website is

makeの仕方がよく分からん。まあ中身はPerlスクリプトなのでそのまま実行できますね。

オプションはこんな感じ。

imapsync --host1 $HOST1 --user1 $USER1 --password1 $PASS1 --user2 $USER2 --password2 $PASS2 --gmail2 --folder INBOX --f1f2 INBOX=Mobame --nofoldersizes --nofoldersizesatend --noresyncflags --search "SENTSINCE 06-Apr-2019 OR FROM mls.ngt48.com FROM sp.hkt48.jp"

自分はGmailで"Mobame"ラベルを付けているので、--f1f2 INBOX=Mobame の設定でInboxのメールをMobameフォルダ(IMAP上ではラベルはフォルダ扱いになる)にコピーする。

--nofoldersizes --nofoldersizesatend は時短のため。

--noresyncflags はGmail側で既読にしたメールが次回のsyncで未読に戻ってしまうのを防ぐため。

--searchオプション、IMAPのsearch queryがポーランド記法らしく癖がある。モバメのアドレスを増やす場合は "OR OR FROM mls.ngt48.com FROM sp.hkt48.jp FROM mm.akb48.co.jp"という感じで書けばよい(と思う)。

おまけ(Cygwinで動かす場合)

Cygwinで動かすとpsのオプションがなんちゃらというエラーが出る。これはツール自身のメモリー使用量を "ps -o vsz" で取得しているため。

Thunderbirdを常駐させておくことに比べたらメモリー使用量なんて微々たるものなのでエラーなど無視しておいてもいいんだけど、せっかくなので直しておく。

https://github.com/imapsync/imapsync/pull/22

WindowsだけじゃなくてCygwinの場合もtasklistからメモリー使用量を取得するようにする。

実はここの修正だけでは不十分で、memory_consumption_of_pids_win32()関数内でCygwinのPID -> WindowsのPIDの変換をする必要があります。

Perlで書かれてると治すのが楽でいいわ。笑

sub memory_consumption_of_pids_win32 {
        # Windows
        my @PID = @_;
        if ( 'cygwin' eq $OSNAME ) {
                @PID = map { open my $fh, '<', "/proc/$_/winpid"; $_=<$fh>; tr/\x0A\x0D//d; $_ } @PID;
        }

スキャンしたファイルをHuginでstitchする

雑誌のスキャンはドキュメントスキャナよりフラットベッドスキャナの方が圧倒的に使いやすい。

スキャン画像に変な白い線や黒い線が入ってイライラすることもないし、硬い紙も柔らかい紙も気にせずスキャンできるし、複数ページの巻き込みもない。

買う前には気づかなかったメリットとしては、ポスターやA4ワイド雑誌など、一度でスキャンできないものも複数の画像をつなぎ合わせて(stitch)きれいにスキャンできる。

今まではスキャナに付いてたScan-n-Stitch Deluxeというのを使っていて、全く不満はなかったのだけど、ファイルが増えてくるといちいちポチポチやるのが面倒くさい。バッチで一気に処理したい。

と思って調べていると、Huginというパノラマ画像作成ツールがコマンドラインツールをサポートしてるらしい。というか、Hugin自体がコマンドのフロントエンド(GUI)になってるっぽい。

Hugin tutorial — Stitching flat scanned images

チュートリアルにやり方が載っているのでそのままやれば良い。

・・・はずなのだけど、全然うまくいかん。たぶん高機能すぎて使い方が分からないんだと思う。そもそもカメラを前提にしてるので、設定項目も意味がわからん。

で、試行錯誤の末、超簡単なやり方を見つけた。

f:id:kkobayashi_a:20190318012550p:plain

アシスタントの画面から、"1. Load Images" で画像を読み込み。FOVは10を設定すれば良い。次に"2. Align"を実行。そして"3. Create panorama"で画像を生成。

・・・普通にアシスタントに沿っていれば問題なかった。チュートリアルとはなんだったのか。

親切なことにチュートリアルのページからシェルスクリプトをダウンロードすることができる。これを実行すると期待通りコマンドから一連の処理ができる。

現在のバージョン(2018.0.0)で動くように少し手を加えたけど、概ね元のスクリプトで動くっぽい。

#! /bin/sh
# hugin command tools script to stitch scanned images, fov unknown
# use of fov >= 10 should be OK, could simply set FOV=10
# Terry Duell 2013, 2014

# usage...run-scan-pto_var.sh outputprefix fov

#get the output file prefix
Prefix="$1"

# get the fov
FOV=10

pto_gen --projection=0 --fov=$FOV -o project.pto "$2" "$3"
pto_lensstack -o project1.pto --new-lens i1 project.pto
cpfind -o project1.pto --multirow project1.pto
cpclean -o project2.pto project1.pto
linefind -o project3.pto project2.pto
pto_var -o setoptim.pto --opt 'r,d,e,!r0,!d0,!e0' project3.pto
autooptimiser -n -o autoptim.pto setoptim.pto
pano_modify  --projection=0 --fov=AUTO --center --canvas=AUTO --crop=AUTO --ldr-file=JPG --ldr-compression=95 -o autoptim2.pto autoptim.pto
hugin_executor --stitching --prefix=$Prefix autoptim2.pto

# pto2mk -o project.mk -p $Prefix autoptim2.pto
# make -j 2 -f project.mk all

# open pto files from each step in hugin to check how it all works

古いバージョンではpto2mk & makeでやっていた処理が、今ではhugin_executorで一気にできるようになったんですね。makeをインストールしてない環境でも簡単に実行できるのはよい。

なお3枚以上の画像を結合する場合はpto_lensstackで画像ごとに新しいレンズを割り当てるようにすればよいのだと思う(未確認)。