OSDN Git Service

Migrated to jakarta namespace and bumped version number to 2.0.0
[spring-ext/ozacc-mail.git] / src / main / java / com / ozacc / mail / fetch / impl / MailConverterImpl.java
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 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;
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 * 12L;
60
61         /**
62          * @see com.ozacc.mail.fetch.MailConverter#convertIntoMails(jakarta.mail.internet.MimeMessage[])
63          */
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());
72                 }
73                 log.debug("計" + messages.length + "通のMimeMessageをMailに変換しました。");
74                 return results;
75         }
76
77         /**
78          * @param mm
79          * @param mail 
80          */
81         private void setReceivedHeaders(MimeMessage mm, ReceivedMail mail) {
82                 String[] headerValues = null;
83                 try {
84                         headerValues = mm.getHeader("Received");
85                 } catch (MessagingException e) {
86                         // ignore
87                         log.warn(e.getMessage());
88                 }
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 + "'");
96
97                                         Matcher m = receivedHeaderPattern.matcher(received);
98                                         if (m.matches()) {
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);
104                                         }
105                                 }
106                         }
107
108                 }
109         }
110
111         /**
112          * 指定されたMimeMessageに添付されているファイルを全て抽出し、
113          * システムプロパティにセットされた一時ファイルディレクトリ内に保存した後、
114          * そのファイルを指定されたReceivedMailにセットします。
115          * <p>
116          * 保存された添付ファイルはJVM終了時に削除されます。
117          * 
118          * @param mm
119          * @param mail 
120          */
121         private void setAttachmentFiles(MimeMessage mm, ReceivedMail mail) {
122                 try {
123                         cleanTempDir();
124
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";
132                                 }
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);
139                                 try {
140                                         writeTo(f, is);
141                                 } finally {
142                                         if (is != null) {
143                                                 is.close();
144                                         }
145                                 }
146
147                                 f.getParentFile().deleteOnExit();
148                                 f.deleteOnExit();
149
150                                 mail.addFile(f, fileName);
151                                 log.debug((i + 1) + "個目の添付ファイルを保存しました。[" + path + "]");
152                         }
153                 } catch (IOException e) {
154                         log.error("添付ファイルの取得に失敗しました。", e);
155                 } catch (MessagingException e) {
156                         // ignore
157                         log.warn(e.getMessage());
158                 }
159         }
160
161         /**
162          * 一時ディレクトリ内に保存された添付ファイルの内、生存時間を越えているものを削除します。
163          */
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) {
175                                 deleteDir(dir);
176                         }
177                 }
178         }
179
180         /**
181          * 一時ディレクトリのパスを返します。
182          * 
183          * @return 一時ディレクトリのパス
184          */
185         private String getTempDirPath() {
186                 return System.getProperty(JAVA_IO_TMPDIR);
187         }
188
189         /**
190          * 指定されたディレクトリを中身のファイルを含めて削除します。
191          * 
192          * @param dir 削除するディレクトリ
193          */
194         private void deleteDir(File dir) {
195                 File[] files = dir.listFiles();
196                 for (int i = 0; i < files.length; i++) {
197                         File f = files[i];
198                         f.delete();
199                 }
200                 dir.delete();
201         }
202
203         /**
204          * 指定されたInputStreamデータを指定されたファイルに保存します。
205          * 
206          * @param destFile 保存するファイル
207          * @param is ソースとなるInputStream
208          * @throws FileNotFoundException
209          * @throws IOException 
210          */
211         private void writeTo(File destFile, InputStream is) throws FileNotFoundException, IOException {
212                 BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFile),
213                                 1024 * 50);
214                 try {
215                         copy(is, bos);
216                 } finally {
217                         if (bos != null) {
218                                 bos.close();
219                         }
220                 }
221         }
222
223         private static void copy(InputStream in, OutputStream out) throws IOException {
224                 byte[] buffer = new byte[1024 * 4];
225                 int n = 0;
226                 while (-1 != (n = in.read(buffer))) {
227                         out.write(buffer, 0, n);
228                 }
229         }
230
231         /**
232          * 指定されたMimeMessageからX-Header、References、In-Reply-Toヘッダを解析し、
233          * 指定されたReceivedMailにセットします。
234          * 
235          * @param mm
236          * @param mail
237          */
238         private void setXHeaders(MimeMessage mm, ReceivedMail mail) {
239                 log.debug("X-HeaderをMailにセットします。");
240                 Enumeration<Header> headerEnum = null;
241                 try {
242                         headerEnum = mm.getAllHeaders();
243                 } catch (MessagingException e) {
244                         // ignore
245                         log.warn(e.getMessage());
246                 }
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() + "']");
255                         }
256                 }
257         }
258
259         private void setReplyToAddress(MimeMessage mm, ReceivedMail mail) {
260                 log.debug("Reply-ToアドレスをMailにセットします。");
261                 Address[] addresses = null;
262                 try {
263                         addresses = mm.getReplyTo();
264                 } catch (MessagingException e) {
265                         // ignore
266                         log.warn(e.getMessage());
267                 }
268                 if (addresses != null && addresses.length > 1) {
269                         log.debug(addresses.length + "つのReply-Toアドレスが見つかりました。最初のアドレスのみ取得されます。");
270                         mail.setReplyTo((InternetAddress)addresses[0]);
271                 } else {
272                         log.debug("Reply-Toアドレスは見つかりませんでした。");
273                 }
274         }
275
276         /**
277          * メールの容量(byte)をMimeMessageから取得してReceivedMailにセットします。
278          * 取得に失敗した場合は -1 をセットします。
279          * 
280          * @param mm
281          * @param mail 
282          */
283         private void setSize(MimeMessage mm, ReceivedMail mail) {
284                 try {
285                         mail.setSize(mm.getSize());
286                 } catch (MessagingException e) {
287                         mail.setSize(-1);
288                 }
289         }
290
291         /**
292          * @param mm
293          * @param mail
294          * @throws MessagingException 
295          */
296         private void setHtmlText(MimeMessage mm, ReceivedMail mail) {
297                 try {
298                         HtmlPartExtractor hpe = new HtmlPartExtractor();
299                         MultipartUtility.process(mm, hpe);
300                         String htmlText = hpe.getHtml();
301                         mail.setHtmlText(htmlText);
302                 } catch (MessagingException e) {
303                         // ignore
304                         log.warn(e.getMessage());
305                 }
306         }
307
308         private void setText(MimeMessage mm, ReceivedMail mail) {
309                 try {
310                         String text = MultipartUtility.getPlainText(mm);
311                         mail.setText(text);
312                 } catch (MessagingException e) {
313                         // ignore
314                         log.warn(e.getMessage());
315                 }
316         }
317
318         private void setMessageId(MimeMessage mm, ReceivedMail mail) {
319                 try {
320                         String messageId = mm.getMessageID();
321                         mail.setMessageId(messageId);
322                         log.debug("Message-IDをMailにセットしました。[Message-ID='" + messageId + "']");
323                 } catch (MessagingException e) {
324                         // ignore
325                         log.warn(e.getMessage());
326                 }
327         }
328
329         /**
330          * 指定されたMimeMessageから件名を取得し、ReceivedMailにセットします。
331          * sk_jpのMailUtility.decodeText()メソッドを用いて、件名の文字化けを回避します。
332          * 
333          * @param mm
334          * @param mail
335          */
336         private void setSubject(MimeMessage mm, ReceivedMail mail) {
337                 try {
338                         String subject = MailUtility.decodeText(mm.getSubject());
339                         mail.setSubject(subject);
340                 } catch (MessagingException e) {
341                         // ignore
342                         log.warn(e.getMessage());
343                 }
344         }
345
346         private void setDate(MimeMessage mm, ReceivedMail mail) {
347                 try {
348                         Date d = mm.getSentDate();
349                         mail.setDate(d);
350                 } catch (MessagingException e) {
351                         // ignore
352                         log.warn(e.getMessage());
353                 }
354         }
355
356         /**
357          * Return-Pathアドレスは必ずしもセットされてはいません。
358          * 特にspam系のメールでは不正なフォーマットのメールアドレスが
359          * セットされている場合もあるので要注意。
360          * 
361          * @param mm
362          * @param mail
363          */
364         private void setReturnPath(MimeMessage mm, ReceivedMail mail) {
365                 log.debug("Return-Pathアドレスを検出します。");
366                 String[] returnPath = null;
367                 try {
368                         returnPath = mm.getHeader("Return-Path");
369                 } catch (MessagingException e) {
370                         // ignore
371                         log.warn(e.getMessage());
372                 }
373                 if (returnPath != null && returnPath.length > 0) {
374                         String email = returnPath[0].substring(1, returnPath[0].length() - 1);
375                         if (email.length() > 0) {
376                                 try {
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 + "']");
381                                 }
382                         } else {
383                                 log.debug("Return-Pathアドレスは見つかりませんでした。");
384                         }
385                 } else {
386                         log.debug("Return-Pathアドレスは見つかりませんでした。");
387                 }
388         }
389
390         private void setFromAddress(MimeMessage mm, ReceivedMail mail) {
391                 log.debug("Fromアドレスを検出します。");
392                 Address[] addresses = null;
393                 try {
394                         addresses = mm.getFrom();
395                 } catch (MessagingException e) {
396                         // ignore
397                         log.warn(e.getMessage());
398                 }
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() + "']");
405                         }
406                 } else {
407                         log.debug("Fromアドレスは見つかりませんでした。");
408                 }
409         }
410
411         private void setRecipientAddresses(MimeMessage mm, ReceivedMail mail) {
412                 /*
413                  * TOアドレスのパース
414                  */
415                 log.debug("Toアドレスを検出します。");
416                 Address[] toAddresses = null;
417                 try {
418                         toAddresses = mm.getRecipients(Message.RecipientType.TO);
419                 } catch (AddressException e) {
420                         log.warn("不正なメールアドレスが検出されました。[" + e.getRef() + "]");
421                 } catch (MessagingException e) {
422                         // ignore
423                         log.warn(e.getMessage());
424                 }
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];
429                                 mail.addTo(address);
430                                 log.debug("ToアドレスをMailにセットしました。[To='" + address.toUnicodeString() + "']");
431                         }
432                 } else {
433                         log.debug("Toアドレスは見つかりませんでした。");
434                 }
435
436                 /*
437                  * CCアドレスのパース
438                  */
439                 log.debug("Ccアドレスを検出します。");
440                 Address[] ccAddresses = null;
441                 try {
442                         ccAddresses = mm.getRecipients(Message.RecipientType.CC);
443                 } catch (AddressException e) {
444                         log.warn("不正なメールアドレスが検出されました。[" + e.getRef() + "]");
445                 } catch (MessagingException e) {
446                         // ignore
447                         log.warn(e.getMessage());
448                 }
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];
453                                 mail.addCc(address);
454                                 log.debug("CcアドレスをMailにセットしました。[Cc='" + address.toUnicodeString() + "']");
455                         }
456                 } else {
457                         log.debug("Ccアドレスは見つかりませんでした。");
458                 }
459         }
460
461         /**
462          * @see com.ozacc.mail.fetch.MailConverter#convertIntoMail(jakarta.mail.internet.MimeMessage)
463          */
464         public ReceivedMail convertIntoMail(MimeMessage mm) {
465                 ReceivedMail mail = createReceivedMail();
466                 setReturnPath(mm, mail);
467                 setReceivedHeaders(mm, mail);
468                 setDate(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);
475                 setText(mm, mail);
476                 setHtmlText(mm, mail);
477                 setAttachmentFiles(mm, mail);
478                 setSize(mm, mail);
479                 mail.setMessage(mm);
480                 return mail;
481         }
482
483         protected ReceivedMail createReceivedMail() {
484                 return new ReceivedMail();
485         }
486
487 }