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