    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 *      http://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="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a>
    035 * and allows a character set to be specified.
    036 * <p>
    037 * <a href="http://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="http://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     *
    090     * @see Charset
    091     * @since 1.15
    092     */
    093    public BCodec(final Charset charset, final CodecPolicy decodingPolicy) {
    094        super(charset);
    095        this.decodingPolicy = decodingPolicy;
    096    }
    097
    098    /**
    099     * Constructor which allows for the selection of a default Charset
    100     *
    101     * @param charsetName
    102     *            the default Charset to use.
    103     * @throws java.nio.charset.UnsupportedCharsetException
    104     *             If the named Charset is unavailable
    105     * @since 1.7 throws UnsupportedCharsetException if the named Charset is unavailable
    106     * @see Charset
    107     */
    108    public BCodec(final String charsetName) {
    109        this(Charset.forName(charsetName));
    110    }
    111
    112    /**
    113     * Decodes a Base64 object into its original form. Escaped characters are converted back to their original
    114     * representation.
    115     *
    116     * @param value
    117     *            Base64 object to convert into its original form
    118     * @return original object
    119     * @throws DecoderException
    120     *             Thrown if the argument is not a {@code String}. Thrown if a failure condition is encountered
    121     *             during the decode process.
    122     */
    123    @Override
    124    public Object decode(final Object value) throws DecoderException {
    125        if (value == null) {
    126            return null;
    127        }
    128        if (value instanceof String) {
    129            return decode((String) value);
    130        }
    131        throw new DecoderException("Objects of type " + value.getClass().getName() + " cannot be decoded using BCodec");
    132    }
    133
    134    /**
    135     * Decodes a Base64 string into its original form. Escaped characters are converted back to their original
    136     * representation.
    137     *
    138     * @param value
    139     *            Base64 string to convert into its original form
    140     * @return original string
    141     * @throws DecoderException
    142     *             A decoder exception is thrown if a failure condition is encountered during the decode process.
    143     */
    144    @Override
    145    public String decode(final String value) throws DecoderException {
    146        try {
    147            return decodeText(value);
    148        } catch (final UnsupportedEncodingException | IllegalArgumentException e) {
    149            throw new DecoderException(e.getMessage(), e);
    150        }
    151    }
    152
    153    @Override
    154    protected byte[] doDecoding(final byte[] bytes) {
    155        if (bytes == null) {
    156            return null;
    157        }
    158        return new Base64(0, BaseNCodec.getChunkSeparator(), false, decodingPolicy).decode(bytes);
    159    }
    160
    161    @Override
    162    protected byte[] doEncoding(final byte[] bytes) {
    163        if (bytes == null) {
    164            return null;
    165        }
    166        return Base64.encodeBase64(bytes);
    167    }
    168
    169    /**
    170     * Encodes an object into its Base64 form using the default Charset. Unsafe characters are escaped.
    171     *
    172     * @param value
    173     *            object to convert to Base64 form
    174     * @return Base64 object
    175     * @throws EncoderException
    176     *             thrown if a failure condition is encountered during the encoding process.
    177     */
    178    @Override
    179    public Object encode(final Object value) throws EncoderException {
    180        if (value == null) {
    181            return null;
    182        }
    183        if (value instanceof String) {
    184            return encode((String) value);
    185        }
    186        throw new EncoderException("Objects of type " + value.getClass().getName() + " cannot be encoded using BCodec");
    187    }
    188
    189    /**
    190     * Encodes a string into its Base64 form using the default Charset. Unsafe characters are escaped.
    191     *
    192     * @param strSource
    193     *            string to convert to Base64 form
    194     * @return Base64 string
    195     * @throws EncoderException
    196     *             thrown if a failure condition is encountered during the encoding process.
    197     */
    198    @Override
    199    public String encode(final String strSource) throws EncoderException {
    200        return encode(strSource, getCharset());
    201    }
    202
    203    /**
    204     * Encodes a string into its Base64 form using the specified Charset. Unsafe characters are escaped.
    205     *
    206     * @param strSource
    207     *            string to convert to Base64 form
    208     * @param sourceCharset
    209     *            the Charset for {@code value}
    210     * @return Base64 string
    211     * @throws EncoderException
    212     *             thrown if a failure condition is encountered during the encoding process.
    213     * @since 1.7
    214     */
    215    public String encode(final String strSource, final Charset sourceCharset) throws EncoderException {
    216        return encodeText(strSource, sourceCharset);
    217    }
    218
    219    /**
    220     * Encodes a string into its Base64 form using the specified Charset. Unsafe characters are escaped.
    221     *
    222     * @param strSource
    223     *            string to convert to Base64 form
    224     * @param sourceCharset
    225     *            the Charset for {@code value}
    226     * @return Base64 string
    227     * @throws EncoderException
    228     *             thrown if a failure condition is encountered during the encoding process.
    229     */
    230    public String encode(final String strSource, final String sourceCharset) throws EncoderException {
    231        try {
    232            return encodeText(strSource, sourceCharset);
    233        } catch (final UnsupportedCharsetException e) {
    234            throw new EncoderException(e.getMessage(), e);
    235        }
    236    }
    237
    238    @Override
    239    protected String getEncoding() {
    240        return "B";
    241    }
    242
    243    /**
    244     * Returns true if decoding behavior is strict. Decoding will raise a
    245     * {@link DecoderException} if trailing bits are not part of a valid Base64 encoding.
    246     *
    247     * <p>The default is false for lenient encoding. Decoding will compose trailing bits
    248     * into 8-bit bytes and discard the remainder.
    249     *
    250     * @return true if using strict decoding
    251     * @since 1.15
    252     */
    253    public boolean isStrictDecoding() {
    254        return decodingPolicy == CodecPolicy.STRICT;
    255    }
    256}