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 */
016package org.opengion.fukurou.mail;
017
018import org.opengion.fukurou.system.OgRuntimeException ;         // 6.4.2.0 (2016/01/29)
019import org.opengion.fukurou.system.LogWriter;
020import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
021
022import java.io.UnsupportedEncodingException;
023import java.util.Properties;
024import java.util.Date;
025
026import jakarta.activation.FileDataSource;
027import jakarta.activation.DataHandler;
028import jakarta.mail.internet.InternetAddress;
029import jakarta.mail.internet.AddressException;
030import jakarta.mail.internet.MimeMessage;
031import jakarta.mail.internet.MimeMultipart;
032import jakarta.mail.internet.MimeBodyPart;
033import jakarta.mail.internet.MimeUtility;
034import jakarta.mail.Authenticator;                                                                      // 6.2.4.1 (2015/05/22) SMTP_AUTH 対応
035import jakarta.mail.PasswordAuthentication;                                                     // 6.2.4.1 (2015/05/22) SMTP_AUTH 対応
036import jakarta.mail.Store;
037import jakarta.mail.Transport;
038import jakarta.mail.Session;
039import jakarta.mail.Message;
040import jakarta.mail.MessagingException;
041import jakarta.mail.IllegalWriteException;
042
043/**
044 * MailTX は、SMTPプロトコルによるメール送信プログラムです。
045 *
046 * E-Mail で日本語を送信する場合、ISO-2022-JP(JISコード)化して、7bit で
047 * エンコードして送信する必要がありますが、Windows系の特殊文字や、unicodeと
048 * 文字のマッピングが異なる文字などが、文字化けします。
049 * 対応方法としては、
050 * 1.Windows-31J + 8bit 送信
051 * 2.ISO-2022-JP に独自変換 + 7bit 送信
052 * の方法があります。
053 * 今回、この2つの方法について、対応いたしました。
054 *
055 * ※ 6.3.8.0 (2015/09/11)
056 *    useSSL属性=true に設定すると、protocolに、smtps を使用します。
057 *
058 * @version  4.0
059 * @author   Kazuhiko Hasegawa
060 * @since    JDK5.0,
061 */
062public class MailTX {
063        private static final String AUTH_PBS   = "POP_BEFORE_SMTP";             // 5.4.3.2
064        private static final String AUTH_SMTPA = "SMTP_AUTH";                   // 6.2.4.1 (2015/05/22) SMTP_AUTH 対応
065
066        /** メーラーの名称  {@value} */
067        public static final String MAILER = "openGion Mail Ver 6.0";
068
069        private final String    charset  ;      // Windwos-31J , MS932 , UTF-8 , ISO-2022-JP
070        private String[]                filename ;
071        private String                  message  ;
072        private Session                 session  ;
073        private MimeMultipart   mmPart   ;
074        private MimeMessage             mimeMsg  ;
075        private MailCharset             mcSet    ;
076
077        /**
078         * メールサーバーとデフォルト文字エンコーディングを指定して、オブジェクトを構築します。
079         *
080         * デフォルト文字エンコーディングは、ISO-2022-JP です。
081         *
082         * @param       host    メールサーバー
083         * @throws      IllegalArgumentException 引数が null の場合。
084         */
085        public MailTX( final String host ) {
086                this( host,"ISO-2022-JP" );
087        }
088
089        /**
090         * メールサーバーとデフォルト文字エンコーディングを指定して、オブジェクトを構築します。
091         *
092         * 文字エンコーディングには、Windwos-31J , MS932 , UTF-8 , ISO-2022-JP を指定できます。
093         *
094         * @og.rev 5.4.3.2 (2012/01/06) 認証対応のため
095         * @og.rev 5.8.1.1 (2014/11/14) 認証ポート追加
096         * @og.rev 6.3.8.0 (2015/09/11) SSL接続するかどうかを指定するパラメータを追加します。
097         *
098         * @param       host    メールサーバー
099         * @param       charset 文字エンコーディング
100         * @throws      IllegalArgumentException 引数が null の場合。
101         */
102        public MailTX( final String host , final String charset ) {
103//              this( host,charset,null,null,null,null,null,false );
104                this( host,charset,null,null,null,null,null,false,false );              // TSL,SSL
105        }
106
107        /**
108         * メールサーバーと文字エンコーディングを指定して、オブジェクトを構築します。
109         * 認証を行う場合は認証方法を指定します。
110         *
111         * 文字エンコーディングには、Windwos-31J , MS932 , ISO-2022-JP を指定できます。
112         *
113         * @og.rev 5.1.9.0 (2010/08/01) mail.smtp.localhostの設定追加
114         * @og.rev 5.4.3.2 (2012/01/06) 認証対応(POP Before SMTP)。引数3つ追加(将来的にはAuthentication対応?)
115         * @og.rev 5.8.1.1 (2014/11/14) 認証ポート追加
116         * @og.rev 6.2.4.1 (2015/05/22) SMTP_AUTH 対応
117         * @og.rev 6.3.8.0 (2015/09/11) SSL接続するかどうかを指定するパラメータを追加します。
118         * @og.rev 5.9.29.2 (2018/02/16) STARTTLS対応(キーワードをVer5 にあわせます)
119         * @og.rev 5.10.20.1 (2020/03/03) 添付ファイル名文字化け対策
120         *
121         * @param       host            メールサーバー
122         * @param       charset         文字エンコーディング
123         * @param       smtpPort        SMTPポート
124         * @param       authType        認証方法(POP_BEFORE_SMTP , SMTP_AUTH)
125         * @param       authPort        認証ポート
126         * @param       authUser        認証ユーザ
127         * @param       authPass        認証パスワード
128         * @param       useStarttls 暗号化通信設定(STARTTLS) 5.9.29.2
129         * @param       useSSL          SSL接続するかどうか
130         * @throws      IllegalArgumentException 引数が null の場合。
131         */
132        public MailTX( final String host , final String charset, final String smtpPort
133//                              ,final String authType, final String authPort, final String authUser, final String authPass, final boolean useSSL) {
134                                ,final String authType, final String authPort, final String authUser, final String authPass, final boolean useStarttls, final boolean useSSL ) {
135                if( host == null ) {
136                        final String errMsg = "host に null はセット出来ません。";
137                        throw new IllegalArgumentException( errMsg );
138                }
139
140                if( charset == null ) {
141                        final String errMsg = "charset に null はセット出来ません。";
142                        throw new IllegalArgumentException( errMsg );
143                }
144
145                this.charset = charset;
146
147                mcSet = MailCharsetFactory.newInstance( charset );
148
149                // 5.10.20.1 (2020/03/03) 添付ファイル名文字化け対策(暫定)
150                System.setProperty("mail.mime.splitlongparameters", "false");
151                System.setProperty("mail.mime.encodeparameters", "false" );
152
153                final Properties prop = new Properties();
154                prop.setProperty("mail.mime.charset"                    , charset );
155                prop.setProperty("mail.mime.decodetext.strict"  , "false" );
156                prop.setProperty("mail.mime.address.strict"             , "false" );
157                prop.setProperty("mail.smtp.host"                               , host );
158                // 5.1.9.0 (2010/08/01) 設定追加
159                prop.setProperty("mail.smtp.localhost"                  , host );
160                prop.setProperty("mail.host"                                    , host );       // MEssage-ID の設定に利用
161
162                // 5.4.3.2 ポート追加
163                if( smtpPort != null && smtpPort.length() > 0 ){
164                        prop.setProperty("mail.smtp.port"                       , smtpPort);    // MEssage-ID の設定に利用
165                }
166
167                // 6.2.4.1 (2015/05/22) SMTP_AUTH 対応
168                Authenticator myAuth = null;
169                if( AUTH_SMTPA.equals( authType ) ) {
170                        prop.setProperty("mail.smtp.auth"                       , "true" );
171                        prop.setProperty("mail.transport.protocol"      , "smtps" );    // 6.3.8.0 (2015/09/11)
172                        // 6.3.9.0 (2015/11/06) 名前付き static 内部クラスにリファクタリング(findbugs)
173        //              myAuth = new MyAuthenticator( authUser,authPass );
174                        myAuth = new Authenticator() {                                  // 5.8.7.1 (2015/05/22) SMTP認証用クラス
175                                /**
176                                 * パスワード認証が必要な時には呼ばれる。
177                                 *
178                                 * @return      PasswordAuthenticationオブジェクト
179                                 */
180                                @Override
181                                protected PasswordAuthentication getPasswordAuthentication() {
182                                        return new PasswordAuthentication( authUser,authPass );
183                                }
184                        };
185                }
186
187                // 6.3.8.0 (2015/09/11) SSL接続するかどうかを指定するパラメータを追加します。
188                // 5.9.29.2 (2018/02/16) STARTTLS対応  (キーワードをVer5 にあわせます)
189//              if( useSSL ) {
190                if ( useStarttls || useSSL ) {
191                        prop.setProperty("mail.smtp.starttls.enable"            , "true" );                                                     // 6.3.8.0 (2015/09/11)
192                        prop.setProperty("mail.smtp.starttls.required"          , "true" );                                                     // 6.3.8.0 (2015/09/11)
193                //      prop.setProperty("mail.smtp.socketFactory.class"        , "javax.net.ssl.SSLSocketFactory" );
194                //      prop.setProperty("mail.smtp.socketFactory.fallback"     , "false" );
195                //      prop.setProperty("mail.smtp.socketFactory.port"         , String.valueOf( smtpPort ) );         // 587
196                //      prop.setProperty("mail.transport.protocol"                      , "smtps" );    // 6.3.8.0 (2015/09/11)
197                }
198
199                // 6.9.1.0 (2018/02/26) STL/SSL 両対応
200                if ( useSSL ) {
201                        prop.setProperty("mail.smtp.socketFactory.class"        , "javax.net.ssl.SSLSocketFactory" );
202                        prop.setProperty("mail.smtp.socketFactory.fallback"     , "false" );
203                        prop.setProperty("mail.smtp.socketFactory.port"         , String.valueOf( smtpPort ) );         // 465
204                        prop.setProperty("mail.transport.protocol"                      , "smtps" );    // 6.3.8.0 (2015/09/11)
205                }
206
207                session = Session.getInstance( prop, myAuth );          // 6.2.4.1 (2015/05/22) SMTP_AUTH 対応
208
209                // POP before SMTP認証処理 5.4.3.2
210                if( AUTH_PBS.equals( authType ) ){
211                        try{
212                                // 5.8.1.1 (2014/11/14) 認証ポート追加
213                                final int aPort = authPort == null || authPort.isEmpty() || authPass == null || authPass.isEmpty() ? -1 : Integer.parseInt(authPort) ;
214                                final Store store = session.getStore("pop3");
215                                store.connect( host,aPort,authUser,authPass );  // 5.8.1.1 (2014/11/14) 認証ポート追加
216                                store.close();
217                        }
218                        catch( final MessagingException ex ) {
219                                final String errMsg = "POP3 Auth Exception: "+ host + "/" + authUser;
220                                throw new OgRuntimeException( errMsg,ex );
221                        }
222                }
223
224                mimeMsg = new MimeMessage( session );
225        }
226
227        /**
228         * メールを送信します。
229         *
230         */
231        public void sendmail() {
232                try {
233                        mimeMsg.setSentDate( new Date() );
234
235                        if( filename == null || filename.length == 0 ) {
236                                mcSet.setTextContent( mimeMsg,message );
237                        }
238                        else {
239                                mmPart = new MimeMultipart();
240                                mimeMsg.setContent( mmPart );
241                                // テキスト本体の登録
242                                addMmpText( message );
243
244                                // 添付ファイルの登録
245                                for( int i=0; i<filename.length; i++ ) {
246                                        addMmpFile( filename[i] );
247                                }
248                        }
249
250                        mimeMsg.setHeader("X-Mailer", MAILER );
251                        mimeMsg.setHeader("Content-Transfer-Encoding", mcSet.getBit() );
252                        Transport.send( mimeMsg );
253                }
254                catch( final AddressException ex ) {
255                        final String errMsg = "Address Exception: " + ex.getMessage() ;
256                        throw new OgRuntimeException( errMsg,ex );
257                }
258                catch( final MessagingException mex ) {
259                        final String errMsg = "MessagingException: " + mex.getMessage() ;
260                        throw new OgRuntimeException( errMsg,mex );
261                }
262        }
263
264        /**
265         * MimeMessageをリセットします。
266         *
267         * sendmail() でメールを送信後、セッションを閉じずに別のメールを送信する場合、
268         * リセットしてから、各種パラメータを再設定してください。
269         * その場合は、すべてのパラメータが初期化されていますので、もう一度
270         * 設定しなおす必要があります。
271         *
272         */
273        public void reset() {
274                mimeMsg = new MimeMessage(session);
275        }
276
277        /**
278         * 送信元(FROM)アドレスをセットします。
279         *
280         * @param   from 送信元(FROM)アドレス
281         */
282        public void setFrom( final String from ) {
283                try {
284                        if( from != null ) {
285                                mimeMsg.setFrom( getAddress( from ) );
286                        }
287                } catch( final AddressException ex ) {
288                        final String errMsg = "Address Exception: " + ex.getMessage() ;
289                        throw new OgRuntimeException( errMsg,ex );
290                } catch( final MessagingException mex ) {
291                        final String errMsg = "MessagingException: " + mex.getMessage() ;
292                        throw new OgRuntimeException( errMsg,mex );
293                }
294        }
295
296        /**
297         * 送信先(TO)アドレス配列をセットします。
298         *
299         * @param   to 送信先(TO)アドレス配列(可変長引数)
300         */
301        public void setTo( final String... to ) {
302                try {
303                        if( to != null && to.length > 0 ) {             // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
304                                mimeMsg.setRecipients( Message.RecipientType.TO, getAddress( to ) );
305                        }
306                } catch( final AddressException ex ) {
307                        final String errMsg = "Address Exception: " + ex.getMessage() ;
308                        throw new OgRuntimeException( errMsg,ex );
309                } catch( final MessagingException mex ) {
310                        final String errMsg = "MessagingException: " + mex.getMessage() ;
311                        throw new OgRuntimeException( errMsg,mex );
312                }
313        }
314
315        /**
316         * 送信先(CC)アドレス配列をセットします。
317         *
318         * @param   cc 送信先(CC)アドレス配列(可変長引数)
319         */
320        public void setCc( final String... cc ) {
321                try {
322                        if( cc != null && cc.length > 0 ) {             // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
323                                mimeMsg.setRecipients( Message.RecipientType.CC, getAddress( cc ) );
324                        }
325                } catch( final AddressException ex ) {
326                        final String errMsg = "Address Exception: " + ex.getMessage() ;
327                        throw new OgRuntimeException( errMsg,ex );
328                } catch( final MessagingException mex ) {
329                        final String errMsg = "MessagingException: " + mex.getMessage() ;
330                        throw new OgRuntimeException( errMsg,mex );
331                }
332        }
333
334        /**
335         * 送信先(BCC)アドレス配列をセットします。
336         *
337         * @param   bcc 送信先(BCC)アドレス配列(可変長引数)
338         */
339        public void setBcc( final String... bcc ) {
340                try {
341                        if( bcc != null && bcc.length > 0 ) {           // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
342                                mimeMsg.setRecipients( Message.RecipientType.BCC, getAddress( bcc ) );
343                        }
344                } catch( final AddressException ex ) {
345                        final String errMsg = "Address Exception: " + ex.getMessage() ;
346                        throw new OgRuntimeException( errMsg,ex );
347                } catch( final MessagingException mex ) {
348                        final String errMsg = "MessagingException: " + mex.getMessage() ;
349                        throw new OgRuntimeException( errMsg,mex );
350                }
351        }
352
353        /**
354         * 送信先(TO)アドレス配列をクリアします。
355         * @og.rev 4.3.6.0 (2009/04/01) 新規追加
356         *
357         */
358        public void clearTo() {
359                try {
360                        mimeMsg.setRecipients( Message.RecipientType.TO, (InternetAddress[])null );
361                }
362                // 7.2.9.5 (2020/11/28) PMD:'catch' branch identical to 'IllegalWriteException' branch
363                catch( final IllegalWriteException | IllegalStateException ex ) {
364                        final String errMsg = "Address Exception: " + ex.getMessage() ;
365                        throw new OgRuntimeException( errMsg,ex );
366                }
367//              catch( final IllegalWriteException ex ) {
368//                      final String errMsg = "Address Exception: " + ex.getMessage() ;
369//                      throw new OgRuntimeException( errMsg,ex );
370//              }
371//              catch( final IllegalStateException ex ) {
372//                      final String errMsg = "Address Exception: " + ex.getMessage() ;
373//                      throw new OgRuntimeException( errMsg,ex );
374//              }
375                catch( final MessagingException mex ) {
376                        final String errMsg = "MessagingException: " + mex.getMessage() ;
377                        throw new OgRuntimeException( errMsg,mex );
378                }
379        }
380
381        /**
382         * 送信先(CC)アドレス配列をクリアします。
383         *
384         * @og.rev 4.3.6.0 (2009/04/01) 新規追加
385         */
386        public void clearCc() {
387                try {
388                        mimeMsg.setRecipients( Message.RecipientType.CC, (InternetAddress[])null );
389                }
390                // 7.2.9.5 (2020/11/28) PMD:'catch' branch identical to 'IllegalWriteException' branch
391                catch( final IllegalWriteException | IllegalStateException ex ) {
392                        final String errMsg = "Address Exception: " + ex.getMessage() ;
393                        throw new OgRuntimeException( errMsg,ex );
394                }
395//              catch( final IllegalWriteException ex ) {
396//                      final String errMsg = "Address Exception: " + ex.getMessage() ;
397//                      throw new OgRuntimeException( errMsg,ex );
398//              }
399//              catch( final IllegalStateException ex ) {
400//                      final String errMsg = "Address Exception: " + ex.getMessage() ;
401//                      throw new OgRuntimeException( errMsg,ex );
402//              }
403                catch( final MessagingException mex ) {
404                        final String errMsg = "MessagingException: " + mex.getMessage() ;
405                        throw new OgRuntimeException( errMsg,mex );
406                }
407        }
408
409        /**
410         * 送信先(BCC)アドレス配列をクリアします。
411         * @og.rev 4.3.6.0 (2009/04/01) 新規追加
412         *
413         */
414        public void clearBcc() {
415                try {
416                        mimeMsg.setRecipients( Message.RecipientType.BCC, (InternetAddress[])null );
417                }
418                // 7.2.9.5 (2020/11/28) PMD:'catch' branch identical to 'IllegalWriteException' branch
419                catch( final IllegalWriteException | IllegalStateException ex ) {
420                        final String errMsg = "Address Exception: " + ex.getMessage() ;
421                        throw new OgRuntimeException( errMsg,ex );
422                }
423//              catch( final IllegalWriteException ex ) {
424//                      final String errMsg = "Address Exception: " + ex.getMessage() ;
425//                      throw new OgRuntimeException( errMsg,ex );
426//              }
427//              catch( final IllegalStateException ex ) {
428//                      final String errMsg = "Address Exception: " + ex.getMessage() ;
429//                      throw new OgRuntimeException( errMsg,ex );
430//              }
431                catch( final MessagingException mex ) {
432                        final String errMsg = "MessagingException: " + mex.getMessage() ;
433                        throw new OgRuntimeException( errMsg,mex );
434                }
435        }
436
437        /**
438         * 返信元(replyTo)アドレス配列をセットします。
439         *
440         * @param   replyTo 返信元(replyTo)アドレス配列(可変長引数)
441         */
442        public void setReplyTo( final String... replyTo ) {
443                try {
444                        if( replyTo != null && replyTo.length > 0 ) {           // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
445                                mimeMsg.setReplyTo( getAddress( replyTo ) );
446                        }
447                } catch( final AddressException ex ) {
448                        final String errMsg = "Address Exception: " + ex.getMessage() ;
449                        throw new OgRuntimeException( errMsg,ex );
450                } catch( final MessagingException mex ) {
451                        final String errMsg = "MessagingException: " + mex.getMessage() ;
452                        throw new OgRuntimeException( errMsg,mex );
453                }
454        }
455
456        /**
457         * タイトルをセットします。
458         *
459         * @param   subject タイトル
460         */
461        public void setSubject( final String subject ) {
462                // Servlet からの読み込みは、iso8859_1 でエンコードされた文字が
463                // セットされるので、ユニコードに変更しておかないと文字化けする。
464                // JRun 3.0 では、問題なかったが、tomcat3.1 では問題がある。
465                try {
466                        if( subject != null ) {
467                                mimeMsg.setSubject( mcSet.encodeWord( subject ) );
468                        }
469                } catch( final AddressException ex ) {
470                        final String errMsg = "Address Exception: " + ex.getMessage() ;
471                        throw new OgRuntimeException( errMsg,ex );
472                } catch( final MessagingException mex ) {
473                        final String errMsg = "MessagingException: " + mex.getMessage() ;
474                        throw new OgRuntimeException( errMsg,mex );
475                }
476        }
477
478        /**
479         * 添付ファイル名配列をセットします。
480         *
481         * @param   fname 添付ファイル名配列(可変長引数)
482         */
483        public void setFilename( final String... fname ) {
484                if( fname != null && fname.length > 0 ) {               // 6.1.1.0 (2015/01/17) 可変長引数でもnullは来る。
485                        final int size = fname.length;
486                        filename = new String[size];
487                        System.arraycopy( fname,0,filename,0,size );
488                }
489        }
490
491        /**
492         * メッセージ(本文)をセットします。
493         *
494         * @param   msg メッセージ(本文)
495         */
496        public void setMessage( final String msg ) {
497                // なぜか、メッセージの最後は、<CR><LF>をセットしておく。
498
499                if( msg == null ) { message = CR; }
500                else {              message = msg + CR; }
501        }
502
503        /**
504         * デバッグ情報の表示を行うかどうかをセットします。
505         *
506         * @param   debug 表示有無[true/false]
507         */
508        public void setDebug( final boolean debug ) {
509            session.setDebug( debug );
510        }
511
512        /**
513         * 指定されたファイルをマルチパートに追加します。
514         *
515         * @param   fileStr マルチパートするファイル名
516         */
517        private void addMmpFile( final String fileStr ) {
518                try {
519                        final MimeBodyPart mbp = new MimeBodyPart();
520                        final FileDataSource fds = new FileDataSource(fileStr);
521                        mbp.setDataHandler(new DataHandler(fds));
522                        mbp.setFileName(MimeUtility.encodeText(fds.getName(), charset, "B"));
523                        mbp.setHeader("Content-Transfer-Encoding", "base64");
524                        mmPart.addBodyPart(mbp);
525                }
526                catch( final UnsupportedEncodingException ex ) {
527                        final String errMsg = "Multipart UnsupportedEncodingException: " + ex.getMessage() ;
528                        throw new OgRuntimeException( errMsg,ex );
529                }
530                catch( final MessagingException mex ) {
531                        final String errMsg = "MessagingException: " + mex.getMessage() ;
532                        throw new OgRuntimeException( errMsg,mex );
533                }
534        }
535
536        /**
537         * 指定された文字列をマルチパートに追加します。
538         *
539         * @param   textStr マルチパートする文字列
540         */
541        private void addMmpText( final String textStr ) {
542                try {
543                        final MimeBodyPart mbp = new MimeBodyPart();
544                        mbp.setText(textStr, charset);
545                        mbp.setHeader("Content-Transfer-Encoding", mcSet.getBit());
546                        mmPart.addBodyPart(mbp, 0);
547                }
548                catch( final MessagingException mex ) {
549                        final String errMsg = "MessagingException: " + mex.getMessage() ;
550                        throw new OgRuntimeException( errMsg,mex );
551                }
552        }
553
554        /**
555         * 文字エンコードを考慮した InternetAddress を作成します。
556         *
557         * @param   adrs オリジナルのアドレス文字列
558         *
559         * @return  文字エンコードを考慮した InternetAddress
560         */
561        private InternetAddress getAddress( final String adrs ) {
562                final InternetAddress rtnAdrs ;
563                final int sep = adrs.indexOf( '<' );
564                if( sep >= 0 ) {
565                        final String address  = adrs.substring( sep+1,adrs.indexOf( '>' ) ).trim();
566                        final String personal = adrs.substring( 0,sep ).trim();
567
568                        rtnAdrs = mcSet.getAddress( address,personal );
569                }
570                else {
571                        try {
572                                rtnAdrs = new InternetAddress( adrs );
573                        }
574                        catch( final AddressException ex ) {
575                                final String errMsg = "指定のアドレスをセットできません。"
576                                                                        + "adrs=" + adrs + " , msg=" + ex.getMessage() ;
577                                throw new OgRuntimeException( errMsg,ex );
578                        }
579                }
580
581                return rtnAdrs ;
582        }
583
584        /**
585         * 文字エンコードを考慮した InternetAddress を作成します。
586         * これは、アドレス文字配列から、InternetAddress 配列を作成する、
587         * コンビニエンスメソッドです。
588         * 処理そのものは、#getAddress( String ) をループしているだけです。
589         *
590         * @param   adrs アドレス文字配列(可変長引数)
591         *
592         * @return  文字エンコード後のInternetAddress配列
593         * @see     #getAddress( String )
594         */
595        private InternetAddress[] getAddress( final String... adrs ) {
596                InternetAddress[] rtnAdrs = new InternetAddress[adrs.length];
597                for( int i=0; i<adrs.length; i++ ) {
598                        rtnAdrs[i] = getAddress( adrs[i] );
599                }
600
601                return rtnAdrs ;
602        }
603
604//      /**
605//       * jakarta.mail.Authenticator クラスの名前付き static 内部クラス
606//       *
607//       * SMTP認証用クラスとして使用します。6.2.4.1 (2015/05/22)
608//       *
609//       * 名前付き static 内部クラスにリファクタリングします(findbugs)。
610//       *
611//       * @og.rev 6.3.9.0 (2015/11/06) 新規追加
612//       * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
613//       *
614//       * @return  Authenticatorオブジェクト
615//       * @see     jakarta.mail.Authenticator
616//       */
617//      private static final class MyAuthenticator extends Authenticator {
618//              private final String authUser ;                         // 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
619//              private final String authPass ;                         // 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
620//
621//              /**
622//               * ユーザ,パスワードを指定したコンストラクター。
623//               *
624//               * @og.rev 6.3.9.0 (2015/11/06) 新規追加
625//               * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. It is a good practice to call super() in a constructor
626//               *
627//               * @param       authUser        認証ユーザ
628//               * @param       authPass        認証パスワード
629//               */
630//              public MyAuthenticator( final String authUser, final String authPass ) {
631//                      super();
632//                      this.authUser = authUser;
633//                      this.authPass = authPass;
634//              }
635//
636//              /**
637//               * パスワード認証が必要な時に呼ばれます。
638//               *
639//               * @og.rev 6.3.9.0 (2015/11/06) 新規追加
640//               *
641//               * @return      PasswordAuthenticationオブジェクト
642//               */
643//              @Override
644//              protected PasswordAuthentication getPasswordAuthentication() {
645//                      return new PasswordAuthentication( authUser,authPass );
646//              }
647//      }
648
649        /**
650         * コマンドから実行できる、テスト用の main メソッドです。
651         *
652         * Usage: java org.opengion.fukurou.mail.MailTX &lt;from&gt; &lt;to&gt; &lt;host&gt; [&lt;file&gt; ....]
653         * で、複数の添付ファイルを送付することができます。
654         *
655         * @og.rev 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
656         *
657         * @param       args    コマンド引数配列
658         */
659        public static void main( final String[] args ) {
660                if( args.length < 3 ) {
661                        LogWriter.log("Usage: java org.opengion.fukurou.mail.MailTX <from> <to> <host> [<file> ....]");
662                        return ;
663                }
664
665                final String host  = args[2] ;
666                final String chset = "ISO-2022-JP" ;
667
668                final MailTX sender = new MailTX( host,chset );
669
670                sender.setFrom( args[0] );
671                final String[] to = { args[1] };
672                sender.setTo( to );
673
674                if( args.length > 3 ) {
675                        final String[] filename = new String[args.length-3];
676                        // 6.3.6.0 (2015/08/16) System.arraycopy が使える箇所は、置き換えます。
677                        System.arraycopy( args,3,filename,0,filename.length );          // 6.3.6.0 (2015/08/16)
678                        sender.setFilename( filename );
679                }
680
681                sender.setSubject( "メール送信テスト" );
682                final String msg = "これはテストメールです。" + CR
683                                                +       "うまく受信できましたか?" + CR;
684                sender.setMessage( msg );
685
686                sender.sendmail();
687        }
688}