2章 メッセージ送受信の仕組み

この章ではメッセージ送受信のプロトコルについて整理します。JavaMailを普通に使うかぎりは、それぞれのプロトコルがどのように送受信を行っているかはほとんど意識する必要がないようになっているのですが、知っておいて損はない知識です。
また、メッセージヘッダに関しての規約はJavaMailを使っていてもその知識の必要に迫られることはよくあります。
とにかくJavaMailを使ってみたいという方は本章をとばして3章へ進んでいただいてかまわないのですが、本章で出てくる用語が後々にも出てきますので、必要な時に参照して下さい。特に各メッセージヘッダ名、envelope-from/envelope-toはある程度把握しておいた方がよいでしょう。

メッセージ送受信プロトコル

メッセージの送受信の方法はRFC(Request For Comments)によって規定されています。現在インターネット上を流れるメッセージのほとんどは、SMTP(RFC821)とその拡張プロトコルによってやりとりされています。
JavaMailの送信の例でも出てきたSMTPProviderは、インターネット/イントラネット上のSMTPサーバに対して接続/送信を行うものでしたね。

ここで紹介するのはJavaMailの各Providerが内部的に行っていることになりますので、今すぐ知っていなければならない知識ではありませんが、仕組みは理解しておくべきです。

SMTP:Simple Mail Transfer Protocol(RFC821)

SMTPはメッセージを二つのホスト間で転送する時の手順です。2台のホストで動作するSMTPサーバの間で、一方がSenderもう一方がReceiverとなって、メッセージの転送を行います。
SMTPサーバプログラムのことをMTA(Mail Transfer Agent)と呼びます。MTAは常にサーバとしてではなくあるときは他のMTAに対するクライアントとして動作するので、実はSMTPサーバと呼ぶのは変なのです。本書ではMTA、または状況に応じてSenderMTA/ReceiverMTAという呼び方で使い分けることにします。

