View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.net;
19  
20  import java.io.UnsupportedEncodingException;
21  import java.nio.charset.Charset;
22  import java.nio.charset.StandardCharsets;
23  import java.nio.charset.UnsupportedCharsetException;
24  
25  import org.apache.commons.codec.CodecPolicy;
26  import org.apache.commons.codec.DecoderException;
27  import org.apache.commons.codec.EncoderException;
28  import org.apache.commons.codec.StringDecoder;
29  import org.apache.commons.codec.StringEncoder;
30  import org.apache.commons.codec.binary.Base64;
31  import org.apache.commons.codec.binary.BaseNCodec;
32  
33  /**
34   * Identical to the Base64 encoding defined by <a href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a>
35   * and allows a character set to be specified.
36   * <p>
37   * <a href="http://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> describes techniques to allow the encoding of non-ASCII
38   * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message
39   * handling software.
40   * </p>
41   * <p>
42   * This class is immutable and thread-safe.
43   * </p>
44   *
45   * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message
46   *          Header Extensions for Non-ASCII Text</a>
47   *
48   * @since 1.3
49   */
50  public class BCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
51  
52      /**
53       * The default decoding policy.
54       */
55      private static final CodecPolicy DECODING_POLICY_DEFAULT = CodecPolicy.LENIENT;
56  
57      /**
58       * If true then decoding should throw an exception for impossible combinations of bits at the
59       * end of the byte input. The default is to decode as much of them as possible.
60       */
61      private final CodecPolicy decodingPolicy;
62  
63      /**
64       * Default constructor.
65       */
66      public BCodec() {
67          this(StandardCharsets.UTF_8);
68      }
69  
70      /**
71       * Constructor which allows for the selection of a default Charset
72       *
73       * @param charset
74       *            the default string Charset to use.
75       *
76       * @see Charset
77       * @since 1.7
78       */
79      public BCodec(final Charset charset) {
80          this(charset, DECODING_POLICY_DEFAULT);
81      }
82  
83      /**
84       * Constructor which allows for the selection of a default Charset.
85       *
86       * @param charset
87       *            the default string Charset to use.
88       * @param decodingPolicy The decoding policy.
89       *
90       * @see Charset
91       * @since 1.15
92       */
93      public BCodec(final Charset charset, final CodecPolicy decodingPolicy) {
94          super(charset);
95          this.decodingPolicy = decodingPolicy;
96      }
97  
98      /**
99       * 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 }