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