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 }