| 1 |
<?xml version="1.0" encoding="iso-2022-jp"?>
|
| 2 |
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
| 3 |
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
| 4 |
<html xml:lang="ja" xmlns="http://www.w3.org/1999/xhtml">
|
| 5 |
<head>
|
| 6 |
<title xml:lang="en">Message::* Perl modules</title>
|
| 7 |
<link rel="index" href="./" />
|
| 8 |
<?xml-stylesheet href="/s/simpledoc"?>
|
| 9 |
<link rel="stylesheet" href="/s/simpledoc" />
|
| 10 |
<link rev="made" href="mailto:w@suika.fam.cx" />
|
| 11 |
<link rel="copyright" href="/c/pd" title="Public Domain." />
|
| 12 |
<meta name="author" content="若葉" />
|
| 13 |
<meta name="keywords" content="Perl, module, pm, Message, RFC 822, RFC 2822, RFC 1036, son-of-RFC 1036, MIME, Usefor, HTTP, CGI, header, field" />
|
| 14 |
</head>
|
| 15 |
<body>
|
| 16 |
<h1>Message::* Perl modules</h1>
|
| 17 |
|
| 18 |
<h2>はじめのはじめに</h2>
|
| 19 |
|
| 20 |
<p>たとえば Perl で書かれた CGI script, それも掲示板なんかには、
|
| 21 |
こんなくだらない code が載っていたりします。</p>
|
| 22 |
|
| 23 |
<pre class="application-x-perl">
|
| 24 |
jcode'convert(*from, "jis");
|
| 25 |
jcode'convert(*subject, "jis");
|
| 26 |
jcode'convert(*message, "jis");
|
| 27 |
open (MAIL, "| $sendmail");
|
| 28 |
print MAIL "From: $mail ($from)\n";
|
| 29 |
print MAIL "To: $mailto\n";
|
| 30 |
print MAIL "Subject: $subject\n";
|
| 31 |
print MAIL "\n";
|
| 32 |
print MAIL "$message";
|
| 33 |
print MAIL "\n";
|
| 34 |
close (MAIL);
|
| 35 |
</pre>
|
| 36 |
|
| 37 |
<p>これでは視認性も良くないですし、うっかり修正し間違えると
|
| 38 |
変なメッセージを送信してしまいます。
|
| 39 |
(筆者はしょっちゅうはまってました:-)
|
| 40 |
(それに多くの code では、
|
| 41 |
HTML でのクロスサイトスクリプティング (CSS) 問題と
|
| 42 |
類似の問題への対処をしていません。)</p>
|
| 43 |
|
| 44 |
<p>オブジェクト指向を取り入れて次のような感じでメッセージを
|
| 45 |
構成したいところです。</p>
|
| 46 |
|
| 47 |
<pre class="application-x-perl">
|
| 48 |
use Message::Entity;
|
| 49 |
my $msg = new Message::Entity;
|
| 50 |
my $hdr = $msg->header;
|
| 51 |
$hdr->add ('From')->add ('me@bar.example');
|
| 52 |
$hdr->add ('To')->add ('foo@bar.example', display_name => 'Mr. foo');
|
| 53 |
$hdr->add ('Subject' => $subject);
|
| 54 |
$msg->body ($body);
|
| 55 |
|
| 56 |
# $smtp->send は SMTP で送信する method と仮定。
|
| 57 |
$smtp->send ($msg);
|
| 58 |
</pre>
|
| 59 |
|
| 60 |
<p><a href="http://www.cpan.org/" xml:lang="en">CPAN</a> を探すと、
|
| 61 |
これに似たようなことができそうなモジュールはあるようですが、
|
| 62 |
実際に使ってみると、与える値によっては <a href="urn:ietf:rfc:822">RFC 822</a>/<a href="urn:ietf:rfc:2822">2822</a> に違反する
|
| 63 |
結果を出力するなどの不満があります。 (例えば今の例で
|
| 64 |
<code xml:lang="en">To:</code> 領域に使っている
|
| 65 |
<code xml:lang="en">display_name</code> で「.」が含まれますが、
|
| 66 |
RFC 2822 的には新しいメッセージでは互換性のため
|
| 67 |
<code xml:lang="en" class="bnf rfc2822">quoted-string</code>
|
| 68 |
にする必要があります。しかしそのまま出力されます。)</p>
|
| 69 |
|
| 70 |
<p class="note">参考: 「.」の場合は RFC 2822 的には正しく解釈
|
| 71 |
されなければなりませんが (出力はすべきでない)、
|
| 72 |
これ以外の文字、例えば制御文字 <code class="character">ESCAPE</code> でも同じようになります。
|
| 73 |
こちらは完全に間違いです。</p>
|
| 74 |
<p class="note">参考: 実装方針としては不正な値はモジュールに
|
| 75 |
渡す前に弾くべきという考え方もあるでしょう。
|
| 76 |
でもそんなのは不便です。</p>
|
| 77 |
|
| 78 |
<p>ということで、はじめは既存のモジュールの wrapper (あるいは補完)
|
| 79 |
を書くつもりでしたが、なんだかごちゃごちゃしていて、
|
| 80 |
それなら車輪の再発明になっても一から書いてみようと考えました。</p>
|
| 81 |
|
| 82 |
<h2>特色 (という程のものでもない。)</h2>
|
| 83 |
|
| 84 |
<ol>
|
| 85 |
<li>結構(謎)オブジェクト指向です。</li>
|
| 86 |
<li>RFC 822/2822 の <code class="bnf rfc2822">group</code> を解釈出来ます。</li>
|
| 87 |
<li><a href="urn:ietf:id:draft-ietf-usefor-msg-id-alt-00">draft-ietf-usefor-msg-id-alt-00</a> に基づいた送信アドレスなどによる <code class="rfc2822">Message-ID</code> を生成出来ます。</li>
|
| 88 |
<li>文字コード独立 (CSI) です。 (但し RFC 822 である都合上(謎)、
|
| 89 |
ASCII 互換である必要はあります。 EBCDIC とかは無理です:-<)</li>
|
| 90 |
</ol>
|
| 91 |
|
| 92 |
<h2>各仕様への対応状況</h2>
|
| 93 |
|
| 94 |
<ol>
|
| 95 |
<li>電子メイルのメッセージ (RFC 822, RFC 2822)
|
| 96 |
の全機能に (抜けが無ければ) 対応しています。
|
| 97 |
但し長さ制限などはチェックしていません。 (MIME の
|
| 98 |
<code class="mime">Content-Transfer-Encoding</code>
|
| 99 |
と一緒に実装予定)</li>
|
| 100 |
<li>電子ニュース記事 (<a href="/uri-res/N2L?urn:ietf:rfc:1036">RFC 1036</a>,
|
| 101 |
<a href="spec/son-of-RFC1036">son-of-RFC1036</a>,
|
| 102 |
<a href="/uri-res/N2L?urn:ietf:id:draft-usefor-article-06">
|
| 103 |
draft-usefor-article (06)</a>) の頭領域の多くに対応しています。</li>
|
| 104 |
<li>MIME の本体部分 (body part) にはまだ対応していません。</li>
|
| 105 |
<li>MIME の追加頭領域
|
| 106 |
(<a href="/uri-res/N2L?urn:ietf:rfc:2045">RFC 2045</a>,
|
| 107 |
<code class="mime">Content-Disposition</code>) に対応しています。
|
| 108 |
パラメーター値拡張 (<a href="/uri-res/N2L?urn:ietf:rfc:2231">RFC 2231</a>)
|
| 109 |
も入出力ともに実装しました。</li>
|
| 110 |
<li>MIME 符号化語 (<code class="mime bnf">encoded-word</code>)
|
| 111 |
の解読に対応しています:-) 但し別途変換処理を指定する必要があります。
|
| 112 |
(<a href="#code">文字コードの扱い</a>参照)</li>
|
| 113 |
<li>HTTP/1.0, HTTP/1.1, CGI/1.1, CGI/1.2 の頭領域のうち、
|
| 114 |
ごく一部に対応しています。 MHTML の
|
| 115 |
<code class="mime">Content-Location</code> にも対応しています。</li>
|
| 116 |
<li>日付形式では RFC 822/<a href="urn:ietf:rfc:1123">1123</a>,
|
| 117 |
<a href="urn:ietf:rfc:733">RFC 733</a>, asctime, ISO 8601 (HTML)
|
| 118 |
などに対応しています。</li>
|
| 119 |
</ol>
|
| 120 |
|
| 121 |
<h2>制限事項</h2>
|
| 122 |
|
| 123 |
<ol>
|
| 124 |
<li>類似モジュール(謎)のように、ファイル名やファイル・ハンドルを
|
| 125 |
渡して読み込ませることが出来ません。</li>
|
| 126 |
<li>大きなメッセージでも一気に読み込み、全て主記憶領域で
|
| 127 |
保持しています。ですからあまり大きなメッセージの処理には
|
| 128 |
向いていないでしょう。</li>
|
| 129 |
<li><code>CR</code> や <code>LF</code> が単体で出現する場合、
|
| 130 |
正しく処理出来ません。 (<code>CRLF</code> と等価とみなします。)
|
| 131 |
将来の版ではオプションで制御可能になるかもしれません。</li>
|
| 132 |
<li>あったら良さそうな機能が未実装かもしれません。
|
| 133 |
(<a href="mailto:w@suika.fam.cx">電子メイル</a>などで教えて下さい。)</li>
|
| 134 |
<li>各モジュールのオプション体系があまり整備されていません。
|
| 135 |
(それでも気持ち悪くない程度には体系的だと思います。)</li>
|
| 136 |
<li>説明文 (document) が良い加減です。</li>
|
| 137 |
</ol>
|
| 138 |
|
| 139 |
<h2>今後の予定</h2>
|
| 140 |
|
| 141 |
<ol>
|
| 142 |
<li>電子ニュースの頭領域 (RFC 1036,
|
| 143 |
<a href="spec/son-of-RFC1036">son-of-RFC1036</a>,
|
| 144 |
draft-usefor-article) の完全実装</li>
|
| 145 |
<li><del>MIME の頭領域の実装。</del></li>
|
| 146 |
<li>追加/非標準の頭領域の実装。</li>
|
| 147 |
<li>MIME 本体 (<code class="bnf rfc822">body</code>) の実装。</li>
|
| 148 |
<li><del>文字符号変換のための hook の実装?</del></li>
|
| 149 |
<li>documentation。</li>
|
| 150 |
<li>使用例の作成。</li>
|
| 151 |
</ol>
|
| 152 |
|
| 153 |
<h2>必要環境</h2>
|
| 154 |
|
| 155 |
<ol>
|
| 156 |
<li>Perl (perl 5.6 以降または<span title="human parser">人間解析者</span>:-))
|
| 157 |
<p class="note"><code class="bnf rfc822">comment</code>
|
| 158 |
を表すのに正規表現 <code class="regex">(??{ <var>code</var> })</code>
|
| 159 |
を使っているので、これを解釈出来る、
|
| 160 |
5.6 以降の版である必要があります。</p>
|
| 161 |
</li>
|
| 162 |
<li>Digest::MD2, Digest::MD5, Digest::SHA1
|
| 163 |
<p>Message-ID の生成にこれらを使用する場合のみ、
|
| 164 |
<code>Message::Field::MsgID::MsgID</code> が使います。</p>
|
| 165 |
<p>これらが用意されていない環境ではエラーになるので、
|
| 166 |
(現状では) 上記モジュールの該当部分を書き換えて対処して下さい。</p>
|
| 167 |
</li>
|
| 168 |
<li>文字コード変換処理
|
| 169 |
<p>日本語メッセージを扱うなら必須でしょう。
|
| 170 |
詳しくは<a href="#code">文字コードの扱い</a>
|
| 171 |
の章をご参照下さい。</p>
|
| 172 |
</li>
|
| 173 |
</ol>
|
| 174 |
|
| 175 |
<h2>入手</h2>
|
| 176 |
|
| 177 |
<p>suika.fam.cx の SSH account をお持ちの場合、 CVS から入手出来ます。</p>
|
| 178 |
|
| 179 |
<p class="example">$ cvs -d :ext:<var xml:lang="en">username</var>@suika.fam.cx:/home/cvs -d perl/lib/Message/</p>
|
| 180 |
|
| 181 |
<p>Web からも取り出せます。 <<a href="/gate/cvs/perl/lib/Message/">http://suika.fam.cx/gate/cvs/perl/lib/Message/</a>> (tarball で一括取得も出来ます。)</p>
|
| 182 |
|
| 183 |
<h2>ライセンス</h2>
|
| 184 |
|
| 185 |
<p>Message::* Perl modules は自由ソフトウェアです。
|
| 186 |
GNU GPL に従って利用出来ます。詳しくは各ファイルを御覧下さい。</p>
|
| 187 |
|
| 188 |
<h2>参考文献</h2>
|
| 189 |
|
| 190 |
<ul>
|
| 191 |
<li><a href="spec/">関連する仕様書 (RFC, Internet-Draft 等)</a></li>
|
| 192 |
</ul>
|
| 193 |
|
| 194 |
<h2 id="code">文字コードの扱い</h2>
|
| 195 |
|
| 196 |
<p>卑しいことで頭を悩ますのは嫌なので(藁)、
|
| 197 |
Message::* は符号化方法独立 (CSI) を目指して実装しています。
|
| 198 |
(但し ASCII のしがらみだけは断ち切っていません:-))
|
| 199 |
0x00 〜 0x7F が ASCII (または ASCII と見なして良いもの) である
|
| 200 |
場合は、 Message::* を通したことでデータが壊れることは
|
| 201 |
無いと思います。</p>
|
| 202 |
|
| 203 |
<p>(もちろん、 RFC 822 など各仕様に照らして正統(的)で
|
| 204 |
ある必要があります。 <code class="bnf rfc822">atom</code>
|
| 205 |
に8ビット・コードが含まれていると正しく扱えません。)
|
| 206 |
(早い話が、 <code class="bnf rfc822">quoted-string</code>
|
| 207 |
などでは8ビット透過だということです。回りくどくてごめんなさい。)</p>
|
| 208 |
|
| 209 |
<p>既定の状態では文字コードに関係する変換処理は行われません。
|
| 210 |
しかし、フック関数っぽいもの(謎)を指定することで、
|
| 211 |
変換処理をさせられます。</p>
|
| 212 |
|
| 213 |
<p>指定出来るフック関数っぽいものは2種類です。
|
| 214 |
<code>DECODER</code> は、元のメッセージを解析する時
|
| 215 |
(<code class="perl">parse ()</code>) に適宜呼び出されます。
|
| 216 |
<code>ENCODER</code> は、メッセージとして文字列化する際
|
| 217 |
(<code class="perl">stringify ()</code> など) に適宜呼び出されます。</p>
|
| 218 |
|
| 219 |
<p>これらの関数は、当然、当該処理が呼び出される前に指定しておく
|
| 220 |
必要があります。
|
| 221 |
<samp class="perl">Message::Entity->parse</samp> などする前に
|
| 222 |
定義しておくと良いでしょう。</p>
|
| 223 |
|
| 224 |
<pre class="example perl">
|
| 225 |
require Message::MIME::Charset;
|
| 226 |
$Message::MIME::Charset::DECODER{'*default'} = sub {jcode::euc ($_[1])};
|
| 227 |
$Message::MIME::Charset::ENCODER{'*default'} = sub {jcode::jis ($_[1], 'euc')};
|
| 228 |
</pre>
|
| 229 |
|
| 230 |
<p>この例では、 jcode.pl を変換処理に使います。
|
| 231 |
(もちろん、既に <code class="perl">require</code>
|
| 232 |
されていると仮定しています。)</p>
|
| 233 |
<p>最初の <code class="perl">require</code> で、変換処理を担当している
|
| 234 |
<code class="perl">Message::MIME::Charset</code> を読み込みます。
|
| 235 |
(こうしておかないと、後から既定値 (= 無変換) で
|
| 236 |
<code class="perl">*default</code> が上書きされてしまいます。)</p>
|
| 237 |
|
| 238 |
<p>この code を使ったスクリプトは内部処理を日本語 EUC
|
| 239 |
で行うと仮定しています。ですから、 <code class="perl">DECODER</code>
|
| 240 |
で日本語 EUC に変換します。</p>
|
| 241 |
<p>また、日本語メッセージでは <code>ISO-2022-JP</code>
|
| 242 |
を使うのが慣習ですから、 <code class="perl">ENCODER</code>
|
| 243 |
では 7ビット JIS に変換しています。</p>
|
| 244 |
<p>処理を行う関数は、引数が2つ以上与えられます。
|
| 245 |
1つ目の引数は呼び出した class module, いわゆる
|
| 246 |
<code class="perl">$self</code> です。(この場合 self ではありませんが:-)
|
| 247 |
でも普通は必要ないでしょう。</p>
|
| 248 |
<p>2つ目の引数は処理対象の文字列です。</p>
|
| 249 |
<p>3つ目以降の引数は、追加オプションのハッシュです。
|
| 250 |
ただし、現在追加オプションは定義されていません。</p>
|
| 251 |
<p>関数が返す値は(今のところ)一つだけです。
|
| 252 |
処理が終わった文字列です。変換結果として何もなくなってしまったら、
|
| 253 |
もちろん空文字列を返して構いません。 (<code class="perl">undef</code>
|
| 254 |
よりも空文字列の方が望ましいでしょう。)</p>
|
| 255 |
|
| 256 |
<p>さて、上記の例では「<code>*default</code>」の EN/DECODER
|
| 257 |
を指定しましたが、ここには代わりに charset 名を指定出来ます。</p>
|
| 258 |
|
| 259 |
<pre class="perl example">
|
| 260 |
$Message::MIME::Charset::DECODER{'iso-2022-jp'} = sub {jcode::euc ($_[1], 'jis')};
|
| 261 |
</pre>
|
| 262 |
|
| 263 |
<p>ここでは、 <code>ISO-2022-JP</code> を内部コードに変換する
|
| 264 |
方法を定義しています。 charset 名 (および「<code>*default</code>」
|
| 265 |
は必ず小文字で書いて下さい!)</p>
|
| 266 |
<p>MIME body や、 encoded-word, RFC 2231 の拡張パラメーター値
|
| 267 |
など、 charset が指定されている時はその charset 名の変換関数が
|
| 268 |
呼び出されます。 (指定された charset 名の変換関数が未定義の時は、
|
| 269 |
何も処理しません。) これ以外の場面では、 <code>*default</code>
|
| 270 |
で定義された関数が使われます。</p>
|
| 271 |
|
| 272 |
<p>最後に、日本語メッセージを扱う際の例を挙げておきます。</p>
|
| 273 |
|
| 274 |
<pre class="example perl">
|
| 275 |
<span class="comment">## jcode.pl を使用</span>
|
| 276 |
require 'jcode.pl';
|
| 277 |
require Message::MIME::Charset;
|
| 278 |
$Message::MIME::Charset::DECODER{'*default'} = sub {jcode::euc ($_[1])};
|
| 279 |
$Message::MIME::Charset::DECODER{'iso-2022-jp'} = sub {jcode::euc ($_[1], 'jis')};
|
| 280 |
$Message::MIME::Charset::DECODER{'euc-jp'} = sub {$_[1]};
|
| 281 |
$Message::MIME::Charset::DECODER{'shift_jis'} = sub {jcode::euc ($_[1], 'sjis')};
|
| 282 |
$Message::MIME::Charset::ENCODER{'*default'} = sub {
|
| 283 |
my $s = $_[1];
|
| 284 |
<span class="comment">## 正規化</span>
|
| 285 |
jcode::tr(\$s,
|
| 286 |
"\xa3\xb0-\xa3\xb9\xa3\xc1-\xa3\xda\xa3\xe1-\xa3\xfa\xa1\xf5".
|
| 287 |
"\xa1\xa4\xa1\xa5\xa1\xa7\xa1\xa8\xa1\xa9\xa1\xaa\xa1\xae".
|
| 288 |
"\xa1\xb0\xa1\xb2\xa1\xbf\xa1\xc3\xa1\xca\xa1\xcb\xa1\xce".
|
| 289 |
"\xa1\xcf\xa1\xd0\xa1\xd1\xa1\xdc\xa1\xf0\xa1\xf3\xa1\xf4".
|
| 290 |
"\xa1\xf6\xa1\xf7\xa1\xe1\xa2\xaf\xa2\xb0\xa2\xb2\xa2\xb1".
|
| 291 |
"\xa1\xe4\xa1\xe3\xA1\xC0\xA1\xA1"
|
| 292 |
=> '0-9A-Za-z&,.:;?!`^_/|()[]{}+$%#*@=\'"~-><\\ ');
|
| 293 |
jcode::jis ($s, 'euc', 'z')
|
| 294 |
};
|
| 295 |
</pre>
|
| 296 |
|
| 297 |
<pre class="example perl">
|
| 298 |
<span class="comment">## Jcode.pm を使用</span>
|
| 299 |
use Jcode;
|
| 300 |
require Message::MIME::Charset;
|
| 301 |
$Message::MIME::Charset::DECODER{'*default'} = sub {jcode::euc ($_[1])};
|
| 302 |
$Message::MIME::Charset::DECODER{'iso-2022-jp'} = sub {Jcode->new ($_[1], 'jis')->euc};
|
| 303 |
$Message::MIME::Charset::DECODER{'euc-jp'} = sub {$_[1]};
|
| 304 |
$Message::MIME::Charset::DECODER{'shift_jis'} = sub {Jcode->new ($_[1], 'sjis')->euc};
|
| 305 |
$Message::MIME::Charset::DECODER{'utf-8'} = sub {Jcode->new ($_[1], 'utf8')->euc};
|
| 306 |
$Message::MIME::Charset::ENCODER{'*default'} = sub {Jcode->new ($_[1], 'euc')->jis};
|
| 307 |
$Message::MIME::Charset::ENCODER{'utf-8'} = sub {Jcode->new ($_[1], 'euc')->utf8};
|
| 308 |
</pre>
|
| 309 |
|
| 310 |
<p>Perl 5.8 で Encode モジュールが使えるようになれば、
|
| 311 |
もっと楽になると期待しています。</p>
|
| 312 |
|
| 313 |
<div class="navigation">
|
| 314 |
[<a href="/" title="このサーバーの首頁">/</a>
|
| 315 |
<a href="/map" title="このサーバーの案内" rel="index">地図</a>
|
| 316 |
<a href="/search/" title="このサーバーの検索">検索</a>]
|
| 317 |
<a href="http://validator.w3.org/check/referer" xml:lang="en"><img
|
| 318 |
src="http://www.w3.org/Icons/valid-xhtml11" id="w3c-html"
|
| 319 |
alt="Valid XHTML 1.1!" style="height: 31px; width: 88px" /></a>
|
| 320 |
<a href="http://jigsaw.w3.org/css-validator/validator?uri=http://suika.fam.cx/~wakaba/Message-pm/introduction.ja.html" xml:lang="en">
|
| 321 |
<img style="width: 88px; height: 31px" id="w3c-css"
|
| 322 |
src="http://jigsaw.w3.org/css-validator/images/vcss"
|
| 323 |
alt="Valid CSS!" /></a>
|
| 324 |
</div>
|
| 325 |
<div class="update">$Date: 2002/04/01 09:14:50 $</div>
|
| 326 |
<ul class="myuri">
|
| 327 |
<li><URL:<a href="http://suika.fam.cx/~wakaba/Message-pm/introduction">http://suika.fam.cx/~wakaba/Message-pm/introduction</a>></li>
|
| 328 |
<li><CVS:<a href="http://suika.fam.cx/gate/cvs/perl/web/Message-pm/">suika.fam.cx:/home/cvs/perl/web/Message-pm/</a>></li>
|
| 329 |
</ul>
|
| 330 |
</body></html>
|