メッセージを送信する手順は実際にやってみると簡単で、ごく当たり前の操作しかしていません。以下はJavaMailでデバッグ出力をOnにしたときに出力されるSMTPセッションの部分です。デモのmsgsendsampleを実行しています(MTAは「JAMES」http://java.apache.org/に対して接続しています。エンコードされない日本語が返ってきたりまだ怪しい動作はありますが…)。

DEBUG: SMTPTransport trying to connect to host "localhost", port 25

DEBUG SMTP RCVD: 220 hiratsuka SMTP Server (JAMES SMTP Server 1.0b1) ready ?y, 8 7 2000 22:31:06 +0900

DEBUG SMTP SENT: helo shin
DEBUG SMTP RCVD: 250 hiratsuka Hello shin (localhost [127.0.0.1])

DEBUG: SMTPTransport connected to host "localhost", port: 25

DEBUG SMTP SENT: mail from: <postmaster@localhost>
DEBUG SMTP RCVD: 250 Sender <postmaster@localhost> OK

DEBUG SMTP SENT: rcpt to: <shin@localhost>
DEBUG SMTP RCVD: 250 Recipient <shin@localhost> OK

Verified Addresses
  shin@localhost
DEBUG SMTP SENT: data
DEBUG SMTP RCVD: 354 Ok Send data ending with <CRLF>.<CRLF>

DEBUG SMTP SENT:
.
DEBUG SMTP RCVD: 250 Message received

DEBUG SMTP SENT: quit

先頭行はlocalhostのポート25番に接続したことを示しています。
次の行は、ReceiverMTAからのウェルカムメッセージとして、"hiratsuka SMTP Server (JAMES SMTP Server 1.0b1) ready ?y, 8 7 2000 22:31:06 +0900"という文字列が返されています。通常Senderはこのメッセージ内容を無視しますが、その手前(行頭)にある"220"等のステータスコードはチェックする必要があります(ステータスコードの詳細は各プロトコルについて記述されたRFCを参照して下さい)。日付の曜日の箇所がJavaのDateFormatを用いているため、日本語の曜日を返そうとして文字化けが起こっていますがここでは気にせずに。

次の行はReceiverMTAに対してhelo(hello)コマンドを送信しています。heloコマンドは、その後の空白に続いてSenderMTAのホスト名を記述することになっています(「パラメタに指定する」と呼びます)。次の行はReceiverMTAの応答です。ステータスコード"250"は正常に受理されたことを示します。

ここまでが接続時のネゴシエーションです。heloのパラメタである「SenderMTAのホスト名」はRFC821ではドメイン名と書かれていますが、現在はここに何を書いてもほとんど意味をなさないようになっています。理由は、heloのパラメタはクライアントが宣言するものですが、クライアントがグローバルなホスト名を持たないことが多くなってきましたので、記述すべき内容がない場合が多いことと、そういったものに対しては正しいものであるかを検証する手段もないためです(実際正しくないわけですから仕方がありません)。現在はheloのパラメタは一応Received:ヘッダに記録はされます(それすらしないMTAも多いですが)が、その時に接続してきたホストのIPアドレスとそれを元にドメイン名を逆引きしたものを同時に記録するものがほとんどです。

さて、この後は一通のメッセージ送信手続きになります。
mail from:コマンドにより送信者のメイルアドレスを送信しています。これはenvelope-fromと呼ばれ、メッセージヘッダのFrom:とは別のものであり、厳密な送信元を表し、エラーメイル(bounceと呼ばれる返送メッセージ。以降バウンスメッセージと呼びます)の返送先ともなります。
ReceiverMTAの応答は"250"となっています。

その後、rcpt to:(recipient to)コマンドにより宛て先(受信者)のメイルアドレスを送信しています。これについてもenvelope-toという呼び名があり、メッセージヘッダのTo:等とは別のものです。
ReceiverMTAの応答は"250"となっています。
また、その後の"Verified Addresses"とはJavaMailが出力しているデバッグメッセージで、rcpt to:コマンドが"250"応答を返したものが出力されています(rcpt to:を複数回送信することができ、その場合に一部のアドレスに対して"250"応答を返さなかった場合にこの部分の表示が変わります)。

その後、dataコマンドを送信しています。
ReceiverMTAの応答は"354"です。これは、「その後に<CRLF>.<CRLF>という文字列が送信されるまでの複数行の入力をメッセージデータとして受け付けますよ」とReceiverMTAが言っています。

JavaMailのデバッグ出力には表示されていませんが、この後にメッセージデータの送信が行われています。メッセージデータはRFC822フォーマットと呼ばれる、ヘッダ行が複数続き、空行の後にボディ(本文)が続くようなものです。
次の

DEBUG SMTP SENT:
.
DEBUG SMTP RCVD: 250 Message received

の部分がメッセージデータの終了を示す<CRLF>.<CRLF>の送信と、その応答として"250"を受け取っていることを示しています(<CRLF>が'.'の前後に改行として表示されているのがわかりますね)。

最後にquitコマンドを送信して、セッションが終了します。quitを送信するとReceiverMTAは"221"を返し、ReceiverMTA側から切断されます。

さて、まとめますと、helo -> mail from: -> rcpt to: -> data -> メッセージデータ -> . -> quitです。最低限の単純なやりとりですね。

注:ここで示したようなセッションの状況を表現するとき、一般的には以下のような表記規則に従います。
そして、インターネットプロトコルはほとんどのものが行単位の可読テキストの送受信によって行われます。
これはTCPパケットダンプからでも、通信内容を解析しやすくするためです。逆に言えば、通信内容は盗聴されるとすぐに第三者にも解析できるということになります。
S: クライアントが送信した内容(Sent):C(Client)と表記することあり
R: サーバからの応答(Received):S(Server)と表記することあり
JavaMailのデバッグ出力では"SENT""RCVD"となっていますね。

このあたりはJavaMailが勝手にやってくれるので、通常の方法で送信するかぎり意識することはありません。
では、これからJavaMailを使う場合にも重要なことについて説明したいと思います(ただし、普通に使うぶんには考えなくてよい余談に近い内容です。知識としては重要ですが)。
先程述べたmail from:で送信するenvelope-fromとrcpt to:で送信するenvelope-toです。

上に少し書いたとおり、envelope-fromとenvelope-toはメッセージヘッダとは別のものです。ただし、通常はメッセージヘッダのFrom:やTo: cc: bcc:などに書かれているアドレスが使われます。具体的には、rcpt to:は複数回連続で送信することで、同一のメッセージを複数の宛て先に送信でき、通常MUAはReceiverMTAに対して、メッセージヘッダ上のTo: cc: bcc: Resent-To: Resent-cc: Resent-bcc:に書かれている全てのメイルアドレスについてのrcpt to:コマンドを送信します(そしてbcc:ヘッダは削除されます)。

しかし、それはあくまで「通常」であり、メッセージヘッダの送信者/宛て先とは異なるアドレスを指定することも可能です。
なぜそのようなことができるのか?っといえば「規約が古いから」と言うほかはないのですが、この仕様のせいで、メッセージヘッダから本当の送信元を突き止めるのが非常に難解になっています。
ただ、これをまっとうに利用したい状況もありえます。例えば、ファイアウォール内のMTAに対して、From:はISP等のメイルアドレスで送信先も異なるドメイン宛としたい場合、envelope-fromはファイアウォール内のMTAのホストを指している必要がありますので、From:とenvelope-fromで異なるアドレスにする必要があります。

通常MTAは自分のホスト宛のメッセージか自分のホストから出すメッセージしか処理しないようになっています。
以前はどのMTAも、どこからどこへのメッセージでも転送しようとしましたが、不正中継によりどこから来たか分からないメッセージが頻発したため、envelope-toが自分のホストを指すものか、envelope-fromが自分のホスト/または設定により許されたホストの場合のみ受信→転送するようにしているものがほとんどです(それでも不完全、または何も対処していないものもいまだにあります)。

上図ではinner.hoge.co.jpというドメインを表すMTA(*31)は自分を宛て先とするものか、差し出し人がinner.hoge.co.jpになっているもの以外は転送を拒否します。
hoge.co.jpは宛て先が自分、またはリレーを許可されたホストであるinner.hoge.co.jpであるものか、差し出し人が同様にhoge.co.jpかinner.hoge.co.jpになっているもの以外は転送を拒否します。(*32)

つまりFrom:をshin@isp.ne.jpにしたくても、To: がinner.hoge.co.jpでない場合は「リレー拒否」とされてしまうので、From:とは異なるenvelope-fromを指定するというわけです。

長くなりましたが、とにかくメッセージヘッダにおける送信者/宛て先情報とSMTPにおける送信者/宛て先情報は異なり、SMTPにおける送信者(envelope-from)/宛て先(envelope-to)が重要な意味を占めます。しかし、受信したメッセージには基本的にそれらの情報は含まれないということは理解しておいた方がよいでしょう。

*31:正確にはinner.hoge.co.jpというドメインのDNSのMXレコードというものから引くことができるホストで動作しているMTAという意味です。MTAの動作しているホストと対象としているドメイン(メイルアドレスの@の右側に来るもの)で表されるホストは別のものです(同じホストにもできますが)。

*32:これだけだとenvelope-fromを偽装されるとどうしようもありません。ISPなどではheloのタイミングでそれを送信してきたIPを元に正確な接続元をチェックし(heloのパラメタは信用しない)、それが自社のダイアルアップサーバに接続されたクライアントでない場合はリレーを拒否する等の手段を取っています。それでも自分に投げ込まれてくるメッセージのenvelope-fromがデタラメな場合に関しては大概のものはチェックなしに受信してしまうため、送信元不明(またはなりすまし)のメッセージも送ることができます。
このようなメッセージではReceived:が手がかりになりますが、この部分もある程度偽装できてしまうので、どこまでが本当(MTAが付加したもの)かを判断しなければならず、MTAのログなどの他の情報と組み合わせないと送信元を特定できないことになります。

SMTPセッション中ではmail from:からdataまでを複数回繰り返して、1セッションで複数のメッセージを送信することもできます。rcpt to:のこととあわせて記述すれば、
helo -> [mail from: -> [rcpt to:] -> data -> メッセージデータ -> .] -> quit
で、[]で括った部分は繰り返せるということです。

自分のメイルボックスにあるメッセージの参照

POP3:Post Office Protocol version 3(RFC1939)

POP3はメイルボックスに格納されたメッセージをリモートホストからダウンロードするための手順です。メイルボックスにアクセスするPOP3サーバと、それに対してアクセスを行うPOP3クライアントとの間の通信手順ということになります。
POP3や後ほど紹介するIMAP4のクライアント側のプログラムをMUA(Mail User Agent)と呼びます(*)。
一般の「メイラ」はMUAということになり、JavaMailもMUAの機能を提供するものです。

*:本来はMUAと言えばメッセージを構築/解釈するプログラムのことであり、ローカルホストのメイルボックスを参照したり、ローカルホストで動作するMTAにメッセージを流し込むようなものを指しますが、特にWindows環境では各ホストでMTAが動作するような環境は非現実的であることから、メイラはSenderSMTPの機能やPOP3/IMAP4クライアントの機能を持っていたりします。本書ではそのようなものも総称してMUAと呼んでいます(sprit MUA modelといいます)。JavaMailがMUAの機能を提供するとはこのことを指しています。

POP3はSMTP以上に単純なプロトコルです。単純でも一応認証機能があるので、SMTPで認証しきれない部分をPOP3の認証でカバーしているISPもあります。
以下はSMTPの時と同様にJavaMailでデバッグ出力をOnにしたときに出力されるPOP3セッションの部分です。デモのmsgshow.javaを実行しています(なお、msgshow.javaの使い方自体は3章で説明します)。
出力結果は大幅に省略してPOP3セッションの部分のみ記述しています。msgshowのオプションはメッセージ番号指定無しの場合のものです。

>java msgshow -T pop3 -H pop.myrealbox.com -U shin -P *********** -v -D

DEBUG: getProvider() returning javax.mail.Provider[STORE,pop3,com.sun.mail.pop3.POP3Store,Sun Microsy stems, Inc]
POP3: connecting to host "pop.myrealbox.com", port 110
S: +OK myrealbox.com Novonyx POP3 Server $Revision: 1.13 $
C: USER shin
S: +OK Password required
C: PASS *************
S: +OK
C: STAT
S: +OK 1 890
Total messages = 1
New messages = 0
-------------------------------
C: TOP 1 0
S: +OK
X-Auth-No:
Return-Path: <sk-jp.com!shin>
Received: from ns2.webk.net [210.166.240.3] by myrealbox.com
  :

C: LIST 1
S: +OK 1 890
--------------------------
MESSAGE #1:
This is the message envelope
---------------------------
FROM: Shin <shin@sk-jp.com>
TO: shin@myrealbox.com
SUBJECT: ふ〜について
SendDate: Sat Oct 14 01:48:00 JST 2000
FLAGS:
X-Mailer: Datula version 1.51.01 for Windows
C: QUIT
S: +OK myrealbox.com signing off

先頭行はSunのPOP3Providerが選ばれたことを示すデバッグメッセージです。

次の行はpop.myrealbox.comというホストのポート110番に接続しようとしていることを示しています。
次の行は、POPサーバからのウェルカムメッセージ(グリーティングメッセージとも呼ばれます)として、"myrealbox.com Novonyx POP3 Server $Revision: 1.13 $"という文字列が返されています。通常POPクライアントはこのメッセージ内容を無視しますが(APOPプロトコルは別)、その手前(行頭)にある"+OK"のステータスはチェックする必要があります(POP3のステータスコードは"+OK""-ERR"の何れかです)。
※POP3Providerのデバッグ出力ではS: -> Server、C: -> Client を表していますね。

接続が確立されたら、ユーザ名とパスワードを送信します。まずUSERコマンドでPOPアカウント名を送信します。サーバからは"+OK"が返されます。
次にPASSコマンドでパスワードを送信します。間違っていなければ"+OK"が返されます。

後は自由にコマンドを発行できます。STATでメッセージ数と総サイズを取り出し、LISTでメッセージの番号とサイズ一覧の取り出し、TOPでメッセージのヘッダ部とボディの一部の取り出し、RETRでメッセージデータ全文の取り出しと言った具合です。いずれも正常に受理されればまず"+OK"を返し、エラーの場合は"-ERR"を返します。
これらのコマンドにおいては"+OK"の後にコマンドに応じた出力が続くようになっており、クライアントはそれらの出力文字列を解釈することになります。

セッションの終了はQUITコマンドです。QUITを送信するとPOPサーバは"+OK"を返し、POPサーバ側から切断されます。

本当に単純ですね。コマンドの数も多くはないので容易に覚えてしまえます。コマンドの一覧は付録3にまとめてありますが、ここではいくつかのコマンドとその特徴について記します。

現在の多くのMUAはPOP3クライアントの機能を持ちます。MUAはPOP3サーバからメッセージを取得してローカルディスクに保存し、一覧を表示したり内容を表示したりしているわけです。その取得の方法は、LISTコマンドによってメッセージ番号を取得して、そのメッセージ番号を指定してRETRコマンドで一通ずつ取得するというものです。各メッセージの削除はメッセージ番号を指定してDELEコマンドを送信することで行われます。
DELEコマンドを送信しない場合はメイルボックスからメイルは削除されません。そうすると、次回のアクセス時に再びそのメッセージを取得することができます。
しかし、POP3の使われ方として、メッセージをサーバに残すことで他のクライアントからも受信できるようにしておいて、同じクライアントでは一度受信したメッセージを受信したくないということはよくあることです。
このためには、クライアント側で前回どこまで読み出したかを覚えておく必要があります。
このような場合、受信したメッセージ番号を覚えておいて、次で受信するときはそれより後の番号から読み込めばよいかというとそうは行きません。メッセージ番号はメイルボックスにあるメイルに対して1から連番が付けられます。つまり、メッセージが削除されるとメッセージ番号がずれることを意味しています。
DELEコマンドは実行直後に削除が行われるのではなく削除マークが付加されるだけであり、QUITコマンド実行時に実際の削除が行われますので1セッション中でメッセージ番号がずれることはありませんが、次回のセッション時にはメッセージ番号が変わっているわけですね。

このような目的のために、RFC1460で規定された時点では無かったものですが、RFC1725->RFC1939の拡張でUIDLというコマンドが用意されています。
UIDLはPOP3アクセスにおけるメッセージのユニークIDを返すコマンドです。クライアントはUIDLコマンドで返されたIDを保存しておき、次回アクセス時に、既にクライアントに保存されたIDを持つメッセージはダウンロード(RETR)しないという手段が取れます。

POP3サーバは単にメッセージをクライアントに渡すだけで、メッセージが未読であるか既読であるか等の状態は管理していません。
そのため、クライアントはサーバ上のメッセージが未読であるか既読であるかを知るすべがありません。実際にはサーバとしてはダウンロードされたことを覚えておく等のことは簡単にできますが、それをクライアントに伝える手段が現在のPOP3プロトコルにはありません(*)。

*:過去のPOP3の定義(RFC1460)では最後にアクセスされたメッセージ番号を得るLASTコマンドがありましたが、UIDLコマンドに置き換わりました。つまり、そのような情報はクライアントで管理しましょうという事です。

APOPについて

APOPについて簡単に説明します。APOPはAuthenticate POPを意味し、POPより安全な認証機構を持っています。
仕組みとしては、サーバ/クライアントがパスワードそのものをやりとりするのではなく、クライアントがサーバから最初に返された、毎回異なる特定の文字列とパスワードを並べたものを暗号化したものを送信し、サーバは送られてきたものと、自身が知っているパスワードによりクライアントと同様に暗号化処理を行った結果のものが一致するか否かで認証を行うというものです。
POPにおけるUSER/PASSコマンドの組がAPOPコマンドに置き換わります。



暗号化にはMD5が使われます。これは不可逆符号化と呼ばれ、同じキーから暗号化文字列を生成すると同じものが生成されるが、暗号化文字列から元の文字列を復元するのは不可能(に近い)と言うものです。
従って、クライアント側で暗号化した文字列と、サーバ側で同じ手順で暗号化した文字列を比較するようになっています。

このようにするとネットワーク上をパスワードそのものが流れることがなくなります。また、このようにセッションの度に異なるコードを受け渡しすることで認証を行う方式を使い捨てのパスワードという意味でOne Time Password(OTP)と呼びます。
ただし、サーバ上には暗号化されていないパスワード文字列が保持されている必要がありますので、Unixパスワード(暗号化された状態で保持されている)はそのままでは使えません。

IMAP4:Internet Message Access Protocol version 4(RFC2060)

IMAP4はメイルボックスに格納されたメッセージをリモートホストから参照/編集するための手順です。POP3との違いは、メッセージをクライアントにダウンロードして、それを参照するのではなく、サーバ上のものを直接参照/編集するということです。
そのようにすることによって、受信環境が変わってもちゃんと前回のメッセージ閲覧操作の続きから続けることができます。POP3だとその時点でメイルボックスに残っているメッセージを取り出すだけですので、違う環境でダウンロード+削除してしまったメッセージは元の環境では読み込めなくなりますし、サーバにメッセージを残すようにしてもどのメッセージが既読であるかすらわかりません。IMAP4はPOP3の場合にMUAで行っているようなフォルダによるメッセージの整理や未読/既読管理等をサーバ上のメッセージに対して行うことができます。WebMailシステムはこのアプローチが主流になっています。

IMAP4プロトコルで可能なことを以下に記します。なお、IMAP4(RFC1730)では国際化がなされていないので、ここではIMAP4rev1(RFC2060)の機能に絞ります。

SMTPやPOP3ではプロトコルとしてはメッセージを送る/取り出すだけだったのにIMAP4はずいぶんいろいろなことができるようですね。プロトコル自体もSMTPやPOP3とは大きく異なる性質を持っています。
IMAPプロトコルでは、クライアント->サーバ->クライアントのように要求(コマンド)->応答の流れを繰り返すものではありません。サーバからの応答をいちいち待っていては、ユーザはおちおち違うフォルダをクリックする事すらしていられません。クライアントが要求を出して、サーバが応答を返すのが基本ではあるのですが、サーバの応答を待たずにどんどん要求を出してもよく、それどころかサーバも好きな時にクライアントにメッセージを送ることができます。この仕組みによって、クライアントから問い合わせを行わなくても新着メッセージの到着をリアルタイムに知ることができたりするのです。
その代わり、サーバから送られてくる情報が、どのコマンドに対する応答なのか?あるいは一方的にサーバから送られたものなのかを判断できなければなりませんので、クライアントは送信したコマンドについて覚えておく必要があります。実装は格段に複雑になるわけですが、JavaMailはそれを隠蔽してくれているわけですからすばらしいですね。

では、IMAP4プロトコルで単にメッセージを取得する部分を、JavaMailのdemoのmsgshow.javaのデバッグ出力を元に見ていきましょう。なお、パスワードに相当する部分は******で表しています。

>java msgshow -T imap -H imap.myrealbox.com -U shin -P *********** -v -D

DEBUG: getProvider() returning javax.mail.Provider[STORE,imap,com.sun.mail.imap.IMAPStore,Sun Microsystems, Inc]
* OK myrealbox.com Novonyx IMAP4 Server server ready <157d.971451472@myrealbox.com>
A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=LOGIN STARTTLS XSENDER X-NETSCAPE X-NOVONYX LOGINDISABLED
A0 OK CAPABILITY completed,
A1 AUTHENTICATE LOGIN
+ VXNlcm5hbWU6AA==
c2hpbg==
+ UGFzc3dvcmQ6AA==
*********************
A1 OK AUTHENTICATE completed
A2 LIST "" INBOX
* LIST () "/" "INBOX"
A2 OK LIST completed
DEBUG: connection available -- size: 1
A3 SELECT INBOX
* 1 EXISTS
* 1 RECENT
* FLAGS (\Answered \Flagged \Deleted \Draft \Seen)
* OK [UIDVALIDITY 210683] UID validity status
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Draft \Seen)] Permanent flags
A3 OK [READ-WRITE] SELECT completed
Total messages = 1
New messages = 1
-------------------------------
A4 FETCH 1 (ENVELOPE INTERNALDATE RFC822.SIZE FLAGS BODY.PEEK[HEADER.FIELDS (X-Mailer)])
* 1 FETCH (FLAGS (\Recent) ENVELOPE
            ("Sat, 14 Oct 2000 00:41:28 +0900" "=?ISO-2022-JP?B?GyRCJD0kbBsoQg==?="
              (("Shin" NIL "shin" "sk-jp.com"))
              (("Shin" NIL "shin" "sk-jp.com"))
              (("Shin" NIL "shin" "sk-jp.com"))
              ((NIL NIL "shin" "myrealbox.com"))
                NIL NIL NIL "<200010131537.PAA27674@w3.webk.net<"
            ) INTERNALDATE "14-Oct-2000 00:41:28 +0900"
              RFC822.SIZE 919 BODY[HEADER.FIELDS ("X-Mailer")] {48}
X-Mailer: Datula version 1.51.01 for Windows

)
A4 OK FETCH completed
--------------------------
MESSAGE #1:
This is the message envelope
---------------------------
FROM: Shin <shin@sk-jp.com<
TO: shin@myrealbox.com
SUBJECT: それ
SendDate: Sat Oct 14 00:41:28 JST 2000
FLAGS: \Recent
X-Mailer: Datula version 1.51.01 for Windows
A5 EXAMINE INBOX
* 1 EXISTS
* 0 RECENT
* FLAGS (\Answered \Flagged \Deleted \Draft \Seen)
* OK [UIDVALIDITY 210683] UID validity status
* OK [PERMANENTFLAGS ()] Permanent flags
A5 OK [READ-ONLY] EXAMINE completed
A6 CLOSE
A6 OK CLOSE completed
DEBUG: added an Authenticated connection -- size: 1
A7 LOGOUT
* BYE IMAP4rev1 Server signing off
A7 OK LOGOUT completed
A8 LOGOUT

