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