七転び八起きブログ

MIME::Toolsドキュメント私家版日本語訳

2005年01月14日   :: CGI

MIME::Toolsの理解がどうにもいい加減な気がするので、cpanのドキュメントを訳してみました。私家版なので、翻訳ミスなどあるかと思います。もしありましたら、コメントお待ちし取ります('A`)

しかし英語が出来ないことが年を経るごとに弱点に…。こないだ見たシュワンツのライディングスクールのDVDも字幕無かったら完全にアウトでした。何か強烈なモチベーションを得られそうなものを探さなければ。

原文はMIME-tools 5.503(cpanが最近重いのでこっち) また、So you want to make a Mime Parser ...も役に立つかと思いますのでクリップ。

しかし基本的に好きな分野ではないせいか、コードを書いているとすぐに飽きます。Blog Hacksの作者の人たちとか、あれですかね、絵描きが、絵さえ描いていれば他に何もいらないと思うような、そういう感じなんでしょうか。うぅむ。

名前

MIME-tools

構文

MIME形式のメッセージを分析して、指定したディレクトリにMIMEコンポーネントを出力するという基本的なコードをいくつか書きます。

use MIME::Parser;
###パーサを作り、オプションをいくつか設定する。
my $parser = new MIME::Parser; $parser->output_under("$ENV{HOME}/mimemail");

### パースして入力
$entity = $parser->parse(\*STDIN) or die "parse failed\n";

### トップレベルのエンティティをチラッと見る。
$entity->dump_skeleton;

続いて、3つのパートを含んだMIME形式のメッセージを組み上げて、さらにそれを送信するコードを書きます。

use MIME::Entity;

###トップレベルの要素を作り、メールのヘッダーをセットする。
$top = MIME::Entity->build(Type =>"multipart/mixed",
From => "me\@myhost.com",
To => "you\@yourhost.com",
Subject => "Hello, nurse!");

### パート1 単純なテキスト
$top->attach(Path=>"./testin/short.txt");

### パート2: GIF画像
$top->attach(Path => "./docs/mime-sm.gif",
Type => "image/gif",
Encoding => "base64");

### パート3 いくつかの一般的な文書
$top->attach(Data=>$message);

### 送るぞ
open MAIL, "| /usr/lib/sendmail -t -oi -oem" or die "open: $!";
$top->print(\*MAIL);
close MAIL;

MIME::Toolsの各ページを見れば、もっと色々スクリプトの例が載っています。

記述

MIME::ToolsはPerl5のコレクションです。

これは、シングルパートないしマルチパート(ネストしている場合もある)のMIMEメッセージを分析したり、デコードしたり、生成する為のモジュールです。
(そうだよ少年、これは気味でもGIF画像を添付したメールを遅れるってことだよ。)

必要条件

MIME::Toolsを使うには、システムに以下の物が必要です。

File::Path
File::Spec
IPC::Open2 (オプション)
IO::Scalar, ... from the IO-stringy distribution
MIME::Base64
MIME::QuotedPrint
Net::SMTP
Mail::Internet, ... from the MailTools distribution.

これらの必須モジュールが入っているかどうか、また、そのバージョンがいくつなのかは、お手持ちのシステムのMalefile.PL等をご覧下さい。

クイックツアー

クラスの概観

一般的に直接扱うであろうクラスの概観はこんな感じです。


    (START HERE)            results() .-----------------.
          \                 .-------->| MIME::          |
           .-----------.   /          | Parser::Results |
           | MIME::    |--'           `-----------------'
           | Parser    |--.           .-----------------.
           `-----------'   \ filer()  | MIME::          |
              | parse()     `-------->| Parser::Filer   |
              | gives you             `-----------------'
              | a...                                  | output_path() 
              |                                       | determines
              |                                       | path() of...
              |    head()       .--------.            |
              |    returns...   | MIME:: | get()      |
              V       .-------->| Head   | etc...     |
           .--------./          `--------'            |
     .---> | MIME:: |                                 |
     `-----| Entity |           .--------.            |
   parts() `--------'\          | MIME:: |           /
   returns            `-------->| Body   |<---------'
   sub-entities    bodyhandle() `--------'
   (if any)        returns...       | open()
                                    | returns...
                                    |
                                    V
                                .--------. read()
                                | IO::   | getline()
                                | Handle | print()
                                `--------' etc...

図解すると、こういう感じで構文を解析しているのです。

"Parser"はMIMEストリームを解析します。
ParserはMIME::のインスタンスです。
これをファイルハンドルの様に、入力ストリームに持っていくことで、そのメッセージを解析できます。
解析が成功裏に終われば、MIMEエンティティが返されます。
解析されたメッセージは、エンティティとして表現されます。
エンティティとは、Mail::Internetのサブクラスである、MIME:Entity:のインスタンスです。
エンティティは、Content-typeやsenderやsubjectline等のメッセージヘッダーの情報を持っています。
エンティティの"body"部分は、メッセージデータの場所を知っています。
そのメッセージデータを読み書きするために、"open"することが出来ます。そうすることで入出力のハンドル( I/O handle )を持ってくることができるのです。
"body"を開いて、入出力ハンドル( I/O handle )を得て、メッセージデータを読み書きできます。
このハンドルは、IO::と基本的には同じです。ハンドルとファイルハンドル……これはどんなクラスでもありうるものです。それが、小さくて、基礎となっているデータソースへの標準的な読み書きの手順をサポートしている限りは。

2つのパートを含んでいる典型的なマルチパートのメッセージ(グリーティングメッセージとGIF画像)は、MIME::Entityのツリーになっています。それぞれのパーツもそれぞれMIME::Headを持っています。こんな感じに。


    .--------.
    | MIME:: | Content-type: multipart/mixed
    | Entity | Subject: Happy Samhaine!
    `--------'
         |
         `----.
        parts |
              |   .--------.
              |---| MIME:: | Content-type: text/plain; charset=us-ascii
              |   | Entity | Content-transfer-encoding: 7bit
              |   `--------'
              |   .--------.
              |---| MIME:: | Content-type: image/gif
                  | Entity | Content-transfer-encoding: base64
                  `--------' Content-disposition: inline;
                               filename="hs.gif"

メッセージの構文解析

まずはMIME::Parserインスタンスを作り、どこに抽出したファイルを置くかや、ファイルの命名ルールなどのパラメータを決めるところから解析は始まります。

そのインスタンスを、MIMEメッセージが待っている、読み取り可能なファイルハンドルに持って行きます。それが上手くいくと、Mail:Internetのサブクラスである、MIME::Entityを得ることが出来ます。これは以下のものを内に持っています。

  • MIMEのヘッダーのデータを持っているMIME::Head。これはMIME::Headerのサブクラスです。
  • メッセージの本文の場所を知っているMIME::Body。あなたは、これを"open"して読み取りることができます。データへのファイルハンドルをくれるのです。これはファイルハンドルと同様のオブジェクトです。そして、IO::Handleインターフェイスのサブクラスである限りどんなクラスでもあり売ります。

もし元のメッセージデータがマルチパートだったら、MIME::Entityオブジェクトは、空ではない"parts"のリストを持っています。それはそれぞれMIME::Entityを持っています(それはさらにネスとしてるかもしれません)。

内部では、パーサ(MIME::Parser)は、必要なときはMIME::Decorderインスタンスに、エンコードされたデータをデコードさせます。MIME::Decorderはbase64等のサポートしているエンコーディングから、デコード可能なインスタンスへのマッピングをします。このマッピングはnewやexperimentメソッドで変更を加えることが出来ます。また、それ自体でデコーダとして使うことが出来ます。

メッセージを組み上げる

メッセージの組み上げは、全てMIME::Entityを通じて行います。シングルパートのメッセージなら、MIME::Entity/buildコンストラクタで簡単に作ることが出来ます。

マルチパートのメッセージなら、まず MIME::Entity::build()メソッドトップレベルのマルチパートを作ります。そして MIME::Entity::attach() でパーツをくっつけていきます。
  ※注意:私たちが「メールと添付したGIF画像」と思っているものは、実際は2つのパートを含むマルチパートメッセージです。一つ目はテキスト、2つ目はGIF画像です。

MIMEエンティティを作っているとき、二つの重要な情報を与えてあげないといけません。 content type と content transfer encodingです。これは直接にファイルのタイプを決めます。例えば、 HTML file なら text/htmlです。もう一つの、 encodingは、しかしながらトリッキーです。例えば、7bit-compliantなHTMLもあれば、非常に長くて、信頼性を得るために quoted-printableで送られないとまずいものもあります。

メールを送る

MIME::EntityはMail::Internetを継承していますので、それを使ってメールを送ることが出来ます。例えば

$entity->smtpsend;

エンコードとデコードのサポート

MIME::Decorderクラスは同様にエンコードすることもできます。これは、MIME::Entityを出力するときに行われます。全てのスタンダードなエンコードがサポートされています(詳細に関してはをMIME入門書をご覧下さい)


    Encoding:        | Normally used when message contents are:
    -------------------------------------------------------------------
    7bit             | 7-bit data with under 1000 chars/line, or multipart.
    8bit             | 8-bit data with under 1000 chars/line.
    binary           | 8-bit data with some long lines (or no line breaks).
    quoted-printable | Text files with some 8-bit chars (e.g., Latin-1 text).
    base64           | Binary files.

あなたが与えられた文書に対して、どのエンコーディングを選ぶかは、主に(1)あなたが文書の内容(テキストなのかバイナリなのかとか)について何を知っているか、(2)7ビット通信を行うインターネットでの電子メール送受信において、信頼できるメッセージを必要とするか、次第です。

一般的には、 quoted-printable で base64 なものだけが、保障できる信頼性のある送受信データです。他の三つ、まず no-encodingはただ単にデータを送ります。そのデータが1000行未満の7ビットASCIIであり、マルチパート境界でコンフリクトを起こしていない限りにおいて、信頼できます。

私は、 content-type と encodingを。ファイルパスから自動的に推定できるように作ったつもりですが、問題もあるようです…少なくともMail::capは…。

メッセージのロギング

MIME::Toolsは様々な外部からの入力を扱う、複雑なツールキットです。ロギングは、裏で本当なにがおこなわれているかを知る助けになります。MIME::Tools自身で記録しているメッセージがいくつかあります。

Debug Messages
STDERRに直接、 "MIME-tools :debug "という接頭辞をつけて出力します。あなたがDebuggingするように設定しない限り、ログは出力されません。
 
Warning messages
異例の事態になったときに、標準のPerlがwarn()で出すものと同じメカニズムで記録されます。 $^Wが真で、MIME::Toolsがquietに設定されていなければ記録されます。
Error Messages
何かが失敗したときに、標準のPerlがwarn()で出すものと同じメカニズムで記録されます。 $^Wが真で、MIME::Toolsがquietに設定されていなければ記録されます。
Usage messages
上の”典型的な”ものとは違って、データ処理や使い方の警告です。あまり良くない使い方や怪しげな呼び出しを開発者に警告します。$^Wが真で、MIME::Toolsがquietに設定されていなければ記録されます。

MIME::Parser(ないし内部のヘルパークラス)が上記のようなメッセージを伝えたいとき、上の様な関数が呼び出される前に、即座にMIME::Parser::Resultオブジェクトが呼び出されます。つまり、各Parserはそれぞれ独自にトレースログを持っているということです。

 

ツールキットを設定する

debugging
デバッグのオンオフを設定します。デフォルトはオフです。 (例 : MIME::Tools->debugging(1); )
quiet
warningとerrorメッセージの報告をオンオフします。デフォルトはオンです。 (例 :  MIME::Tools->quiet(1); )
version
MIME::Toolsのバージョンを返します。(例 :  print MIME::Tools->version, "\n";  )

THINGS YOU SHOULD DO

(省略)

THINGS I DO THAT YOU SHOULD KNOW ABOUT

(省略)

 

MIME入門書

MIMEを解析しなければいけないけれど、詳しいことが良く分からない…大丈夫です。

用語集

ここにあるのは、RFC-1521に適合した専門用語についての定義です。それぞれは同等のMIMEモジュールを携えています…。

atattchment
"atattchment"マルチパートに関して、一般に広まっている俗語です。「これが前に約束してた画像だよ」みたいなメッセージは、これには含まれません。システム上は、単なるトップレベルのエンティティの下にあるMIME:Entityです。恐らく、partsの中の一つになっています。
body
エンティティの"body"とは、ヘッダーと、実際のいわゆる”メッセージ”を含む部分です。例えば、GIF画像の添付書類があるなら、そのGIFファイルのbodyは、 base64によってコード化されたGIFファイルそれ自体です。bodyはMIME::Bodyインスタンスによってあらわされます。bodyhandle()メソッドを使って実際のメール本文を得ることが出来ます。
body parts
マルチパートのエンティティのパートの一つなので、"body parts"はまた、bodyとheaderを持ちます。なのでこれで"bodyのbody"というということの意味が分かりますね。bodyはエンティティとほぼ同義です。もちろん、MIME::Entityインスタンスです。
entity
エンティティは、メッセージ部分か、body partsのどちらかを意味しています。すべてのエンティティにはheaderとbodyがあります。エンティティはMIME::Entityで表されます。
エンティティからheader(MIME::Header)とbody(MIME::Body)を得るメソッドがあります。
header
MIMEメッセージの一番上にあるものです。これは "Content-type", "Content-transfer-encoding",などを含んでいます。全てのMIMEエンティティはMIME::Headerで表されるヘッダーを持っています。headメソッドで、エンティティからMIME::Headerを得ることが出来ます。
message
一般的に、messageはネットワークで伝達されている、完全に(ないしトップレベルが)テキストであるものを意味します。
現在、"messages"をきちんと明白に定義するパッケージは、MIME::にはありません。messageとは、ファイルやファイルハンドルから読まれうるデータのストリームです。あなたは、MIME::Parserで返されたMME::Entityを、messageの全てであるとして扱えます。

Content-types

これは、通常どんな形式のデータがMIMEメッセージとして示されるかを、 majortype/minortype と言う感じで記述したものです。主要なものは以下の様になっています。より包括的なリストは、RFC-2046で見つけることが出来るかもしれません。

application
他のカテゴリーにはまらないデータで、特にアプリケーションで処理されるもの。
audio
オーディオ。
image
グラフィックスデータ。
message
通常のメッセージ、メールまたはMIMEメッセージ 。
multipart
他のメッセージを含むメッセージ 。
text
人が読むことになっている、文のデータ。
video
ビデオかビデオ+オーディオデータ。

Content transfer encodings

これは、メッセージのbodyが安全な転送のためにどのようにパッケージされるかということです。主要な5つのMIMEエンコーディングが以下に挙げられています。より包括的なリストは、RFC-2046で見つけることが出来るかもしれません。

7bit
コード化が全く行われません。 このラベルは8ビットの文字は一切無いことを主張し、改行コードを含んだ文字数が1000を超えないことを主張しています。
8bit
コード化は全く行われません。このラベルは8ビットの文字があるかもしれないことを主張し、改行コードを含んだ文字数が1000を超えないことを主張しています。
binary
コード化は全く行われません。このラベルは8ビットの文字があるかもしれないことを主張し、改行コードを含んだ文字数が1000を超えるやもしれないことを主張しています。まずメールゲートウェイを通り抜けられることは無いでしょう。
base64
スタンダードなエンコーディング。(それは任意の2進のデータを7ビットのドメインにマッピングします)。 非ASCII文字を含むメッセージ(例えば、ラテン語-1、ラテン語-2、またはいかなる他の8ビットのアルファベットも)をコード化することの役に立ちます。
quoted-printable
スタンダードなエンコーディング。(それは任意のline-orientedのデータを7ビットのドメインにマッピングします)。非ASCII文字を含むメッセージ(例えば、ラテン語-1、ラテン語-2、またはいかなる他の8ビットのアルファベットも)をコード化することの役に立ちます。

 

 

Moblog設置ドキュメンタリー[その3]

2005年01月11日   :: CGI

さて、続いてMT; MT::Entry; MT::Blog; MT::Image;あたりの確認をしなければ…よく考えたらまだ料理で言ったら下ごしらえが終わってない段階なんだよなぁ('A`)

