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