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