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 }