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