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 java.io.InputStream; 019 import java.io.OutputStream; 020 import java.io.ByteArrayOutputStream; 021 import java.io.ByteArrayInputStream; 022 import java.io.UnsupportedEncodingException; 023 import java.io.IOException; 024 025 import javax.activation.DataHandler; 026 import javax.activation.DataSource; 027 import javax.mail.internet.InternetAddress; 028 import javax.mail.internet.MimeMessage; 029 import javax.mail.internet.MimeUtility; 030 import javax.mail.MessagingException; 031 import com.sun.mail.util.BASE64EncoderStream; 032 033 import java.nio.charset.Charset; // 5.5.2.6 (2012/05/25) 034 035 /** 036 * MailCharset は、E-Mail 送信時?エンコードに応じた??行う為の? 037 * インターフェースです? 038 * 039 * E-Mail で日本語を送信する場合?ISO-2022-JP(JISコー?化して?bit で 040 * エンコードして送信する?がありますが、Windows系の特殊文字や、unicodeと 041 * ??マッピングが異なる文字などが??化けします? 042 * 対応方法としては? 043 * 『1.Windows-31J + 8bit 送信? 044 * 『2.ISO-2022-JP に独自変換 + 7bit 送信? 045 * の方法があります? 046 * 今回、この?つの方法につ?、それぞれサブクラス化を行い、??きるように 047 * したのが?こ?インターフェース、およ?、サブクラスです? 048 * 049 * 『1.Windows-31J + 8bit 送信』?方法???常の JavaMail API に準拠して 050 * 処?行う、Mail_Windows31J_Charset サブクラスで実?て?す? 051 * 古?イラーおよび、古?ールサーバ?ではメール転送できな?? 052 * こ?方式?、社?使用する場合?みに、利用できますが、主としてWindows系の 053 * 社?ス?においては、こちら?方が?なにかとトラブルは少な?思います? 054 * 055 * 『2.ISO-2022-JP に独自変換 + 7bit 送信』?実??? 056 * JAVA PRESS Vol.37 (http://www.gihyo.co.jp/magazines/javapress)の 057 * 【特??決定版??サーバサイドJavaの日本語?? 058 * 第3?JavaMailの日本語???ログラミング……木下信 059 *“?ルチ?ラ?フォー??な日本語メール送信?完?解説 060 * でのサンプルアプリケーション 061 * http://www.gihyo.co.jp/book/2004/225371/download/toku1_3.zip 062 * を?使用して、Mail_ISO2022JP_Charset サブクラスで実?て?す? 063 * 064 * これら?サブクラスは、MailCharsetFactory ファクトリクラスより、作?されます? 065 * そ?場合?引数のキャラクタセ?名?、Windows-31J 、MS932 か?それ以外となって?す? 066 * それ以外が?された場合?、ISO-2022-JP を使用します? 067 * 068 * @version 4.0 069 * @author Kazuhiko Hasegawa 070 * @since JDK5.0, 071 */ 072 public interface MailCharset { 073 074 /** 075 * ?ストをセ?します? 076 * Part#setText() の代わりにこちらを使??します? 077 * 078 * @param mimeMsg MimeMessage?取り込み件数 079 * @param text 設定するテキス? 080 * @throws RuntimeException(MessagingException) 081 */ 082 void setTextContent( MimeMessage mimeMsg, String text ) ; 083 084 /** 085 * 日本語を含???用?ストを生?します? 086 * 変換結果は ASCII なので、これをそ?まま setSubject ?InternetAddress 087 * のパラメタとして使用してください? 088 * 089 * @param text 設定するテキス? 090 * 091 * @return 日本語を含???用?ス? 092 * @throws RuntimeException(UnsupportedEncodingException) 093 */ 094 String encodeWord( String text ) ; 095 096 /** 097 * 日本語を含?ドレスを生成します? 098 * personal に、日本語が含まれると想定して?す? 099 * サブクラスで、日本語??行う場合?方法?、それぞれ異なります? 100 * 101 * @param address アドレス部? 102 * @param personal 日本語?説明部? 103 * 104 * @return 日本語を含?ドレス 105 * @throws RuntimeException(UnsupportedEncodingException) 106 */ 107 InternetAddress getAddress( String address,String personal ) ; 108 109 /** 110 * Content-Transfer-Encoding を指定する?合? ビット数を返します? 111 * 112 * Windows系は?bit / ISO-2022-JP 系は?bit になります? 113 * 114 * @return ビット数 115 */ 116 String getBit() ; 117 } 118 119 /** 120 * MailCharsetFactory は、MailCharset インターフェースを実?たサブクラス? 121 * 作?する ファクトリクラスです? 122 * 123 * 引数のキャラクタセ?名が、Windows-31J 、MS932 の場合?? 124 * 『1.Windows-31J + 8bit 送信?の実?ある、Mail_Windows31J_Charset 125 * サブクラスを返します? 126 * それ以外が?された場合?、ISO-2022-JP を使用して、??.ISO-2022-JP に独自変換 + 7bit 送信? 127 * の実?ある、Mail_ISO2022JP_Charset サブクラスを返します? 128 * 129 * @version 4.0 130 * @author Kazuhiko Hasegawa 131 * @since JDK5.0, 132 */ 133 class MailCharsetFactory { 134 135 /** 136 * インスタンスの生?を抑止します? 137 */ 138 private MailCharsetFactory() { 139 // 何もありません?PMD エラー回避) 140 } 141 142 /** 143 * キャラクタセ?に応じた?MailCharset オブジェクトを返します? 144 * 145 * Windows-31J 、MS932 、Shift_JIS の場合?、Mail_Windows31J_Charset 146 * そ?他?、ISO-2022-JP として、Mail_ISO2022JP_Charset を返します? 147 * 148 * 注意:null の場合?、デフォルトではなく?Mail_ISO2022JP_Charset を返します? 149 * 150 * @param charset キャラクタセ?[Windows-31J/MS932/Shift_JIS/そ?他] 151 * 152 * @return MailCharset 153 */ 154 static MailCharset newInstance( final String charset ) { 155 final MailCharset mcset; 156 157 if( "MS932".equalsIgnoreCase( charset ) || 158 "Shift_JIS".equalsIgnoreCase( charset ) || 159 "Windows-31J".equalsIgnoreCase( charset ) ) { 160 mcset = new Mail_Windows31J_Charset( charset ); 161 } 162 else { 163 mcset = new Mail_ISO2022JP_Charset(); 164 } 165 return mcset ; 166 } 167 } 168 169 /** 170 * MailCharset インターフェースを実??Windwos-31J エンコード時のサブクラスです? 171 * 172 * 『1.Windows-31J + 8bit 送信?の実?す? 173 * 174 * @version 4.0 175 * @author Kazuhiko Hasegawa 176 * @since JDK5.0, 177 */ 178 class Mail_Windows31J_Charset implements MailCharset { 179 private final String charset ; // "Windows-31J" or "MS932" 180 181 /** 182 * 引数に、エンコード方式を?して、作?するコンストラクタです? 183 * 184 * @param charset String 185 */ 186 public Mail_Windows31J_Charset( final String charset ) { 187 this.charset = charset; 188 } 189 190 /** 191 * ?ストをセ?します? 192 * Part#setText() の代わりにこちらを使??します? 193 * 194 * @param mimeMsg MimeMessage 195 * @param text String 196 * @throws RuntimeException(MessagingException) 197 */ 198 public void setTextContent( final MimeMessage mimeMsg, final String text ) { 199 try { 200 mimeMsg.setText( text,charset ); // "text/plain" Content 201 } 202 catch( MessagingException ex ) { 203 String errMsg = "???ストをセ?できません? 204 + "text=" + text + " , charset=" + charset ; 205 throw new RuntimeException( errMsg,ex ); 206 } 207 } 208 209 /** 210 * 日本語を含???用?ストを生?します? 211 * 変換結果は ASCII なので、これをそ?まま setSubject ?InternetAddress 212 * のパラメタとして使用してください? 213 * 214 * @param text String 215 * 216 * @return 日本語を含???用?ス? 217 * @throws RuntimeException(UnsupportedEncodingException) 218 */ 219 public String encodeWord( final String text ) { 220 try { 221 return MimeUtility.encodeText( text, charset, "B" ); 222 } 223 catch( UnsupportedEncodingException ex ) { 224 String errMsg = "??エンコードが出来ません? 225 + "text=" + text + " , charset=" + charset ; 226 throw new RuntimeException( errMsg,ex ); 227 } 228 } 229 230 /** 231 * 日本語を含?ドレスを生成します? 232 * personal に、日本語が含まれると想定して?す? 233 * サブクラスで、日本語??行う場合?方法?、それぞれ異なります? 234 * 235 * @param address String 236 * @param personal String 237 * 238 * @return InternetAddress 239 * @throws RuntimeException(UnsupportedEncodingException) 240 */ 241 public InternetAddress getAddress( final String address,final String personal ) { 242 try { 243 return new InternetAddress( address,personal,charset ); 244 } 245 catch( UnsupportedEncodingException ex ) { 246 String errMsg = "??エンコードが出来ません? 247 + "address=" + address + " , charset=" + charset ; 248 throw new RuntimeException( errMsg,ex ); 249 } 250 } 251 252 /** 253 * Content-Transfer-Encoding を指定する?合? ビット数を返します? 254 * 255 * Windows系は?bit / ISO-2022-JP 系は?bit になります? 256 * 257 * @return ビット数("8bit" 固? 258 */ 259 public String getBit() { 260 return "8bit" ; 261 } 262 } 263 264 /** 265 * MailCharset インターフェースを実??ISO-2022-JP エンコード時のサブクラスです? 266 * 267 * 『2.ISO-2022-JP に独自変換 + 7bit 送信?の実?す? 268 * 269 * @version 4.0 270 * @author Kazuhiko Hasegawa 271 * @since JDK5.0, 272 */ 273 class Mail_ISO2022JP_Charset implements MailCharset { 274 275 /** 276 * プラ?フォー?存??ォルト? Charset です? 277 * プラ?フォー?存?を?慮する場合?エンコード指定で作?しておく事をお勧めします? 278 * 279 * @og.rev 5.5.2.6 (2012/05/25) findbugs対? 280 */ 281 private static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ; 282 283 /** 284 * ?ストをセ?します? 285 * Part#setText() の代わりにこちらを使??します? 286 * 287 * @param mimeMsg MimeMessage 288 * @param text String 289 * @throws RuntimeException(MessagingException) 290 */ 291 public void setTextContent( final MimeMessage mimeMsg, final String text ) { 292 try { 293 // mimeMsg.setText(text, "ISO-2022-JP"); 294 mimeMsg.setDataHandler(new DataHandler(new JISDataSource(text))); 295 } 296 catch( MessagingException ex ) { 297 String errMsg = "???ストをセ?できません? 298 + "text=" + text ; 299 throw new RuntimeException( errMsg,ex ); 300 } 301 } 302 303 /** 304 * 日本語を含???用?ストを生?します? 305 * 変換結果は ASCII なので、これをそ?まま setSubject ?InternetAddress 306 * のパラメタとして使用してください? 307 * 308 * @param text String 309 * 310 * @return 日本語を含???用?ス? 311 * @throws RuntimeException(UnsupportedEncodingException) 312 */ 313 public String encodeWord( final String text ) { 314 try { 315 return "=?ISO-2022-JP?B?" + 316 new String( 317 BASE64EncoderStream.encode( 318 CharCodeConverter.sjisToJis( 319 UnicodeCorrecter.correctToCP932(text).getBytes("Windows-31J") 320 ) 321 ) 322 ,DEFAULT_CHARSET ) + "?="; // 5.5.2.6 (2012/05/25) findbugs対? 323 } 324 catch( UnsupportedEncodingException ex ) { 325 String errMsg = "??エンコードが出来ません? 326 + "text=" + text + " , charset=Windows-31J" ; 327 throw new RuntimeException( errMsg,ex ); 328 } 329 } 330 331 /** 332 * 日本語を含?ドレスを生成します? 333 * personal に、日本語が含まれると想定して?す? 334 * サブクラスで、日本語??行う場合?方法?、それぞれ異なります? 335 * 336 * @param address String 337 * @param personal String 338 * 339 * @return InternetAddress 340 * @throws RuntimeException(UnsupportedEncodingException) 341 */ 342 public InternetAddress getAddress( final String address,final String personal ) { 343 try { 344 return new InternetAddress( address,encodeWord( personal ) ); 345 } 346 catch( UnsupportedEncodingException ex ) { 347 String errMsg = "??エンコードが出来ません? 348 + "address=" + address ; 349 throw new RuntimeException( errMsg,ex ); 350 } 351 } 352 353 /** 354 * Content-Transfer-Encoding を指定する?合? ビット数を返します? 355 * 356 * Windows系は?bit / ISO-2022-JP 系は?bit になります? 357 * 358 * @return ビット数("7bit" 固? 359 */ 360 public String getBit() { 361 return "7bit" ; 362 } 363 } 364 365 /** 366 * ?スト?本?送信するための DataSource です? 367 * 368 * Windows-31J でバイトコードに変換した後?独自エンコードにて? 369 * Shift-JIS ?JIS 変換して?す? 370 * 371 * @version 4.0 372 * @author Kazuhiko Hasegawa 373 * @since JDK5.0, 374 */ 375 class JISDataSource implements DataSource { 376 private final byte[] data; 377 378 public JISDataSource( final String str ) { 379 try { 380 data = CharCodeConverter.sjisToJis( 381 UnicodeCorrecter.correctToCP932(str).getBytes("Windows-31J")); 382 383 } catch (UnsupportedEncodingException e) { 384 String errMsg = "Windows-31J でのエンコー?ングが?来ません? + str; 385 throw new RuntimeException( errMsg,e ); 386 } 387 } 388 389 /** 390 * ??タの MIME タイプを??の形で返します? 391 * かならず有効なタイプを返すべきです? 392 * DataSource の実???タタイプを 決定できな??合?? 393 * getContentType は "application/octet-stream" を返すこと?提案します? 394 * 395 * @return MIME タイ? 396 */ 397 public String getContentType() { 398 return "text/plain; charset=ISO-2022-JP"; 399 } 400 401 /** 402 * ??タを表?InputStream を返します? 403 * それができな??合?適?例外をスローします? 404 * 405 * @return InputStream 406 * @throws IOException 407 */ 408 public InputStream getInputStream() throws IOException { 409 return new ByteArrayInputStream( data ); 410 } 411 412 /** 413 * ??タが書込可能な?OutputStream を返します? 414 * それができな??合?適?例外をスローします? 415 * 416 * ※ こ?クラスでは実?れて?せん? 417 * 418 * @return OutputStream 419 * @throws IOException 420 */ 421 public OutputStream getOutputStream() throws IOException { 422 String errMsg = "こ?クラスでは実?れて?せん?; 423 // throw new UnsupportedOperationException( errMsg ); 424 throw new IOException( errMsg ); 425 } 426 427 /** 428 * こ?オブジェクト? '名前' を返します? 429 * こ?名前は下層のオブジェクト?性質によります? 430 * ファイルをカプセル化す?DataSource な?オブジェクト? 431 * ファイル名を返すようにするかもしれません? 432 * 433 * @return オブジェクト?名前 434 */ 435 public String getName() { 436 return "JISDataSource"; 437 } 438 } 439 440 /** 441 * ?関係?コンバ?タです? 442 * ?コード?オリジナルは<a href="http://www-cms.phys.s.u-tokyo.ac.jp/~naoki/CIPINTRO/CCGI/kanjicod.html">Japanese Kanji Code</a>にて公開されて?も?です? 443 * また?http://www.sk-jp.com/cgi-bin/treebbs.cgi?kako=1&all=644&s=681 444 * にて YOSI さんが?開されたコードも参?にして??と?か実質同じで?? 445 * 446 * @version 4.0 447 * @author Kazuhiko Hasegawa 448 * @since JDK5.0, 449 */ 450 class CharCodeConverter { 451 private static final byte[] SJIS_KANA; // 5.1.9.0 (2010/09/01) public ?private へ変更 452 453 /** 454 * インスタンスの生?を抑止します? 455 */ 456 private CharCodeConverter() { 457 // 何もありません?PMD エラー回避) 458 } 459 460 static { 461 try { 462 // 全角への変換??ブル 463 SJIS_KANA = "。?」?・ヲァィゥェォャュョ??アイウエオカキクケコサシスセソタチツ?ナニヌネノハヒフヘ?マミ?モヤユヨラリルレロワン゛?".getBytes("Shift_JIS"); 464 } catch( UnsupportedEncodingException ex ) { 465 throw new RuntimeException( "CANT HAPPEN",ex ); 466 } 467 } 468 469 /** 470 * Shift_JIS エンコー?ングスキー?基づくバイト?? 471 * ISO-2022-JP エンコー?ングスキー?変換します? 472 * 「半角カナ?は対応する?角文字に変換します? 473 * 474 * @param sjisBytes byte[] エンコードするShift_JISバイト?? 475 * 476 * @return byte[] 変換後?ISO-2022-JP(JIS)バイト??not null) 477 */ 478 public static byte[] sjisToJis( final byte[] sjisBytes ) { 479 ByteArrayOutputStream out = new ByteArrayOutputStream(); 480 boolean nonAscii = false; 481 int len = sjisBytes.length; 482 for(int i = 0; i < len; i++ ) { 483 if(sjisBytes[i] >= 0) { 484 if(nonAscii) { 485 nonAscii = false; 486 out.write(0x1b); 487 out.write('('); 488 out.write('B'); 489 } 490 out.write(sjisBytes[i]); 491 } else { 492 if(!nonAscii) { 493 nonAscii = true; 494 out.write(0x1b); 495 out.write('$'); 496 out.write('B'); 497 } 498 int bt = sjisBytes[i] & 0xff; 499 if(bt >= 0xa1 && bt <= 0xdf) { 500 // 半角カナ?全角に変換 501 int kanaIndex = (bt - 0xA1) * 2; 502 sjisToJis(out, SJIS_KANA[kanaIndex], SJIS_KANA[kanaIndex + 1]); 503 } else { 504 i++; 505 if(i == len) { break; } 506 sjisToJis(out, sjisBytes[i - 1], sjisBytes[i]); 507 } 508 } 509 } 510 if(nonAscii) { 511 out.write(0x1b); 512 out.write('('); 513 out.write('B'); 514 } 515 return out.toByteArray(); 516 } 517 518 /** 519 * ?文字??バイ?Shift_JIS コードを JIS コードに変換して書き?します? 520 */ 521 private static void sjisToJis( 522 final ByteArrayOutputStream out, final byte bhi, final byte blo) { 523 int hi = (bhi << 1) & 0xFF; 524 int lo = blo & 0xFF; 525 if(lo < 0x9F) { 526 if(hi < 0x3F) { hi += 0x1F; } else { hi -= 0x61; } 527 if(lo > 0x7E) { lo -= 0x20; } else { lo -= 0x1F; } 528 } else { 529 if(hi < 0x3F) { hi += 0x20; } else { hi -= 0x60; } 530 lo -= 0x7E; 531 } 532 out.write(hi); 533 out.write(lo); 534 } 535 } 536 537 /** 538 * unicode と、JIS との?コード?関係で、変換して?す? 539 * 540 * 0x301c(〜) を?0xff5e(~) へ? 541 * 0x2016(‖) を?0x2225(∥) へ? 542 * 0x2212(−) を?0xff0d(-) へ? 543 * それぞれコード変換します? 544 * 545 * @version 4.0 546 * @author Kazuhiko Hasegawa 547 * @since JDK5.0, 548 */ 549 class UnicodeCorrecter { 550 551 /** 552 * インスタンスの生?を抑止します? 553 */ 554 private UnicodeCorrecter() { 555 // 何もありません?PMD エラー回避) 556 } 557 558 /** 559 * Unicode ??の補正を行います? 560 * "MS932" コンバ?タでエンコードしようとした際に 561 * 正常に変換できな??補正します? 562 */ 563 public static String correctToCP932( final String str ) { 564 String rtn = ""; 565 566 if( str != null ) { 567 int cnt = str.length(); 568 StringBuilder buf = new StringBuilder( cnt ); 569 for(int i=0; i<cnt; i++) { 570 buf.append(correctToCP932(str.charAt(i))); 571 } 572 rtn = buf.toString() ; 573 } 574 return rtn ; 575 } 576 577 /** 578 * キャラクタ単位に、Unicode ??の補正を行います? 579 * 580 * 風間殿のペ?ジを参?して?す? 581 * @see <a href="http://www.ingrid.org/java/i18n/encoding/ja-conv.html" target="_blank"> 582 * http://www.ingrid.org/java/i18n/encoding/ja-conv.html</a> 583 */ 584 public static char correctToCP932( final char ch ) { 585 char rtn = ch; 586 587 switch (ch) { 588 // case 0x00a2: return 0xffe0; // ≪ 589 // case 0x00a3: return 0xffe1; // ? 590 // case 0x00ac: return 0xffe2; // μ 591 // case 0x03bc: return 0x00b5; // ・ 592 // case 0x2014: return 0x2015; // ?? 593 // case 0x2016: return 0x2225; // ≫ 594 // case 0x2212: return 0xff0d; // ? 595 // case 0x226a: return 0x00ab; // ∥ 596 // case 0x226b: return 0x00bb; // ヴ 597 // case 0x301c: return 0xff5e; // ?? 598 // case 0x30f4: return 0x3094; // ?? 599 // case 0x30fb: return 0x00b7; // ?? 600 // case 0xff0c: return 0x00b8; // ? 601 // case 0xffe3: return 0x00af; // ? 602 603 case 0x00a2: rtn = 0xffe0; break; // ??(1-81, CENT SIGN) 604 case 0x00a3: rtn = 0xffe1; break; // ? (1-82, POUND SIGN) 605 case 0x00a5: rtn = 0x005c; break; // \ (D/12, YEN SIGN) 606 case 0x00ac: rtn = 0xffe2; break; // ? (2-44, NOT SIGN) 607 case 0x2016: rtn = 0x2225; break; // ∥ (1-34, DOUBLE VERTICAL LINE) 608 case 0x203e: rtn = 0x007e; break; // ~ (F/14, OVERLINE) 609 case 0x2212: rtn = 0xff0d; break; // ??(1-61, MINUS SIGN) 610 case 0x301c: rtn = 0xff5e; break; // ??(1-33, WAVE DASH) 611 612 // case 0x301c: return 0xff5e; 613 // case 0x2016: return 0x2225; 614 // case 0x2212: return 0xff0d; 615 default: break; // 4.0.0 (2005/01/31) 616 } 617 return rtn; 618 } 619 }