    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.codec.net;
    019
    020import java.io.UnsupportedEncodingException;
    021import java.nio.charset.Charset;
    022import java.nio.charset.StandardCharsets;
    023import java.nio.charset.UnsupportedCharsetException;
    024
    025import org.apache.commons.codec.CodecPolicy;
    026import org.apache.commons.codec.DecoderException;
    027import org.apache.commons.codec.EncoderException;
    028import org.apache.commons.codec.StringDecoder;
    029import org.apache.commons.codec.StringEncoder;
    030import org.apache.commons.codec.binary.Base64;
    031import org.apache.commons.codec.binary.BaseNCodec;
    032
    033/**
    034 * Identical to the Base64 encoding defined by <a href="https://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a>
    035 * and allows a character set to be specified.
    036 * <p>
    037 * <a href="https://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> describes techniques to allow the encoding of non-ASCII
    038 * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message
    039 * handling software.
    040 * </p>
    041 * <p>
    042 * This class is immutable and thread-safe.
    043 * </p>
    044 *
    045 * @see <a href="https://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message
    046 *          Header Extensions for Non-ASCII Text</a>
    047 *
    048 * @since 1.3
    049 */
    050public class BCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
    051
    052    /**
    053     * The default decoding policy.
    054     */
    055    private static final CodecPolicy DECODING_POLICY_DEFAULT = CodecPolicy.LENIENT;
    056
    057    /**
    058     * If true then decoding should throw an exception for impossible combinations of bits at the
    059     * end of the byte input. The default is to decode as much of them as possible.
    060     */
    061    private final CodecPolicy decodingPolicy;
    062
    063    /**
    064     * Default constructor.
    065     */
    066    public BCodec() {
    067        this(StandardCharsets.UTF_8);
    068    }
    069
    070    /**
    071     * Constructor which allows for the selection of a default Charset
    072     *
    073     * @param charset
    074     *            the default string Charset to use.
    075     *
    076     * @see Charset
    077     * @since 1.7
    078     */
    079    public BCodec(final Charset charset) {
    080        this(charset, DECODING_POLICY_DEFAULT);
    081    }
    082
    083    /**
    084     * Constructor which allows for the selection of a default Charset.
    085     *
    086     * @param charset
    087     *            the default string Charset to use.
    088     * @param decodingPolicy The decoding policy.
    089     * @see Charset
    090     * @since 1.15
    091     */
    092    public BCodec(final Charset charset, final CodecPolicy decodingPolicy) {
    093        super(charset);
    094        this.decodingPolicy = decodingPolicy;
    095    }
    096
    097    /**
    098     * Constructor which allows for the selection of a default Charset
    099     *
    100     * @param charsetName
    101     *            the default Charset to use.
    102     * @throws java.nio.charset.UnsupportedCharsetException
    103     *             If the named Charset is unavailable.
    104     * @since 1.7 throws UnsupportedCharsetException if the named Charset is unavailable
    105     * @see Charset
    106     */
    107    public BCodec(final String charsetName) {
    108        this(Charset.forName(charsetName));
    109    }
    110
    111    /**
    112     * Decodes a Base64 object into its original form. Escaped characters are converted back to their original
    113     * representation.
    114     *
    115     * @param value
    116     *            Base64 object to convert into its original form.
    117     * @return original object.
    118     * @throws DecoderException
    119     *             Thrown if the argument is not a {@code String}. Thrown if a failure condition is encountered
    120     *             during the decode process.
    121     */
    122    @Override
    123    public Object decode(final Object value) throws DecoderException {
    124        if (value == null) {
    125            return null;
    126        }
    127        if (value instanceof String) {
    128            return decode((String) value);
    129        }
    130        throw new DecoderException("Objects of type " + value.getClass().getName() + " cannot be decoded using BCodec");
    131    }
    132
    133    /**
    134     * Decodes a Base64 string into its original form. Escaped characters are converted back to their original
    135     * representation.
    136     *
    137     * @param value
    138     *            Base64 string to convert into its original form.
    139     * @return original string.
    140     * @throws DecoderException
    141     *             A decoder exception is thrown if a failure condition is encountered during the decode process.
    142     */
    143    @Override
    144    public String decode(final String value) throws DecoderException {
    145        try {
    146            return decodeText(value);
    147        } catch (final UnsupportedEncodingException | IllegalArgumentException e) {
    148            throw new DecoderException(e.getMessage(), e);
    149        }
    150    }
    151
    152    @Override
    153    protected byte[] doDecoding(final byte[] bytes) {
    154        if (bytes == null) {
    155            return null;
    156        }
    157        return new Base64(0, BaseNCodec.getChunkSeparator(), false, decodingPolicy).decode(bytes);
    158    }
    159
    160    @Override
    161    protected byte[] doEncoding(final byte[] bytes) {
    162        if (bytes == null) {
    163            return null;
    164        }
    165        return Base64.encodeBase64(bytes);
    166    }
    167
    168    /**
    169     * Encodes an object into its Base64 form using the default Charset. Unsafe characters are escaped.
    170     *
    171     * @param value
    172     *            object to convert to Base64 form.
    173     * @return Base64 object.
    174     * @throws EncoderException
    175     *             thrown if a failure condition is encountered during the encoding process.
    176     */
    177    @Override
    178    public Object encode(final Object value) throws EncoderException {
    179        if (value == null) {
    180            return null;
    181        }
    182        if (value instanceof String) {
    183            return encode((String) value);
    184        }
    185        throw new EncoderException("Objects of type " + value.getClass().getName() + " cannot be encoded using BCodec");
    186    }
    187
    188    /**
    189     * Encodes a string into its Base64 form using the default Charset. Unsafe characters are escaped.
    190     *
    191     * @param strSource
    192     *            string to convert to Base64 form.
    193     * @return Base64 string.
    194     * @throws EncoderException
    195     *             thrown if a failure condition is encountered during the encoding process.
    196     */
    197    @Override
    198    public String encode(final String strSource) throws EncoderException {
    199        return encode(strSource, getCharset());
    200    }
    201
    202    /**
    203     * Encodes a string into its Base64 form using the specified Charset. Unsafe characters are escaped.
    204     *
    205     * @param strSource
    206     *            string to convert to Base64 form.
    207     * @param sourceCharset
    208     *            the Charset for {@code value}.
    209     * @return Base64 string.
    210     * @throws EncoderException
    211     *             thrown if a failure condition is encountered during the encoding process.
    212     * @since 1.7
    213     */
    214    public String encode(final String strSource, final Charset sourceCharset) throws EncoderException {
    215        return encodeText(strSource, sourceCharset);
    216    }
    217
    218    /**
    219     * Encodes a string into its Base64 form using the specified Charset. Unsafe characters are escaped.
    220     *
    221     * @param strSource
    222     *            string to convert to Base64 form.
    223     * @param sourceCharset
    224     *            the Charset for {@code value}.
    225     * @return Base64 string.
    226     * @throws EncoderException
    227     *             thrown if a failure condition is encountered during the encoding process.
    228     */
    229    public String encode(final String strSource, final String sourceCharset) throws EncoderException {
    230        try {
    231            return encodeText(strSource, sourceCharset);
    232        } catch (final UnsupportedCharsetException e) {
    233            throw new EncoderException(e.getMessage(), e);
    234        }
    235    }
    236
    237    @Override
    238    protected String getEncoding() {
    239        return "B";
    240    }
    241
    242    /**
    243     * Returns true if decoding behavior is strict. Decoding will raise a
    244     * {@link DecoderException} if trailing bits are not part of a valid Base64 encoding.
    245     *
    246     * <p>The default is false for lenient encoding. Decoding will compose trailing bits
    247     * into 8-bit bytes and discard the remainder.
    248     *
    249     * @return true if using strict decoding.
    250     * @since 1.15
    251     */
    252    public boolean isStrictDecoding() {
    253        return decodingPolicy == CodecPolicy.STRICT;
    254    }
    255}