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 018// import java.io.InputStream; 019// import java.io.OutputStream; 020// import java.io.ByteArrayOutputStream; 021// import java.io.ByteArrayInputStream; 022import java.io.UnsupportedEncodingException; 023// import java.io.IOException; 024 025// import javax.activation.DataHandler; 026// import javax.activation.DataSource; 027import javax.mail.internet.InternetAddress; 028import javax.mail.internet.MimeMessage; 029// import javax.mail.internet.MimeUtility; 030import 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コード)化して、7bit で 040 * エンコードして送信する必要がありますが、Windows系の特殊文字や、unicodeと 041 * 文字のマッピングが異なる文字などが、文字化けします。 042 * 対応方法としては、 043 * 『1.Windows-31J + 8bit 送信』 044 * 『2.ISO-2022-JP に独自変換 + 7bit 送信』 045 * の方法があります。 046 * 今回、この2つの方法について、それぞれサブクラス化を行い、処理できるように 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 * 【特集1】 決定版! サーバサイド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 */ 072public 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系は、8bit / ISO-2022-JP 系は、7bit になります。 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 を使用して、『2.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系は、8bit / ISO-2022-JP 系は、7bit になります。 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系は、8bit / ISO-2022-JP 系は、7bit になります。 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// * 1文字の2バイト 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//}