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