1 package com.ozacc.mail.fetch.impl;
3 import java.io.BufferedOutputStream;
5 import java.io.FileNotFoundException;
6 import java.io.FileOutputStream;
7 import java.io.FilenameFilter;
8 import java.io.IOException;
9 import java.io.InputStream;
10 import java.io.OutputStream;
11 import java.util.Date;
12 import java.util.Enumeration;
13 import java.util.regex.Matcher;
14 import java.util.regex.Pattern;
16 import jakarta.mail.Address;
17 import jakarta.mail.Header;
18 import jakarta.mail.Message;
19 import jakarta.mail.MessagingException;
20 import jakarta.mail.internet.AddressException;
21 import jakarta.mail.internet.InternetAddress;
22 import jakarta.mail.internet.MimeMessage;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
27 import com.ozacc.mail.fetch.MailConverter;
28 import com.ozacc.mail.fetch.ReceivedMail;
29 import com.ozacc.mail.fetch.ReceivedMail.ReceivedHeader;
30 import com.ozacc.mail.fetch.impl.sk_jp.AttachmentsExtractor;
31 import com.ozacc.mail.fetch.impl.sk_jp.HtmlPartExtractor;
32 import com.ozacc.mail.fetch.impl.sk_jp.MailUtility;
33 import com.ozacc.mail.fetch.impl.sk_jp.MultipartUtility;
36 * MimeMessageからMailを生成するクラス。
38 * 変換時に生じたチェック例外は、このクラス内でキャッチされ無視されます。
39 * 例外が生じた項目(差出人や宛先など)に該当するMailインスタンスのプロパティには何もセットされません。
42 * @author Tomohiro Otsuka
44 * @version $Id: MailConverterImpl.java,v 1.1.2.3 2006/03/03 06:01:18 otsuka Exp $
46 public class MailConverterImpl implements MailConverter {
48 private static final String ATTACHMENT_DIR_PREFIX = "OML_";
50 private static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
52 private static Log log = LogFactory.getLog(MailConverterImpl.class);
54 private static Pattern receivedHeaderPattern = Pattern.compile("^from (.+?) .*by (.+?) .*$");
57 * 保存された添付ファイルの生存時間。デフォルトは12時間。
59 private long attachmentLifetime = 3600 * 1000 * 12L;
62 * @see com.ozacc.mail.fetch.MailConverter#convertIntoMails(jakarta.mail.internet.MimeMessage[])
64 public ReceivedMail[] convertIntoMails(MimeMessage[] messages) {
65 log.debug("計" + messages.length + "通のMimeMessageをMailに変換します。");
66 ReceivedMail[] results = new ReceivedMail[messages.length];
67 for (int i = 0; i < messages.length; i++) {
68 log.debug((i + 1) + "通目のMimeMessageをMailに変換します。");
69 results[i] = convertIntoMail(messages[i]);
70 log.debug((i + 1) + "通目のMimeMessageをMailに変換しました。");
71 log.debug(results[i].toString());
73 log.debug("計" + messages.length + "通のMimeMessageをMailに変換しました。");
81 private void setReceivedHeaders(MimeMessage mm, ReceivedMail mail) {
82 String[] headerValues = null;
84 headerValues = mm.getHeader("Received");
85 } catch (MessagingException e) {
87 log.warn(e.getMessage());
89 if (headerValues != null) {
90 for (int i = 0; i < headerValues.length; i++) {
91 String received = headerValues[i];
92 // from で始まるものだけを抽出し、改行を削除
93 if (received.startsWith("from")) {
94 received = received.replace("\n", "").replaceAll("\\s+", " ");
95 log.debug("Received='" + received + "'");
97 Matcher m = receivedHeaderPattern.matcher(received);
99 String from = m.group(1);
100 String by = m.group(2);
101 log.debug("Sent from '" + from + "', Received by '" + by + "'");
102 ReceivedHeader rh = new ReceivedHeader(from, by);
103 mail.addReceviedHeader(rh);
112 * 指定されたMimeMessageに添付されているファイルを全て抽出し、
113 * システムプロパティにセットされた一時ファイルディレクトリ内に保存した後、
114 * そのファイルを指定されたReceivedMailにセットします。
116 * 保存された添付ファイルはJVM終了時に削除されます。
121 private void setAttachmentFiles(MimeMessage mm, ReceivedMail mail) {
125 AttachmentsExtractor ae = new AttachmentsExtractor(
126 AttachmentsExtractor.MODE_IGNORE_MESSAGE);
127 MultipartUtility.process(mm, ae);
128 for (int i = 0, num = ae.getCount(); i < num; i++) {
129 String fileName = ae.getFileName(i);
130 if (fileName == null || "".equals(fileName)) {
131 fileName = "attachment" + (i + 1) + ".tmp";
133 String path = getTempDirPath() + File.separator + ATTACHMENT_DIR_PREFIX
134 + System.currentTimeMillis() + File.separator + fileName;
135 log.debug((i + 1) + "個目の添付ファイルを保存します。[" + path + "]");
136 File f = new File(path);
137 f.getParentFile().mkdirs();
138 InputStream is = ae.getInputStream(i);
147 f.getParentFile().deleteOnExit();
150 mail.addFile(f, fileName);
151 log.debug((i + 1) + "個目の添付ファイルを保存しました。[" + path + "]");
153 } catch (IOException e) {
154 log.error("添付ファイルの取得に失敗しました。", e);
155 } catch (MessagingException e) {
157 log.warn(e.getMessage());
162 * 一時ディレクトリ内に保存された添付ファイルの内、生存時間を越えているものを削除します。
164 private void cleanTempDir() {
165 File tempDir = new File(getTempDirPath());
166 File[] omlDirs = tempDir.listFiles((FilenameFilter) (dir, name) -> name
167 .startsWith(ATTACHMENT_DIR_PREFIX));
168 log.debug("現在" + omlDirs.length + "個の添付ファイル用ディレクトリ[" + tempDir.getAbsolutePath()
169 + "]が一時ディレクトリに存在します。");
170 long now = System.currentTimeMillis();
171 for (int i = 0; i < omlDirs.length; i++) {
172 File dir = omlDirs[i];
173 log.debug(dir.lastModified() + "");
174 if (now - dir.lastModified() >= attachmentLifetime) {
183 * @return 一時ディレクトリのパス
185 private String getTempDirPath() {
186 return System.getProperty(JAVA_IO_TMPDIR);
190 * 指定されたディレクトリを中身のファイルを含めて削除します。
192 * @param dir 削除するディレクトリ
194 private void deleteDir(File dir) {
195 File[] files = dir.listFiles();
196 for (int i = 0; i < files.length; i++) {
204 * 指定されたInputStreamデータを指定されたファイルに保存します。
206 * @param destFile 保存するファイル
207 * @param is ソースとなるInputStream
208 * @throws FileNotFoundException
209 * @throws IOException
211 private void writeTo(File destFile, InputStream is) throws FileNotFoundException, IOException {
212 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile),
223 private static void copy(InputStream in, OutputStream out) throws IOException {
224 byte[] buffer = new byte[1024 * 4];
226 while (-1 != (n = in.read(buffer))) {
227 out.write(buffer, 0, n);
232 * 指定されたMimeMessageからX-Header、References、In-Reply-Toヘッダを解析し、
233 * 指定されたReceivedMailにセットします。
238 private void setXHeaders(MimeMessage mm, ReceivedMail mail) {
239 log.debug("X-HeaderをMailにセットします。");
240 Enumeration<Header> headerEnum = null;
242 headerEnum = mm.getAllHeaders();
243 } catch (MessagingException e) {
245 log.warn(e.getMessage());
247 while (headerEnum != null && headerEnum.hasMoreElements()) {
248 Header header = headerEnum.nextElement();
249 if (header.getName().startsWith("X-")
250 || "References".equalsIgnoreCase(header.getName())
251 || "In-Reply-To".equalsIgnoreCase(header.getName())) {
252 mail.addHeader(header.getName(), header.getValue());
253 log.debug(header.getName() + "をMailにセットしました。[" + header.getName() + "='"
254 + header.getValue() + "']");
259 private void setReplyToAddress(MimeMessage mm, ReceivedMail mail) {
260 log.debug("Reply-ToアドレスをMailにセットします。");
261 Address[] addresses = null;
263 addresses = mm.getReplyTo();
264 } catch (MessagingException e) {
266 log.warn(e.getMessage());
268 if (addresses != null && addresses.length > 1) {
269 log.debug(addresses.length + "つのReply-Toアドレスが見つかりました。最初のアドレスのみ取得されます。");
270 mail.setReplyTo((InternetAddress)addresses[0]);
272 log.debug("Reply-Toアドレスは見つかりませんでした。");
277 * メールの容量(byte)をMimeMessageから取得してReceivedMailにセットします。
278 * 取得に失敗した場合は -1 をセットします。
283 private void setSize(MimeMessage mm, ReceivedMail mail) {
285 mail.setSize(mm.getSize());
286 } catch (MessagingException e) {
294 * @throws MessagingException
296 private void setHtmlText(MimeMessage mm, ReceivedMail mail) {
298 HtmlPartExtractor hpe = new HtmlPartExtractor();
299 MultipartUtility.process(mm, hpe);
300 String htmlText = hpe.getHtml();
301 mail.setHtmlText(htmlText);
302 } catch (MessagingException e) {
304 log.warn(e.getMessage());
308 private void setText(MimeMessage mm, ReceivedMail mail) {
310 String text = MultipartUtility.getPlainText(mm);
312 } catch (MessagingException e) {
314 log.warn(e.getMessage());
318 private void setMessageId(MimeMessage mm, ReceivedMail mail) {
320 String messageId = mm.getMessageID();
321 mail.setMessageId(messageId);
322 log.debug("Message-IDをMailにセットしました。[Message-ID='" + messageId + "']");
323 } catch (MessagingException e) {
325 log.warn(e.getMessage());
330 * 指定されたMimeMessageから件名を取得し、ReceivedMailにセットします。
331 * sk_jpのMailUtility.decodeText()メソッドを用いて、件名の文字化けを回避します。
336 private void setSubject(MimeMessage mm, ReceivedMail mail) {
338 String subject = MailUtility.decodeText(mm.getSubject());
339 mail.setSubject(subject);
340 } catch (MessagingException e) {
342 log.warn(e.getMessage());
346 private void setDate(MimeMessage mm, ReceivedMail mail) {
348 Date d = mm.getSentDate();
350 } catch (MessagingException e) {
352 log.warn(e.getMessage());
357 * Return-Pathアドレスは必ずしもセットされてはいません。
358 * 特にspam系のメールでは不正なフォーマットのメールアドレスが
359 * セットされている場合もあるので要注意。
364 private void setReturnPath(MimeMessage mm, ReceivedMail mail) {
365 log.debug("Return-Pathアドレスを検出します。");
366 String[] returnPath = null;
368 returnPath = mm.getHeader("Return-Path");
369 } catch (MessagingException e) {
371 log.warn(e.getMessage());
373 if (returnPath != null && returnPath.length > 0) {
374 String email = returnPath[0].substring(1, returnPath[0].length() - 1);
375 if (email.length() > 0) {
377 mail.setReturnPath(email);
378 log.debug("Return-PathアドレスをMailにセットしました。[Return-Path='" + email + "']");
379 } catch (IllegalArgumentException e) {
380 log.warn("Return-Pathアドレスが不正なメールアドレスフォーマットです。[Return-Path='" + email + "']");
383 log.debug("Return-Pathアドレスは見つかりませんでした。");
386 log.debug("Return-Pathアドレスは見つかりませんでした。");
390 private void setFromAddress(MimeMessage mm, ReceivedMail mail) {
391 log.debug("Fromアドレスを検出します。");
392 Address[] addresses = null;
394 addresses = mm.getFrom();
395 } catch (MessagingException e) {
397 log.warn(e.getMessage());
399 if (addresses != null) {
400 log.debug(addresses.length + "つのFromアドレスが見つかりました。");
401 for (int j = 0; j < addresses.length; j++) {
402 InternetAddress address = (InternetAddress)addresses[j];
403 mail.setFrom(address);
404 log.debug("FromアドレスをMailにセットしました。[From='" + address.toUnicodeString() + "']");
407 log.debug("Fromアドレスは見つかりませんでした。");
411 private void setRecipientAddresses(MimeMessage mm, ReceivedMail mail) {
415 log.debug("Toアドレスを検出します。");
416 Address[] toAddresses = null;
418 toAddresses = mm.getRecipients(Message.RecipientType.TO);
419 } catch (AddressException e) {
420 log.warn("不正なメールアドレスが検出されました。[" + e.getRef() + "]");
421 } catch (MessagingException e) {
423 log.warn(e.getMessage());
425 if (toAddresses != null) {
426 log.debug(toAddresses.length + "つのToアドレスが見つかりました。");
427 for (int j = 0; j < toAddresses.length; j++) {
428 InternetAddress address = (InternetAddress)toAddresses[j];
430 log.debug("ToアドレスをMailにセットしました。[To='" + address.toUnicodeString() + "']");
433 log.debug("Toアドレスは見つかりませんでした。");
439 log.debug("Ccアドレスを検出します。");
440 Address[] ccAddresses = null;
442 ccAddresses = mm.getRecipients(Message.RecipientType.CC);
443 } catch (AddressException e) {
444 log.warn("不正なメールアドレスが検出されました。[" + e.getRef() + "]");
445 } catch (MessagingException e) {
447 log.warn(e.getMessage());
449 if (ccAddresses != null) {
450 log.debug(ccAddresses.length + "つのCcアドレスが見つかりました。");
451 for (int j = 0; j < ccAddresses.length; j++) {
452 InternetAddress address = (InternetAddress)ccAddresses[j];
454 log.debug("CcアドレスをMailにセットしました。[Cc='" + address.toUnicodeString() + "']");
457 log.debug("Ccアドレスは見つかりませんでした。");
462 * @see com.ozacc.mail.fetch.MailConverter#convertIntoMail(jakarta.mail.internet.MimeMessage)
464 public ReceivedMail convertIntoMail(MimeMessage mm) {
465 ReceivedMail mail = createReceivedMail();
466 setReturnPath(mm, mail);
467 setReceivedHeaders(mm, mail);
469 setFromAddress(mm, mail);
470 setRecipientAddresses(mm, mail);
471 setMessageId(mm, mail);
472 setReplyToAddress(mm, mail);
473 setSubject(mm, mail);
474 setXHeaders(mm, mail);
476 setHtmlText(mm, mail);
477 setAttachmentFiles(mm, mail);
483 protected ReceivedMail createReceivedMail() {
484 return new ReceivedMail();