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 java.io.InputStream; 019import java.io.OutputStream; 020import java.io.ByteArrayInputStream; 021import java.io.UnsupportedEncodingException; 022import java.io.IOException; 023import java.util.Base64; // 8.4.0.0 (2022/12/23) 024import java.nio.charset.Charset; // 5.5.2.6 (2012/05/25) 025 026import jakarta.activation.DataHandler; 027import jakarta.activation.DataSource; 028import jakarta.mail.internet.InternetAddress; 029import jakarta.mail.internet.MimeMessage; 030import jakarta.mail.internet.MimeUtility; 031import jakarta.mail.MessagingException; 032// import com.sun.mail.util.BASE64EncoderStream; // 8.4.0.0 (2022/12/23) java.util.Base64.Encoder に置き換え 033 034import org.opengion.fukurou.system.OgRuntimeException ; // 6.4.2.0 (2016/01/29) 035import org.opengion.fukurou.util.UnicodeCorrecter; // 5.9.3.3 (2015/12/26) package を、mail → util に移動のため 036 037/** 038 * MailCharsetFactory は、MailCharset インターフェースを実装したサブクラスを 039 * 作成する ファクトリクラスです。 040 * 041 * 引数のキャラクタセット名が、Windows-31J、MS932 の場合は、 042 * <del>6.3.8.0 (2015/09/11) 『1.Windows-31J + 8bit 送信』 の実装である、Mail_Windows31J_Charset</del> 043 * 『1.Windows-31J/UTF-8 + 8bit 送信』 の実装である、Mail_8bit_Charset 044 * サブクラスを返します。 045 * それ以外が指定された場合は、ISO-2022-JP を使用して、『2.ISO-2022-JP に独自変換 + 7bit 送信』 046 * の実装である、Mail_ISO2022JP_Charset サブクラスを返します。 047 * 048 * @og.rev 6.3.8.0 (2015/09/11) キャラクタセットに、UTF-8 を追加します。 049 * Mail_Windows31J_Charset のクラス名を変更します。 050 * 051 * @version 4.0 052 * @author Kazuhiko Hasegawa 053 * @since JDK5.0, 054 */ 055// class MailCharsetFactory { 056final class MailCharsetFactory { // 8.5.4.2 (2024/01/12) PMD 7.0.0 ClassWithOnlyPrivateConstructorsShouldBeFinal 057 058 /** 059 * デフォルトコンストラクターをprivateにして、 060 * オブジェクトの生成をさせないようにする。 061 */ 062 private MailCharsetFactory() { 063 // 何もありません。(PMD エラー回避) 064 } 065 066 /** 067 * キャラクタセットに応じた、MailCharset オブジェクトを返します。 068 * 069 * Windows-31J、MS932、Shift_JIS の場合は、Mail_Windows31J_Charset 070 * その他は、ISO-2022-JP として、Mail_ISO2022JP_Charset を返します。 071 * 072 * 注意:null の場合は、デフォルトではなく、Mail_ISO2022JP_Charset を返します。 073 * 074 * @og.rev 6.3.8.0 (2015/09/11) キャラクタセットに、UTF-8 を追加します。 075 * 076 * @param charset キャラクタセット[Windows-31J/MS932/Shift_JIS/その他] 077 * 078 * @return MailCharsetオブジェクト 079 */ 080 /* default */ static MailCharset newInstance( final String charset ) { 081 final MailCharset mcset; 082 083 if( "MS932".equalsIgnoreCase( charset ) || 084 "Shift_JIS".equalsIgnoreCase( charset ) || 085 "Windows-31J".equalsIgnoreCase( charset ) || 086 "UTF-8".equalsIgnoreCase( charset ) ) { // 6.3.8.0 (2015/09/11) 087 mcset = new Mail_8bit_Charset( charset ); // 6.3.8.0 (2015/09/11) 088 } 089 else { 090 mcset = new Mail_ISO2022JP_Charset(); 091 } 092 return mcset ; 093 } 094 095 /** 096 * MailCharset インターフェースを実装した Windwos-31J/UTF-8 エンコード時のサブクラスです。 097 * 098 * 『1.Windows-31J/UTF-8 + 8bit 送信』 の実装です。 099 * 100 * @og.rev 6.3.8.0 (2015/09/11) キャラクタセットに、UTF-8 を追加します。 101 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 102 * 103 * @version 4.0 104 * @author Kazuhiko Hasegawa 105 * @since JDK5.0, 106 */ 107 private static final class Mail_8bit_Charset implements MailCharset { 108 private final String charset ; // "Windows-31J" or "MS932" 109 110 /** 111 * 引数に、エンコード方式を指定して、作成するコンストラクタです。 112 * 113 * @og.rev 6.3.8.0 (2015/09/11) キャラクタセットに、UTF-8 を追加します。 114 * 115 * @param charset エンコード 116 */ 117 public Mail_8bit_Charset( final String charset ) { 118 this.charset = charset; 119 } 120 121 /** 122 * テキストをセットします。 123 * Part#setText() の代わりにこちらを使うようにします。 124 * 125 * ※ 内部で、MessagingException が発生した場合は、RuntimeException に変換されて throw されます。 126 * 127 * @param mimeMsg MimeMessageオブジェクト 128 * @param text テキスト 129 */ 130 @Override // MailCharset 131 public void setTextContent( final MimeMessage mimeMsg, final String text ) { 132 try { 133 mimeMsg.setText( text,charset ); // "text/plain" Content 134 } 135 catch( final MessagingException ex ) { 136 final String errMsg = "指定のテキストをセットできません。" 137 + "text=" + text + " , charset=" + charset ; 138 throw new OgRuntimeException( errMsg,ex ); 139 } 140 } 141 142 /** 143 * 日本語を含むヘッダ用テキストを生成します。 144 * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress 145 * のパラメタとして使用してください。 146 * 147 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。 148 * 149 * @param text テキスト 150 * 151 * @return 日本語を含むヘッダ用テキスト 152 * @og.rtnNotNull 153 */ 154 @Override // MailCharset 155 public String encodeWord( final String text ) { 156 try { 157 return MimeUtility.encodeText( text, charset, "B" ); 158 } 159 catch( final UnsupportedEncodingException ex ) { 160 final String errMsg = "指定のエンコードが出来ません。" 161 + "text=" + text + " , charset=" + charset ; 162 throw new OgRuntimeException( errMsg,ex ); 163 } 164 } 165 166 /** 167 * 日本語を含むアドレスを生成します。 168 * personal に、日本語が含まれると想定しています。 169 * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。 170 * 171 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。 172 * 173 * @param address RFC822形式のアドレス 174 * @param personal 個人名 175 * 176 * @return InternetAddressオブジェクト 177 * @og.rtnNotNull 178 */ 179 @Override // MailCharset 180 public InternetAddress getAddress( final String address,final String personal ) { 181 try { 182 return new InternetAddress( address,personal,charset ); 183 } 184 catch( final UnsupportedEncodingException ex ) { 185 final String errMsg = "指定のエンコードが出来ません。" 186 + "address=" + address + " , charset=" + charset ; 187 throw new OgRuntimeException( errMsg,ex ); 188 } 189 } 190 191 /** 192 * Content-Transfer-Encoding を指定する場合の ビット数を返します。 193 * 194 * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。 195 * 196 * @return ビット数("8bit" 固定) 197 * @og.rtnNotNull 198 */ 199 @Override // MailCharset 200 public String getBit() { 201 return "8bit" ; 202 } 203 } 204 205 /** 206 * MailCharset インターフェースを実装した ISO-2022-JP エンコード時のサブクラスです。 207 * 208 * 『2.ISO-2022-JP に独自変換 + 7bit 送信』 の実装です。 209 * 210 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 211 * @version 4.0 212 * @author Kazuhiko Hasegawa 213 * @since JDK5.0, 214 */ 215 private static final class Mail_ISO2022JP_Charset implements MailCharset { 216 217 /** 218 * プラットフォーム依存のデフォルトの Charset です。 219 * プラットフォーム依存性を考慮する場合、エンコード指定で作成しておく事をお勧めします。 220 * 221 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応 222 * @og.rev 8.5.3.2 (2023/10/13) JDK21注意。JDK17までは、Windows-31J だったが、JDK21から、UTF-8 に変更されている。 223 */ 224 private static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ; 225 226 /** 227 * デフォルトのコンストラクタ 228 * 229 * @og.rev 8.5.5.1 (2024/02/29) デフォルトのコンストラクタは必ず用意しておく。 230 */ 231 public Mail_ISO2022JP_Charset() { 232 super(); 233 } 234 235 /** 236 * テキストをセットします。 237 * Part#setText() の代わりにこちらを使うようにします。 238 * 239 * ※ 内部で、MessagingException が発生した場合は、RuntimeException に変換されて throw されます。 240 * 241 * @param mimeMsg MimeMessageオブジェクト 242 * @param text テキスト 243 */ 244 @Override // MailCharset 245 public void setTextContent( final MimeMessage mimeMsg, final String text ) { 246 try { 247 // mimeMsg.setText(text, "ISO-2022-JP"); 248 mimeMsg.setDataHandler(new DataHandler(new JISDataSource(text))); 249 } 250 catch( final MessagingException ex ) { 251 final String errMsg = "指定のテキストをセットできません。" 252 + "text=" + text ; 253 throw new OgRuntimeException( errMsg,ex ); 254 } 255 } 256 257 /** 258 * 日本語を含むヘッダ用テキストを生成します。 259 * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress 260 * のパラメタとして使用してください。 261 * 262 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。 263 * 264 * @og.rev 8.4.0.0 (2022/12/23) java.util.Base64.Encoder に置き換え 265 * 266 * @param text テキスト 267 * 268 * @return 日本語を含むヘッダ用テキスト 269 * @og.rtnNotNull 270 */ 271 @Override // MailCharset 272 public String encodeWord( final String text ) { 273 try { 274 return "=?ISO-2022-JP?B?" + 275 new String( 276 // BASE64EncoderStream.encode( 277 Base64.getEncoder().encode( 278 CharCodeConverter.sjisToJis( 279 UnicodeCorrecter.correctToCP932(text).getBytes("Windows-31J") 280 ) 281 ) 282 ,DEFAULT_CHARSET ) + "?="; // 5.5.2.6 (2012/05/25) findbugs対応 283 } 284 catch( final UnsupportedEncodingException ex ) { 285 final String errMsg = "指定のエンコードが出来ません。" 286 + "text=" + text + " , charset=Windows-31J" ; 287 throw new OgRuntimeException( errMsg,ex ); 288 } 289 } 290 291 /** 292 * 日本語を含むアドレスを生成します。 293 * personal に、日本語が含まれると想定しています。 294 * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。 295 * 296 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。 297 * 298 * @param address RFC822形式のアドレス 299 * @param personal 個人名 300 * 301 * @return InternetAddressオブジェクト 302 * @og.rtnNotNull 303 */ 304 @Override // MailCharset 305 public InternetAddress getAddress( final String address,final String personal ) { 306 try { 307 return new InternetAddress( address,encodeWord( personal ) ); 308 } 309 catch( final UnsupportedEncodingException ex ) { 310 final String errMsg = "指定のエンコードが出来ません。" 311 + "address=" + address ; 312 throw new OgRuntimeException( errMsg,ex ); 313 } 314 } 315 316 /** 317 * Content-Transfer-Encoding を指定する場合の ビット数を返します。 318 * 319 * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。 320 * 321 * @return ビット数("7bit" 固定) 322 * @og.rtnNotNull 323 */ 324 @Override // MailCharset 325 public String getBit() { 326 return "7bit" ; 327 } 328 } 329 330 /** 331 * テキストの本文を送信するための DataSource です。 332 * 333 * Windows-31J でバイトコードに変換した後、独自エンコードにて、 334 * Shift-JIS ⇒ JIS 変換しています。 335 * 336 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。 337 * 338 * @version 4.0 339 * @author Kazuhiko Hasegawa 340 * @since JDK5.0, 341 */ 342 private static final class JISDataSource implements DataSource { 343 private final byte[] data; 344 345 /** 346 * JIS(Windows-31J) に対応した DataSource オブジェクトのコンストラクタ 347 * 348 * Windows-31J でバイトコードに変換した後、独自エンコードにて、 349 * Shift-JIS ⇒ JIS 変換しています。 350 * 351 * @param str 変換する文字列 352 */ 353 public JISDataSource( final String str ) { 354 try { 355 data = CharCodeConverter.sjisToJis( 356 UnicodeCorrecter.correctToCP932(str).getBytes("Windows-31J")); 357 } catch( final UnsupportedEncodingException ex ) { 358 final String errMsg = "Windows-31J でのエンコーディングが出来ません。" + str; 359 throw new OgRuntimeException( errMsg,ex ); 360 } 361 } 362 363 /** 364 * データの MIME タイプを文字列の形で返します。 365 * かならず有効なタイプを返すべきです。 366 * DataSource の実装がデータタイプを 決定できない場合は、 367 * getContentType は "application/octet-stream" を返すことを 提案します。 368 * 369 * @return MIMEタイプ("text/plain; charset=ISO-2022-JP" 固定) 370 * @og.rtnNotNull 371 */ 372 @Override // DataSource 373 public String getContentType() { 374 return "text/plain; charset=ISO-2022-JP"; 375 } 376 377 /** 378 * データを表す InputStreamオブジェクト を返します。 379 * それができない場合は適切な例外をスローします。 380 * 381 * @return InputStreamオブジェクト 382 * @throws IOException ※ このメソッドからは、IOException は throw されません。 383 * @og.rtnNotNull 384 */ 385 @Override // DataSource 386 public InputStream getInputStream() throws IOException { 387 return new ByteArrayInputStream( data ); 388 } 389 390 /** 391 * データが書込可能なら OutputStreamオブジェクト を返します。 392 * それができない場合は適切な例外をスローします。 393 * 394 * ※ このクラスでは実装されていません。 395 * 396 * @return OutputStreamオブジェクト 397 * @throws IOException ※ このメソッドを実行すると、必ず throw されます。 398 */ 399 @Override // DataSource 400 public OutputStream getOutputStream() throws IOException { 401 final String errMsg = "このクラスでは実装されていません。"; 402 // throw new UnsupportedOperationException( errMsg ); 403 throw new IOException( errMsg ); 404 } 405 406 /** 407 * このオブジェクトの '名前' を返します。 408 * この名前は下層のオブジェクトの性質によります。 409 * ファイルをカプセル化する DataSource なら オブジェクトの 410 * ファイル名を返すようにするかもしれません。 411 * 412 * @return オブジェクトの名前( "JISDataSource" 固定) 413 * @og.rtnNotNull 414 */ 415 @Override // DataSource 416 public String getName() { 417 return "JISDataSource"; 418 } 419 } 420}