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