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 }