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}