うーんはっきりいって何がなんだかわからないかもしれませんね。JavaMailのIMAPProviderのデバッグ出力にはC:(クライアントの送信内容)/S:(サーバの送信内容)といった表示がないので一層解りづらいかもしれません。ただ、IMAPの場合はこのような表記が一般的なようです。なおこの出力はプロトコル出力だけでなくmsgshow.javaの出力結果も混ざっています。IMAP4の通信に関連する部分は斜体で表記している部分になります。
この章はJavaMailを使う上では基本的に意識しなくてもよいはずのプロトコルを敢えて説明しているわけですが、プロトコルを知っておけばトラブルの時にこのようなデバッグ出力を見て解決できるわけですし、めげずに読んでみましょう。

先頭行はPOP3の時と同様に、SunのIMAPProviderが選ばれたことを示すデバッグメッセージです。

次の行はIMAPサーバからのウェルカムメッセージです。行頭が'*'か'+'で始まるものは全てサーバから送られてきたメッセージです。
次の行はクライアントがサーバに対して、どのような機能を持っているか尋ねています。IMAPサーバは実装によって適合水準が異なりますので、最初にそのサーバがどの機能までサポートするか聞いているわけです。実はPOP3やSMTPも同様に拡張機能をサポートしているかを問い合わせて、クライアントがそれに応じた対応を行う仕組みはあります。

A0 CAPABILITY
* CAPABILITY IMAP4 IMAP4rev1 AUTH=LOGIN STARTTLS XSENDER X-NETSCAPE X-NOVONYX LOGINDISABLED
A0 OK CAPABILITY completed,

行頭のA0(および以降のコマンドのA1,A2,…)は、「タグ」と呼びます。先に記したように、クライアントは送信したコマンドに対する応答の待ち合わせをしませんので、サーバから何か送られた時に、それがどのコマンドに対する応答なのかを判断できる必要があります。このために、クライアントはコマンドの先頭にユニークな「タグ」を付加して送信し、サーバはそのコマンドに対する応答に同じ「タグ」を付加して送信するのです。ただ、サーバが複数行の応答を返す場合は、その最終行のOK/NO/BADの何れかを含む「結果応答」にのみタグが付加されます。つまりクライアントが送信したタグ付きの要求に対して、サーバは様々なタグ無し応答行に加えて最後にタグ付きの結果応答を返します。

ここでは、クライアントはCAPABILITYというコマンドを送信し、サーバはその次の二行を応答として返していることを表します。
行頭が'*'の応答はサーバからの継続応答を表す場合と、サーバから一方的に送信された通知を表します。
CAPABILITYコマンドは一行のタグ無し応答の後にタグ付きの結果応答を返す事になっていますので、このような応答になります。クライアントはこのサーバがIMAP4rev1の機能をサポートし、認証方法にLOGIN認証機構を使用することがわかります。結果応答はタグ付き応答OKです。

A1 AUTHENTICATE LOGIN
+ VXNlcm5hbWU6AA==
c2hpbg==
+ UGFzc3dvcmQ6AA==
*********************
A1 OK AUTHENTICATE completed

次はクライアントがAUTHENTICATEコマンドを送信して認証を行っています。IMAPにはLOGIN <user> <password>形式の認証方法もありますが、この方法では生のパスワードがネットワーク上を流れるため通常は使用されません。ここではAUTHENTICATEコマンドを送信してLOGIN認証方式(LOGINコマンドとは異なります)を要求しています。これも実は大したことはなくて単にユーザ名/パスワードをBase64エンコードして送信するというだけのものです("生"よりはましといった程度です)。実際には認証方式にはより安全なものがあります(*)。
この部分の読み方を説明します。AUTHENTICATE LOGINコマンドを送信すると、サーバは"+ VXNlcm5hbWU6AA=="という応答を返しました。行頭が'+'の応答は、サーバからクライアントに対しての何らかの要求を表します。これは主に認証のときに使われる接頭辞です。後に続く文字列は実は"Username:"という文字列がBase64エンコードされたものです。
次の行は、クライアントが"shin"というユーザ名をBase64エンコードしたものを送信しています。同様に、次の行でPassword:が要求され、クライアントがパスワードをBase64エンコードしたもの(上記は伏せ字になっています)を送信している事を表します。
サーバからのユーザ名/パスワードの問い合わせに答えた事で、サーバはOKタグ付き応答を返しました。これで認証は完了です。

*:ftp://ftp.isi.edu/in-notes/iana/assignments/sasl-mechanismsでIANAに登録された認証方式名の一覧を見ることができます。

A2 LIST "" INBOX
* LIST () "/" "INBOX"
A2 OK LIST completed

認証が完了したら、次にJavaMailはLISTコマンドを送信しています。LISTコマンドはフォルダの一覧を返すコマンドで、パラメタにワイルドカードを含めたフォルダ名を指定することができます。ここではINBOXフォルダを検索し、サーバは"INBOX"という名称のフォルダがフォルダツリーのルートに一つ存在することをクライアントに伝えています。

A3 SELECT INBOX
* 1 EXISTS
* 1 RECENT
* FLAGS (\Answered \Flagged \Deleted \Draft \Seen)
* OK [UIDVALIDITY 210683] UID validity status
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Draft \Seen)] Permanent flags
A3 OK [READ-WRITE] SELECT completed

次に、JavaMailは今存在を確認したINBOXフォルダを指定してSELECTコマンドを送信しています。SELECTコマンドは指定されたフォルダを選択して、フォルダ内のメッセージを編集可能状態にするものです。SELECTコマンドの応答としてフォルダの状態と、フォルダ内のメッセージの状態が返されています。

このあたりの感覚は、ターミナルやMS-DOSプロンプトでcdコマンドでカレントディレクトリを移動する感覚でイメージすればよいのではないでしょうか(実際フォルダとファイルシステムにおけるディレクトリを一対一に対応させているIMAPサーバがほとんどです)。IMAPの世界ではどのフォルダも選択されていない状態があり、選択されたフォルダ内のファイル(メッセージ)にしかアクセスできないというのが違いです。

A4 FETCH 1 (ENVELOPE INTERNALDATE RFC822.SIZE FLAGS BODY.PEEK[HEADER.FIELDS (X-Mailer)])
* 1 FETCH (FLAGS (\Recent) ENVELOPE
            ("Sat, 14 Oct 2000 00:41:28 +0900" "=?ISO-2022-JP?B?GyRCJD0kbBsoQg==?="
              (("Shin" NIL "shin" "sk-jp.com"))
              (("Shin" NIL "shin" "sk-jp.com"))
              (("Shin" NIL "shin" "sk-jp.com"))
              ((NIL NIL "shin" "myrealbox.com"))
                NIL NIL NIL "<200010131537.PAA27674@w3.webk.net<"
            ) INTERNALDATE "14-Oct-2000 00:41:28 +0900"
              RFC822.SIZE 919 BODY[HEADER.FIELDS ("X-Mailer")] {48}
X-Mailer: Datula version 1.51.01 for Windows

)
A4 OK FETCH completed

注:"* 1 FETCH"の行は長いので改行させてインデントしていますが、実際にはインデントされている部分は一行です。

フォルダが選択状態(編集可能状態)になったところで、FETCHコマンドによりフォルダ内の1番目のメッセージから必要な情報を取得しています。今回はmsgshow.javaをメッセージ番号指定無しで呼び出しているため(msgshow.javaの動作詳細については3章で説明します)、INBOX内の全てのメッセージのENVELOPE(送信者/宛て先情報など)を表示するという操作が実行されており、ここではメッセージが一通しかなかったのでその一通に対して、ENVELOPEと受信日時/サイズ/状態フラグおよびヘッダのX-Mailer:の内容の取得を要求しています。
サーバはそれに対して要求された項目を返しているのが読み取れると思います。コマンドに指定した各項目ごとにその内容が付加されたものがタグ無し応答("*"の行)として返され、次に"A4 OK"という風にタグ付きの結果応答が返されています。その後の斜体になっていない部分はmsgshow.javaが取得したメッセージ内の情報を画面に出力している箇所です。

A5 EXAMINE INBOX
* 1 EXISTS
* 0 RECENT
* FLAGS (\Answered \Flagged \Deleted \Draft \Seen)
* OK [UIDVALIDITY 210683] UID validity status
* OK [PERMANENTFLAGS ()] Permanent flags
A5 OK [READ-ONLY] EXAMINE completed

EXAMINEコマンドはSELECTと同様にフォルダの選択を行うコマンドで、フォルダをREAD_ONLYモードで選択します。この部分はJavaMail1.2がコネクションプールの実現のために発行しているダミーコマンドですので、なぜここで「選択」するのかは詮索しないでおきます。

A6 CLOSE
A6 OK CLOSE completed
A7 LOGOUT
* BYE IMAP4rev1 Server signing off
A7 OK LOGOUT completed

最後に選択していたフォルダから抜けるという意味のCLOSEコマンドを送信し、LOGOUTコマンドでセッションを終了しています。

IMAP4のコマンドはたくさんあるのですが、基本的なコマンドのやりとりの流れはこのような感じです。
ただ、ここに挙げた例では、タグA0〜A7までのコマンドについて即座に応答が返ってきていますが、応答より先に次のコマンド送信が行われる場合があることは先程述べたとおりです。

また、IMAP4サーバ上ではメッセージ毎に未読/既読などの状態を管理することができます。また、フォルダ毎に読み出し専用等の属性を持つこともできます。どのような属性/状態を持ち、またどうやって変更/参照するかは付録4および4章のJavaMail APIについての説明、そして、RFC2060を参照してください。

電子メイルのフォーマット

現在の電子メイルのフォーマットは、RFC822を基本としており、その後MIME(Multipurpose Internet Mail Extention)などの拡張構文が追加されています。
ここでは、RFC822で定義されているヘッダとそれ以降のRFCで定義された拡張ヘッダから有用と思われるもの、および一部の独自ヘッダをご紹介します。

JavaMailを用いても、ほとんどのヘッダはプログラマが指定することになるので、ヘッダに関する知識はある程度必要になります。よく使うヘッダに関してはMimeMessageクラスのメソッドとして宣言されていますが、該当するメソッドがない場合はsetHeader()メソッドを用いることになります。
JavaMailではヘッダの構文規則を意識する部分が減っているので、例えばDate:やTo:等の構造化フィールド(structured field)における日付フォーマットやアドレスフォーマットのようにJavaMail APIによってカプセル化された部分の説明は省略しています。

