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をそのまま(前後? &gt;, &lt;)は取り除きます?
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    }