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    *      http://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.util.BitSet;
22  
23  import org.apache.commons.codec.CharEncoding;
24  import org.apache.commons.codec.DecoderException;
25  import org.apache.commons.codec.EncoderException;
26  import org.apache.commons.codec.StringDecoder;
27  import org.apache.commons.codec.StringEncoder;
28  
29  /**
30   * <p>
31   * Similar to the Quoted-Printable content-transfer-encoding defined in <a
32   * href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a> and designed to allow text containing mostly ASCII
33   * characters to be decipherable on an ASCII terminal without decoding.
34   * </p>
35   * 
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   * 
42   * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message
43   *          Header Extensions for Non-ASCII Text</a>
44   * 
45   * @author Apache Software Foundation
46   * @since 1.3
47   * @version $Id: QCodec.java 1170351 2011-09-13 21:09:09Z ggregory $
48   */
49  public class QCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
50      /**
51       * The default charset used for string decoding and encoding.
52       */
53      private final String charset;
54  
55      /**
56       * BitSet of printable characters as defined in RFC 1522.
57       */
58      private static final BitSet PRINTABLE_CHARS = new BitSet(256);
59      // Static initializer for printable chars collection
60      static {
61          // alpha characters
62          PRINTABLE_CHARS.set(' ');
63          PRINTABLE_CHARS.set('!');
64          PRINTABLE_CHARS.set('"');
65          PRINTABLE_CHARS.set('#');
66          PRINTABLE_CHARS.set('$');
67          PRINTABLE_CHARS.set('%');
68          PRINTABLE_CHARS.set('&');
69          PRINTABLE_CHARS.set('\'');
70          PRINTABLE_CHARS.set('(');
71          PRINTABLE_CHARS.set(')');
72          PRINTABLE_CHARS.set('*');
73          PRINTABLE_CHARS.set('+');
74          PRINTABLE_CHARS.set(',');
75          PRINTABLE_CHARS.set('-');
76          PRINTABLE_CHARS.set('.');
77          PRINTABLE_CHARS.set('/');
78          for (int i = '0'; i <= '9'; i++) {
79              PRINTABLE_CHARS.set(i);
80          }
81          PRINTABLE_CHARS.set(':');
82          PRINTABLE_CHARS.set(';');
83          PRINTABLE_CHARS.set('<');
84          PRINTABLE_CHARS.set('>');
85          PRINTABLE_CHARS.set('@');
86          for (int i = 'A'; i <= 'Z'; i++) {
87              PRINTABLE_CHARS.set(i);
88          }
89          PRINTABLE_CHARS.set('[');
90          PRINTABLE_CHARS.set('\\');
91          PRINTABLE_CHARS.set(']');
92          PRINTABLE_CHARS.set('^');
93          PRINTABLE_CHARS.set('`');
94          for (int i = 'a'; i <= 'z'; i++) {
95              PRINTABLE_CHARS.set(i);
96          }
97          PRINTABLE_CHARS.set('{');
98          PRINTABLE_CHARS.set('|');
99          PRINTABLE_CHARS.set('}');
100         PRINTABLE_CHARS.set('~');
101     }
102 
103     private static final byte BLANK = 32;
104 
105     private static final byte UNDERSCORE = 95;
106 
107     private boolean encodeBlanks = false;
108 
109     /**
110      * Default constructor.
111      */
112     public QCodec() {
113         this(CharEncoding.UTF_8);
114     }
115 
116     /**
117      * Constructor which allows for the selection of a default charset
118      * 
119      * @param charset
120      *                  the default string charset to use.
121      * 
122      * @see <a href="http://download.oracle.com/javase/1.5.0/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
123      */
124     public QCodec(final String charset) {
125         super();
126         this.charset = charset;
127     }
128 
129     @Override
130     protected String getEncoding() {
131         return "Q";
132     }
133 
134     @Override
135     protected byte[] doEncoding(byte[] bytes) {
136         if (bytes == null) {
137             return null;
138         }
139         byte[] data = QuotedPrintableCodec.encodeQuotedPrintable(PRINTABLE_CHARS, bytes);
140         if (this.encodeBlanks) {
141             for (int i = 0; i < data.length; i++) {
142                 if (data[i] == BLANK) {
143                     data[i] = UNDERSCORE;
144                 }
145             }
146         }
147         return data;
148     }
149 
150     @Override
151     protected byte[] doDecoding(byte[] bytes) throws DecoderException {
152         if (bytes == null) {
153             return null;
154         }
155         boolean hasUnderscores = false;
156         for (byte b : bytes) {
157             if (b == UNDERSCORE) {
158                 hasUnderscores = true;
159                 break;
160             }
161         }
162         if (hasUnderscores) {
163             byte[] tmp = new byte[bytes.length];
164             for (int i = 0; i < bytes.length; i++) {
165                 byte b = bytes[i];
166                 if (b != UNDERSCORE) {
167                     tmp[i] = b;
168                 } else {
169                     tmp[i] = BLANK;
170                 }
171             }
172             return QuotedPrintableCodec.decodeQuotedPrintable(tmp);
173         } 
174         return QuotedPrintableCodec.decodeQuotedPrintable(bytes);       
175     }
176 
177     /**
178      * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
179      * 
180      * @param pString
181      *                  string to convert to quoted-printable form
182      * @param charset
183      *                  the charset for pString
184      * @return quoted-printable string
185      * 
186      * @throws EncoderException
187      *                  thrown if a failure condition is encountered during the encoding process.
188      */
189     public String encode(final String pString, final String charset) throws EncoderException {
190         if (pString == null) {
191             return null;
192         }
193         try {
194             return encodeText(pString, charset);
195         } catch (UnsupportedEncodingException e) {
196             throw new EncoderException(e.getMessage(), e);
197         }
198     }
199 
200     /**
201      * Encodes a string into its quoted-printable form using the default charset. Unsafe characters are escaped.
202      * 
203      * @param pString
204      *                  string to convert to quoted-printable form
205      * @return quoted-printable string
206      * 
207      * @throws EncoderException
208      *                  thrown if a failure condition is encountered during the encoding process.
209      */
210     public String encode(String pString) throws EncoderException {
211         if (pString == null) {
212             return null;
213         }
214         return encode(pString, getDefaultCharset());
215     }
216 
217     /**
218      * Decodes a quoted-printable string into its original form. Escaped characters are converted back to their original
219      * representation.
220      * 
221      * @param pString
222      *                  quoted-printable string to convert into its original form
223      * 
224      * @return original string
225      * 
226      * @throws DecoderException
227      *                  A decoder exception is thrown if a failure condition is encountered during the decode process.
228      */
229     public String decode(String pString) throws DecoderException {
230         if (pString == null) {
231             return null;
232         }
233         try {
234             return decodeText(pString);
235         } catch (UnsupportedEncodingException e) {
236             throw new DecoderException(e.getMessage(), e);
237         }
238     }
239 
240     /**
241      * Encodes an object into its quoted-printable form using the default charset. Unsafe characters are escaped.
242      * 
243      * @param pObject
244      *                  object to convert to quoted-printable form
245      * @return quoted-printable object
246      * 
247      * @throws EncoderException
248      *                  thrown if a failure condition is encountered during the encoding process.
249      */
250     public Object encode(Object pObject) throws EncoderException {
251         if (pObject == null) {
252             return null;
253         } else if (pObject instanceof String) {
254             return encode((String) pObject);
255         } else {
256             throw new EncoderException("Objects of type " + 
257                   pObject.getClass().getName() + 
258                   " cannot be encoded using Q codec");
259         }
260     }
261 
262     /**
263      * Decodes a quoted-printable object into its original form. Escaped characters are converted back to their original
264      * representation.
265      * 
266      * @param pObject
267      *                  quoted-printable object to convert into its original form
268      * 
269      * @return original object
270      * 
271      * @throws DecoderException
272      *                  Thrown if the argument is not a <code>String</code>. Thrown if a failure condition is
273      *                  encountered during the decode process.
274      */
275     public Object decode(Object pObject) throws DecoderException {
276         if (pObject == null) {
277             return null;
278         } else if (pObject instanceof String) {
279             return decode((String) pObject);
280         } else {
281             throw new DecoderException("Objects of type " + 
282                   pObject.getClass().getName() + 
283                   " cannot be decoded using Q codec");
284         }
285     }
286 
287     /**
288      * The default charset used for string decoding and encoding.
289      * 
290      * @return the default string charset.
291      */
292     public String getDefaultCharset() {
293         return this.charset;
294     }
295 
296     /**
297      * Tests if optional transformation of SPACE characters is to be used
298      * 
299      * @return <code>true</code> if SPACE characters are to be transformed, <code>false</code> otherwise
300      */
301     public boolean isEncodeBlanks() {
302         return this.encodeBlanks;
303     }
304 
305     /**
306      * Defines whether optional transformation of SPACE characters is to be used
307      * 
308      * @param b
309      *                  <code>true</code> if SPACE characters are to be transformed, <code>false</code> otherwise
310      */
311     public void setEncodeBlanks(boolean b) {
312         this.encodeBlanks = b;
313     }
314 }