ヘッダはHeader-Name: valueの形式(*)であり、トレイスフィールド/デイツフィールド/ソースフィールド/ディスティネーションフィールド/リファレンスフィールド/オプショナルフィールドというように分類されています。
オプショナルフィールドにはエクステンションフィールドとユーザデファインドフィールドが含まれます。
では、それぞれのヘッダについて見ていきましょう。

なお、本書での書式の記法はRFC822とは若干異なっています。少しでも簡潔且つわかりやすくするため、[]->省略可、* ->0回以上の繰り返し、だけを使っています。その分若干厳密さに書ける箇所もありますが、必要な情報はほぼ揃えてあります。
より正確な情報はもちろんRFCを見ていただく必要があります。

注:各ヘッダの説明には「設定/参照」として、JavaMail APIにおける該当ヘッダ設定/参照メソッド名を記述していますが、これはそのヘッダ専用のメソッドがあるか否かを示しています。これらの欄に「無し」と記述されていても、それはそのヘッダに特化したメソッドがないだけであり、この欄の記述内容に関係なく、Part#setHeader()/Part#getHeader()メソッド等で全てのヘッダの設定/参照が可能です。

*:"From "ヘッダ("From:"ではなく)というものもありますが、これはインターネット上を流れるものではありません。

トレイスフィールド

トレイスフィールドはメッセージの転送の軌跡を表すフィールドです。

trace

Return-path: ---- バウンスメッセージの戻り経路
書式 Return-path: メイルアドレス
付加される箇所 最終受信MTA
設定メソッド 無し
参照メソッド 無し

Return-Path:は最終受信MTAによってメイルボックスに入れられるときに付加されます。valueは、そのMTAに対するenvelope-fromの内容になります。Return-Path:はバウンスメッセージの返送先となります。
バウンスメッセージは通常MTAが返送しますので、MUAがこの内容を意識する必要はありません。しかし、ヘッダの情報は実際のところ信用できないため、ここに書かれたenvelope-fromの方がいくらか信用できるということで、ここのアドレスにメッセージを出したいケースもあります。
例えば、メッセージを自動受信して何らかの処理をするようなプログラムの場合、受信/解析エラー発生時などはここに書かれたアドレスにバウンスメッセージを送信すれば良いことになります。

ただし、POP3でメッセージを取り出そうとしたときなどにReturn-Path:は削除されてしまう場合があります。MTAおよびPOP3サーバによっては削除しないものもありますが、POP3で取り出して何らかの処理をしようとしてバウンスメッセージを送信しようとする場合は注意が必要です。
バウンスメッセージを返したい場合、概ね以下のような優先順位で返送先アドレスを決定すればよいとされています。
・Return-Path:
・From:
・Sender:
・Reply-To:
・Return-Receipt-To:
・Errors-To:
・Resent-Sender:
・Resent-From:
・Resent-Reply-To:

JavaMailで受信したメイルに対してバウンスメッセージを返したい場合は、上記の順番に存在するヘッダを検索する処理を書くべきでしょう。
もちろん「返信」の場合はMessage#reply()を用いれば適切(*)にTo:が設定されます。

*:実際には問題あり。4章を参照してください。

Received: ---- 転送履歴
書式 Received: from ホスト
by ホスト
[via 物理パス]
[with LINK/MAILプロトコル]*
[id メッセージID]
[for メイルアドレス]
; 受信日時
付加される箇所 MTA
設定メソッド 無し
参照メソッド 無し

メッセージがMTAに届く度に付加されます。メッセージ転送履歴ですので、メッセージがどこからどの経路を使って配送されたかを知る有力な手がかりとなります。
宛て先ドメインのMTAにいきなり届かない場合もあるのは前に述べたとおりです。
Received:は構造化フィールドで、記述形式が決まっています。
個々の節は以下のような意味になります。それぞれの節の末尾に()で囲まれた補足情報が入ってもかまいません。

from そのMTAに送信を行ったMTAのドメイン名。
by メッセージを受信したMTAのドメイン名。
via どのような物理伝送路でその区間の転送が行われたか。
with メッセージの転送に使われた接続レベルのプロトコル名(主にSMTP/ESMTP)。
id 受信したMTA上でメッセージを識別するためのID。
for 宛て先メイルアドレス。通常は経路の途中で変わることはありません。

JavaMailには、特にReceived:を解釈するためのAPIは用意されていません。必要とあらば、Part#getHeader()で参照して、上記の構造の文字列を解析する処理を書くことになります。ただ、通常はその必要はないでしょう。一点だけ挙げるなら、Received:に記述される「受信日時」情報は、メッセージの到着日時を表しますので(Date:は作成/送信日時を表す)、この部分の解釈を試みることは有効かもしれません。JavaMailにはMessage#getReceivedDate()というメソッドがありますが、これはIMAPProviderの場合はINTERNALDATE属性の値を返しますが、POP3Providerではnullが返されます。POP3で受信日時を取得したければReceived:の解釈に挑戦してもいいかもしれませんね。

余談ですが、差し出し人を偽ろうとするメッセージではReceived:も偽ろうとしているものもあります。しかし、MTAが配送経路で自動付加する部分を改ざんすることはできないため、予めダミーのReceived:を付加しておいてまぎらわそうとする程度しかできません(もちろん一見矛盾のないように余計な経路を付け足しておくわけですが)。つまりはReceived:を上から順に辿って一つ一つの経路について確認を取っていけばどのMTAが最初に利用されたかは突き止めることができ、そこの管理者に頼めばどの端末から送信されたかも突き止めることができるでしょう。

デイツフィールド

デイツフィールドはメッセージの送信日時を表すヘッダ群です。

Date: ---- 送信日時
書式 Date: 日時文字列
付加される箇所 MUA
設定メソッド Message#setSentDate()
参照メソッド Message#getSentDate()

メッセージを送信しようとした日時をMUAのローカルタイムを元に設定します。sendmailはDate:がないメッセージには勝手にDate:を付加すると言う機能がありますが、この機能に期待してDate:を付加しないメッセージを送信してはいけません。

JavaMailではDateヘッダを自動的に付加する機能はありませんので、メッセージ送信前にMessage#setSentDate()を呼び出して、Date:ヘッダを付加する必要があります。

Resent-Date: ---- 転送メッセージの場合の送信日時
書式 Resent-Date: 日時文字列
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

メッセージの転送時に用いられるヘッダです。「転送」で送信されるメッセージにはDate:ヘッダの代わりにResent-Date:が付けられます。というか付けられるべきでした。しかし、このヘッダを付けてくれるMUAは少なく、オリジナルメッセージのDate:を書き替えてしまう場合が多いようです。
そのような挙動のMUAではResent-Date:を表示する機能も持たない場合がほとんどであるため、有効に活用されているとはいい辛いですね。

以降に紹介するものも含めて、Resent-xxx:ヘッダは転送メッセージを送信する場合に付加し、オリジナルのヘッダは改変しないというものですが、「転送」機能でオリジナルのFrom:To:Date:等を書き換えてしまうMUAがほとんどでした。しかし、これからはMIMEのContent-Type: message/rfc822を用いて転送を実現することが多くなりますので、いよいよResent-xxx:ヘッダの出番は少なくなりそうです(いや、現状でもほとんど出てくることはないのですが)。

JavaMailを使う場合においても「転送」の場合はMultipartで転送するように実装してしまってかまわないでしょう。
ただし、Multipartを解釈しないシステムがなくなったわけではないことはお忘れなく。そのような可能性がある場合(不特定多数への送信など)は、Resent-xxx:を用いるべきなのですが、なんと、JavaMail自体もResent-xxx:を認識して正しい送信先を決めてくれないため、Resent-xxx:ヘッダを付加したメイルを送信するときは、Transport#send(Message, Address[])を用いて明示的にenvelope-toを指定しなければなりません。

この件はBugParadeにも登録されていますが、やはりResent-xxx:ヘッダを解釈するMUAがほとんどないためこの機能は不要であるとの評価がされています。http://developer.java.sun.com/developer/bugParade/bugs/4294576.html

結局RFCに忠実ではないですが、Multipart以外で「転送」を行う場合はTo:From:等を書き換えて転送を行う方が一般的になってしまっているということでしょうか。
確かに、Resent-xxx:を解釈しないMUAがそのようなメイルを受信した場合、転送者の情報が見えなかったり、それに対する「返信」操作で意図しない結果になってしまうので仕方がありませんね。

ソースフィールド

ソースフィールドはメッセージの作成/送信元を表すヘッダ群です。
オリジナルメッセージの作成/送信元を表すoriginator、転送メッセージの作成/送信元を表すresent、メッセージのリレー単位での送信元を示すtraceという区分けがあります。

originator

From: ---- 差し出し人
書式 From: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド Message#setFrom()
Message#addFrom()
参照メソッド Message#getFrom()

差し出し人。厳密には「メッセージを送信したい人」となります。
Reply-To:、Sender:が省略された場合はFrom:の内容がそれらに相当します。
From:ヘッダがない場合はSender:ヘッダが記述されていなければなりません。
誤解の多い点なのですが、Sender:Resent-Sender:を除くソースフィールドの各ヘッダには複数のメイルアドレスが指定できます。差し出し人が連名になるのはよくあることですよね。
From:ヘッダに複数のアドレスが指定される場合はSender:ヘッダが必要です。

JavaMailではMessage#setFrom(Address)などで設定が可能です。
もちろんaddFrom()で複数のアドレスを設定することもできます。

Sender: ---- 送信者
書式 Sender: メイルアドレス
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

一般に、差し出し人(From:)とは異なる実際の送信者がいる場合に付加します。
Sender:ヘッダがない場合はFrom:が送信者となります。
例えば、From:がグループを表している場合などに、実際の送信者を特定するために付加します。もちろんFrom:が一人の場合も付加する場合はあり得ます。

JavaMailではPart#setHeader(String, String)などで設定が可能です。

Reply-To: ---- 返信先
書式 Reply-To: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド Message#setReplyTo()
参照メソッド Message#getReplyTo()(注)

存在しない場合はFrom:が返信先アドレスとなります。
メイリングリストなどでは返信をメイリングリスト宛にしたいために、配送時にReply-To:ヘッダを付加します。
単に別のメイルアドレスで返信を受け取りたい場合などに、送信時に指定する場合もあります。
実際にはMUAで「返信」機能を使ったときにTo:にこのアドレスが表示されると言うだけのものです。

特定のMUAでは環境設定項目として単に「返信アドレス」と書かれていたりしますが、これでは省略した場合にFrom:に返信されることを知らない人が反射的にFrom:のアドレスを書いてしまうので、困ることがあります。
つまり、Reply-To:が付いていた場合、メイリングリストマネージャが"Reply-To:メイリングリストのアドレス"を付加しない場合がありますので、メイリングリストから流れてきた記事に返信したら、その元記事の投稿者だけに返信が配送された、といった問題が起こります。

JavaMailではMessage#setReplyTo()などで設定/参照が可能です。ただし、getReplyTo()はReply-To:ヘッダが存在しない場合にFrom:ヘッダの内容を返してくるので注意が必要です。メイラの「返信」機能を想定しているわけです。純粋にReply-To:ヘッダの内容が取得したい場合は、Part#getHeader("Reply-To")を用いましょう。

resent

Resent-From: ---- 転送メッセージの場合の作成者
Resent-Sender: ---- 転送メッセージの場合の送信者
Resent-Reply-To: ---- 転送メッセージの場合の返信先
書式 Resent-From: メイルアドレス[, メイルアドレス]*
Resent-Sender: メイルアドレス
Resent-Reply-To: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

転送メッセージの場合の送信元/返信アドレスを表します。
これらは解釈してくれないMUAも多いとResent-Date:の所に書きましたが、この中では特にResent-Reply-To:やResent-From:が問題になります。Resent-Reply-To:が付いた記事に返信しようとした場合は、Reply-To:やFrom:より優先してResent-Reply-To:に書かれたアドレスを返信先アドレスとしなければなりませんが、対応していないMUAはそれをしてくれません。
JavaMailもやはりこの部分は正しく実装されておらず、Message#getReplyTo()は単にReply-To:From:の順に検索するだけです。
これも理由はResent-Date:の所に書いた通りなのですが、今となっては、Resent-xxx:ヘッダを下手に解釈するほうが問題が発生する場合があるということでしょう。