これらのモジュールはMovableType Perl APIと呼ばれるものらしいです。
APIのマニュアルは日本語版は無いですが、本家のものがあります。
 #MT API index - Index of MT API documentation
 
また、日本の方には『Blog Hacks』作者の一人でもある伊藤直也氏の解説記事が、開発者用ブログの方にあります。これは大変役に立ちそう…。多謝。
 #Techknow Movable Type: Movable Type Perl API Hacks その1 - MT API の基礎
 

それでは、ひとまず親玉っぽいMTから覗いて見ることに。
ちなみに昔のはこちら。

 #Moblog設置ドキュメンタリー[その2]::[7korobi8oki.com]
 #Moblog設置ドキュメンタリー[その1]::[7korobi8oki.com]

一応今回でモジュールの読み込みは終了予定。

MT - Movable Type

概要にはMTクラスは、MT関連ライブラリーの上位のクラスです。このライブラリは全てのリビルド作業をします。他のMTの機能はMT::AppとかMT::App::CMS等を使います。とあります。また、MT関連のモジュールを使用する場合は、まずはこのモジュールで対象のMTを読み込むのが普通な予感。

my $mt = MT->new( Config => "/usr/local/MT/mt.cfg" );

で、インスタンスを作成。この時自動的にカレントディレクトリからmt.cfgを探して設定を読み込みます。もしmt.cfgがデフォルトと違うところにある場合は、Configでディレクトリを指定します。これで、$mtというMTインスタンスを通じて、対象のブログを操作することが出来るようになります。

