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