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