つづいてリビルド関連。

$mt->rebuild(
      BlogID => $blog_id,
      EntryCallback => sub { printf "%d/%d\r", ++$i, $total },
      # [Option]
      # ArchiveType => ( Individual | Daily | Weekly | Monthly | Category ) ;
      # NoIndexes => ( true | false ) ;
      # Limit => NUM ;
      # Offset => NUM ;
);


こんな感じにすると、$blog_idに対応するブログを(IDはMTの管理画面で確認)リビルドし、コールバックでサブルーチンを呼び出します。この場合は、何番目の記事が現在リビルド中なのか出力されるはず。

また、どのアーカイブをリビルドするかとか、インデックステンプレートをリビルドするかどうか等オプションがあるようです。

ArchiveType : リビルドしたいアーカイブタイプを指定。
NoIndexes : インデックステンプレートをリビルドするか。
Limit : リビルドを最後のNUMエントリーにしぼることができる。
Offset : MTのタグと同じで、最後のNUM個をリビルドしないことができる。

リビルドはエントリー単位で行うことも出来ます。この辺は MT::Entry で。テンプレートのリビルドもMT::Templateで。ちなみに、どちらもそれぞれのオブジェクトを引数に取って、$mt->rebuild_entry( %args )
とか$mt->rebuild_indexes( %args )とかすればOKそうです。

