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  
24  import org.apache.commons.codec.CodecPolicy;
25  import org.apache.commons.codec.DecoderException;
26  import org.apache.commons.codec.EncoderException;
27  import org.apache.commons.codec.StringDecoder;
28  import org.apache.commons.codec.StringEncoder;
29  import org.apache.commons.codec.binary.Base64;
30  import org.apache.commons.codec.binary.BaseNCodec;
31  
32  /**
33   * Identical to the Base64 encoding defined by <a href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a>
34   * and allows a character set to be specified.
35   * <p>
36   * <a href="http://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> describes techniques to allow the encoding of non-ASCII
37   * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message
38   * handling software.
39   * </p>
40   * <p>
41   * This class is immutable and thread-safe.
42   * </p>
43   *
44   * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message
45   *          Header Extensions for Non-ASCII Text</a>
46   *
47   * @since 1.3
48   */
49  public class BCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
50  
51      /**
52       * The default decoding policy.
53       */
54      private static final CodecPolicy DECODING_POLICY_DEFAULT = CodecPolicy.LENIENT;
55  
56      /**
57       * The default Charset used for string decoding and encoding.
58       */
59      private final Charset charset;
60  
61      /**
62       * If true then decoding should throw an exception for impossible combinations of bits at the
63       * end of the byte input. The default is to decode as much of them as possible.
64       */
65      private final CodecPolicy decodingPolicy;
66  
67      /**
68       * Default constructor.
69       */
70      public BCodec() {
71          this(StandardCharsets.UTF_8);
72      }
73  
74      /**
75       * Constructor which allows for the selection of a default Charset
76       *
77       * @param charset
78       *            the default string Charset to use.
79       *
80       * @see Charset
81       * @since 1.7
82       */
83      public BCodec(final Charset charset) {
84          this(charset, DECODING_POLICY_DEFAULT);
85      }
86  
87      /**
88       * Constructor which allows for the selection of a default Charset.
89       *
90       * @param charset
91       *            the default string Charset to use.
92       * @param decodingPolicy The decoding policy.
93       *
94       * @see Charset
95       * @since 1.15
96       */
97      public BCodec(final Charset charset, final CodecPolicy decodingPolicy) {
98          this.charset = charset;
99          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 }