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="https://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a>
35 * and allows a character set to be specified.
36 * <p>
37 * <a href="https://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="https://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 }