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 }