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 }