JavamailでEvelope Fromを操作する

だいぶ間が開いてしまいました。
この間、なかなかに大変な案件に付き合っていて、まぁご想像にお任せする状態でしたが…元気です。
おかげでWebSphereのいじり方がだいぶ分かって、一つ成長できた…のかな。
しかし、GUIベースでメンテナンスするのは性に合わないですね。
「手順書」を書くよりも「これ使って」と設定ファイルをポンの方が、正確でトラブル避けられるよなぁ…と。
まぁ、管理上いろいろと良くないから、ダメなのは分かるのですが。


余計な話しついでにもう一つ。
一つ前のエントリーの件ですが、12/2に0.4.154.29で修正されました。
この間、クラッシュにはだいぶ苦しめられましたが…一安心です。


さて、閑話休題
本題のほうと行きましょう。


ふと、JavamailでENVELOPE FROMを使いたくなりました。
今までならこういうとき、普通にjava.net.Socketを使って直接書いていたわけですが、行儀正しくするとどうなるのかの実験としてです。
しかし、どうすればいいのか。わからなかったので、探ってみました。

簡単にやるならProperty?

とりあえずググってみると、どうやらPropertyで指定可能のようです。
早速試してみます。
まずテスト用に、ローカルにRadish!をインストール。
次にJAF 1.1.1とSunのJavamail 1.4.1リファレンス実装を利用して、下のコードを動かし、動作を確認してみました。

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.Message.RecipientType;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

