MIME::Expander 0.02 is released

Perl でメールを扱うときには、じぶんはたいてい Email::MIME を利用します。これだけで間に合っているので、ほかとの比較もべつにしていないのですが、特に添付ファイル付きのメールを扱う時に、各パートに簡単にアクセスできるのが重宝します。

さて、ときに、そうして取り出した添付ファイル、専門用語的には MIME パートと言うでしょうか、現実的には、それらは圧縮されたアーカイブだったりするのが常ではないでしょうか。すると、各パートを取り出したあとに、それらを展開(解凍)しなければなりません。しなければならない、ということは必ずしもありませんが、まあ、展開しますよね。

そのいつものひと手間をも惜しむらく、あるとき、モジュールにするべく取り組んだのですが、その成果が MIME::Expander というユーティリティ・モジュールになりました。これをちょっとだけ紹介します。

POD に書いた英語は翻訳サイトに掛けたでたらめなものなので、あまり読んでほしくないのですが、そこにはいろいろのメソッドがざっと並んではいるものの、使うのは walk メソッドになります。

その動きは基本的に Email::MIMEwalk_parts メソッドのようなイメージになります。 walk_parts メソッドは、自身のメッセージの各パートをめぐり、パートごとにコールバック・ルーチンを走らせます。

例えば、 A.txtB.txt 二つがアーカイブされた C.zip という添付ファイルがひとつ付いた、 D.eml というメッセージがあるとします。

D.eml を、 Email::MIMEwalk_parts メソッドで処理すると、コールバックには本体の D.eml 自体のパートと、いわゆる添付ファイルである C.zip を持ったパートが渡ってくるので、実装者はそらが ZIP アーカイブであることを確認し、展開しなければなりません。しなければならない、ということは必ずしもありませんが、まあ。

1
2
3
4
5
6
7
8
9
10
11
my $em = Email::MIME->new("D.eml");
$em->walk_parts(sub {
  my $part = shift;
  if( $part->content_type =~ m[application/(x-)?zip(-compressed)?]i ){
    # ZIP に違いない
    my $uzip = IO::Uncompress::Unzip->new(...);
    $unzip->...;
    ...
    ...
  }
});

MIME::Expanderwalk メソッドではその手間を省きます。

その callback ルーチンには、 A.txtB.txt と、それぞれを持った単体のパートが渡ってきます(加えて D.eml 自体のパート)。 C.zip のパート自体は用無しなので、渡ってきません。

1
2
3
4
5
6
my $me = MIME::Expander->new;
$me->walk("D.eml", sub {
  my $part = shift; # is an instance of Email::MIME
  $part->body;      # A.txt, B.txt, D.eml
  ...
});

このように、MIME::Expander ではひとつのパートをコールバックへ渡す前に、展開可能であれば、それを展開し、幾つかのファイルになったそれぞれを、単体の Email::MIME パートに包んで、それらをコールバックに渡します。

現在は、つぎのアーカイブ形式を自動的に展開します。

  • application/bzip2
  • application/gzip
  • application/tar
  • application/zip
  • message/rfc822
  • multipart/mixed

末尾の二つはアーカイブではありませんが、似たようなものなので(?)同じように取り扱います。また、入れ子になったアーカイブも、アーカイブでなくなるまで再帰的に展開しようとします。そしてもし、たとえば ZIP ファイルそのものが欲しい時は、オプションを指定することによって、 ZIP は展開しない、といった動きもできるように、しました。

1
my $me = MIME::Expander->new({ expects => ['application/zip'] });

ほかにもオプションはありますが、些細なことなので端折ります──。

さて、こうしていちおうは形になって、じぶんがふだん扱う範囲ではうまく動いているのですが、しかし白状すると、内部で行っている MIME タイプの判定にちょっと不安があります。

ファイル・タイプの判定自体は外部の Perl モジュール(現在は File::MMagic )に任せているのではありますが、ほかいくつかの異なる判定モジュール( File::MimeInfoFile::LibMagic と)で異なる結果が得られたりするんです。たとえば上にさりげなさそうに例示しました Content-type を正規表現で判定しているところがあるのですが、そのとおり一口に ZIP といっても application/zip だったり application/x-zip だったり、はたまた application/x-zip-compressed だったり… 。まったく違うとまでは至らずとも、こうした些細な違いがちらほらあります。

MIME の仕様をしっかり理解できていないせいであるという自省はありながらも、じっさいどうなの? という猜疑心もちょっとあったりします。今後、そのあたりを整理できるとよいのですが、──願わくば、このあたりの事情に明るい諸先生方の目に触れることによって、「これじゃいかん」といったご指摘をいただけたらなと思う所存であります。

オチも含めてお粗末ではありますが MIME::Expander の紹介でした。