1/*
2 * Copyright 2001-2004 The Apache Software Foundation.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.apache.commons.codec.net;
18
19import java.io.ByteArrayOutputStream;
20import java.io.UnsupportedEncodingException;
21import java.util.BitSet;
22import org.apache.commons.codec.BinaryDecoder;
23import org.apache.commons.codec.BinaryEncoder;
24import org.apache.commons.codec.DecoderException;
25import org.apache.commons.codec.EncoderException;
26import org.apache.commons.codec.StringDecoder;
27import org.apache.commons.codec.StringEncoder;
28
29/**
30 * <p>
31 * Codec for the Quoted-Printable section of <a href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521 </a>.
32 * </p>
33 * <p>
34 * The Quoted-Printable encoding is intended to represent data that largely consists of octets that correspond to
35 * printable characters in the ASCII character set. It encodes the data in such a way that the resulting octets are
36 * unlikely to be modified by mail transport. If the data being encoded are mostly ASCII text, the encoded form of the
37 * data remains largely recognizable by humans. A body which is entirely ASCII may also be encoded in Quoted-Printable
38 * to ensure the integrity of the data should the message pass through a character- translating, and/or line-wrapping
39 * gateway.
40 * </p>
41 *
42 * <p>
43 * Note:
44 * </p>
45 * <p>
46 * Rules #3, #4, and #5 of the quoted-printable spec are not implemented yet because the complete quoted-printable spec
47 * does not lend itself well into the byte[] oriented codec framework. Complete the codec once the steamable codec
48 * framework is ready. The motivation behind providing the codec in a partial form is that it can already come in handy
49 * for those applications that do not require quoted-printable line formatting (rules #3, #4, #5), for instance Q codec.
50 * </p>
51 *
52 * @see <a href="http://www.ietf.org/rfc/rfc1521.txt"> RFC 1521 MIME (Multipurpose Internet Mail Extensions) Part One:
53 *          Mechanisms for Specifying and Describing the Format of Internet Message Bodies </a>
54 *
55 * @author Apache Software Foundation
56 * @since 1.3
57 * @version $Id: QuotedPrintableCodec.java,v 1.7 2004/04/09 22:21:07 ggregory Exp $
58 *
59 * @deprecated Please use {@link java.net.URL#openConnection} instead.
60 *     Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
61 *     for further details.
62 */
63@Deprecated
64public class QuotedPrintableCodec implements BinaryEncoder, BinaryDecoder, StringEncoder, StringDecoder {
65    /**
66     * The default charset used for string decoding and encoding.
67     */
68    private String charset = StringEncodings.UTF8;
69
70    /**
71     * BitSet of printable characters as defined in RFC 1521.
72     */
73    private static final BitSet PRINTABLE_CHARS = new BitSet(256);
74
75    private static byte ESCAPE_CHAR = '=';
76
77    private static byte TAB = 9;
78
79    private static byte SPACE = 32;
80    // Static initializer for printable chars collection
81    static {
82        // alpha characters
83        for (int i = 33; i <= 60; i++) {
84            PRINTABLE_CHARS.set(i);
85        }
86        for (int i = 62; i <= 126; i++) {
87            PRINTABLE_CHARS.set(i);
88        }
89        PRINTABLE_CHARS.set(TAB);
90        PRINTABLE_CHARS.set(SPACE);
91    }
92
93    /**
94     * Default constructor.
95     */
96    public QuotedPrintableCodec() {
97        super();
98    }
99
100    /**
101     * Constructor which allows for the selection of a default charset
102     *
103     * @param charset
104     *                  the default string charset to use.
105     */
106    public QuotedPrintableCodec(String charset) {
107        super();
108        this.charset = charset;
109    }
110
111    /**
112     * Encodes byte into its quoted-printable representation.
113     *
114     * @param b
115     *                  byte to encode
116     * @param buffer
117     *                  the buffer to write to
118     */
119    private static final void encodeQuotedPrintable(int b, ByteArrayOutputStream buffer) {
120        buffer.write(ESCAPE_CHAR);
121        char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
122        char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
123        buffer.write(hex1);
124        buffer.write(hex2);
125    }
126
127    /**
128     * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped.
129     *
130     * <p>
131     * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
132     * RFC 1521 and is suitable for encoding binary data and unformatted text.
133     * </p>
134     *
135     * @param printable
136     *                  bitset of characters deemed quoted-printable
137     * @param bytes
138     *                  array of bytes to be encoded
139     * @return array of bytes containing quoted-printable data
140     */
141    public static final byte[] encodeQuotedPrintable(BitSet printable, byte[] bytes) {
142        if (bytes == null) {
143            return null;
144        }
145        if (printable == null) {
146            printable = PRINTABLE_CHARS;
147        }
148        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
149        for (int i = 0; i < bytes.length; i++) {
150            int b = bytes[i];
151            if (b < 0) {
152                b = 256 + b;
153            }
154            if (printable.get(b)) {
155                buffer.write(b);
156            } else {
157                encodeQuotedPrintable(b, buffer);
158            }
159        }
160        return buffer.toByteArray();
161    }
162
163    /**
164     * Decodes an array quoted-printable characters into an array of original bytes. Escaped characters are converted
165     * back to their original representation.
166     *
167     * <p>
168     * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
169     * RFC 1521.
170     * </p>
171     *
172     * @param bytes
173     *                  array of quoted-printable characters
174     * @return array of original bytes
175     * @throws DecoderException
176     *                  Thrown if quoted-printable decoding is unsuccessful
177     */
178    public static final byte[] decodeQuotedPrintable(byte[] bytes) throws DecoderException {
179        if (bytes == null) {
180            return null;
181        }
182        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
183        for (int i = 0; i < bytes.length; i++) {
184            int b = bytes[i];
185            if (b == ESCAPE_CHAR) {
186                try {
187                    int u = Character.digit((char) bytes[++i], 16);
188                    int l = Character.digit((char) bytes[++i], 16);
189                    if (u == -1 || l == -1) {
190                        throw new DecoderException("Invalid quoted-printable encoding");
191                    }
192                    buffer.write((char) ((u << 4) + l));
193                } catch (ArrayIndexOutOfBoundsException e) {
194                    throw new DecoderException("Invalid quoted-printable encoding");
195                }
196            } else {
197                buffer.write(b);
198            }
199        }
200        return buffer.toByteArray();
201    }
202
203    /**
204     * Encodes an array of bytes into an array of quoted-printable 7-bit characters. Unsafe characters are escaped.
205     *
206     * <p>
207     * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
208     * RFC 1521 and is suitable for encoding binary data and unformatted text.
209     * </p>
210     *
211     * @param bytes
212     *                  array of bytes to be encoded
213     * @return array of bytes containing quoted-printable data
214     */
215    public byte[] encode(byte[] bytes) {
216        return encodeQuotedPrintable(PRINTABLE_CHARS, bytes);
217    }
218
219    /**
220     * Decodes an array of quoted-printable characters into an array of original bytes. Escaped characters are converted
221     * back to their original representation.
222     *
223     * <p>
224     * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
225     * RFC 1521.
226     * </p>
227     *
228     * @param bytes
229     *                  array of quoted-printable characters
230     * @return array of original bytes
231     * @throws DecoderException
232     *                  Thrown if quoted-printable decoding is unsuccessful
233     */
234    public byte[] decode(byte[] bytes) throws DecoderException {
235        return decodeQuotedPrintable(bytes);
236    }
237
238    /**
239     * Encodes a string into its quoted-printable form using the default string charset. Unsafe characters are escaped.
240     *
241     * <p>
242     * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
243     * RFC 1521 and is suitable for encoding binary data.
244     * </p>
245     *
246     * @param pString
247     *                  string to convert to quoted-printable form
248     * @return quoted-printable string
249     *
250     * @throws EncoderException
251     *                  Thrown if quoted-printable encoding is unsuccessful
252     *
253     * @see #getDefaultCharset()
254     */
255    public String encode(String pString) throws EncoderException {
256        if (pString == null) {
257            return null;
258        }
259        try {
260            return encode(pString, getDefaultCharset());
261        } catch (UnsupportedEncodingException e) {
262            throw new EncoderException(e.getMessage());
263        }
264    }
265
266    /**
267     * Decodes a quoted-printable string into its original form using the specified string charset. Escaped characters
268     * are converted back to their original representation.
269     *
270     * @param pString
271     *                  quoted-printable string to convert into its original form
272     * @param charset
273     *                  the original string charset
274     * @return original string
275     * @throws DecoderException
276     *                  Thrown if quoted-printable decoding is unsuccessful
277     * @throws UnsupportedEncodingException
278     *                  Thrown if charset is not supported
279     */
280    public String decode(String pString, String charset) throws DecoderException, UnsupportedEncodingException {
281        if (pString == null) {
282            return null;
283        }
284        return new String(decode(pString.getBytes(StringEncodings.US_ASCII)), charset);
285    }
286
287    /**
288     * Decodes a quoted-printable string into its original form using the default string charset. Escaped characters are
289     * converted back to their original representation.
290     *
291     * @param pString
292     *                  quoted-printable string to convert into its original form
293     * @return original string
294     * @throws DecoderException
295     *                  Thrown if quoted-printable decoding is unsuccessful
296     * @throws UnsupportedEncodingException
297     *                  Thrown if charset is not supported
298     * @see #getDefaultCharset()
299     */
300    public String decode(String pString) throws DecoderException {
301        if (pString == null) {
302            return null;
303        }
304        try {
305            return decode(pString, getDefaultCharset());
306        } catch (UnsupportedEncodingException e) {
307            throw new DecoderException(e.getMessage());
308        }
309    }
310
311    /**
312     * Encodes an object into its quoted-printable safe form. Unsafe characters are escaped.
313     *
314     * @param pObject
315     *                  string to convert to a quoted-printable form
316     * @return quoted-printable object
317     * @throws EncoderException
318     *                  Thrown if quoted-printable encoding is not applicable to objects of this type or if encoding is
319     *                  unsuccessful
320     */
321    public Object encode(Object pObject) throws EncoderException {
322        if (pObject == null) {
323            return null;
324        } else if (pObject instanceof byte[]) {
325            return encode((byte[]) pObject);
326        } else if (pObject instanceof String) {
327            return encode((String) pObject);
328        } else {
329            throw new EncoderException("Objects of type "
330                + pObject.getClass().getName()
331                + " cannot be quoted-printable encoded");
332        }
333    }
334
335    /**
336     * Decodes a quoted-printable object into its original form. Escaped characters are converted back to their original
337     * representation.
338     *
339     * @param pObject
340     *                  quoted-printable object to convert into its original form
341     * @return original object
342     * @throws DecoderException
343     *                  Thrown if quoted-printable decoding is not applicable to objects of this type if decoding is
344     *                  unsuccessful
345     */
346    public Object decode(Object pObject) throws DecoderException {
347        if (pObject == null) {
348            return null;
349        } else if (pObject instanceof byte[]) {
350            return decode((byte[]) pObject);
351        } else if (pObject instanceof String) {
352            return decode((String) pObject);
353        } else {
354            throw new DecoderException("Objects of type "
355                + pObject.getClass().getName()
356                + " cannot be quoted-printable decoded");
357        }
358    }
359
360    /**
361     * Returns the default charset used for string decoding and encoding.
362     *
363     * @return the default string charset.
364     */
365    public String getDefaultCharset() {
366        return this.charset;
367    }
368
369    /**
370     * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
371     *
372     * <p>
373     * This function implements a subset of quoted-printable encoding specification (rule #1 and rule #2) as defined in
374     * RFC 1521 and is suitable for encoding binary data and unformatted text.
375     * </p>
376     *
377     * @param pString
378     *                  string to convert to quoted-printable form
379     * @param charset
380     *                  the charset for pString
381     * @return quoted-printable string
382     *
383     * @throws UnsupportedEncodingException
384     *                  Thrown if the charset is not supported
385     */
386    public String encode(String pString, String charset) throws UnsupportedEncodingException {
387        if (pString == null) {
388            return null;
389        }
390        return new String(encode(pString.getBytes(charset)), StringEncodings.US_ASCII);
391    }
392}
393