JavaMailではPart#setHeader(String, String)などで設定が可能ですが、これらは特に副作用の可能性が高いため使わない方がよさそうです。

ディスティネーションフィールド

ディスティネーションフィールドはメッセージの送信先を表すヘッダ群です。

To: ---- 送信先
書式 To: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド Message#addRecipient()
Message#setRecipient()
Message#setRecipients()
Message#addRecipients()
参照メソッド Message#getRecipients()
Message#getAllRecipients()

言わずと知れた「宛て先」ですね。複数の宛て先メイルアドレスを並べることができます。

JavaMailではMessage#setRecipients(Message.RecipientType.TO, Address)などで設定が可能です。

cc: ---- カーボンコピーの送信先
書式 cc: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド Message#addRecipient()
Message#setRecipient()
Message#setRecipients()
Message#addRecipients()
参照メソッド Message#getRecipients()
Message#getAllRecipients()

コピーの送信先です。
「宛て先はTo:の人だけどあなたにも送っておくね」と言うときに使います。

JavaMailではMessage#setRecipients(Message.RecipientType.CC, Address)などで設定が可能です。

bcc: ---- ブラインドカーボンコピーの送信先
書式 bcc: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド Message#addRecipient()
Message#setRecipient()
Message#setRecipients()
Message#addRecipients()
参照メソッド Message#getRecipients()
Message#getAllRecipients()

送信先には表示されない送信先です。自分のメイルアドレスを書いておくと便利です。
メッセージを送信する前にこのヘッダはMUAにより削除されます(JavaMailも削除してくれます)。ちなみにMTAはこれらの(ディスティネーションフィールドの)ヘッダとは無関係にenvelope-toを見て送信先を決定します。多くのMUAがこれらのヘッダを元にenvelope-toを生成しているということであってTo:やcc:に書かれているアドレスと実際の宛て先が常に一致しているわけではありません。

JavaMailではMessage#setRecipients(Message.RecipientType.BCC, Address)などで設定が可能です。

Resent-To: ---- 転送メッセージの場合のTo送信先
Resent-cc: ---- 転送メッセージの場合のcc送信先
Resent-bcc: ---- 転送メッセージの場合のbcc送信先
書式 Resent-To: メイルアドレス[, メイルアドレス]*
Resent-cc: メイルアドレス[, メイルアドレス]*
Resent-bcc: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

転送/返送時に設定するヘッダです。To: cc: bcc:は元のメッセージのままとなります。
MUAはこちらに書かれた宛て先を優先して、envelope-toを生成しなければなりません。といいつつ、これもResent-Date:に書いたとおり解釈/使用してくれるMUAがほとんどありません…。
通常のMUAでは転送時にこのヘッダを用いず、To:などを書き替えてしまいます(どちらかというと本文のみ引用してヘッダは新規送信と同様に扱われるというべきですね)。
ユーザが転送操作を行った場合であれば、そこからの再送という意味でTo:を書き替えてもよいと考えられますが、自動転送機能などの場合は、元々どのアドレス宛に送られたメッセージであるかが残っていた方が良いため、これらのヘッダを使用するべきでした。
しかし、転送時にTo:を書き替えるようなMUAの場合は、ほぼ間違いなくResent-To:を解釈してはくれません。
やはり、Resent-Date:の所に書いたとおり、今後はmessage/rfc822でメッセージ全体を内包(または添付)して転送するのが主流になります。

JavaMailではPart#setHeader(String, String)などで設定が可能です。しかし、一般のMUAが正しく解釈しないことを考えると、これらのヘッダを付加する場合はReply-To:も付けておくべきでしょう。
また、設定したとしても、JavaMailはResent-xxx:自体に対応していないため普通に送信しようとするとTo:等のアドレスに送信してしまいます。これらのヘッダを正しく記述したい場合は、送信時に実際の宛て先であるResent-xxx:の内容に相当するアドレス群を、Transport#send()の第二パラメタに指定する必要があります(結局のところ使わない方がいいというところですね)。

リファレンスフィールド

