001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018 package org.apache.commons.codec.net; 019 020 import java.io.UnsupportedEncodingException; 021 import java.util.BitSet; 022 023 import org.apache.commons.codec.DecoderException; 024 import org.apache.commons.codec.EncoderException; 025 import org.apache.commons.codec.CharEncoding; 026 import org.apache.commons.codec.StringDecoder; 027 import org.apache.commons.codec.StringEncoder; 028 029 /** 030 * <p> 031 * Similar to the Quoted-Printable content-transfer-encoding defined in <a 032 * href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a> and designed to allow text containing mostly ASCII 033 * characters to be decipherable on an ASCII terminal without decoding. 034 * </p> 035 * 036 * <p> 037 * <a href="http://www.ietf.org/rfc/rfc1522.txt">RFC 1522</a> describes techniques to allow the encoding of non-ASCII 038 * text in various portions of a RFC 822 [2] message header, in a manner which is unlikely to confuse existing message 039 * handling software. 040 * </p> 041 * 042 * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message 043 * Header Extensions for Non-ASCII Text</a> 044 * 045 * @author Apache Software Foundation 046 * @since 1.3 047 * @version $Id: QCodec.java 797857 2009-07-25 23:43:33Z ggregory $ 048 */ 049 public class QCodec extends RFC1522Codec implements StringEncoder, StringDecoder { 050 /** 051 * The default charset used for string decoding and encoding. 052 */ 053 private final String charset; 054 055 /** 056 * BitSet of printable characters as defined in RFC 1522. 057 */ 058 private static final BitSet PRINTABLE_CHARS = new BitSet(256); 059 // Static initializer for printable chars collection 060 static { 061 // alpha characters 062 PRINTABLE_CHARS.set(' '); 063 PRINTABLE_CHARS.set('!'); 064 PRINTABLE_CHARS.set('"'); 065 PRINTABLE_CHARS.set('#'); 066 PRINTABLE_CHARS.set('$'); 067 PRINTABLE_CHARS.set('%'); 068 PRINTABLE_CHARS.set('&'); 069 PRINTABLE_CHARS.set('\''); 070 PRINTABLE_CHARS.set('('); 071 PRINTABLE_CHARS.set(')'); 072 PRINTABLE_CHARS.set('*'); 073 PRINTABLE_CHARS.set('+'); 074 PRINTABLE_CHARS.set(','); 075 PRINTABLE_CHARS.set('-'); 076 PRINTABLE_CHARS.set('.'); 077 PRINTABLE_CHARS.set('/'); 078 for (int i = '0'; i <= '9'; i++) { 079 PRINTABLE_CHARS.set(i); 080 } 081 PRINTABLE_CHARS.set(':'); 082 PRINTABLE_CHARS.set(';'); 083 PRINTABLE_CHARS.set('<'); 084 PRINTABLE_CHARS.set('>'); 085 PRINTABLE_CHARS.set('@'); 086 for (int i = 'A'; i <= 'Z'; i++) { 087 PRINTABLE_CHARS.set(i); 088 } 089 PRINTABLE_CHARS.set('['); 090 PRINTABLE_CHARS.set('\\'); 091 PRINTABLE_CHARS.set(']'); 092 PRINTABLE_CHARS.set('^'); 093 PRINTABLE_CHARS.set('`'); 094 for (int i = 'a'; i <= 'z'; i++) { 095 PRINTABLE_CHARS.set(i); 096 } 097 PRINTABLE_CHARS.set('{'); 098 PRINTABLE_CHARS.set('|'); 099 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://java.sun.com/j2se/1.4.2/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 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(), e); 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(), e); 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 }