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