Message-ID: ---- そのメッセージを表すユニークな識別子
Resent-Message-ID: ---- 転送メッセージの識別子
書式 Message-ID: <addr-spec識別子>
Resent-Message-ID: <addr-spec識別子>
付加される箇所 MUA/MTA
設定メソッド 無し(MimeMessage#saveChanges())
参照メソッド MimeMessage#getMessageID()

そのメッセージを特定するためのユニークなID(メッセージID)を表記します。本来は日時情報と送信元メイルアドレス、メッセージカウント値などを元にMUAが生成します。オプショナルでしたが、現在は全てのメッセージに付加されるべきものとして認知されています。
しかし、MUAがMessage-ID:を生成しようとした場合、そのIDの完全な一意性が保証できないため(ほぼ確実という方法はあります)、MTAに生成/付加させた方が確実だという意見も多くあります。ただ、全てのMTAがMessage-ID:生成機能を持つわけではない(もともとMTAの役割ではないため)ことと、MTAが間違ったMessage-ID:を付加してしまうものもあることから、自分の利用するMTAが正しくMessage-ID:を付加する事が分かっている場合のみMUA側でMessage-ID:を付けないようにするという運用方法が現状もっとも妥当なようです。

さて、ここの書式にはaddr-specと書いてあるのですが、実はこれはTo:From:等と同じ書式という意味です(本書では「メイルアドレス」と記しましたが)。
つまりメイルアドレスとして有効な書式である必要があります。その条件の一つは文字列中の右端の@より右側(ドメインパート)にグローバルなFQDN(Fully Qualified Domain Name:完全修飾ドメイン名)が記述されていることです。このルールが守られないと文字列の一意性が保証できなくなります。上記の間違ったMessage-ID:というのはこのドメインパートにグローバルなFQDN以外の文字列が入るようなものです。

JavaMailではこのヘッダを送信時(厳密にはMimeMessage#saveChanges()呼び出し時)に自動的に付加してくれます。
しかし、正しく使用しないとJavaMailも不正なMessage-ID:を付加してしまう場合があります。詳細は4章のコラム「JavaMailにおけるMessage-ID:ヘッダの扱いについて」を参照してください。

In-Reply-To: ---- 返信メッセージの場合の返信元メッセージの識別子
書式 In-Reply-To: メッセージIDまたはメッセージを特定する文字列[, メッセージIDまたはメッセージを特定する文字列]*
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

どの記事に関する「返信」メッセージであるかを示します。valueには返信元のメッセージIDを記述します。
RFC822ではメッセージを特定できる情報であればよいとしか書かれていなかったため、In-Reply-To:ヘッダに返信元メッセージのDate:ヘッダの内容やFrom:の内容を記述するMUAもあるようです。しかし、現在はほぼメッセージIDを記述する事が標準となっています。さらに、標準としてはIn-Reply-To:には一つの直接の返信メッセージのIDのみを記述する事になりつつあります(定義では複数指定可能となっていますが)。
次のreferences:と共に、メッセージのスレッド表現を可能にするために、「返信」である全てのメッセージに付加されるべきヘッダです。

JavaMailではPart#setHeader(String, String)などで設定が可能です。

References: ---- 参照するメッセージの識別子
書式 References: メッセージIDまたはメッセージを特定する文字列[, メッセージIDまたはメッセージを特定する文字列]*
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

どの記事を「参照」しているかを示します。valueにはIn-Reply-To:と同じ情報に加えて、その返信元メッセージの返信元となったメッセージIDのリストを記述します。言い換えると、返信元メッセージが元々持っていたReferences:ヘッダの末尾に、そのメッセージのメッセージIDを追加したものを設定します。
RFC822ではあくまで「参照」しているメッセージという定義でしたので、返信元メッセージでなくても引用したメッセージのメッセージID等を記述するものという位置付けでしたが、現在は、メッセージのスレッド表現を可能とするための直接の返信メッセージのリストという意味に変わってきています。
受信したMUAはIn-Reply-To:かこのヘッダを見て、記事の参照元を特定します。
In-Reply-To:と共に、メッセージのスレッド表現を可能にするために、「返信」である全てのメッセージに付加されるべきヘッダです。
しかし、一部のMUAは正しくこれらのヘッダを付けてくれずに迷惑をかけます(必須ではないのですが)。そのようなMUAは使わないようにして欲しいものです。

JavaMailではPart#setHeader(String, String)などで設定が可能です。

Keywords: ---- 検索キーワード
書式 Keywords: キーワード[, キーワード]*
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

メイルの検索時などに参照するための、本文の内容に即したキーワードを含めることができます。
HTMLの<META name="keywords" ...>と同じ用途といえます。
ただし、メイルは記事の大きさが大きくなりにくいので、全文検索を行うことの方が多いですし、ほとんどのMUAがこのヘッダの入力機能を提供しませんので使われることはないに等しいでしょう。

JavaMailではPart#setHeader(String, String)などで設定が可能です。

オプショナルフィールド

オプショナルフィールドは前記のフィールドに属さないヘッダ群です。
拡張用に、エクステンションフィールド/ユーザデファインドフィールドという区分けが含まれます。
RFC822以降に電子メイルメッセージのさまざまな拡張により、オプショナルフィールドに属する重要なヘッダがたくさん規定されています。中には特定のMUAだけが解釈するようなものもありますが、RFCに規定されている限りはそれは標準です。そうでないものはユーザデファインドフィールドとして定義/利用する必要があります。

Subject: ---- 題名
書式 Subject: 文字列
付加される箇所 MUA
設定メソッド Message#setSubject()
参照メソッド Message#getSubject()

メッセージの主題を記述します。「題名」等と呼ばれます。
Subject:に関しては日本語の扱いに関してさまざまな問題が出ていました。現在はRFC2047で規定されるヘッダにおける非ASCII文字のエンコード方法に従ってエンコードされた文字列が記述されることになるのですが、この部分を正しく実装していないMUAが多いため、RFCに忠実なJavaMailで正常にデコードできないといったものです。これらについては4章で説明しています。

JavaMailではMimeMessage#setSubject(String, String)でUnicode文字列とエンコード方式を指定することで、JavaMailが自動的にエンコードした文字列を設定してくれます(ただし、若干の問題あり。こちらも4章を参照下さい)。

Comments: ---- コメント
書式 Comments: 文字列
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

コメントです。公的文書の場合などで本文中にコメントを書きたくない場合は使うこともあるかもしれませんが、現在はほぼ使うことはないでしょう。

Encrypted: ---- 暗号化方法
書式 Encrypted: 符号化スキーム 符号化キー
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

暗号化メッセージの場合に、メッセージの暗号化方法と、その方法に応じたパラメタを記述します。
現在はPGP/MIME、S/MIMEの何れかを使って暗号化を行うのが普通ですので、このヘッダが使われることはほぼないと言ってよいでしょう。

エクステンションフィールド

エクステンションフィールドはRFC822以降で定義されたヘッダ群です。

MIME-Version: ---- MIMEバージョン
書式 MIME-Version: バージョン
付加される箇所 MUA
設定メソッド 無し(MimeMessage#saveChanges())
参照メソッド 無し

MIMEメッセージであることを宣言するヘッダです。バージョンは現在1.0固定となっています。
MIME-Version:ヘッダが存在しないメイルはMIMEではないメイルということになります。

JavaMailでは送信時に自動的に付加されます。また、JavaMailはMIME対応されたメッセージしか送受信できないことになっています。これは実は問題で、現在でも非MIMEであるメッセージを送信するMUAは多数存在します。これらの送信するメッセージをJavaMailはそのままでは解釈できないのです。この問題に対する回避方法は4章で説明します。

Content-Type: ---- パートのボディ種別
書式 Content-Type: MIMEタイプ; パラメタ
付加される箇所 MUA
設定メソッド 無し(MimeMessage#saveChanges())
参照メソッド Part#getContentType()

Content-Type:は各パートのボディの種別を表します。本書ではそれがPart interfaceで表されることから「パート」という表記に統一していますが、RFC2045ではヘッダとボディを含むマルチパートメッセージの各パートのことをエンティティと呼んでいます。

ボディの種別であるMIMEタイプは、メディアタイプ/サブタイプの形式で表され、メディアタイプには、text、image、audio、video、applicationの5種類と、複合形式を表すmultipart、messageの2種類、計7種類が存在します(*)。

また、RFC2077でmodelメディアタイプが規定されています。modelは図面等を表現するデータ形式のために用意されたメディアタイプで、model/vrmlやmodel/mesh等があります。

*:MIMEタイプはメディアタイプと呼ばれる事もあり、本書でメディアタイプと呼んでいる'/'の手前の部分のことをトップレベルメディアタイプと呼んだりしますが、本書では上記のようにメディアタイプ/サブタイプ == MIMEタイプと言う表現で統一します。

主なMIMEタイプの一部を以下に記します。IANAに登録された最新のMIMEタイプ一覧は、ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/などから取得することができます。

MIMEタイプ 意味
text/plain プレインテキスト
text/html HTML
text/xml XML
text/enriched リッチテキスト
text/rfc822-headers メッセージヘッダ
application/gzip gzipファイル
application/java-archive jarファイル
application/msword MS-WORD文書
application/octet-stream バイトストリーム(形式の指定無し)
application/pdf pdfファイル
application/x-postpet ペット情報(^^)
audio/midi midi音楽
audio/mpeg mpeg音声
image/gif gif画像
image/jpeg jpeg画像
video/mpeg mpeg動画
message/rfc822 Email形式
message/partial (巨大メイルにおける)分割メッセージの一部
message/external-body 外部にあるメッセージ本体へのアクセス方法が指定される
multipart/form-data FORMのパラメタ形式(name=value)
multipart/alternative 同一内容を複数種類の形式を持つパートに分けるコンテナ
text/plainとtext/htmlなど
multipart/mixed 複数パートのコンテナ
multipart/parallel 複数パートのコンテナ(全てのパートが同時に見えるもの)
audio/basicとimage/jpegなど
multipart/related 複数パートのコンテナ(内部で他のパートにリンクするもの)
text/htmlとimage/gifなど
multipart/report MTAが送出するエラーなどのリポートメイル用
multipart/signed S/MIME署名付き
multipart/encrypted PGP署名付き
multipart/voice-message ボイスメッセージ

multipart、messageメディアタイプについては、後程詳しく説明します。他のメディアタイプは文字どおりですので特に説明は不要ですね(applicationは他の4種類に属さない「その他」のメディアタイプといったところです)。

パラメタはMIMEタイプに応じて変わります。他のヘッダについても言えることですが、パラメタは';'で区切られname=valueの形式となっています。
Content-Type:ヘッダで特に「パラメタ」の書式を記載したのは、日本語メッセージに欠かせないcharsetパラメタを指定するのがContent-Type:ヘッダだからです。

charsetパラメタにはパートのボディの文字エンコーディングを指定します。日本ではShift-JISやEUC-JP、ISO-2022-JPが使われますが、ほとんどのメッセージはISO-2022-JPでエンコードされています。これはISO-2022-JPが7bitに符号化されるため、ボディのエンコード(*)無しで送信可能であることが大きいです。
日本語メッセージの文字エンコーディングにISO-2022-JPが用いられるべきであることは、RFC1467に記されています。

JavaMailではボディの設定時にContent-Type:を指定することになります。これはMimeMessage#setContent()やsetDataHandler()呼び出し時に適切なMIMEタイプを与えておくことを指しますが、それさえやれば、後はそのデータタイプに応じた処理のいくつかはJavaMailがやってくれるということです。また、MimeMessage#setText()の場合、charsetパラメタを与えれば、Cotent-Type: text/plain; charset=xxxxというヘッダが自動的に付加されます。

なお、Content-Type:ヘッダに与える文書の種別はMIME規定前/後で異なっています。
MIMEではないインターネットメイルではContentType: textなどのヘッダが付加されたりしていましたが、JavaMailはMIMEに従ったメイルしか解釈できません。このようなメイルが届いた場合に対処するためには、アプリケーション側でtextをtext/plainに変換してあげるなどの対処が必要になります(4章を参照)。

*:文字エンコーディングとボディのエンコーディング(転送エンコーディング)を区別してください。転送エンコーディングは次のContent-Transfer-Encoding:で指定されるものです。

Content-Transfer-Encoding: ---- パートのエンコード方法
書式 Content-Transfer-Encoding: 符号化スキーム
付加される箇所 MUA
設定メソッド 無し(MimeMessage#saveChanges())
参照メソッド MimePart#getEncoding()

各パートのボディ全体の符号化方法を表します。過去のメイルの伝送路は7bitに制限されていたため、8bit単位の情報の送受信を行うためには何らかの符号化が必要でした。現在は8bitでも送受信可能な場合が多く、Content-Transfer-Encoding: 8bitという指定も存在しますが、バイナリの送受信などにはやはり可視文字のみにエンコードしたものを送信する方が望ましいです。

指定されるエンコーディングには以下のようなものがあります。

エンコーディング名(符号化スキーム) 意味
7bit 符号化無し
8bit 符号化無し8bitデータ
base64 16bitを3bytesで表現
quoted-printable 特定の文字を"%文字コード"形式で表現
binary 符号化無し不可視データ
uuencode/x-uuencode Unix系で利用されてきたスキーム
BinHex Macintosh系で利用されてきたスキーム

現在は7bit/base64/quoted-printableが主流です。JavaMailは読み込みに関して上記のうちBinHexを除く6種類をサポートしています。
JavaMailではボディの設定時に自動的に付加されます(厳密にはsaveChanges()呼び出し時)。どのエンコーディングが選択されるかは、Content-Type:がtext/***であるか否かによって変わります。詳しくは4章で説明します。

Content-Description: ---- パートに関する記述
書式 Content-Description: 文字列
付加される箇所 MUA
設定メソッド Part#setDescription()
参照メソッド Part#getDescription()

各パートの内容を表すに文字列です。
どのようなものであるかという説明文やファイル名を記述したりします。マルチパートの場合の各パートに対するSubject:のようなものですのでMUAが解釈するものではありません(標準で見れるようになっているメイラはほとんどないですが…)。

JavaMailではPart#setDescription()/Part#getDescription()などで設定/参照が可能です。

Content-Disposition: ---- パートに関する情報
書式 Content-Disposition: {attachment|inline}; パラメタ
付加される箇所 MUA
設定メソッド Part#setDispotition()
Part#setFileName()
参照メソッド Part#getDispotition()
Part#getFileName()

直訳では「配置」あるいは「性質」といった意味になります。各パートが添付であるか内包であるか、またファイル名等の情報が表されます。
マルチパートメッセージの各パートについて、添付ファイルである場合はattachment、内包(メッセージ内への埋め込みで、一般にメッセージとともにそのパートの内容が表示されるもの)の場合はinlineを指定します。
また、パラメタとして、"filename""creation-date""modification-date""read-date""size"といったものが指定できます。
重要なのは"filename"パラメタで、添付ファイル名はここに指定されたものが最優先で参照されます(JavaMailを含む多くのMUAはContent-Type:のnameパラメタもチェックしますが、本来はContent-Disposition:の内容が正しく設定されていなければなりません)。

詳細はRFC2183に記述されています。また、日本語を含むファイル名をどのように表現するかに関してはRFC2231で規定されていますが、未だこの形式に従っていないMUAが数多く存在します。JavaMail1.2でもやはりまだ対応がされていませんので、自分で対処する必要があります。
これについては、4章のMimePart#setFileName()/getFileName()の説明を参照して下さい。

JavaMailではPart#setDisposition()/Part#getDisposition()などで設定が可能です。

Content-ID: ---- パートのユニークID
書式 Content-ID: <addr-spec識別子>
付加される箇所 MUA
設定メソッド MimeMessage#setContentID() <- ただし、意味無し
参照メソッド Part#getContentID()

そのパートを特定するためのユニークなIDを表記します。Message-ID:と同様の生成規則が用いられます。
Content-IDはRFC2045の記述時点でContent-Type: message/external-bodyおよび、multipart/alternativeなメッセージで使用されることが想定されていました。
また、RFC2387で、multipart/relatedでのパートをまたぐ参照にも使われることが規定されています。
message/external-bodyは、その本体がMessage-ID:またはContent-IDで示された先にあることを示します(RFC1837)。
multipart/alternativeでは、その内部の(同じ意味を持つ)各パートは同じContent-ID:を保持すべきと記述されています。それなら、multipart/alternativeそのもののパートにContent-ID:が一つあればいいと思うのですが、multipart/alternative内部の各パートが全く同じ情報とは限らない(情報落ちが発生しているかもしれない)ですので、そのような場合は各パートに異なるContent-ID:を付ける意味があります(マルチパートについては章末の「マルチパートメッセージとは?」を参照して下さい)。

Content-Type: multipart/relatedが指定されたメッセージでは他のパートからcid://形式のURL(RFC2392)でContent-IDを指示することで、他のパートを参照することができます。

JavaMail1.2にはMimeMessage#setContentID(String)というメソッドがありますが、これは間違いで、Content-ID:はパート(MimePart)に対して設定するものですのでメッセージ本体に設定してもあまり意味がありません。本来MimePart#setContentID()がなければならないのですが、現状はまだ修正されていませんので、Part#setHeader("Content-ID", id)を使って設定を行うことになります。
参照はMimePart#getContentID()で可能です。

Content-MD5: ---- ボディのメッセージダイジェスト値
書式 Content-MD5: ボディ全体のダイジェストをbase64エンコードした文字列
付加される箇所 MUA
設定メソッド MimePart#setContentMD5()
参照メソッド MimePart#getContentMD5()

そのパートのボディ全体をMD5アルゴリズムでダイジェスト化したものを設定します。この情報を元に、ボディが改変されていないかをチェックすることが可能です。
ただし、このヘッダは任意ですので、受信した側がContent-MD5:ヘッダを発見した場合にそれとボディ部の照合を行うかもしれませんし、無視するかもしれません。
JavaMailとしてもこのヘッダをどう解釈するかはプログラマに委ねられており、照合を行うならば、その処理をプログラマが記述する必要があります。

JavaMailではMimeBodyPart#setContentMD5(String)などで設定が可能です。ただ、実際に設定/照合を行おうとする場合はbase64エンコード/デコードと本文のMD5ダイジェストの生成を自分で行わなければなりません。

Content-Language: ---- ボディの言語
書式 Content-Language: 言語タグ[, 言語タグ]*
付加される箇所 MUA
設定メソッド MimePart#setContentLanguage()
参照メソッド MimePart#getContentLanguage()

そのパートのボディの言語を指定します。
言語タグにはja, en, fr等を与えます。一般にmultipart/alternative(後述)と組み合わせて、一つのメッセージ内に複数の言語で同一内容が記述されたメッセージを送信する場合に使用されます。
この形式を使用すると、メッセージ中で指定された言語のうち、MUAがサポートする言語の中でもっとも優先順位の高い言語で記されたパートを表示することができます。

詳細はRFC1766を参照してください。

Return-Receipt-To: ---- 配送済み報告を受けるアドレス
書式 Return-Receipt-To: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

配送が成功した場合に配送済み報告メイルを送信するアドレスです。
このヘッダが付加されたメイルを受信した時に、ここに記述したアドレスに対し報告メイルを返送するMTAがあります。
しかし、このヘッダはRFCで規定されていないベンダ独自拡張であり、現在はRFC2076で非推奨であることが明記されています。

Disposition-Notification-To: ---- 開封済み報告を受けるアドレス
書式 Disposition-Notification-To: メイルアドレス[, メイルアドレス]*
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

メイルを受信し、開封したときにMUAが開封確認メイルを返送するアドレスです。こちらはRFCに記載されていますが、一部のMUAにしか実装されていないようです。

Organization: ---- 所属組織名
書式 Organization: 文字列
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

メッセージ保有者の所属組織名を記述します。
RFC1036でNetNews用ヘッダとして規定されていますが、現在はメイルにも自動付加できるように「所属」の設定欄を持つMUAが多くなっています。
ちなみに、ご存知ない方もおられるかもしれませんが、メイラの設定で「所属」のような項目に入力した文字列はだいたいこのヘッダに記述され、それは相手先に毎回送信されます。受信したメッセージの送信者の「所属」を標準で表示するメイラは少ないかもしれませんが、ヘッダ情報を見れば解るわけです。自分の個人情報と勘違いして適当な文字列を入力していると恥をかくかもしれません。

Precedence: ---- 配送の優先度
書式 Precedence: {special-delivery|first-class|list|bulk|junk}
付加される箇所 MUA
設定メソッド 無し
参照メソッド 無し

sendmailに対して、メッセージの配送タイミングに影響を与えます。special-deliveryなら即座に配送ですが、junkではメッセージがMTAのキューにいれられ、後で配送されるといった感じです。指定がない場合はfirst-classと同等の扱いとなります。
RFCに規定されていないsendmail独自ヘッダなのですが、例えばbulk/junkを指定した場合には、不要と思われる自動返送メッセージ(不在応答等)を配送しなくなるといった機能がありますので、サーバの負荷を気にする場合やメイリングリストなどでは(ほぼsendmailのためだけに)このヘッダを付加する場合もあり得ます。

ユーザデファインドフィールド

ユーザデファインドフィールドはMUA/MTA実装者が任意に付けてもよいヘッダ群です。これらのヘッダは基本的にプログラムが解釈するものではありません。ユーザデファインドフィールドは"X-"で始まっている必要があります。
任意のものでよいため、一覧することはできませんが、ここでは良く使われるものをいくつか紹介します。

X-Mailer: ---- 使用MUA名

MUAが自分の名前とバージョンを記述します。
相手がどのメイラを用いて送信したかを知ることができます。

X-ML-Name: ---- メイリングリスト名

メイリングリストマネージャが、配送時にメイリングリスト名を付加します。
このヘッダを付けてくれるメイリングリストであればMUAでの自動振り分けがやりやすくなります。

X-ML-Count: ---- メイリングリストの場合の記事番号

メイリングリストマネージャが、配送時にその記事の番号を付加します。

X-UIDL: ---- POPにおけるメッセージのユニークID

POP3でメイルを取得する場合にPOP3サーバが付加します。POP3のUIDLコマンドで取得できるPOP3サーバが生成するユニークIDです。
UIDLについてはPOP3のについての説明を参照して下さい。

X-PRIORITY: ---- メッセージの優先度

このヘッダをサポートするMUAでは、優先度の高いメイルを目立たせるようにしています。

X-Face: ---- 顔

mewなどにあるアイコン添付機能で使用されます。
48*48ピクセルの画像データをエンコードしたデータが続きます。このヘッダをサポートするMUAではメッセージ表示時に「顔」(とは限らないけど)が表示されます。

X-URL: ---- Webページ

差し出し人の運営するWebページのURLを記述します。

ヘッダの順番

ヘッダの順番は以下のようになることが推奨されています。

  1. Return-Path:
  2. Received:
  3. Date:
  4. From:
  5. Subject:
  6. Sender:
  7. To:
  8. cc:
  9. その他

ところで、RFC822の「4.1. SYNTAX」にはこのように書かれているわけですが、その後のBNFを見る限り、

  1. Date:
  2. Resent-Date:
  3. Return-Path:
  4. Received:
  5. Sender:
  6. From:
  7. Reply-To:
  8. Resent-Sender:
  9. Resent-From:
  10. Resent-Reply-To:
  11. ディスティネーション
  12. その他

の順番と書かれています。うーん。実際にはReceived:を先頭に付加するために前者の方が用いられていますが…。

マルチパートメッセージとは?

マルチパートメッセージとは、一つのメッセージに複数種類のコンテンツが含まれるメッセージのことです。Content-Type: がmultipartというメディアタイプを持つことが、内部に複数のパートを含むメッセージであることを示します。また、messageというメディアタイプも複合形式を表すメディアタイプです。
ここではこれらについて説明していきます。

messageメディアタイプ

先にmessageメディアタイプの方について説明します。これはボディが(別の)メッセージを表現していることを示します。サブタイプにはrfc822、partial等から何れかが指定されます。message/rfc822というMIMEタイプでは、そのパートのボディに一つのメッセージ全体が含まれることを示します。
これは以下のようなものです。

Date: Fri, 15 Sep 2000 22:36:49 +0900
From: Shin <shin@localhost>
Subject: Fw: This is forwarded message
To: shin@localhost
X-Mailer: Datula version 1.50.45 for Windows
Mime-Version: 1.0
Content-Type: message/rfc822                     <--- (1)

Date: Fri, 15 Sep 2000 22:29:01 +0900            <--- (2)
From: Shin <shin@localhost>
Subject: This is forwarded message
To: shin@localhost
X-Mailer: Datula version 1.50.45 for Windows
Mime-Version: 1.0
Content-Type: text/plain; charset=ISO-2022-JP

テストです。

(1)までがメッセージ本体のヘッダ部です。Content-Type: message/rfc822が指定されています。空行の後の(2)以降がメッセージのボディになるわけですが、要するに、メッセージのボディ部に全く別のメッセージ全体が(ヘッダ/ボディを含めて)配置されているということです。
このようなメッセージをメイラで表示すると以下のようになります(筆者のDatulaというメイラではこのようになるということです。他のメイラでは表現や内包されたメッセージを見る方法などが異なります)。

添付ファイルのような扱いとなっているATTD2D5.emlを開くと「テストです。」の文字列を含むメッセージが表示されます。

基本的にはmessage/rfc822はメッセージを転送する時に利用するものと考えて下さい。元のメッセージをヘッダも含めて全く改変せずに転送できるので便利です。
また、次のmultipartメディアタイプと組み合わせることで添付ファイルのように複数のメッセージをまとめて転送なども可能になります。
MTAが返送するエラーメイルにも、エラーとなったメイルがmessage/rfc822で添付されてきたりします。

messageメディアタイプには他にもexternal-body、news、http、disposition-notificationなどのサブタイプがありますが、ほとんどはmessage/rfc822でしょう。
実際JavaMailはsetContentに対して"message/rfc822"を指定した場合のみ、MimeMessageオブジェクトを直接設定する事をサポートしており、他の形式については特殊な方法を使わないとボディの設定が行えません。特殊な方法とは独自のDataSourceクラスを用いる方法で、4章で説明します。

multipartメディアタイプ

では、MIMEの核となるmultipartメディアタイプについて説明します。
multipartメディアタイプはボディが複数のパートをまとめたものである事を意味するものです。それぞれのパートはメッセージそのものと同様にヘッダとボディを持ちます。ただし、マルチパートの各パートが持つヘッダはメッセージそのもののヘッダのサブセットとなり、"Content-"が先頭につくもののみが意味を成します(他のヘッダも付けられますが基本的に解釈されません)。

サブタイプには、複数のパートがどのように関連するか、正確には複数のパートを合成した結果が何を表すかによっていくつかの種類に分けられます。最も汎用的で多く用いられるのはmultipart/mixedです。これは単に複数のパートが存在する事のみを意味し、添付ファイルなどはこの形式となります。

multipart/mixed

multipart/mixedの例として、添付ファイルを伴ったメッセージを以下に示します。

Date: Sun, 08 Oct 2000 03:41:53 +0900
From: Shin <shin@localhost>
Subject: =?ISO-2022-JP?B?GyRCJUYlOSVIJWElQyU7ITwlOCRHJDkbKEI=?=
To: shin@localhost
X-Mailer: Datula version 1.51.01 for Windows
Mime-Version: 1.0
Content-Type: multipart/mixed;                           <--- (1)
 boundary="------=_970944112.FF0391EF.FF03DA53"          <--- (2)

This is a multi-part message in MIME format.             <--- (3)

--------=_970944112.FF0391EF.FF03DA53                    <--- (4)
Content-Type: text/plain; charset=ISO-2022-JP

テキストファイルを添付しています。                            <--- 実際にはISO-2022-JPの文字コードです。

--------=_970944112.FF0391EF.FF03DA53                    <--- (5)
Content-Type: text/plain; charset=UNKNOWN-8BIT;
 name="test.txt"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;                         <--- (6)
 filename="test.txt"

g2WDWINng3SDQINDg4uCxYK3DQo=
--------=_970944112.FF0391EF.FF03DA53--                  <--- (7)

各行は以下のような意味を持っています。

  1. このメッセージそのもののContent-Type:はmultipart/mixedです。複数のパートが含まれる事を表しています。
  2. Content-Type:ヘッダのパラメタであるboundaryです。このパラメタはmultipartメディアタイプの場合の必須パラメタで、各パートの境界線はこの文字列で区切られる事を表します。
  3. メッセージのボディの先頭行には、MIME非対応MUAがこのメッセージを表示しようとした時にこのボディがそのまま表示されてしまう事から、このメッセージがマルチパートメッセージである事を示すメッセージを慣習的に記述しておく事になっています。
    マルチパートメッセージの表示に対応したMUAではこの文字列は表示されません。
    なお、JavaMailはこの一文を付けてくれないのですが、特にこの文章はなくても害はないでしょう。
  4. メッセージの一つ目のパートの開始を表すboundary文字列です。先頭のパートはtext/plainパートです。
    よく見るとboundaryパラメタに指定された文字列の前に"--"が付加されています。
  5. メッセージの一つ目のパートの終了(二つ目のパートの開始)を表すboundary文字列です。二つ目のパートはtext/plainの添付ファイルです。
  6. Content-Disposition:ヘッダでこのパートが添付ファイル(attachment)でファイル名が"test.txt"である事を表します。
    なおこのパートのボディはファイル内容がbase64エンコードされたものです。ちなみにファイルにはShift_JISで"テストファイルです"と書かれています。
  7. メッセージの二つ目のパートの終了を表すboundary文字列です。このマルチパートの終端を表すboundaryは末尾に"--"が付加されます。

上記のメッセージをメイラで表示すると以下のようになります。

一般的なメイラはContent-Disposition: が"inline"または指定無しのパートをメッセージのボディとして表示します。この例では一つ目のパートである「テキストファイルを添付しています。」が表示され、"test.txt"ファイルは添付ファイルとして扱われていますね。

ところで、先程のmessageメディアタイプの例であるContent-Type: message/rfc822のメッセージは、メイラ上では添付ファイルのような扱いでしたが本文が存在しませんでした。このような場合もmultipart/mixedを用いてtext/plainとmessage/rfc822パートを合成する事で、テキストの本文+メッセージ形式の添付パートという表現になります。これなら複数のメッセージを添付する事も容易ですので、メッセージの「転送」機能はこの形式で行われるのが主流となってきています。

Date: Fri, 15 Sep 2000 22:36:49 +0900
From: Shin <shin@localhost>
Subject: Fw: This is forwarded message
To: shin@localhost
X-Mailer: Datula version 1.50.45 for Windows
Mime-Version: 1.0
Content-Type: multipart/mixed;
 boundary="------=_969025009.FF03AB0F.FF038F47"

This is a multi-part message in MIME format.

--------=_969025009.FF03AB0F.FF038F47
Content-Type: text/plain; charset=ISO-2022-JP

メッセージを転送します。                                     <--- 実際にはISO-2022-JPの文字コードです。

--------=_969025009.FF03AB0F.FF038F47
Content-Type: message/rfc822

Date: Fri, 15 Sep 2000 22:29:01 +0900
From: Shin <shin@localhost>
Subject: This is forwarded message
To: shin@localhost
X-Mailer: Datula version 1.50.45 for Windows
Mime-Version: 1.0
Content-Type: text/plain; charset=ISO-2022-JP

転送されたメッセージです。                                   <--- 実際にはISO-2022-JPの文字コードです。

--------=_969025009.FF03AB0F.FF038F47--

このようなメッセージをメイラで表示すると以下のようになります。

multipart/alternative

次にHTMLメッセージや他の特殊な形式のメッセージを送る時に用いられる(べき)ものであるmultipart/alternativeについて説明します。
multipart/alternativeのパートはMUAによってその内部の(同じ内容を表す)パートのいずれか一つを表示するという意味を持っています。例えば、HTMLメッセージはtext/htmlのパートとtext/plainのパートをmultipart/alternativeで合成したメッセージとして送信します(下図)。

Date: Fri, 15 Sep 2000 22:36:49 +0900
From: Shin <shin@localhost>
Subject: HTML message
To: shin@localhost
MIME-Version: 1.0
Content-Type: multipart/alternative;
 boundary="----=_NextPart_000_00A9_01C01F7E.6FC51060"

This is a multi-part message in MIME format.

------=_NextPart_000_00A9_01C01F7E.6FC51060
Content-Type: text/plain; charset="ISO-2022-JP"

HTMLメッセージのテストです。

------=_NextPart_000_00A9_01C01F7E.6FC51060
Content-Type: text/html; charset="ISO-2022-JP"

<HTML>
<HEAD>
</HEAD>
<BODY>
<U>HTMLメッセージ</U>の<EM>テスト</EM>です。
</BODY>
</HTML>
------=_NextPart_000_00A9_01C01F7E.6FC51060--

このようにすることによって、受信側のMUAがMIMEに対応してはいるが、text/htmlのメッセージを表示する機能をサポートしていないような場合には、text/plainパートの方を画面に表示することが可能になります。受信MUAがMIME自体に対応していないような場合は上記の「This is a multi-part〜」以降がそのまま表示される事になりますがtext/plainのパートはなんとか読むことができます(上記例はHTMLパートも単純なのでそのまま読めてしまいますが)。

ちなみに、上記メッセージをHTMLレンダリングをサポートするMUAで表示すると以下のように見えます。

text/htmlをmultipart/alternativeを用いずに直接メッセージそのものに指定することもできますが、これは受信者がHTMLメッセージを受信してもよい事がはっきりしている場合のみ行うようにします。確かにそのような場合までテキストパートを付けるのは無駄にメッセージサイズを大きくしているだけですので、HTMLパートのみのメッセージにすることにも意味がありますが、たとえHTMLをレンダリング可能なMUAであっても、セキュリティやレンダリングの遅さが気になる人はHTML表示機能をOffにしていることもよくありますので、やはりtext/plainパートが存在しないメッセージは送信すべきではないでしょう。

multipart/related

multipart/relatedもHTMLのような複合ドキュメントの送信時に比較的使われることの多いMIMEタイプです。これは内部の各パートが参照関係にあるものを表現するもので、RFC2387で最新の仕様が記述されています。もっとも多いであろう使われ方は、HTMLパートにおける<IMG>タグのSRCなどで参照される画像ファイル(リソース)を同じメッセージの別パートに埋めこむ場合などです。つまり、HTMLのパートから、同じメッセージ内の別のパートに存在する画像や別のHTML文書などへのリンクにより、別のパートを参照するような使われ方をします。
このときにどうやって別のパートそのものを指定するかという問題があります。これを解決するのがContent-ID:です。

まず、各パートにはContent-ID:が存在するものとします。このとき、それらのパートの親にあたるパートがmultipart/relatedだった場合、そのContent-Type:は以下のような書式になります。

Content-Type: multipart/related; type="root-mime/types"; [start="root-cid"; start-info="cid-list/value"]

typeパラメタにはその複合ドキュメント本体を表すパートのContent-Type:と同一の値が入ります。start/start-infoは省略可能ですが、複合ドキュメント本体を表すパートを示すContent-ID:のURL形式(RFC2392)と他の使用パートや関連パートとの関係を表現する任意のパラメタを記述します。startが省略された場合は先頭のパートをルートとします。
typeパラメタに示されたMIMEタイプをどのように解釈するかはMUAに依存し、MUAのPlug-in等によって未知のMIMEタイプがmultipart/relatedのtypeパラメタに現れても解釈可能にするべき、と規定されています。

つまり、multipart/relatedはHTMLに限らない、任意の複合ドキュメントを表現するMIMEタイプですが、通常はそのようなドキュメントの場合はわざわざmultipart/relatedに分解してそれを解釈するためのアプリケーションを用意するより、application/*メディアタイプを用いて直接アプリケーション独自のデータ形式を解釈するようになっているものが多いようです。

そのようなわけで、現状はほとんどはHTMLのためということになります(*)が、ことHTMLに関しては、画像等をHTML文書中に埋め込むわけでもなく、外部のURLを参照してダイアルアップ等の環境でネットワークに接続されてしまうわけに行かない場合はこのMIMEタイプが活躍するわけです。

参考までに、multipart/relatedを利用した画像埋めこみHTMLメッセージのサンプルを以下に記しておきます。

*:この形式を指してMHTML(MIME E-mail Encapsulation of Aggregate Documents, such as HTML:RFC2557)という用語が用いられるほどですから。ただ、RFC2387ではHTML以外のMIMEタイプをmultipart/relatedで統合する例が載っています。

Date: Wed, 15 Nov 2000 08:33:25 +0900
From: =?ISO-2022-JP?B?GyRCTFoyPBsoQiAbJEI/LhsoQg==?= <shin@localhost>
Subject: =?ISO-2022-JP?B?GyRCMmhBfElVJC0bKEJIVE1MGyRCJWElJCVrGyhC?=
To: shin@localhost
Mime-Version: 1.0
Content-Type: multipart/related; type="text/html";
 boundary="------=_974244804.FFE694AF.FFE6B47B"

This is a multi-part message in MIME format.

--------=_974244804.FFE694AF.FFE6B47B
Content-Type: text/html; charset=ISO-2022-JP

<HTML>
<BODY>
<H1>てすと</H1>
<IMG src="cid:sample@domain">
<P>
イメージ埋めこみメッセージです。
</P>
</BODY>
</HTML>
--------=_974244804.FFE694AF.FFE6B47B
Content-Type: image/jpeg
Content-Transfer-Encoding: base64
Content-ID: <sample@domain>

/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQE
BQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/
  :
  :
ztkE8jQwSTkKQIZZByf4sH73Xk4/SgpWHNH9pdo4r8NJjJeVfLYD1BI/zwKV
uxLSvof/2Q==
--------=_974244804.FFE694AF.FFE6B47B--

"cid:sample@domain"というURIで次のパートのContent-ID:を示しているのが解ると思います。この形式を覚えておけばJavaMailでも画像を埋めこんだメッセージを送信したり、少し頑張ればHTMLメッセージの表示を行えるようになります。

ただ、この形式全体がHTMLメッセージであることから、multipart/alternativeの項で説明した通り、これもtext/plainパートとmultipart/relatedパートをmultipart/alternativeに包んで送信した方がよいかもしれません。それを行ったとしても嫌われることもありますが…。受信者のMUA次第で対応を変える必要があることだけは意識しておきましょう。

参考までにこのメッセージをMUAで表示した時の図を示しておきます。

その他

multipartメディアタイプのサブタイプとしては、他にも、digest、parallel、appledouble、header-set、form-data、report、voice-message、signed、encrypted、byterangesといったものがあります。たくさんありますねえ。JavaMailはmultipartメディアタイプを個々のパートに分解するまでは行いますが、そのサブタイプに応じた処理はプログラマに任されていますので、JavaMailを使ってアプリケーションを作成される皆さんはこれらのサブタイプのいくつかを解釈する処理も記述しなければならないかもしれません。しかし、その個々の処理までは本書の範囲外ですので、以下のURLから取得できるMIMEタイプ一覧に、それぞれのMIMEタイプがどのような意味であるか/どのように解釈されるべきかを記したRFC番号が記載されていますので、これを元に調べてみてください。

ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/

転送エンコーディング(Transfer Encoding Syntax:TES)

転送エンコーディングとは任意のデータをメッセージ内に埋めこむ際にメッセージ内に現れてはいけないコードが残らないようにデータを符号化することを指し、転送エンコーディングスキームとはその符号化の方式のことを指します。ヘッダのContent-Transfer-Encoding:の項で挙げたいくつかのエンコーディングスキームのうち、ここでMIMEで規定されていて、現在最も一般的なBase64とQuoted-Printableエンコーディングの仕組みについて少し説明します。

Base64

Base64エンコーディングは任意の8Bitデータを64種類の印字可能なAscii文字の列で表現可能にするエンコード方法です。エンコードされた本体部分に使用するコードはアルファベット、数字および'+''/'のみ、すなわち
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
の64文字です。そして、Base64エンコードの終端の半端分を埋める記号として'='が用いられます(*)。
この65文字以外のバイトは全て無視されることになっています。従って改行を入れても問題ありません。逆にインターネットメッセージでは76バイト毎に改行(CRLF)を入れなければなりません。
3バイト(24bit、正確には3オクテットと呼びます)のビット列を6Bit×4に分割して、この6Bitを上記の64種類の文字で表現します。つまり3バイトが4文字で表されます。データの終端が3で割り切れないバイト数だった場合は0のビットが埋まったものと見なして、符号化文字列のその部分に'='を置くと言う約束です。3で割り切れた場合は'='は付きません。

ビット単位でもとのデータを再構成するので、元のデータがどのようなものであったかが判別しにくくなります(読めなくなります)。

*:'='がなくてもデータの終端であることが解れば残りのビットを0で埋めればよいのでデコードは可能です。

Quoted-Printable

制御コードなどの特定の文字コードを"=XX"(XXは文字コードを16進2桁で表現したもの)に変換するエンコード方法です。
エンコードした結果行が長くなりすぎる場合は、行末に'='を付加する事で(エンコード前に一行だったものを)改行させることができます。また空白' 'は(ヘッダの場合は)=20と符号化しなければならないのですが、特例として'_'(アンダースコア)文字として符号化する事が許されています(元々'_'だった箇所は=5Fにエンコードされます)。
これによって、Quoted-Printableをデコードする機能を持たないMUAで表示をした場合でも読み辛さが大幅に軽減できますが、' 'を'_'に変換するか"=20"に変換するかは任意です。
なお、メッセージボディに関しては行末以外のspace/htabはそのままでも構わないとされています。ただし、行末に現れた場合は符号化しなければなりません(原文は"すべき"ですが)。MTAの中には勝手に行末に空白を埋めたり逆に削除したりするものがあるため、符号化されていないと本文が改変されてしまいます。デコード時は行末の空白は無視しなければなりません。
また、ボディの場合改行コードの扱いに気をつけなければなりません。改行は通常そのままですが、元のデータがバイナリデータの場合等はCR/LFのコードを"=0A"/"=0D"のように符号化していなければなりません。しかし、そもそもバイナリなどの元のデータに可読性がないようなものの場合はQuoted-PrintableではなくBase64を使うべきです。

余談
GATEWAY(MTA)の中にはコード変換や行折り返し規則の変換を行うようなものもあり、たとえそのようなGATEWAYを経由したとしても元の体裁を保証したいがために、元々US-ASCIIであってもQuoted-Printableエンコードを行うかもしれません。
また、特殊なGATEWAYの中にはESC(1B)を削除するものもあるようですので、iso-2022文字エンコーディングを使用した文字列はBase64エンコードした方がよい場合もあり得ます。Quoted-Printableでもいいのですが、iso-2022を使った文字列はESCが残っていない限り読むことができないので、RFC1468でQuoted-Printableにする意味がないとされています。しかし、ASCIIの部分は読めるわけですから別にQuoted-Printableでも良い気はしますね。

MIMEのヘッダのエンコーディング("B"/"Q"エンコード)

パートのヘッダにマルチバイト文字(に限りませんが、いわゆる非ASCII文字)を記述するためのエンコード方法として、RFCF2047で"B"/"Q"エンコーディングについて記されています。
MIMEエンコーディングの中のBase64/Quoted-Printableという二種類のエンコード方法をメッセージヘッダ中に適用するものです。
具体的には、=?ISO-2022-JP?B?・・・(JISコードをBase64エンコードしたもの)・・・?=
のように、ISO-2022-JP文字エンコーディングに対してさらに"B"(Base64)エンコードを施したものであるという情報を文字列そのものに含ませることで、相手側でデコードを可能にします。
このヘッダのエンコーディング規則は、日本人にとっては日本語をヘッダに記述できるようになるので大変ありがたいわけですが、日本で実際に流れているメッセージは細かい部分でこのRFC2047に適合していないものがたくさんあるため、RFC2047に従ったJavaMailで正しくデコードが行えないと言う問題がよく発生します。詳しくは4章の「JavaMailにおける日本語の扱いについて」を参照してください。