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 <a href="http://download.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
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 <a href="http://download.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
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 <a href="http://download.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
111     */
112    public BCodec(final String charsetName) {
113        this(Charset.forName(charsetName));
114    }
115
116    /**
117     * Returns true if decoding behavior is strict. Decoding will raise a
118     * {@link DecoderException} if trailing bits are not part of a valid Base64 encoding.
119     *
120     * <p>The default is false for lenient encoding. Decoding will compose trailing bits
121     * into 8-bit bytes and discard the remainder.
122     *
123     * @return true if using strict decoding
124     * @since 1.15
125     */
126    public boolean isStrictDecoding() {
127        return decodingPolicy == CodecPolicy.STRICT;
128    }
129
130    @Override
131    protected String getEncoding() {
132        return "B";
133    }
134
135    @Override
136    protected byte[] doEncoding(final byte[] bytes) {
137        if (bytes == null) {
138            return null;
139        }
140        return Base64.encodeBase64(bytes);
141    }
142
143    @Override
144    protected byte[] doDecoding(final byte[] bytes) {
145        if (bytes == null) {
146            return null;
147        }
148        return new Base64(0, BaseNCodec.getChunkSeparator(), false, decodingPolicy).decode(bytes);
149    }
150
151    /**
152     * Encodes a string into its Base64 form using the specified Charset. Unsafe characters are escaped.
153     *
154     * @param strSource
155     *            string to convert to Base64 form
156     * @param sourceCharset
157     *            the Charset for {@code value}
158     * @return Base64 string
159     * @throws EncoderException
160     *             thrown if a failure condition is encountered during the encoding process.
161     * @since 1.7
162     */
163    public String encode(final String strSource, final Charset sourceCharset) throws EncoderException {
164        if (strSource == null) {
165            return null;
166        }
167        return encodeText(strSource, sourceCharset);
168    }
169
170    /**
171     * Encodes a string into its Base64 form using the specified Charset. Unsafe characters are escaped.
172     *
173     * @param strSource
174     *            string to convert to Base64 form
175     * @param sourceCharset
176     *            the Charset for {@code value}
177     * @return Base64 string
178     * @throws EncoderException
179     *             thrown if a failure condition is encountered during the encoding process.
180     */
181    public String encode(final String strSource, final String sourceCharset) throws EncoderException {
182        if (strSource == null) {
183            return null;
184        }
185        try {
186            return this.encodeText(strSource, sourceCharset);
187        } catch (final UnsupportedEncodingException e) {
188            throw new EncoderException(e.getMessage(), e);
189        }
190    }
191
192    /**
193     * Encodes a string into its Base64 form using the default Charset. Unsafe characters are escaped.
194     *
195     * @param strSource
196     *            string to convert to Base64 form
197     * @return Base64 string
198     * @throws EncoderException
199     *             thrown if a failure condition is encountered during the encoding process.
200     */
201    @Override
202    public String encode(final String strSource) throws EncoderException {
203        if (strSource == null) {
204            return null;
205        }
206        return encode(strSource, this.getCharset());
207    }
208
209    /**
210     * Decodes a Base64 string into its original form. Escaped characters are converted back to their original
211     * representation.
212     *
213     * @param value
214     *            Base64 string to convert into its original form
215     * @return original string
216     * @throws DecoderException
217     *             A decoder exception is thrown if a failure condition is encountered during the decode process.
218     */
219    @Override
220    public String decode(final String value) throws DecoderException {
221        if (value == null) {
222            return null;
223        }
224        try {
225            return this.decodeText(value);
226        } catch (final UnsupportedEncodingException | IllegalArgumentException e) {
227            throw new DecoderException(e.getMessage(), e);
228        }
229    }
230
231    /**
232     * Encodes an object into its Base64 form using the default Charset. Unsafe characters are escaped.
233     *
234     * @param value
235     *            object to convert to Base64 form
236     * @return Base64 object
237     * @throws EncoderException
238     *             thrown if a failure condition is encountered during the encoding process.
239     */
240    @Override
241    public Object encode(final Object value) throws EncoderException {
242        if (value == null) {
243            return null;
244        } else if (value instanceof String) {
245            return encode((String) value);
246        } else {
247            throw new EncoderException("Objects of type " +
248                  value.getClass().getName() +
249                  " cannot be encoded using BCodec");
250        }
251    }
252
253    /**
254     * Decodes a Base64 object into its original form. Escaped characters are converted back to their original
255     * representation.
256     *
257     * @param value
258     *            Base64 object to convert into its original form
259     * @return original object
260     * @throws DecoderException
261     *             Thrown if the argument is not a {@code String}. Thrown if a failure condition is encountered
262     *             during the decode process.
263     */
264    @Override
265    public Object decode(final Object value) throws DecoderException {
266        if (value == null) {
267            return null;
268        } else if (value instanceof String) {
269            return decode((String) value);
270        } else {
271            throw new DecoderException("Objects of type " +
272                  value.getClass().getName() +
273                  " cannot be decoded using BCodec");
274        }
275    }
276
277    /**
278     * Gets the default Charset name used for string decoding and encoding.
279     *
280     * @return the default Charset name
281     * @since 1.7
282     */
283    public Charset getCharset() {
284        return this.charset;
285    }
286
287    /**
288     * Gets the default Charset name used for string decoding and encoding.
289     *
290     * @return the default Charset name
291     */
292    public String getDefaultCharset() {
293        return this.charset.name();
294    }
295}