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