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 is lenient.
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 * Constructs a new instance.
65 */
66 public BCodec() {
67 this(StandardCharsets.UTF_8);
68 }
69
70 /**
71 * Constructs a new instance 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 * Constructs a new instance 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 * Constructs a new instance 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 Base64.builder().setLineLength(0).setLineSeparator(BaseNCodec.getChunkSeparator()).setUrlSafe(false).setDecodingPolicy(decodingPolicy).get()
158 .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 }