View Javadoc

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 			// ignore
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 				// from で始まるものだけを抽出し、改行を削除
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 			// ignore
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 			// ignore
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 			// ignore
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 			// ignore
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 			// ignore
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 			// ignore
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 			// ignore
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 			// ignore
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 			// ignore
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 			// ignore
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 		 * TOアドレスのパース
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 			// ignore
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 		 * CCアドレスのパース
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 			// ignore
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 }