001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.codec.net;
019    
020    import java.io.UnsupportedEncodingException;
021    import java.nio.charset.Charset;
022    
023    import org.apache.commons.codec.Charsets;
024    import org.apache.commons.codec.DecoderException;
025    import org.apache.commons.codec.EncoderException;
026    import org.apache.commons.codec.StringDecoder;
027    import org.apache.commons.codec.StringEncoder;
028    import org.apache.commons.codec.binary.Base64;
029    
030    /**
031     * Identical to the Base64 encoding defined by <a href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a>
032     * and allows a character set to be specified.
033     * <p>
034     * <a href="http://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> describes techniques to allow the encoding of non-ASCII
035     * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message
036     * handling software.
037     * <p>
038     * This class is immutable and thread-safe.
039     *
040     * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message
041     *          Header Extensions for Non-ASCII Text</a>
042     *
043     * @since 1.3
044     * @version $Id: BCodec.html 889935 2013-12-11 05:05:13Z ggregory $
045     */
046    public class BCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
047        /**
048         * The default charset used for string decoding and encoding.
049         */
050        private final Charset charset;
051    
052        /**
053         * Default constructor.
054         */
055        public BCodec() {
056            this(Charsets.UTF_8);
057        }
058    
059        /**
060         * Constructor which allows for the selection of a default charset
061         *
062         * @param charset
063         *            the default string charset to use.
064         *
065         * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
066         * @since 1.7
067         */
068        public BCodec(final Charset charset) {
069            this.charset = charset;
070        }
071    
072        /**
073         * Constructor which allows for the selection of a default charset
074         *
075         * @param charsetName
076         *            the default charset to use.
077         * @throws java.nio.charset.UnsupportedCharsetException
078         *             If the named charset is unavailable
079         * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
080         * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
081         */
082        public BCodec(final String charsetName) {
083            this(Charset.forName(charsetName));
084        }
085    
086        @Override
087        protected String getEncoding() {
088            return "B";
089        }
090    
091        @Override
092        protected byte[] doEncoding(final byte[] bytes) {
093            if (bytes == null) {
094                return null;
095            }
096            return Base64.encodeBase64(bytes);
097        }
098    
099        @Override
100        protected byte[] doDecoding(final byte[] bytes) {
101            if (bytes == null) {
102                return null;
103            }
104            return Base64.decodeBase64(bytes);
105        }
106    
107        /**
108         * Encodes a string into its Base64 form using the specified charset. Unsafe characters are escaped.
109         *
110         * @param value
111         *            string to convert to Base64 form
112         * @param charset
113         *            the charset for <code>value</code>
114         * @return Base64 string
115         * @throws EncoderException
116         *             thrown if a failure condition is encountered during the encoding process.
117         * @since 1.7
118         */
119        public String encode(final String value, final Charset charset) throws EncoderException {
120            if (value == null) {
121                return null;
122            }
123            return encodeText(value, charset);
124        }
125    
126        /**
127         * Encodes a string into its Base64 form using the specified charset. Unsafe characters are escaped.
128         *
129         * @param value
130         *            string to convert to Base64 form
131         * @param charset
132         *            the charset for <code>value</code>
133         * @return Base64 string
134         * @throws EncoderException
135         *             thrown if a failure condition is encountered during the encoding process.
136         */
137        public String encode(final String value, final String charset) throws EncoderException {
138            if (value == null) {
139                return null;
140            }
141            try {
142                return this.encodeText(value, charset);
143            } catch (final UnsupportedEncodingException e) {
144                throw new EncoderException(e.getMessage(), e);
145            }
146        }
147    
148        /**
149         * Encodes a string into its Base64 form using the default charset. Unsafe characters are escaped.
150         *
151         * @param value
152         *            string to convert to Base64 form
153         * @return Base64 string
154         * @throws EncoderException
155         *             thrown if a failure condition is encountered during the encoding process.
156         */
157        @Override
158        public String encode(final String value) throws EncoderException {
159            if (value == null) {
160                return null;
161            }
162            return encode(value, this.getCharset());
163        }
164    
165        /**
166         * Decodes a Base64 string into its original form. Escaped characters are converted back to their original
167         * representation.
168         *
169         * @param value
170         *            Base64 string to convert into its original form
171         * @return original string
172         * @throws DecoderException
173         *             A decoder exception is thrown if a failure condition is encountered during the decode process.
174         */
175        @Override
176        public String decode(final String value) throws DecoderException {
177            if (value == null) {
178                return null;
179            }
180            try {
181                return this.decodeText(value);
182            } catch (final UnsupportedEncodingException e) {
183                throw new DecoderException(e.getMessage(), e);
184            }
185        }
186    
187        /**
188         * Encodes an object into its Base64 form using the default charset. Unsafe characters are escaped.
189         *
190         * @param value
191         *            object to convert to Base64 form
192         * @return Base64 object
193         * @throws EncoderException
194         *             thrown if a failure condition is encountered during the encoding process.
195         */
196        @Override
197        public Object encode(final Object value) throws EncoderException {
198            if (value == null) {
199                return null;
200            } else if (value instanceof String) {
201                return encode((String) value);
202            } else {
203                throw new EncoderException("Objects of type " +
204                      value.getClass().getName() +
205                      " cannot be encoded using BCodec");
206            }
207        }
208    
209        /**
210         * Decodes a Base64 object into its original form. Escaped characters are converted back to their original
211         * representation.
212         *
213         * @param value
214         *            Base64 object to convert into its original form
215         * @return original object
216         * @throws DecoderException
217         *             Thrown if the argument is not a <code>String</code>. Thrown if a failure condition is encountered
218         *             during the decode process.
219         */
220        @Override
221        public Object decode(final Object value) throws DecoderException {
222            if (value == null) {
223                return null;
224            } else if (value instanceof String) {
225                return decode((String) value);
226            } else {
227                throw new DecoderException("Objects of type " +
228                      value.getClass().getName() +
229                      " cannot be decoded using BCodec");
230            }
231        }
232    
233        /**
234         * Gets the default charset name used for string decoding and encoding.
235         *
236         * @return the default charset name
237         * @since 1.7
238         */
239        public Charset getCharset() {
240            return this.charset;
241        }
242    
243        /**
244         * Gets the default charset name used for string decoding and encoding.
245         *
246         * @return the default charset name
247         */
248        public String getDefaultCharset() {
249            return this.charset.name();
250        }
251    }