    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) 『１．Windows-31J + 8bit 送信』 の実装である、Mail_Windows31J_Charset</del>
    043 * 『１．Windows-31J/UTF-8 + 8bit 送信』 の実装である、Mail_8bit_Charset
    044 * サブクラスを返します。
    045 * それ以外が指定された場合は、ISO-2022-JP を使用して、『２．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         * 『１．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         * 『２．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}