public class A {
	public static void main(String[] args) {
		try {
			// サーバ設定
			Properties properties = new Properties();
			properties.setProperty("mail.smtp.host", "localhost");
			Session session = Session.getInstance(properties);
			session.setDebug(true);

			// メール本体の提議
			MimeMessage message = new MimeMessage(session);
			message.setFrom(new InternetAddress("a@example.com", "aaa", "iso-2022-jp"));
			message.setRecipients(RecipientType.TO, "to@example.com");
			message.setSubject("test");
			message.setText("this is test mail", "iso-2022-jp");
			message.setSentDate(new Date());

			// ENV FROMの設定。あえてここでやってみる
			session.getProperties().setProperty("mail.smtp.from", "env_from@example.com");

			// 送信
			Transport.send(message);

		} catch (AddressException e) {
			e.printStackTrace();
		} catch (MessagingException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
}

結果

(略)
EHLO Litchi
250 localhost.localdomain Helo localhost[127.0.0.1], Pleased to meet you.
DEBUG SMTP: use8bit false
MAIL FROM:
250 env_from@example.com... Sender ok
RCPT TO:
250 to@example.com... Recipient ok
(略)


なるほど、確かに。
しかし、これって定まった仕様なのでしょうか。


JSR-919 JavaMail 1.4 Specificationを見ると、このあたりについては
プロパティ値mail.protocol.userあたりの説明で

Specifies the protocol-specific default username for connecting to the Mail server.

とか、setFromの説明として

specifies the sender explicitly from an Address object parameter.

とか書いてあるくらいで、あまり明確には語っていません。
ということは、変更がありそうで不安ですが、行儀正しく行くにはここが限界なのでしょう。
しかし、こんなことでは困るのですが…。

しかもプロパティーじゃ…

しかもこれ、使用上の大きな問題が潜んでいます。
Webサーバ上で実行するとなると、javax.mail.Sessionのインスタンスはスレッド間で共有するかもしれません。
そしてENVELOPE FROMは、FROMに近い性質を持つ都合上、状況に応じて変更したくなるかもしれないと予測できます。
こうなると、Sessionに与えるプロパティでなんとかするのは、他のメール送信スレッドに影響を与える恐れがあるため、少々具合が悪いでしょう。


そもそもENVELOPE FROMは、通常はMimeMessage#setFrom()で与えられた値を見て設定します。
ということは、送信時にそのあたりの選択を行っていると考えるのが妥当でしょう。
ならば、そのあたりを読めば、何か逃げ道があるかもしれません。

ソース、固まってないの?

Javamailのソースコードは、サイトを見ると

pril 19, 2006
JavaMail is now open source! The source code for the JavaMail API Reference Implementation is now available under the CDDL open source license, as a part of Project GlassFish See the mail module page at GlassFish for more details.

とあるとおり、今はGlass Fishプロジェクトにあるようです。
そこで、Glass FishのJavamailのページを見てみると…
ん〜。アーカイブ化されたものはみあたりません。
探し方が悪いのかしら。


仕方なし、cvsからcheck outして取ってきます。
…どっかに固まってないですかねぇ…。

読んでみる

早速読んでみます。
まず、送信時に叩くjavax.mail.Transportですが、どうやらこれ、Sessionオブジェクトから作成されたインスタンスに処理を委譲していて、結局のところSunのJava実装だとcom.sun.mail.smtp.SMTPTransportにその定義がありました。
その中でEnvelope Fromをsetしているのは、983行目から始まるmailFrom()の中にありました。

    protected void mailFrom() throws MessagingException {
	String from = null;
	if (message instanceof SMTPMessage)
	    from = ((SMTPMessage)message).getEnvelopeFrom();
	if (from == null || from.length() <= 0)
	    from = session.getProperty("mail." + name + ".from");
	if (from == null || from.length() <= 0) {
	    Address[] fa;
	    Address me;
	    if (message != null && (fa = message.getFrom()) != null &&
		    fa.length > 0)
		me = fa[0];
	    else
		me = InternetAddress.getLocalAddress(session);

	    if (me != null)
		from = ((InternetAddress)me).getAddress();
	    else
		throw new MessagingException(
					"can't determine local email address");
	}

	String cmd = "MAIL FROM:" + normalizeAddress(from);

SMTPMessageならばgetEnvelopFromを取る

でなければPropertiesから"mail." + name + ".from"をキーにして取る

なければfromからアドレスを選択する。

なければlocal addressを作成する。
という流れのようです。
このnameというのがどこからきているのかというと、プロトコル名称のようです。
つまり、smtpなら mail.smtp.from となるわけなので、結果「(通常は)mail.smtp.from に設定すればEnvelope Fromを制御できる」という言説になるのでしょう。
当然、smtpsならmail.smtps.fromになるわけですね。


さて、本題に戻ると、SMTPMessageを使えばSessionに影響を与えずにセットできそうということが分かりました。
早速試してみます。

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;

import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.Message.RecipientType;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

import com.sun.mail.smtp.SMTPMessage;

public class D {
	public static void main(String[] args) {
		try {
			// サーバ設定
			Properties properties = new Properties();
			properties.setProperty("mail.smtp.host", "localhost");
			Session session = Session.getInstance(properties);
			session.setDebug(true);

			// メール本体の提議
			SMTPMessage message = new SMTPMessage(session);
			message.setFrom(new InternetAddress("a@example.com", "aaa", "iso-2022-jp"));
			message.setRecipients(RecipientType.TO, "to@example.com");
			message.setSubject("test");
			message.setText("this is test mail", "iso-2022-jp");
			message.setSentDate(new Date());

			// ENV FROMの設定
			message.setEnvelopeFrom("env-from@example.com");

			// 送信
			Transport.send(message);

		} catch (AddressException e) {
			e.printStackTrace();
		} catch (MessagingException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
}

結果

(略)
EHLO Litchi
250 localhost.localdomain Helo localhost[127.0.0.1], Pleased to meet you.
DEBUG SMTP: use8bit false
MAIL FROM:
250 env-from@example.com... Sender ok
RCPT TO:
250 to@example.com... Recipient ok
(略)

うまくいきました。

しかしこれって…

しかし、このSMTPMessageですが、あまり使うのはよくないかもしれません。
一つにはcom.sun.mail.smtpというパッケージ内に配置されている事が示す問題で、将来変更されるかもしれないし、排除されるかもしれません。
二つには、そのようなクラスを利用していることから分かるとおり、非常に実装依存な話であり、他の実装を利用せざるを得なくなった場合には使えない話と化します。
まぁ、普通javaでmailといえばSunのJavamailだよね〜…って言い切れると思うので、いいのかもしれませんが…。
更に言うと、JavamailのSessionはsmtp専用ではないため、EnvelopeFromの追加を提案するのもどうかと思いますし、たぶんSunのJavamailの実装者はソコを気にしてこういう実装にしただろう事は想像に難くありません。
はて、となると、どうすればいいのでしょう。
悩ましいものです。