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