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="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     * @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}