001 /*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016 package org.opengion.fukurou.mail;
017
018 import org.opengion.fukurou.util.FileUtil;
019
020 import java.io.IOException;
021 import java.io.UnsupportedEncodingException;
022 import java.io.File;
023 import java.io.PrintWriter;
024 import java.util.Enumeration;
025 import java.util.Map;
026 import java.util.LinkedHashMap;
027 import java.util.Date;
028
029 import javax.mail.Header;
030 import javax.mail.Part;
031 import javax.mail.BodyPart;
032 import javax.mail.Multipart;
033 import javax.mail.Message;
034 import javax.mail.MessagingException;
035 import javax.mail.Flags;
036 import javax.mail.internet.MimeMessage;
037 import javax.mail.internet.MimeUtility;
038 import javax.mail.internet.InternetAddress;
039
040 /**
041 * MailMessage は、受信メールを??るため?ラ?ークラスです?
042 *
043 * メ?ージオブジェクトを引数にとるコンストラクタによりオブジェクトが作?されます?
044 * 日本語?置などを簡易的に扱えるように、ラ?クラス?使用方法を想定して?す?
045 * ?であれば(例えば、添付ファイルを取り?すために、MailAttachFiles を利用する場合など)
046 * ?のメ?ージオブジェクトを取り出すことが可能です?
047 * MailReceiveListener クラスの receive( MailMessage ) メソ?で、メールごとにイベントが
048 * 発生して、??る形態が??す?
049 *
050 * @version 4.0
051 * @author Kazuhiko Hasegawa
052 * @since JDK5.0,
053 */
054 public class MailMessage {
055
056 private static final String CR = System.getProperty("line.separator");
057 private static final String MSG_EX = "メ?ージ??のハンドリングに失敗しました? ;
058
059 private final String host ;
060 private final String user ;
061 private final Message message ;
062 private final Map<String,String> headerMap ;
063
064 private String subject = null;
065 private String content = null;
066 private String messageID = null;
067
068 /**
069 * メ?ージオブジェクトを?して構築します?
070 *
071 * @param message メ?ージオブジェク?
072 * @param host ホス?
073 * @param user ユーザー
074 */
075 public MailMessage( final Message message,final String host,final String user ) {
076 this.host = host;
077 this.user = user;
078 this.message = message;
079 headerMap = makeHeaderMap( null );
080 }
081
082 /**
083 * ?の メ?ージオブジェクトを返します?
084 *
085 * @return メ?ージオブジェク?
086 */
087 public Message getMessage() {
088 return message;
089 }
090
091 /**
092 * ?の ホスト名を返します?
093 *
094 * @return ホスト名
095 */
096 public String getHost() {
097 return host;
098 }
099
100 /**
101 * ?の ユーザー名を返します?
102 *
103 * @return ユーザー?
104 */
105 public String getUser() {
106 return user;
107 }
108
109 /**
110 * メールのヘッ????を文字?に変換して返します?
111 * キーは、??ー??の取り出しと同?す?
112 * ? Return-Path,Delivered-To,Date,From,To,Cc,Subject,Content-Type,Message-Id
113 *
114 * @param key メールのヘッ??キー
115 *
116 * @return キーに対するメールのヘッ????
117 */
118 public String getHeader( final String key ) {
119 return headerMap.get( key );
120 }
121
122 /**
123 * メールの??ヘッ????を文字?に変換して返します?
124 * ヘッ????の取り出しキーと同????リターンコードで結合して?す?
125 * Return-Path,Delivered-To,Date,From,To,Cc,Subject,Content-Type,Message-Id
126 *
127 * @return メールの??ヘッ????
128 */
129 public String getHeaders() {
130 String[] keys = headerMap.keySet().toArray( new String[headerMap.size()] );
131 StringBuilder buf = new StringBuilder( 200 );
132 for( int i=0; i<keys.length; i++ ) {
133 buf.append( keys[i] ).append(":").append( headerMap.get( keys[i] ) ).append( CR );
134 }
135 return buf.toString();
136 }
137
138 /**
139 * メールのタイトル(Subject)を返します?
140 * 日本語文字コード??行って?す?(JIS→unicode変換?
141 *
142 * @og.rev 4.3.3.5 (2008/11/08) 日本語MIMEエンコードされた???mimeDecode で?ードします?
143 *
144 * @return メールのタイトル
145 */
146 public String getSubject() {
147 if( subject == null ) {
148 try {
149 subject = mimeDecode( message.getSubject() );
150
151 // subject = UnicodeCorrecter.correctToCP932( message.getSubject() );
152 }
153 catch( MessagingException ex ) {
154 // メ?ージ??のハンドリングに失敗しました?
155 throw new RuntimeException( MSG_EX,ex );
156 }
157 // catch( UnsupportedEncodingException ex ) {
158 // String errMsg = "?スト情報の?ードに失敗しました? ;
159 // throw new RuntimeException( errMsg,ex );
160 // }
161 }
162 if( subject == null ) { subject = "No Subject" ;}
163 return subject;
164 }
165
166 /**
167 * メールの本?Content)を返します?
168 * 日本語文字コード??行って?す?(JIS→unicode変換?
169 *
170 * @return メールの本?
171 */
172 public String getContent() {
173 if( content == null ) {
174 content = UnicodeCorrecter.correctToCP932( mime2str( message ) );
175 }
176 return content;
177 }
178
179 /**
180 * メ?ージID を取得します?
181 *
182 * 基本?は、メ?ージIDをそのまま(前後? >, <)は取り除きます?
183 * メ?ージIDのな?ールは?unknown." + SentData + "." + From と????
184 * 作?します?
185 * さらに??信日やFrom がな??合?また?、文字?として取り出せな??合?
186 * "unknown" を返します?
187 *
188 * @og.rev 4.3.3.5 (2008/11/08) 送信時刻がNULLの場合?処?追?
189 *
190 * @return メ?ージID
191 */
192 public String getMessageID() {
193 if( messageID == null ) {
194 try {
195 messageID = ((MimeMessage)message).getMessageID();
196 if( messageID != null ) {
197 messageID = messageID.substring(1,messageID.length()-1) ;
198 }
199 else {
200 // 4.3.3.5 (2008/11/08) SentDate ?null のケースがあるため?
201 // String date = String.valueOf( message.getSentDate().getTime() );
202 Date dt = message.getSentDate();
203 if( dt == null ) { dt = message.getReceivedDate(); }
204 Long date = (dt == null) ? 0L : dt.getTime();
205 String from = ((InternetAddress[])message.getFrom())[0].getAddress() ;
206 messageID = "unknown." + date + "." + from ;
207 }
208 }
209 catch( MessagingException ex ) {
210 // メ?ージ??のハンドリングに失敗しました?
211 throw new RuntimeException( MSG_EX,ex );
212 }
213 }
214 return messageID ;
215 }
216
217 /**
218 * メ?ージをメールサーバ?から削除するかど?をセ?します?
219 *
220 * @param flag 削除するかど? true:行う?false:行わな?
221 */
222 public void deleteMessage( final boolean flag ) {
223 try {
224 message.setFlag(Flags.Flag.DELETED, flag);
225 }
226 catch( MessagingException ex ) {
227 // メ?ージ??のハンドリングに失敗しました?
228 throw new RuntimeException( MSG_EX,ex );
229 }
230 }
231
232 /**
233 * メールの?を文字?として表現します?
234 * ????簡易的なメールの?の取り出し?エラー時?メール保存に使用します?
235 *
236 * @return メールの?の??表現
237 */
238 public String getSimpleMessage() {
239 StringBuilder buf = new StringBuilder( 200 );
240
241 buf.append( getHeaders() ).append( CR );
242 buf.append( "Subject:" ).append( getSubject() ).append( CR );
243 buf.append( "===============================" ).append( CR );
244 buf.append( getContent() ).append( CR );
245 buf.append( "===============================" ).append( CR );
246
247 return buf.toString();
248 }
249
250 /**
251 * メールの?と、あれ?添付ファイルを指定?フォル?セーブします?
252 * saveMessage( dir )と、saveAttachFiles( dir,true ) を同時に呼び出して?す?
253 *
254 * @param dir メールと添付ファイルをセーブするフォル?
255 */
256 public void saveSimpleMessage( final String dir ) {
257
258 saveMessage( dir );
259
260 saveAttachFiles( dir,true );
261 }
262
263 /**
264 * メールの?を文字?として??フォル?セーブします?
265 * メ?ージID.txt と?本?セーブします?
266 * ????簡易的なメールの?の取り出し?エラー時?メール保存に使用します?
267 *
268 * @param dir メールの?をセーブするフォル?
269 */
270 public void saveMessage( final String dir ) {
271
272 String msgId = getMessageID() ;
273
274 // 3.8.0.0 (2005/06/07) FileUtil#getPrintWriter を利用?
275 File file = new File( dir,msgId + ".txt" );
276 PrintWriter writer = FileUtil.getPrintWriter( file,"UTF-8" );
277 writer.println( getSimpleMessage() );
278
279 writer.close();
280 }
281
282 /**
283 * メールの添付ファイルが存在する場合に、指定?フォル?セーブします?
284 *
285 * 添付ファイルが存在する場合?み、??実行します?
286 * useMsgId にtrue を設定すると、メ?ージID と?フォル?作?し?そ?下に?
287 * 連番 + "_" + 添付ファイル?でセーブします?(メールには同?ァイル名を?添付できる為)
288 * false の場合?、指定??レクトリ直下に??番 + "_" + 添付ファイル?でセーブします?
289 *
290 * @og.rev 4.3.3.5 (2008/11/08) ?レクトリ?時のセパレータのチェ?を追?
291 *
292 * @param dir 添付ファイルをセーブするフォル?
293 * @param useMsgId メ?ージIDフォル?作?してセーブ?合:true
294 * ???レクトリ直下にセーブする?合:false
295 */
296 public void saveAttachFiles( final String dir,final boolean useMsgId ) {
297
298 final String attDirStr ;
299 if( useMsgId ) {
300 String msgId = getMessageID() ;
301 // 4.3.3.5 (2008/11/08) ?レクトリ?時のセパレータのチェ?を追?
302 if( dir.endsWith( "/" ) ) {
303 attDirStr = dir + msgId + "/";
304 }
305 else {
306 attDirStr = dir + "/" + msgId + "/";
307 }
308 }
309 else {
310 attDirStr = dir ;
311 }
312
313 MailAttachFiles attFiles = new MailAttachFiles( message );
314 String[] files = attFiles.getNames();
315 if( files.length > 0 ) {
316 // String attDirStr = dir + "/" + msgId + "/";
317 // File attDir = new File( attDirStr );
318 // if( !attDir.exists() ) {
319 // if( ! attDir.mkdirs() ) {
320 // String errMsg = "添付ファイルの?レクトリの作?に失敗しました?" + attDirStr + "]";
321 // throw new RuntimeException( errMsg );
322 // }
323 // }
324
325 // 添付ファイル名を?しな?、番号 + "_" + 添付ファイル名になる?
326 for( int i=0; i<files.length; i++ ) {
327 attFiles.saveFileName( attDirStr,null,i );
328 }
329 }
330 }
331
332 /**
333 * 受?確認がセ?されて?場合? 返信先アドレスを返します?
334 * セ?されて???合?、null を返します?
335 * 受?確認?、Disposition-Notification-To ヘッ?セ?される事とし?
336 * こ?ヘッ???を返します?セ?されて?ければ、null を返します?
337 *
338 * @return 返信先アドレス(Disposition-Notification-To ヘッ???)
339 */
340 public String getNotificationTo() {
341 return headerMap.get( "Disposition-Notification-To" );
342 }
343
344 /**
345 * ヘッ????を持った?Enumeration から、??ーと値のペアの??を作?します?
346 *
347 * ヘッ????は、Message#getAllHeaders() か?Message#getMatchingHeaders( String[] )
348 * で得られる Enumeration に、Header オブジェクトとして取得できます?
349 * こ?ヘッ??オブジェクトから?キー(getName()) と値(getValue()) を取り?します?
350 * 結果は、キー:値 の??として、リターンコードで区?ます?
351 *
352 * @og.rev 4.3.3.5 (2008/11/08) 日本語MIMEエンコードされた???mimeDecode で?ードします?
353 *
354 * @param headerList ヘッ????配?
355 *
356 * @return ヘッ????の キー:値 のMap
357 */
358 private Map<String,String> makeHeaderMap( final String[] headerList ) {
359 Map<String,String> headMap = new LinkedHashMap<String,String>();
360 try {
361 final Enumeration<?> enume; // 4.3.3.6 (2008/11/15) Generics警告対?
362 if( headerList == null ) {
363 enume = message.getAllHeaders();
364 }
365 else {
366 enume = message.getMatchingHeaders( headerList );
367 }
368
369 while( enume.hasMoreElements() ) {
370 Header header = (Header)enume.nextElement();
371 String name = header.getName();
372 // 4.3.3.5 (2008/11/08) 日本語MIMEエンコードされた???mimeDecode で?ードします?
373 String value = mimeDecode( header.getValue() );
374 // String value = header.getValue();
375
376 // if( value.indexOf( "=?" ) >= 0 ) {
377 // value = (header.getValue()).replace( '"',' ' ); // メール?ード?ミソ
378 // value = UnicodeCorrecter.correctToCP932( MimeUtility.decodeText( value ) );
379 // }
380
381 String val = headMap.get( name );
382 if( val != null ) {
383 value = val + "," + value;
384 }
385 headMap.put( name,value );
386 }
387 }
388 // catch( UnsupportedEncodingException ex ) {
389 // String errMsg = "Enumeration より、Header オブジェクトが取り出せませんでした? ;
390 // throw new RuntimeException( errMsg,ex );
391 // }
392 catch( MessagingException ex2 ) {
393 // メ?ージ??のハンドリングに失敗しました?
394 throw new RuntimeException( MSG_EX,ex2 );
395 }
396
397 return headMap;
398 }
399
400 /**
401 * Part オブジェクトから???に見つけた text/plain を取り?します?
402 *
403 * Part は、?ルチパートと?Partに?のPartを持って?り?さらにそ?中に?
404 * Part を持って?ような構?をして?す?
405 * ここでは、最初に見つけた、MimeType が?text/plain の場合に、文字?に
406 * 変換して、返して?す?それ以外?場合?再帰?、text/plain ?
407 * 見つかるまで、??続けます?
408 * また?特別に、HN0256 からのトラブルメールは、Content-Type が?text/plain のみに
409 * なって?為 CONTENTS が?JIS のまま、取り?されてしま?め?強制?
410 * Content-Type を?"text/plain; charset=iso-2022-jp" に変更して?す?
411 *
412 * @param part Part?取り込み件数
413 *
414 * @return ??の text/plain ??。見つからな??合?、null を返します?
415 * @throws MessagingException javax.mail 関連のエラーが発生したと?
416 * @throws IOException 入出力エラーが発生したと?
417 */
418 private String mime2str( final Part part ) {
419 String content = null;
420
421 try {
422 if( part.isMimeType("text/plain") ) {
423 // HN0256 からのトラブルメールは、Content-Type が?text/plain のみになって?為
424 // CONTENTS が?JIS のまま、取り?されてしま??強制?変更して?す?
425 if( (part.getContentType()).equals( "text/plain" ) ) {
426 MimeMessage msg = new MimeMessage( (MimeMessage)part );
427 msg.setHeader( "Content-Type","text/plain; charset=iso-2022-jp" );
428 content = (String)msg.getContent();
429 }
430 else {
431 content = (String)part.getContent();
432 }
433 }
434 else if( part.isMimeType("message/rfc822") ) { // Nested Message
435 content = mime2str( (Part)part.getContent() );
436 }
437 else if( part.isMimeType("multipart/*") ) {
438 Multipart mp = (Multipart)part.getContent();
439
440 int count = mp.getCount();
441 for(int i = 0; i < count; i++) {
442 BodyPart bp = mp.getBodyPart(i);
443 content = mime2str( bp );
444 if( content != null ) { break; }
445 }
446 }
447 }
448 catch( MessagingException ex ) {
449 // メ?ージ??のハンドリングに失敗しました?
450 throw new RuntimeException( MSG_EX,ex );
451 }
452 catch( IOException ex2 ) {
453 String errMsg = "?スト情報の取り出しに失敗しました? ;
454 throw new RuntimeException( errMsg,ex2 );
455 }
456
457 return content ;
458 }
459
460 /**
461 * エンコードされた??を??ードします?
462 *
463 * MIMEエンコー?は?=? で開始するエンコード文字? ですが、?合によって、前のスペ?ス?
464 * 存在しな??合があります?
465 * また?メーラーによっては、エンコード文字???ルコー??ションでくくる??入って?
466 * 場合もあります?
467 * これら???のエンコード文字?をデコードします?
468 *
469 * @og.rev 4.3.3.5 (2008/11/08) 日本語MIMEエンコードされた??をデコードします?
470 *
471 * @param text エンコードされた??(されて???合?、そのまま返しま?
472 *
473 * @return ?ードされた??
474 */
475 public static final String mimeDecode( final String text ) {
476 if( text == null || text.indexOf( "=?" ) < 0 ) { return text; }
477
478 // String rtnText = text.replace( '"',' ' ); // メール?ード?ミソ
479 String rtnText = text.replace( '\t',' ' ); // 若干トリ?ーな処?
480 try {
481 // encode-word の =? の前にはスペ?スが??
482 // ここでは、?割して、デコード??行うことで、対?
483 StringBuilder buf = new StringBuilder();
484 int pos1 = rtnText.indexOf( "=?" ); // ?ード?開?
485 int pos2 = 0; // ?ード?終?
486 buf.append( rtnText.substring( 0,pos1 ) );
487 while( pos1 >= 0 ) {
488 pos2 = rtnText.indexOf( "?=",pos1 ) + 2; // ?ード?終?
489 String sub = rtnText.substring( pos1,pos2 );
490 buf.append( UnicodeCorrecter.correctToCP932( MimeUtility.decodeText( sub ) ) );
491 pos1 = rtnText.indexOf( "=?",pos2 ); // ?ード?開?
492 if( pos1 > 0 ) {
493 buf.append( rtnText.substring( pos2,pos1 ) );
494 }
495 }
496 buf.append( rtnText.substring( pos2 ) );
497 rtnText = buf.toString() ;
498 }
499 catch( UnsupportedEncodingException ex ) {
500 String errMsg = "?スト情報の?ードに失敗しました? ;
501 throw new RuntimeException( errMsg,ex );
502 }
503 return rtnText;
504 }
505 }