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    }