    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;
    023
    024import org.apache.commons.codec.CodecPolicy;
    025import org.apache.commons.codec.DecoderException;
    026import org.apache.commons.codec.EncoderException;
    027import org.apache.commons.codec.StringDecoder;
    028import org.apache.commons.codec.StringEncoder;
    029import org.apache.commons.codec.binary.Base64;
    030import org.apache.commons.codec.binary.BaseNCodec;
    031
    032/**
    033 * Identical to the Base64 encoding defined by <a href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a>
    034 * and allows a character set to be specified.
    035 * <p>
    036 * <a href="http://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> describes techniques to allow the encoding of non-ASCII
    037 * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message
    038 * handling software.
    039 * </p>
    040 * <p>
    041 * This class is immutable and thread-safe.
    042 * </p>
    043 *
    044 * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message
    045 *          Header Extensions for Non-ASCII Text</a>
    046 *
    047 * @since 1.3
    048 */
    049public class BCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
    050
    051    /**
    052     * The default decoding policy.
    053     */
    054    private static final CodecPolicy DECODING_POLICY_DEFAULT = CodecPolicy.LENIENT;
    055
    056    /**
    057     * The default Charset used for string decoding and encoding.
    058     */
    059    private final Charset charset;
    060
    061    /**
    062     * If true then decoding should throw an exception for impossible combinations of bits at the
    063     * end of the byte input. The default is to decode as much of them as possible.
    064     */
    065    private final CodecPolicy decodingPolicy;
    066
    067    /**
    068     * Default constructor.
    069     */
    070    public BCodec() {
    071        this(StandardCharsets.UTF_8);
    072    }
    073
    074    /**
    075     * Constructor which allows for the selection of a default Charset
    076     *
    077     * @param charset
    078     *            the default string Charset to use.
    079     *
    080     * @see Charset
    081     * @since 1.7
    082     */
    083    public BCodec(final Charset charset) {
    084        this(charset, DECODING_POLICY_DEFAULT);
    085    }
    086
    087    /**
    088     * Constructor which allows for the selection of a default Charset.
    089     *
    090     * @param charset
    091     *            the default string Charset to use.
    092     * @param decodingPolicy The decoding policy.
    093     *
    094     * @see Charset
    095     * @since 1.15
    096     */
    097    public BCodec(final Charset charset, final CodecPolicy decodingPolicy) {
    098        this.charset = charset;
    099        this.decodingPolicy = decodingPolicy;
    100    }
    101
    102    /**
    103     * Constructor which allows for the selection of a default Charset
    104     *
    105     * @param charsetName
    106     *            the default Charset to use.
    107     * @throws java.nio.charset.UnsupportedCharsetException
    108     *             If the named Charset is unavailable
    109     * @since 1.7 throws UnsupportedCharsetException if the named Charset is unavailable
    110     * @see Charset
    111     */
    112    public BCodec(final String charsetName) {
    113        this(Charset.forName(charsetName));
    114    }
    115
    116    /**
    117     * Decodes a Base64 object into its original form. Escaped characters are converted back to their original
    118     * representation.
    119     *
    120     * @param value
    121     *            Base64 object to convert into its original form
    122     * @return original object
    123     * @throws DecoderException
    124     *             Thrown if the argument is not a {@code String}. Thrown if a failure condition is encountered
    125     *             during the decode process.
    126     */
    127    @Override
    128    public Object decode(final Object value) throws DecoderException {
    129        if (value == null) {
    130            return null;
    131        }
    132        if (value instanceof String) {
    133            return decode((String) value);
    134        }
    135        throw new DecoderException("Objects of type " +
    136              value.getClass().getName() +
    137              " cannot be decoded using BCodec");
    138    }
    139
    140    /**
    141     * Decodes a Base64 string into its original form. Escaped characters are converted back to their original
    142     * representation.
    143     *
    144     * @param value
    145     *            Base64 string to convert into its original form
    146     * @return original string
    147     * @throws DecoderException
    148     *             A decoder exception is thrown if a failure condition is encountered during the decode process.
    149     */
    150    @Override
    151    public String decode(final String value) throws DecoderException {
    152        if (value == null) {
    153            return null;
    154        }
    155        try {
    156            return this.decodeText(value);
    157        } catch (final UnsupportedEncodingException | IllegalArgumentException e) {
    158            throw new DecoderException(e.getMessage(), e);
    159        }
    160    }
    161
    162    @Override
    163    protected byte[] doDecoding(final byte[] bytes) {
    164        if (bytes == null) {
    165            return null;
    166        }
    167        return new Base64(0, BaseNCodec.getChunkSeparator(), false, decodingPolicy).decode(bytes);
    168    }
    169
    170    @Override
    171    protected byte[] doEncoding(final byte[] bytes) {
    172        if (bytes == null) {
    173            return null;
    174        }
    175        return Base64.encodeBase64(bytes);
    176    }
    177
    178    /**
    179     * Encodes an object into its Base64 form using the default Charset. Unsafe characters are escaped.
    180     *
    181     * @param value
    182     *            object to convert to Base64 form
    183     * @return Base64 object
    184     * @throws EncoderException
    185     *             thrown if a failure condition is encountered during the encoding process.
    186     */
    187    @Override
    188    public Object encode(final Object value) throws EncoderException {
    189        if (value == null) {
    190            return null;
    191        }
    192        if (value instanceof String) {
    193            return encode((String) value);
    194        }
    195        throw new EncoderException("Objects of type " +
    196              value.getClass().getName() +
    197              " cannot be encoded using BCodec");
    198    }
    199
    200    /**
    201     * Encodes a string into its Base64 form using the default Charset. Unsafe characters are escaped.
    202     *
    203     * @param strSource
    204     *            string to convert to Base64 form
    205     * @return Base64 string
    206     * @throws EncoderException
    207     *             thrown if a failure condition is encountered during the encoding process.
    208     */
    209    @Override
    210    public String encode(final String strSource) throws EncoderException {
    211        if (strSource == null) {
    212            return null;
    213        }
    214        return encode(strSource, this.getCharset());
    215    }
    216
    217    /**
    218     * Encodes a string into its Base64 form using the specified Charset. Unsafe characters are escaped.
    219     *
    220     * @param strSource
    221     *            string to convert to Base64 form
    222     * @param sourceCharset
    223     *            the Charset for {@code value}
    224     * @return Base64 string
    225     * @throws EncoderException
    226     *             thrown if a failure condition is encountered during the encoding process.
    227     * @since 1.7
    228     */
    229    public String encode(final String strSource, final Charset sourceCharset) throws EncoderException {
    230        if (strSource == null) {
    231            return null;
    232        }
    233        return encodeText(strSource, sourceCharset);
    234    }
    235
    236    /**
    237     * Encodes a string into its Base64 form using the specified Charset. Unsafe characters are escaped.
    238     *
    239     * @param strSource
    240     *            string to convert to Base64 form
    241     * @param sourceCharset
    242     *            the Charset for {@code value}
    243     * @return Base64 string
    244     * @throws EncoderException
    245     *             thrown if a failure condition is encountered during the encoding process.
    246     */
    247    public String encode(final String strSource, final String sourceCharset) throws EncoderException {
    248        if (strSource == null) {
    249            return null;
    250        }
    251        try {
    252            return this.encodeText(strSource, sourceCharset);
    253        } catch (final UnsupportedEncodingException e) {
    254            throw new EncoderException(e.getMessage(), e);
    255        }
    256    }
    257
    258    /**
    259     * Gets the default Charset name used for string decoding and encoding.
    260     *
    261     * @return the default Charset name
    262     * @since 1.7
    263     */
    264    public Charset getCharset() {
    265        return this.charset;
    266    }
    267
    268    /**
    269     * Gets the default Charset name used for string decoding and encoding.
    270     *
    271     * @return the default Charset name
    272     */
    273    public String getDefaultCharset() {
    274        return this.charset.name();
    275    }
    276
    277    @Override
    278    protected String getEncoding() {
    279        return "B";
    280    }
    281
    282    /**
    283     * Returns true if decoding behavior is strict. Decoding will raise a
    284     * {@link DecoderException} if trailing bits are not part of a valid Base64 encoding.
    285     *
    286     * <p>The default is false for lenient encoding. Decoding will compose trailing bits
    287     * into 8-bit bytes and discard the remainder.
    288     *
    289     * @return true if using strict decoding
    290     * @since 1.15
    291     */
    292    public boolean isStrictDecoding() {
    293        return decodingPolicy == CodecPolicy.STRICT;
    294    }
    295}