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.LogWriter;
019    
020    import java.io.UnsupportedEncodingException;
021    import java.util.Properties;
022    import java.util.Date;
023    
024    import javax.activation.FileDataSource;
025    import javax.activation.DataHandler;
026    import javax.mail.internet.InternetAddress;
027    import javax.mail.internet.AddressException;
028    import javax.mail.internet.MimeMessage;
029    import javax.mail.internet.MimeMultipart;
030    import javax.mail.internet.MimeBodyPart;
031    import javax.mail.internet.MimeUtility;
032    import javax.mail.Store;
033    import javax.mail.Transport;
034    import javax.mail.Session;
035    import javax.mail.Message;
036    import javax.mail.MessagingException;
037    import javax.mail.IllegalWriteException;
038    
039    /**
040     * MailTX はã€?¼³?­?´?°プロトコルによるメール送信プログラãƒ?�§すã?
041     *
042     * E-Mail で日本語を送信する場合ã?ISO-2022-JP(JISコーãƒ?化してã€?bit で
043     * エンコードして送信するå¿?¦�がありますが、Windows系の特殊文字や、unicodeと
044     * æ–?­—ã?マッピングが異なる文字などがã?æ–?­—化けしますã?
045     * 対応方法としてはã€?
046     * ?‘.Windows-31J + 8bit 送信
047     * ?’.ISO-2022-JP に独自変換 + 7bit 送信
048     * の方法がありますã?
049     * 今回、この?’つの方法につã�?�¦、対応いたしましたã€?
050     *
051     * @version  4.0
052     * @author   Kazuhiko Hasegawa
053     * @since    JDK5.0,
054     */
055    public class MailTX {
056            private static final String CR = System.getProperty("line.separator");
057            private static final String AUTH_PBS   = "POP_BEFORE_SMTP";             // 5.4.3.2
058    //      private static final String AUTH_SMTPA = "SMTP_AUTH";                   // 5.4.3.2
059    
060            /** メーラーの名称  {@value} */
061            public static final String MAILER = "Hayabusa Mail Ver 4.0";
062    
063            private final String    charset  ;      // Windwos-31J , MS932 , ISO-2022-JP
064            private String[]        filename = null;
065            private String          message  = null;
066            private Session         session  = null;
067            private MimeMultipart mmPart = null;
068            private MimeMessage     mimeMsg  = null;
069            private MailCharset     mcSet    = null;
070    
071            /**
072             * メールサーバã?とãƒ?ƒ•ォルト文字エンコーãƒ?‚£ングを指定して、オブジェクトを構築しますã?
073             *
074             * ãƒ?ƒ•ォルト文字エンコーãƒ?‚£ングは、ISO-2022-JP ですã?
075             *
076             * @param       host    メールサーバã?
077             * @throws      IllegalArgumentException 引数ã�?null の場合ã?
078             */
079            public MailTX( final String host ) {
080                    this( host,"ISO-2022-JP" );
081            }
082    
083            /**
084             * メールサーバã?とãƒ?ƒ•ォルト文字エンコーãƒ?‚£ングを指定して、オブジェクトを構築しますã?
085             *
086             * æ–?­—エンコーãƒ?‚£ングには、Windwos-31J , MS932 , ISO-2022-JP を指定できますã?
087             *
088             * @og.rev 5.4.3.2 (2012/01/06) 認証対応ã?ため
089             *
090             * @param       host    メールサーバã?
091             * @param       charset æ–?­—エンコーãƒ?‚£ング
092             * @throws      IllegalArgumentException 引数ã�?null の場合ã?
093             */
094            public MailTX( final String host , final String charset ) {
095                    this( host,charset,null,null,null,null );
096            }
097    
098            /**
099             * メールサーバã?とæ–?­—エンコーãƒ?‚£ングを指定して、オブジェクトを構築しますã?
100             * 認証を行う場合ã?認証方法をæŒ?®šしますã?
101             *
102             * æ–?­—エンコーãƒ?‚£ングには、Windwos-31J , MS932 , ISO-2022-JP を指定できますã?
103             *
104             * @og.rev 5.1.9.0 (2010/08/01) mail.smtp.localhostの設定追åŠ?
105             * @og.rev 5.4.3.2 (2012/01/06) 認証対å¿?POP Before SMTP)。引数?“つ追åŠ?å°?�¥çš?�«はAuthentication対応ï¼?
106             *
107             * @param       host    メールサーバã?
108             * @param       charset æ–?­—エンコーãƒ?‚£ング
109             * @param       port    SMTPポã?ãƒ?
110             * @param       auth    認証方æ³?5.4.3.2
111             * @param       user    認証ユーザ 5.4.3.2
112             * @param       pass    認証パスワーãƒ?5.4.3.2
113             * @throws      IllegalArgumentException 引数ã�?null の場合ã?
114             */
115            public MailTX( final String host , final String charset, final String port
116                                    ,final String auth, final String user, final String pass) {
117                    if( host == null ) {
118                            String errMsg = "host に null はセãƒ?ƒˆ出来ませんã€?;
119                            throw new IllegalArgumentException( errMsg );
120                    }
121    
122                    if( charset == null ) {
123                            String errMsg = "charset に null はセãƒ?ƒˆ出来ませんã€?;
124                            throw new IllegalArgumentException( errMsg );
125                    }
126    
127                    this.charset = charset;
128    
129                    mcSet = MailCharsetFactory.newInstance( charset );
130    
131                    Properties prop = new Properties();
132                    prop.setProperty("mail.mime.charset", charset);
133                    prop.setProperty("mail.mime.decodetext.strict", "false");
134                    prop.setProperty("mail.mime.address.strict", "false");
135                    prop.setProperty("mail.smtp.host", host);
136                    // 5.1.9.0 (2010/08/01) 設定追åŠ?
137                    prop.setProperty("mail.smtp.localhost", host);
138                    prop.setProperty("mail.host", host);    // MEssage-ID の設定に利用
139                    // 5.4.3.2 ポã?ト追åŠ?
140                    if( port != null && port.length() > 0 ){
141                            prop.setProperty("mail.smtp.port", port);       // MEssage-ID の設定に利用
142                    }
143    
144                    session = Session.getInstance(prop, null);
145    
146                    // POP before SMTP認証処ç�?5.4.3.2
147                    if(AUTH_PBS.equals( auth )){
148                            try{
149                                    Store store = session.getStore("pop3");
150                                    store.connect(host,-1,user,pass); //同ä¸??ストとする
151                                    store.close();
152                            }
153                            catch(MessagingException ex){
154                                    String errMsg = "POP3 Auth Exception: "+ host + "/" + user;
155                                    throw new RuntimeException( errMsg,ex );
156                            }
157                    }
158    
159                    mimeMsg = new MimeMessage(session);
160            }
161    
162            /**
163             * メールをé?信しますã?
164             *
165             */
166            public void sendmail() {
167                    try {
168                            mimeMsg.setSentDate( new Date() );
169    
170                            if( filename == null || filename.length == 0 ) {
171                                    mcSet.setTextContent( mimeMsg,message );
172                            }
173                            else {
174                                    mmPart = new MimeMultipart();
175                                    mimeMsg.setContent( mmPart );
176                                    // ãƒ?‚­スト本体ã?登録
177                                    addMmpText( message );
178    
179                                    // 添付ファイルの登録
180                                    for( int i=0; i<filename.length; i++ ) {
181                                            addMmpFile( filename[i] );
182                                    }
183                            }
184    
185                            mimeMsg.setHeader("X-Mailer", MAILER );
186                            mimeMsg.setHeader("Content-Transfer-Encoding", mcSet.getBit() );
187                            Transport.send( mimeMsg );
188    
189                    }
190                    catch( AddressException ex ) {
191                            String errMsg = "Address Exception: ";
192                            throw new RuntimeException( errMsg,ex );
193                    }
194                    catch ( MessagingException mex ) {
195                            String errMsg = "MessagingException: ";
196                            throw new RuntimeException( errMsg,mex );
197                    }
198            }
199    
200            /**
201             * MimeMessageをリセãƒ?ƒˆしますã?
202             *
203             * sendmail() でメールをé?信後ã?セãƒ?‚·ョンを閉じずに別のメールをé?信する場合ã?
204             * リセãƒ?ƒˆしてから、各種パラメータをå?設定してくださいã€?
205             * そã?場合ã?、すべてのパラメータがå?期化されてã�?�¾すã?で、もã�?¸?º¦
206             * 設定しなおすå¿?¦�がありますã?
207             *
208             */
209            public void reset() {
210                    mimeMsg = new MimeMessage(session);
211            }
212    
213            /**
214             * 送信å…??¦?²?¯?­)アドレスをセãƒ?ƒˆしますã?
215             *
216             * @param   from 送信å…??¦?²?¯?­)アドレス
217             */
218            public void setFrom( final String from ) {
219                    try {
220                            if( from != null ) {
221                                    mimeMsg.setFrom( getAddress( from ) );
222                            }
223                    } catch( AddressException ex ) {
224                            String errMsg = "Address Exception: ";
225                            throw new RuntimeException( errMsg,ex );
226                    } catch ( MessagingException mex ) {
227                            String errMsg = "MessagingException: ";
228                            throw new RuntimeException( errMsg,mex );
229                    }
230            }
231    
232            /**
233             * 送信å…??´?¯)アドレス配å?をセãƒ?ƒˆしますã?
234             *
235             * @param   to 送信å…??´?¯)アドレス配å?
236             */
237            public void setTo( final String[] to ) {
238                    try {
239                            if( to != null ) {
240                                    mimeMsg.setRecipients( Message.RecipientType.TO, getAddress( to ) );
241                            }
242                    } catch( AddressException ex ) {
243                            String errMsg = "Address Exception: ";
244                            throw new RuntimeException( errMsg,ex );
245                    } catch ( MessagingException mex ) {
246                            String errMsg = "MessagingException: ";
247                            throw new RuntimeException( errMsg,mex );
248                    }
249            }
250    
251            /**
252             * 送信å…??£?£)アドレス配å?をセãƒ?ƒˆしますã?
253             *
254             * @param   cc 送信å…??£?£)アドレス配å?
255             */
256            public void setCc( final String[] cc ) {
257                    try {
258                            if( cc != null ) {
259                                    mimeMsg.setRecipients( Message.RecipientType.CC, getAddress( cc ) );
260                            }
261                    } catch( AddressException ex ) {
262                            String errMsg = "Address Exception: ";
263                            throw new RuntimeException( errMsg,ex );
264                    } catch ( MessagingException mex ) {
265                            String errMsg = "MessagingException: ";
266                            throw new RuntimeException( errMsg,mex );
267                    }
268            }
269    
270            /**
271             * 送信å…??¢?£?£)アドレス配å?をセãƒ?ƒˆしますã?
272             *
273             * @param   bcc 送信å…??¢?£?£)アドレス配å?
274             */
275            public void setBcc( final String[] bcc ) {
276                    try {
277                            if( bcc != null ) {
278                                    mimeMsg.setRecipients( Message.RecipientType.BCC, getAddress( bcc ) );
279                            }
280                    } catch( AddressException ex ) {
281                            String errMsg = "Address Exception: ";
282                            throw new RuntimeException( errMsg,ex );
283                    } catch ( MessagingException mex ) {
284                            String errMsg = "MessagingException: ";
285                            throw new RuntimeException( errMsg,mex );
286                    }
287            }
288    
289            /**
290             * 送信å…??´?¯)アドレス配å?をクリアしますã?
291             * @og.rev 4.3.6.0 (2009/04/01) 新規追åŠ?
292             *
293             */
294            public void clearTo() {
295                    try {
296                            mimeMsg.setRecipients( Message.RecipientType.TO, (InternetAddress[])null );
297                    } catch( IllegalWriteException ex ) {
298                            String errMsg = "Address Exception: ";
299                            throw new RuntimeException( errMsg,ex );
300                    } catch( IllegalStateException ex ) {
301                            String errMsg = "Address Exception: ";
302                            throw new RuntimeException( errMsg,ex );
303                    } catch ( MessagingException mex ) {
304                            String errMsg = "MessagingException: ";
305                            throw new RuntimeException( errMsg,mex );
306                    }
307            }
308    
309            /**
310             * 送信å…?CC)アドレス配å?をクリアしますã?
311             * @og.rev 4.3.6.0 (2009/04/01) 新規追åŠ?
312             *
313             */
314            public void clearCc() {
315                    try {
316                            mimeMsg.setRecipients( Message.RecipientType.CC, (InternetAddress[])null );
317                    } catch( IllegalWriteException ex ) {
318                            String errMsg = "Address Exception: ";
319                            throw new RuntimeException( errMsg,ex );
320                    } catch( IllegalStateException ex ) {
321                            String errMsg = "Address Exception: ";
322                            throw new RuntimeException( errMsg,ex );
323                    } catch ( MessagingException mex ) {
324                            String errMsg = "MessagingException: ";
325                            throw new RuntimeException( errMsg,mex );
326                    }
327            }
328    
329            /**
330             * 送信å…?BCC)アドレス配å?をクリアしますã?
331             * @og.rev 4.3.6.0 (2009/04/01) 新規追åŠ?
332             *
333             */
334            public void clearBcc() {
335                    try {
336                            mimeMsg.setRecipients( Message.RecipientType.BCC, (InternetAddress[])null );
337                    } catch( IllegalWriteException ex ) {
338                            String errMsg = "Address Exception: ";
339                            throw new RuntimeException( errMsg,ex );
340                    } catch( IllegalStateException ex ) {
341                            String errMsg = "Address Exception: ";
342                            throw new RuntimeException( errMsg,ex );
343                    } catch ( MessagingException mex ) {
344                            String errMsg = "MessagingException: ";
345                            throw new RuntimeException( errMsg,mex );
346                    }
347            }
348    
349            /**
350             * 返信å…?replyTo)アドレス配å?をセãƒ?ƒˆしますã?
351             *
352             * @param   replyTo 返信å…?replyTo)アドレス配å?
353             */
354            public void setReplyTo( final String[] replyTo ) {
355                    try {
356                            if( replyTo != null ) {
357                                    mimeMsg.setReplyTo( getAddress( replyTo ) );
358                            }
359                    } catch( AddressException ex ) {
360                            String errMsg = "Address Exception: ";
361                            throw new RuntimeException( errMsg,ex );
362                    } catch ( MessagingException mex ) {
363                            String errMsg = "MessagingException: ";
364                            throw new RuntimeException( errMsg,mex );
365                    }
366            }
367    
368            /**
369             * タイトルをセãƒ?ƒˆしますã?
370             *
371             * @param   subject タイトル
372             */
373            public void setSubject( final String subject ) {
374                    // Servlet からの読み込みは、iso8859_1 でエンコードされたæ–?­—が
375                    // セãƒ?ƒˆされるã?で、ユニコードに変更しておかなã�?�¨æ–?­—化けするã?
376                    // JRun 3.0 では、問題なかったが、tomcat3.1 では問題があるã€?
377                    try {
378                            if( subject != null ) {
379                                    mimeMsg.setSubject( mcSet.encodeWord( subject ) );
380                            }
381                    } catch( AddressException ex ) {
382                            String errMsg = "Address Exception: ";
383                            throw new RuntimeException( errMsg,ex );
384                    } catch ( MessagingException mex ) {
385                            String errMsg = "MessagingException: ";
386                            throw new RuntimeException( errMsg,mex );
387                    }
388            }
389    
390            /**
391             * 添付ファイル名é?列をセãƒ?ƒˆしますã?
392             *
393             * @param   fname 添付ファイル名é?åˆ?
394             */
395            public void setFilename( final String[] fname ) {
396                    if( fname != null && fname.length > 0 ) {
397                            int size = fname.length;
398                            filename = new String[size];
399                            System.arraycopy( fname,0,filename,0,size );
400                    }
401            }
402    
403            /**
404             * メãƒ?‚»ージ(本æ–?をセãƒ?ƒˆしますã?
405             *
406             * @param   msg メãƒ?‚»ージ(本æ–?
407             */
408            public void setMessage( final String msg ) {
409                    // なぜか、メãƒ?‚»ージのæœ?¾Œã?ã€?CR><LF>をセãƒ?ƒˆしておくã€?
410    
411                    if( msg == null ) { message = CR; }
412                    else {              message = msg + CR; }
413            }
414    
415            /**
416             * ãƒ?ƒ�ãƒ?‚°æƒ??の表示を行うかどã�?�‹をセãƒ?ƒˆしますã?
417             *
418             * @param   debug 表示有無[true/false]
419             */
420            public void setDebug( final boolean debug ) {
421                session.setDebug( debug );
422            }
423    
424            /**
425             * æŒ?®šされたファイルをã?ルチパートに追åŠ?�—ますã?
426             *
427             * @param   fileStr マルチパートするファイルå�?
428             */
429            private void addMmpFile( final String fileStr ) {
430                    try {
431                            MimeBodyPart mbp = new MimeBodyPart();
432                            FileDataSource fds = new FileDataSource(fileStr);
433                            mbp.setDataHandler(new DataHandler(fds));
434                            mbp.setFileName(MimeUtility.encodeText(fds.getName(), charset, "B"));
435                            mbp.setHeader("Content-Transfer-Encoding", "base64");
436                            mmPart.addBodyPart(mbp);
437                    }
438                    catch( UnsupportedEncodingException ex ) {
439                            String errMsg = "Multipart UnsupportedEncodingException: ";
440                            throw new RuntimeException( errMsg,ex );
441                    }
442                    catch ( MessagingException mex ) {
443                            String errMsg = "MessagingException: ";
444                            throw new RuntimeException( errMsg,mex );
445                    }
446            }
447    
448            /**
449             * æŒ?®šされたæ–?­—å?をã?ルチパートに追åŠ?�—ますã?
450             *
451             * @param   textStr マルチパートする文字å?
452             */
453            private void addMmpText( final String textStr ) {
454                    try {
455                            MimeBodyPart mbp = new MimeBodyPart();
456                            mbp.setText(textStr, charset);
457                            mbp.setHeader("Content-Transfer-Encoding", mcSet.getBit());
458                            mmPart.addBodyPart(mbp, 0);
459                    }
460                    catch ( MessagingException mex ) {
461                            String errMsg = "MessagingException: ";
462                            throw new RuntimeException( errMsg,mex );
463                    }
464            }
465    
466            /**
467             * æ–?­—エンコードをè€??した InternetAddress を作æ?しますã?
468             *
469             * @param   adrs オリジナルのアドレスæ–?­—å?
470             *
471             * @return  æ–?­—エンコードをè€??した InternetAddress
472             */
473            private InternetAddress getAddress( final String adrs ) {
474                    final InternetAddress rtnAdrs ;
475                    int sep = adrs.indexOf( '<' );
476                    if( sep >= 0 ) {
477                            String address  = adrs.substring( sep+1,adrs.indexOf( '>' ) ).trim();
478                            String personal = adrs.substring( 0,sep ).trim();
479    
480                            rtnAdrs = mcSet.getAddress( address,personal );
481                    }
482                    else {
483                            try {
484                                    rtnAdrs = new InternetAddress( adrs );
485                            }
486                            catch( AddressException ex ) {
487                                    String errMsg = "æŒ?®šã?アドレスをセãƒ?ƒˆできませんã€?
488                                                                            + "adrs=" + adrs  ;
489                                    throw new RuntimeException( errMsg,ex );
490                            }
491                    }
492    
493                    return rtnAdrs ;
494            }
495    
496            /**
497             * æ–?­—エンコードをè€??した InternetAddress を作æ?しますã?
498             * これは、アドレスæ–?­—é?列からã?InternetAddress 配å?を作æ?するã€?
499             * コンビニエンスメソãƒ?ƒ‰ですã?
500             * 処ç�?��のもã?はã€?getAddress( String ) をループしてã�?‚‹ã�?�‘ですã?
501             *
502             * @param   adrs アドレスæ–?­—é?åˆ?
503             *
504             * @return  æ–?­—エンコード後ã?InternetAddress配å?
505             * @see     #getAddress( String )
506             */
507            private InternetAddress[] getAddress( final String[] adrs ) {
508                    InternetAddress[] rtnAdrs = new InternetAddress[adrs.length];
509                    for( int i=0; i<adrs.length; i++ ) {
510                            rtnAdrs[i] = getAddress( adrs[i] );
511                    }
512    
513                    return rtnAdrs ;
514            }
515    
516            /**
517             * コマンドから実行できる、テスト用の main メソãƒ?ƒ‰ですã?
518             *
519             * Usage: java org.opengion.fukurou.mail.MailTX &lt;from&gt; &lt;to&gt; &lt;host&gt; [&lt;file&gt; ....]
520             * で、è¤?•°の添付ファイルをé?付することができますã?
521             *
522             * @param       args    コマンド引数配å?
523             * @throws Exception なんらかã?エラーが発生したå?合ã?
524             */
525            public static void main( final String[] args ) throws Exception {
526                    if(args.length < 3) {
527                            LogWriter.log("Usage: java org.opengion.fukurou.mail.MailTX <from> <to> <host> [<file> ....]");
528                            return ;
529                    }
530    
531                    String host  = args[2] ;
532                    String chset = "ISO-2022-JP" ;
533    
534                    MailTX sender = new MailTX( host,chset );
535    
536                    sender.setFrom( args[0] );
537                    String[] to = { args[1] };
538                    sender.setTo( to );
539    
540                    if( args.length > 3 ) {
541                            String[] filename = new String[ args.length-3 ];
542                            for( int i=0; i<args.length-3; i++ ) {
543                                    filename[i] = args[i+3];
544                            }
545                            sender.setFilename( filename );
546                    }
547    
548                    sender.setSubject( "メール送信ãƒ?‚¹ãƒ? );
549                    String msg = "これはãƒ?‚¹トメールですã?" + CR +
550                                                    "ã�?�¾く受信できましたã�?" + CR;
551                    sender.setMessage( msg );
552    
553                    sender.sendmail();
554            }
555    }