また、他にも色々あります。
text filterとか。プラグイン作るときに出てきたグローバルフィルターとか絡みだろうか。しかし結構シンプルにまとまっていてなんかワクワクしますな。今のうちにワクワクしておこう。どうせそのうち土ツボに嵌まるだろうし。

MT::Entry - Movable Type entry record


MT::Entryは、MTドキュメントのMT::EntryのDESCRIPTIONによると、これを通じてエントリーの本文とさらに作者やカテゴリーなどのメタデータを操作できるもののようです。MT::Entryは、MT::Objectのサブクラスなので、基本的な部分はMT::Objectから継承しています。なので、まずはMT::Objectを見てみることに。

MT::Objectは、MTが蓄積しているデータをあらわすクラスらしい(何かうまく日本語に出来ない)普通はそれはRDBに置いてある。SQLとか。BerkleyDBとか。MTは基本的にはデータがどのようなメカニズムで、技術で蓄積されているかについては全く関知せず、そこにどんな名前(?)のデータが入っているかだけ知っているようです。

んで、実際のデータ蓄積メカニズムは、MT::ObjectDriver 次第だそうで。
このように独立させていることで、色々なデータベースに対応できているそうな。


このMT::Objectをベースにして、いくつか独自のメソッドやらを付け加えたのがMT::Entry。なので、MT::Entryに載ってないメソッドとかがあったら、MT::Objectにさかのぼって調べればよさげ。スーパークラスって奴ですか。MT::Objectについてはそれ以上今回は突っ込まない予定。突っ込む前にクラスとか復習しないと玉砕必至('A`)

エントリーを投稿する場合

と言うわけでMT::Entryに戻ります。エントリー投稿までのサンプルスクリプトはこんな感じで書かれています。
 #コードはMT::EntryのSYNOPSISから引用


use MT::Entry;

#MT::Entryインスタンス生成

my $entry = MT::Entry->new;


#エントリー対象のブログIDを指定

$entry->blog_id($blog->id);


#Publishなら2、Draftなら1を指定。しかしMT::Entry::RELEASEサブルーチンて何…?

$entry->status(MT::Entry::RELEASE());

#検索しても出てこないのでとりあえずMTの中のEntry.pmを見ると
# use constant HOLD => 1;
# use constant RELEASE => 2;
# use constant REVIEW => 3;
# use constant FUTURE => 4;
#とあった。定数か。けどなんで()が付いてるんだ…。

#author_idメソッドで$author->idは、MT::Authorクラスのメソッド。

$entry->author_id($author->id);


#各データを入力

$entry->title('My title');
$entry->text('Some text');


#MT::Objectのsaveメソッド。DBにセーブする。
$entry->save
or die $entry->errstr;


その他にもMT::Entryには色々とメソッドがあるようですな。多いのは、既存のエントリーから情報を引っ張ってきたり、編集したりすることだと思うので、ちょっとその辺りをついでに探ってみたいと思います。

エントリーから色々読み出してくる場合


まずは、さっき作ったようなMT::Entryインスタンスを読み込んできます。loadメソッドを使います。まずは基本形。

my @objects = CLASS->load(\%terms, \%arguments);

この中で、CLASSはここではMT::Entryです。\%termsは、この場合EntryIDを入れれば、エントリー単品が対象になります。BlogIDを入れると、エントリーのリストが返ってきます。

\%argumentsは、様々なオプション(direction => "ascend|descend" 、limit => "N"等など。MT::ObjectのLoading an existing object or objects辺りを参照)

なので例えばブログのタイトル一覧を10件出力するには

my $mt = MT->new() ;
my @entryArray = MT::Entry->load(
            { blog_id => 1 },
             { sort => 'created_on',
            direction => 'descend',
            limit => 10 }
                         );

foreach my $tmp ( @entryArray ){
printf("%s\n", $tmp->title);
}

と言う感じでエントリーを読み込んで、作成日時で (sort => 'created_on')、降順 (direction => 'descend')に。10件分のタイトルを出力できます。このあたりはTechknow Movable Type: Movable Type Perl API Hacks その1 - MT API の基礎にしっかり載っていました。ありがたや。ちなみにMTインスタンスは、明示的にConfigでmt.cfgの場所を指定しておいたほうがよさげです。


また、例えばオブジェクト(エントリー)の数を数えるにはこんな感じ。

my $count = MT::Foo->count( { blog_id => 1 });

他にもありそうですが、今回はこんな感じで。ちなみにこのMT::Objectのloadメソッドはエントリーから情報を読み出すほかにも、MT::BlogやMT::TemplateやMT::Author等のインスタンスといった、MT::Objectを上位のクラスに持つものから情報を引き出せます。

MT::Imageは、$img->scale(%arg)くらいなので、まぁなんとかなるかな、と言う感じでパス。というわけでようやくモジュール終わり('A`)


連休中に終わると思った自分が馬鹿だった…。

Moblog設置ドキュメンタリー[その2]

2005年01月10日   :: CGI

というわけで、前回の続きで河馬屋二千年堂さんの、ActivePerlでメールを受けるを読んでいこうと思います。読んでいこうと思いますとか書くと、なんかゼミの輪読みたいですね。どっちかっていうと自分のレベルだと中学のリーディングの授業に近いものが('A`)ジショガテバナセネエ

いやそれとも第2外国語のアラビア語か…。辞書の引き方も独特で、さらに市原悦子みたいなアラブ人の先生になじめなくて2ヶ月で…おかげで4年生になっても外語を取る羽目に…。

ちなみに前回はMoblog設置ドキュメンタリー[その1]::[7korobi8oki.com]からどうぞ。

…さて。順番に、適度に引用しつついきます。
コードは断り無い場合河馬屋二千年堂さんから引用させていただいてます多謝多謝です。

最初に、読み込みモジュールやら定数宣言。POP3サーバの情報はconstantで定数にしておきます。この辺はお好みで。

use strict;
use Jcode;
use MIME::WordDecoder;
use MIME::Parser;
use Net::POP3;
use constant Pop3Svr => 'your.pop3svr';
use constant Pop3Usr => 'POP3user';
use constant Pop3Pwd => 'POP3Pwd';

そして、

MIME::WordDecoder->default(
MIME::WordDecoder->new( [ '*' => sub { jcode(shift)->sjis }, ] )
);


さっそく分からんです…"[]"はいったい何だ。

…気分転換にコンビニに行ってきて再開です。
cpanのMIME::WordDecoderドキュメントのを見ると、こういうコードがありました。

$wd = MIME::WordDecoder->new({'US-ASCII' => "KEEP", ### sub { $_[0] }
                       'ISO-8859-1' => \&keep7bit,
                       'ISO-8859-2' => \&keep7bit,
                             'Big5' => "WARN",
                               '*' => "DIE"});

このコードについての、cpanドキュメントから引用すると、MIME::WordDecoderはA MIME::WordDecoder consists, fundamentally, of a hash which maps a character set name (US-ASCII, ISO-8859-1, etc.) to a subroutine .だそうです。

つまり、MIME::WordDecoderインスタンスを作る際に、そのインスタンスを使ってMIMEデコードする際、各Charsetごと、に処理させるサブルーチンを設定しておく必要がある模様。そして、KEEPやらWARNやらDIEはすでに定義されたサブルーチンで

KEEP 全てそのままスルーする.
IGNORE 特に警告を出さないまま消して、無かったことにする
WARN 警告を出しつつ消して、無かったことにする。
DIE 文字セットを扱えませんというエラーを発する・

となっているようです。
&keep7biは、このCPANドキュメントのサンプル内で定義しているサブルーチンです。
つまりこの場合、
「US-ASCIIはそのまま変換なし」
「ISO-8859-1(US-ASCII にラテン1拡張を加えたもの)は&keep7biサブルーチンで処理」
「ISO-8859-2(US-ASCII にラテン1拡張といくつかの記号を加えたもの)は&keep7biサブルーチンで処理」
「Big5(いわゆる中国語とか)は警告を出しつつ消して、無かったことにする。」
「その他はエラーを出す」

という意味のようです。すると、

MIME::WordDecoder->new( [ '*' => sub { jcode(shift)->sjis }, ] )

って云うのは、とにかく全てjcodeでsjisに変換しろということではなかろうか…と判断。
  #ちなみにJcodeについてはJcode - Japanese Charset Handler参照

[]は分かりません…。無名ハッシュへのリファレンスって解釈でいいのだろうか。でも、cpanの方だと、{}になっている。分からん。保留。

とりあえずこの部分は、MIME変換のデフォルトを「jcodeでsjisに変換」に設定した、という部分だと思います('A`)


ログインとヘッダの処理

続いて。

my $oParse = new MIME::Parser;
$oParse->output_dir('pop3');

#Get From Server
my $oPop = Net::POP3->new(Pop3Svr, Timeout => 60);
$oPop->login(Pop3Usr, Pop3Pwd);
my $rhMsg = $oPop->list();

ここは、Net::POP3の基本的な奴で、出力先を"pop3"ディレクトリにして、POP3サーバに接続して、ログインしてメールのリストを取得するところ。前回このあたりは探ったトコです。Moblog設置ドキュメンタリー[その1]::[7korobi8oki.com]


続いてのforeachブロックは、

foreach my $sMsgId (keys %$rhMsg) {

#1)NET::POP3::getメソッドでメールデータを得て$raContに代入。
#この時点ではまだ、扱いづらい生のメールデータ。
my $raCont = $oPop->get($sMsgId);


#2)それをMIME::Parser::parse_dataメソッドをかまして、MIME:Entityに変換。そして$oEntに代入。
my $oEnt = $oParse->parse_data($raCont);


MIME::Parserでパースされたメールデータは、MIME::Entityクラスになって、様々なMIMEクラスのメソッドで、情報を取り出したり入れたりと操作できるようになります。
なので、続いてヘッダの情報を取り出すのに、めんどくさい正規表現などを使うのではなく、MIME::Entity::headrメソッドを使うだけで、事が済むようになります。モジュール万歳。
こうして、ヘッダを$oHeadに代入。

ちなみに、この結果$oHeadは、MIME::Headerインスタンスになっています。MIME周りのクラス郡は、それぞれ適切なクラスとして生成されるようになっています。MIME-toolsのドキュメントに図解が載っています。

my $oHead = $oEnt->head;

print "\n================================\n";

MIME::HeaderインスタンスであるoHeadからは、getメソッドで簡単にヘッダ情報が取り出せます。
なので、From、To、Subjectのヘッダ情報を取り出し、各変数に代入。

この時点では日本語になっていないので、MIME::WordDecoder::unmimeメソッドで日本語に。
unmimeメソッドはデフォルトに設定したMIME変換を適用するメソッドです。デフォルトの変換とは、さっき設定したjcodeの奴です。

メールデータ→MIME変換→日本語変換、という三段階が日本語などのマルチバイトには必要みたいです。

print "From:", unmime($oHead->get('From'));
print "To :", unmime($oHead->get('To'));
print "Subj:", unmime($oHead->get('Subject'));
PrnCont($oEnt);


MIME::Toolsに含まれる、ParserとかHeadとかBodyとかEntity等が上手く連携して出来てるのね…。
これで、ヘッダはOK。

本文の処理


続いて本文。このスクリプトでは、PrnContサブルーチンで処理。

sub PrnCont($;$) {
my($oEnt, $iLvl) = @_;

$iLvl = 0 unless($iLvl);

引数を2つ取る。一つ目の$oEnt は先ほどMIME::Parserでパースされたデータですな。2つ目は、マルチパートかシングルパートかのフラグか。指定が無ければ自動的に0が入る。ちなみに先ほど$oEntからheaderメソッドでMIME::Headerオブジェクトを作りましたが、別にそれで元のMIME::Entityである$oEntからヘッダ情報が消えたわけではないです。コピーのコピーみたいな感じでしょうか。

さて、MIME::ParserでパースされたデータはMIME::Entityインスタンスなので、MIME::Entityのメソッドが使えます。MIME::Entity::is_multipartメソッドで、データがマルチパートかどうか判別できますので、これを利用します。


SinglePartの場合

シングルパートだったら、以下のようにしてから、jcodeでshift-jisにする。

$oEnt->bodyhandle->as_string

$oEnt->bodyhandleメソッドは、MIME::Entityインスタンスから、いわゆる本文部分をMIME::Bodyとして引っ張り出せるものです。
単純にやるなら"$bodyh = $ent->bodyhandle;"などとやれば左辺に代入されます。
そいでそれをMIME::BODY::as_stringメソッドを使って、文字として取り出します。
これでメールのの本文が、日本語として取り出せました。

このあたりのメソッド群は、cpanのドキュメントの色んな所を見なきゃいかんです('A`)。MIME::BodyのとこやMIME::Parserのとこや、MIME::WordDecoderのとこやらMIME::Headerのところやら。


MultiPartの場合

つづいてマルチパートです。
マルチパートは、階層構造になっている場合があるので、その時の為に、再帰処理をします。
そしてそうでない、ないしそうでなくなった場合、改めてマルチパートの種類を確認します。

具体的には。MIME::Entityのpartsメソッドで各パートにアクセスし、mime_typeメソッドで種類を確認。

$oEnt->parts($i)->mime_type

っていうところ。これが
1)text/plain だったら、シングルパートと同じ処理

2)text/html だったら、やっぱり同じ処理。

3)そのほかだったら、pathメソッドでローカルに保存。返り値のパスを表示。
  #メソッドについてはsearch.cpan.org: MIME::Body


というわけで、マルチパートの添付ファイルの処理もするようです。
以上で一応MIME-toolsでメールの受信をする[河馬屋二千年堂さん]のソース読みの完了となります。何とか、読めた…のだろうか。ものすごく不安だ…。

しかし読み返してみると、あっちにいったりこっちにいったりで読みづらい。自分の普段の会話そっくりだ。

Moblog設置ドキュメンタリー[その1]

   :: CGI

メールを送るだけでBlogを更新するのって何気に難しいですな。一時期moblogの話を色々なところで聞いたので、それほど苦労せずにMTに組み込めたりするのかと思っておりました。

しかしどうやら暗雲が。とりあえず"moblog"で検索してみると出てくるのはサービスばかり。もちろんきちんとしてるし信頼性もあるし手軽でいいのですが、出来れば自前で全てまかないたいという思いが。

というわけで大したWebプログラミングの知識も無く設置してみようと言う、ドキュメンタリーエントリーです。本当に今から悩みながら書くので、どうなるか分かりません('A`)

明日には無かったことになってるかも。

まずmoblogはどんなもんかと調べると、伊藤譲一氏によると、moblogはmail2entryというもので成り立っているらしい。Aron Atkins氏作成らしいですが、この記事からダウンロードできます。

Joi Ito's Web: mail2entry moblog code update


これが、基本的にサーバに入れて動かすもの。我が家のVineに入れられないこともないのでしょうが、そこにたどり着くまでに自分のスキルではどえらく時間がかかりそうなのでナシ…。インストールされた方も結構おられるので、将来のためにメモメモ。
 #Bell's Memorandum: mail2entry
 #matsubokkuri: mail2entry


さて、どうしたものかと検索していると、どうやらWEB-YATAIさんの方で、cgi版が出ている模様。さっそくこれを使わせてもらうことに。

とは言っても、そのまま使っていてもいつまでたってもヘボのままなので、それだけはどうしても嫌なのでソースも読んでいく事に。教材ですなー。

何ていうかみんなゴリゴリ書ける人のブログばっかりだから、たまにゃこういったショボいのもいいのでは…検索すると凄い人たちばっかりだもんよ('A`)キガヒケル

ちなみに自分のスキルは、C言語をやろうと発奮するも『明解C言語』と『ポインタ完全制覇』をやってなんか満足してコードも書かずにいて振り出しに戻り、その後、もう一度発奮してPerlやろうと、O'REILLYの『初めてのPerl』『プログラミングPerl』をなんとか消化した程度です。

Moduleとかの知識は『Blog Hacks』読みながら得たものくらいです。
未来検索の結果を正規表現やらでRSSにしてパースして、Javascriptにしてサイトに表示する位が限界です。さらに、サーバは基本的な操作位しか出来ない。はてさて、どこまでやる気が持つか…('A`)


とりあえずスタート

ひとまず先ほどのWEB-YATAIさんからソースをダウンロード。
印刷して斜め読みした感じでは、対象のPOPサーバにあるメールをModuleで定期的に取得して、それをパース。さらにそれを目標のMTにエントリーとしてPOSTする、というもののようです(本当に読みながら書いているので違うかも)


使うModuleが結構あります。Net::POP3; MIME::Parser; MIME::WordDecoder; Image::Size; Jcode; CGI File::Copy; MT; MT::Entry; MT::Blog; MT::Image;、と10個以上です。
この中で良く分からんものをまずは調べておくことにします。

Net::POP3

とりあえず POP3がPost Office Protocol 3だと今初めて知った('A`)
perldocがNet::POP3 - POP3(Post Office Protocol 3)クライアントクラス (RFC1939)にあるので、とりあえず問題なさげ。簡単なサンプルはモジュールを使って POP3 クライアントを作ってみようにあります。しかしこんな簡単にCGIからPOPとかいじれるのね…もっと、こう、なんか凄いことになっているイメージが。0と1がたくさん出てくるんですよ、えぇえぇ。


しかし、モジュールの概要を調べる前に、ひとまずPOP3そのものについて知っておかないと、痒いところに手が届かない感じで気持ち悪くて仕方ないです。

なので、調べてみる。とりあえずNet::POP3のソースを見てみるも、イマイチどうやってPOP3サーバと通信してるのか分からない(と言うかModuleのソースがよく理解できない。Classとか完全に忘れている)さかのぼってNet::Netrcを見てもよう分からんですわ。

そんなこんなで、モチベーションが急激に右肩下がりですが気を取り直してPOP3サーバの仕組みから探ってみることに。

すると、@ITに魅力的な記事が。さすが。その記事とは、インターネット・プロトコル連載のPOP3の回。
 #ラスト・ワン・ホップ プロトコル「POP3」
 
図1の動作模式図は分かりやすい…POP3もFTPやらHTTPやらと同じで、コマンドのやり取りで成り立ってるのか…。つまり、これを実装すればCGIでPOP3クライアントが出来る、と。大まかな動作の流れが分かったので、これで何とか吸収できそうな予感。

さて、モジュールのメソッドなどは、具体的にはこんな感じのようです。

#目的のPOPサーバにセッションを張る
$pop = Net::POP3->new($hostname) ;


#POP3で言う、USERとPASSコマンドでログイン認証を行う。
# Net::POP3でも、userメソッドとpassメソッドがそれに当たる。

$pop->user($username);
$auth_check = $pop->pass($password);


#LISTコマンドで取得できる
# 1 1142
# 2 1102
#みたいな、メッセージ番号とサイズのリストは、Net::POP3だと、
#同じくlistというメソッドで得られる。

#直接引数にメッセージ番号を渡せば、そのメールのサイズが返る。
$msg = $pop->list($msg_num)

#引数なしだと、keyがメッセージ番号、valueがサイズのハッシュへのリファレンスが返る。
$msg_hash = $pop->list();


#RETRコマンドは、 メッセージ番号を引数として、メッセージのダウンロードを行う。削除はしない。
#Net::POP3だと、getメソッド。メッセージのテキストが入った配列へのリファレンスで来る。
$msg_content_ref = $pop->get($msg_num);


#DELE メッセージ番号で、メッセージを消去
#Net::POP3では、同じくdeleteメソッド。引数にメッセージ番号。
$msg_flag = $pop->delete($msg_num);


#QUITでセッション終了。
#Net::POP3でもquitメソッド。
$msg_flag = $pop->quit();

コマンド名とメソッド名が大体同じで助かるところです。なんとかOK。

しかしPOP3だけでずいぶん時間を食いました。
でも、仕組みがそれなりに理解できたのでさっぱりさっぱり。
土台がどうなってるのか分からないまま上に家を建てるのが、どうにも気持ち悪い性質でして。@ITのプロトコル連載には、他にもHTTPやらMIME、SMTP、FTPもあります。読まねば。何はともあれ@ITの中の人に感謝。

MIME::Parser


さて、続いてMIME::Parserです。何と言うか、すぐに@ITのさっきのMIMEのところを読むべきか…。

読みました。分かりやすい。
 #しかし、何か目的もなしに読むと頭に入らない罠。

MIME::Parserは日本語ドキュメントがないので原文。
search.cpan.org: MIME::Parser - experimental class for parsing MIME streams

しかし、何気に今MIME::Parserで検索すると九割五分以上moblogの記事がひっかかる。


MIME::Parserは、MIME::Toolsのサブクラスみたいです(送信とかするにはそっちが必要。)
#MIME-Toolsの概要はsearch.cpan.org: MIME-toolsから。以下も同。

MIME::Parserは、メールデータをMIME::Entityクラスに変換し、様々なメソッドを通じて操作できるようにするために存在するようです。こいつを通せば、とても扱いやすくなり、メールのヘッダ情報とか本文とかがメソッド一発で簡単に取り出せるようになります。凄い。

my $parser = new MIME::Parser;


#データが "in core"(メモリ内ってことでいいのだろうか)の場合の読み込み方法
$entity = $parser->parse_data($message);


#ファイルハンドルなどのストリームから入力する場合の読み込み方法
$entity = $parser->parse(\*STDIN);


#ファイルから入力するときの読み込み方法
$entity = $parser->parse_open("/some/file.msg");


入力のバリエーションとパースはこんな感じ。RSSを作るのに使った、XML::Parserに似ていてちょっと安心。同じパーサーだから当たり前か('A`)

続いて、パースしたものをどこのディレクトリに展開するかとかどういう名前をつけるかとかそういったセッティングが必要らしい。


#メモリ上に展開する。ただ、メールを装った悪意あるファイルかもしれないので処理に注意。
$parser->output_to_core(1);

#出力ディレクトリを設定する。各メッセージごとに1フォルダ割り当て
$parser->output_under("/tmp");

#出力ディレクトリを設定する
$parser->output_dir("/tmp");

#拡張子を指定する
$parser->output_prefix("msg");


そしてエラー処理について。

#エラーをキャッチしたら
eval { $entity = $parser->parse(\*STDIN) };

#エラー情報を取得( $parser->results )
#最後に処理していたメールのヘッダを得る。
if ($@) {
$results = $parser->results;
$decapitated = $parser->last_head;
}

後オプションとか色々ありましたが、MIME:Parserはこんな感じなのだろうか。イマイチピンときてない。だがとりあえずNet::POP3; MIME::Parser;と最後にMIME::WordDecoder;で、ひとまとまりな気がするのでMIME::WordDecoderを見てから悩むことにしますか…('A`)

MIME::WordDecoder

Cpanのドキュメントはsearch.cpan.org: MIME::WordDecoder - decode RFC-1522 encoded words to a local representationから。
これも、さっきのMIME::Toolsのサブクラス。MIME::Wordの方に実例があった。
 #IME::Words - deal with RFC-1522 encoded words

たとえば下のようなものがあって、

From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>
To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>
CC: =?ISO-8859-1?Q?Andr=E9_?= Pirard <PIRARD@vm1.ulg.ac.be>
Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
=?US-ASCII?Q?.._cool!?=



こいつにWord::Decorderをかますとこうなるらしい



From: Keith Moore
To: Keld J/orn Simonsen
CC: Andr'e Pirard
Subject: If you can read this you understand the example... cool!


…Subjectの中の人は大変だ('A`)
ギャル文字どころじゃないぞ。変換されてるところがあったり無かったり。readは駄目でcoolは素通しなのかよ。

ひとまずメソッドとしては

$enc = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= ';
@decoded_enc = decode_mimewords($enc) ;

みたいにするらしい。配列に入れるのは、返り値が @decoded_enc[0]が[DATA]で、@decoded_enc[1]が[CAHRSET]だからだそうだ。


うーむ。まとめると、NET::POP3でPOP3サーバに接続してメールデータを得て、それをMIME::ParserでパースしてMIME::Entityにする。そしてMIME::WordDecoderで各文字コードにデコード、という感じなのかな…。うぅむ。

なんか文字コードとMIME変換とマルチバイトと、そのあたりの変換がかなりの勢いでごっちゃになっているような気がします。その辺が一番の混乱のもとのような。


そんな感じで机に突っ伏していたら河馬屋二千年堂さんに、ActivePerlでメールを受けるという記事が。シンプルにこういたモジュールを使った、POP3へのアクセススクリプトが載っていました。

ありがたやありがたや…やっぱりサンプルはありがたいですな…。というわけで、まず先にこっちのソースを読みながらmoblogへの基礎固めをする予定です。

次回はMoblog設置ドキュメンタリー[その2]::[7korobi8oki.com]です。

しかし、Google 検索: MIMEは凄いな。


mail TOP