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    *      https://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       * @see Charset
90       * @since 1.15
91       */
92      public BCodec(final Charset charset, final CodecPolicy decodingPolicy) {
93          super(charset);
94          this.decodingPolicy = decodingPolicy;
95      }
96  
97      /**
98       * Constructor which allows for the selection of a default Charset
99       *
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 }