1 package com.ozacc.mail.fetch.impl;
2
3 import java.io.BufferedOutputStream;
4 import java.io.File;
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.util.ArrayList;
11 import java.util.Date;
12 import java.util.Enumeration;
13 import java.util.List;
14 import java.util.regex.Matcher;
15 import java.util.regex.Pattern;
16
17 import javax.mail.Address;
18 import javax.mail.Header;
19 import javax.mail.Message;
20 import javax.mail.MessagingException;
21 import javax.mail.internet.AddressException;
22 import javax.mail.internet.InternetAddress;
23 import javax.mail.internet.MimeMessage;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27
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;
34
35 /***
36 * MimeMessageからMailを生成するクラス。
37 * <p>
38 * 変換時に生じたチェック例外は、このクラス内でキャッチされ無視されます。
39 * 例外が生じた項目(差出人や宛先など)に該当するMailインスタンスのプロパティには何もセットされません。
40 *
41 * @since 1.2
42 * @author Tomohiro Otsuka
43 * @version $Id: MailConverter.java,v 1.1.2.12 2005/01/31 14:03:16 otsuka Exp $
44 */
45 public class MailConverter {
46
47 private static final String ATTACHMENT_DIR_PREFIX = "OML_";
48
49 private static final String JAVA_IO_TMPDIR = "java.io.tmpdir";
50
51 private static Log log = LogFactory.getLog(MailConverter.class);
52
53 private static Pattern receivedHeaderPattern = Pattern.compile("^from (.+?) .*by (.+?) .*$");
54
55 /***
56 * 保存された添付ファイルの生存時間。デフォルトは12時間。
57 */
58 private long attachmentLifetime = 3600 * 1000 * 12;
59
60 private MimeMessage[] messages;
61
62 /***
63 * @param mimeMessage
64 */
65 public MailConverter(MimeMessage mimeMessage) {
66 this(new MimeMessage[] { mimeMessage });
67 }
68
69 /***
70 * @param mimeMessages
71 */
72 public MailConverter(MimeMessage[] mimeMessages) {
73 this.messages = mimeMessages;
74 }
75
76 /***
77 * @return
78 */
79 public ReceivedMail[] convertIntoMails() {
80 log.debug("計" + messages.length + "通のMimeMessageをMailに変換します。");
81 List list = new ArrayList();
82 for (int i = 0; i < messages.length; i++) {
83 log.debug((i + 1) + "通目のMimeMessageをMailに変換します。");
84
85 MimeMessage mm = messages[i];
86 ReceivedMail mail = new ReceivedMail();
87
88 setReturnPath(mm, mail);
89 setReceivedHeaders(mm, mail);
90 setDate(mm, mail);
91 setFromAddress(mm, mail);
92 setRecipientAddresses(mm, mail);
93 setMessageId(mm, mail);
94 setReplyToAddress(mm, mail);
95 setSubject(mm, mail);
96 setXHeaders(mm, mail);
97 setText(mm, mail);
98 setHtmlText(mm, mail);
99 setAttachmentFiles(mm, mail);
100
101 setSize(mm, mail);
102
103 mail.setMessage(mm);
104
105 log.debug((i + 1) + "通目のMimeMessageをMailに変換しました。");
106 log.debug(mail.toString());
107
108 list.add(mail);
109 }
110 log.debug("計" + messages.length + "通のMimeMessageをMailに変換しました。");
111 return (ReceivedMail[])list.toArray(new ReceivedMail[list.size()]);
112 }
113
114 /***
115 * @param mm
116 * @param mail
117 */
118 private void setReceivedHeaders(MimeMessage mm, ReceivedMail mail) {
119 String[] headerValues = null;
120 try {
121 headerValues = mm.getHeader("Received");
122 } catch (MessagingException e) {
123
124 log.warn(e.getMessage());
125 }
126 if (headerValues != null) {
127 for (int i = 0; i < headerValues.length; i++) {
128 String received = headerValues[i];
129
130 if (received.startsWith("from")) {
131 received = received.replaceAll("\n", "").replaceAll("//s+", " ");
132 log.debug("Received='" + received + "'");
133
134 Matcher m = receivedHeaderPattern.matcher(received);
135 if (m.matches()) {
136 String from = m.group(1);
137 String by = m.group(2);
138 log.debug("Sent from '" + from + "', Received by '" + by + "'");
139 ReceivedHeader rh = new ReceivedHeader(from, by);
140 mail.addReceviedHeader(rh);
141 }
142 }
143 }
144
145 }
146 }
147
148 /***
149 * 指定されたMimeMessageに添付されているファイルを全て抽出し、
150 * システムプロパティにセットされた一時ファイルディレクトリ内に保存した後、
151 * そのファイルを指定されたReceivedMailにセットします。
152 * <p>
153 * 保存された添付ファイルはJVM終了時に削除されます。
154 *
155 * @param mm
156 * @param mail
157 */
158 private void setAttachmentFiles(MimeMessage mm, ReceivedMail mail) {
159 try {
160 cleanTempDir();
161
162 AttachmentsExtractor ae = new AttachmentsExtractor(
163 AttachmentsExtractor.MODE_IGNORE_MESSAGE
164 | AttachmentsExtractor.MODE_IGNORE_INLINE);
165 MultipartUtility.process(mm, ae);
166 for (int i = 0, num = ae.getCount(); i < num; i++) {
167 String fileName = ae.getFileName(i);
168 if (fileName == null || "".equals(fileName)) {
169 fileName = "attachment" + (i + 1) + ".tmp";
170 }
171 String path = getTempDirPath() + File.separator + ATTACHMENT_DIR_PREFIX
172 + System.currentTimeMillis() + File.separator + fileName;
173 log.debug((i + 1) + "個目の添付ファイルを保存します。[" + path + "]");
174 File f = new File(path);
175 f.getParentFile().mkdirs();
176 InputStream is = ae.getInputStream(i);
177 writeTo(f, is);
178
179 f.getParentFile().deleteOnExit();
180 f.deleteOnExit();
181
182 mail.addFile(f, fileName);
183 log.debug((i + 1) + "個目の添付ファイルを保存しました。[" + path + "]");
184 }
185 } catch (IOException e) {
186 log.error("添付ファイルの取得に失敗しました。", e);
187 } catch (MessagingException e) {
188
189 log.warn(e.getMessage());
190 }
191 }
192
193 /***
194 * 一時ディレクトリ内に保存された添付ファイルの内、生存時間を越えているものを削除します。
195 */
196 private void cleanTempDir() {
197 File tempDir = new File(getTempDirPath());
198 File[] omlDirs = tempDir.listFiles(new FilenameFilter() {
199
200 public boolean accept(File dir, String name) {
201 return name.startsWith(ATTACHMENT_DIR_PREFIX);
202 }
203 });
204 log.debug("現在" + omlDirs.length + "個の添付ファイル用ディレクトリが一時ディレクトリに存在します。");
205 long now = System.currentTimeMillis();
206 for (int i = 0; i < omlDirs.length; i++) {
207 File dir = omlDirs[i];
208 log.debug(dir.lastModified() + "");
209 if (now - dir.lastModified() >= attachmentLifetime) {
210 deleteDir(dir);
211 }
212 }
213 }
214
215 /***
216 * 一時ディレクトリのパスを返します。
217 *
218 * @return 一時ディレクトリのパス
219 */
220 private String getTempDirPath() {
221 return System.getProperty(JAVA_IO_TMPDIR);
222 }
223
224 /***
225 * 指定されたディレクトリを中身のファイルを含めて削除します。
226 *
227 * @param dir 削除するディレクトリ
228 */
229 private void deleteDir(File dir) {
230 File[] files = dir.listFiles();
231 for (int i = 0; i < files.length; i++) {
232 File f = files[i];
233 f.delete();
234 }
235 dir.delete();
236 }
237
238 /***
239 * 指定されたInputStreamデータを指定されたファイルに保存します。
240 *
241 * @param destFile 保存するファイル
242 * @param is ソースとなるInputStream
243 * @throws FileNotFoundException
244 * @throws IOException
245 */
246 private void writeTo(File destFile, InputStream is) throws FileNotFoundException, IOException {
247 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile));
248 byte[] b = new byte[8192];
249 while (is.read(b) != -1) {
250 bos.write(b);
251 }
252 bos.flush();
253 bos.close();
254 is.close();
255 }
256
257 /***
258 * 指定されたMimeMessageからX-Header、References、In-Reply-Toヘッダを解析し、
259 * 指定されたReceivedMailにセットします。
260 *
261 * @param mm
262 * @param mail
263 */
264 private void setXHeaders(MimeMessage mm, ReceivedMail mail) {
265 log.debug("X-HeaderをMailにセットします。");
266 String[] xHeaders = null;
267 Enumeration headerEnum = null;
268 try {
269 headerEnum = mm.getAllHeaders();
270 } catch (MessagingException e) {
271
272 log.warn(e.getMessage());
273 }
274 while (headerEnum != null && headerEnum.hasMoreElements()) {
275 Header header = (Header)headerEnum.nextElement();
276 if (header.getName().startsWith("X-")
277 || "References".equalsIgnoreCase(header.getName())
278 || "In-Reply-To".equalsIgnoreCase(header.getName())) {
279 mail.addHeader(header.getName(), header.getValue());
280 log.debug(header.getName() + "をMailにセットしました。[" + header.getName() + "='"
281 + header.getValue() + "']");
282 }
283 }
284 }
285
286 private void setReplyToAddress(MimeMessage mm, ReceivedMail mail) {
287 log.debug("Reply-ToアドレスをMailにセットします。");
288 Address[] addresses = null;
289 try {
290 addresses = mm.getReplyTo();
291 } catch (MessagingException e) {
292
293 log.warn(e.getMessage());
294 }
295 if (addresses != null) {
296 log.debug(addresses.length + "つのReply-Toアドレスが見つかりました。最初のアドレスのみ取得されます。");
297 for (int j = 0; j < addresses.length; j++) {
298 Address address = addresses[j];
299 mail.setReplyTo((InternetAddress)address);
300 break;
301 }
302 } else {
303 log.debug("Reply-Toアドレスは見つかりませんでした。");
304 }
305 }
306
307 /***
308 * メールの容量(byte)をMimeMessageから取得してReceivedMailにセットします。
309 * 取得に失敗した場合は -1 をセットします。
310 *
311 * @param mm
312 * @param mail
313 */
314 private void setSize(MimeMessage mm, ReceivedMail mail) {
315 try {
316 mail.setSize(mm.getSize());
317 } catch (MessagingException e) {
318 mail.setSize(-1);
319 }
320 }
321
322 /***
323 * @param mm
324 * @param mail
325 * @throws MessagingException
326 */
327 private void setHtmlText(MimeMessage mm, ReceivedMail mail) {
328 try {
329 HtmlPartExtractor hpe = new HtmlPartExtractor();
330 MultipartUtility.process(mm, hpe);
331 String htmlText = hpe.getHtml();
332 mail.setHtmlText(htmlText);
333 } catch (MessagingException e) {
334
335 log.warn(e.getMessage());
336 }
337 }
338
339 private void setText(MimeMessage mm, ReceivedMail mail) {
340 try {
341 String text = MultipartUtility.getPlainText(mm);
342 mail.setText(text);
343 } catch (MessagingException e) {
344
345 log.warn(e.getMessage());
346 }
347 }
348
349 private void setMessageId(MimeMessage mm, ReceivedMail mail) {
350 try {
351 String messageId = mm.getMessageID();
352 mail.setMessageId(messageId);
353 log.debug("Message-IDをMailにセットしました。[Message-ID='" + messageId + "']");
354 } catch (MessagingException e) {
355
356 log.warn(e.getMessage());
357 }
358 }
359
360 /***
361 * 指定されたMimeMessageから件名を取得し、ReceivedMailにセットします。
362 * sk_jpのMailUtility.decodeText()メソッドを用いて、件名の文字化けを回避します。
363 *
364 * @param mm
365 * @param mail
366 */
367 private void setSubject(MimeMessage mm, ReceivedMail mail) {
368 try {
369 String subject = MailUtility.decodeText(mm.getSubject());
370 mail.setSubject(subject);
371 } catch (MessagingException e) {
372
373 log.warn(e.getMessage());
374 }
375 }
376
377 private void setDate(MimeMessage mm, ReceivedMail mail) {
378 try {
379 Date d = mm.getSentDate();
380 mail.setDate(d);
381 } catch (MessagingException e) {
382
383 log.warn(e.getMessage());
384 }
385 }
386
387 /***
388 * Return-Pathアドレスは必ずしもセットされてはいません。
389 * 特にspam系のメールでは不正なフォーマットのメールアドレスが
390 * セットされている場合もあるので要注意。
391 *
392 * @param mm
393 * @param mail
394 */
395 private void setReturnPath(MimeMessage mm, ReceivedMail mail) {
396 log.debug("Return-Pathアドレスを検出します。");
397 String[] returnPath = null;
398 try {
399 returnPath = mm.getHeader("Return-Path");
400 } catch (MessagingException e) {
401
402 log.warn(e.getMessage());
403 }
404 if (returnPath != null && returnPath.length > 0) {
405 String email = returnPath[0].substring(1, returnPath[0].length() - 1);
406 if (email.length() > 0) {
407 try {
408 mail.setReturnPath(email);
409 log.debug("Return-PathアドレスをMailにセットしました。[Return-Path='" + email + "']");
410 } catch (IllegalArgumentException e) {
411 log.warn("Return-Pathアドレスが不正なメールアドレスフォーマットです。[Return-Path='" + email + "']");
412 }
413 } else {
414 log.debug("Return-Pathアドレスは見つかりませんでした。");
415 }
416 } else {
417 log.debug("Return-Pathアドレスは見つかりませんでした。");
418 }
419 }
420
421 private void setFromAddress(MimeMessage mm, ReceivedMail mail) {
422 log.debug("Fromアドレスを検出します。");
423 Address[] addresses = null;
424 try {
425 addresses = mm.getFrom();
426 } catch (MessagingException e) {
427
428 log.warn(e.getMessage());
429 }
430 if (addresses != null) {
431 log.debug(addresses.length + "つのFromアドレスが見つかりました。");
432 for (int j = 0; j < addresses.length; j++) {
433 InternetAddress address = (InternetAddress)addresses[j];
434 mail.setFrom(address);
435 log.debug("FromアドレスをMailにセットしました。[From='" + address.toUnicodeString() + "']");
436 }
437 } else {
438 log.debug("Fromアドレスは見つかりませんでした。");
439 }
440 }
441
442 private void setRecipientAddresses(MimeMessage mm, ReceivedMail mail) {
443
444
445
446 log.debug("Toアドレスを検出します。");
447 Address[] toAddresses = null;
448 try {
449 toAddresses = mm.getRecipients(Message.RecipientType.TO);
450 } catch (AddressException e) {
451 log.warn("不正なメールアドレスが検出されました。[" + e.getRef() + "]");
452 } catch (MessagingException e) {
453
454 log.warn(e.getMessage());
455 }
456 if (toAddresses != null) {
457 log.debug(toAddresses.length + "つのToアドレスが見つかりました。");
458 for (int j = 0; j < toAddresses.length; j++) {
459 InternetAddress address = (InternetAddress)toAddresses[j];
460 mail.addTo(address);
461 log.debug("ToアドレスをMailにセットしました。[To='" + address.toUnicodeString() + "']");
462 }
463 } else {
464 log.debug("Toアドレスは見つかりませんでした。");
465 }
466
467
468
469
470 log.debug("Ccアドレスを検出します。");
471 Address[] ccAddresses = null;
472 try {
473 ccAddresses = mm.getRecipients(Message.RecipientType.CC);
474 } catch (AddressException e) {
475 log.warn("不正なメールアドレスが検出されました。[" + e.getRef() + "]");
476 } catch (MessagingException e) {
477
478 log.warn(e.getMessage());
479 }
480 if (ccAddresses != null) {
481 log.debug(ccAddresses.length + "つのCcアドレスが見つかりました。");
482 for (int j = 0; j < ccAddresses.length; j++) {
483 InternetAddress address = (InternetAddress)ccAddresses[j];
484 mail.addCc(address);
485 log.debug("CcアドレスをMailにセットしました。[Cc='" + address.toUnicodeString() + "']");
486 }
487 } else {
488 log.debug("Ccアドレスは見つかりませんでした。");
489 }
490 }
491
492 }