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