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