Coverage Report - org.apache.commons.codec.net.QCodec
 
Classes in this File Line Coverage Branch Coverage Complexity
QCodec
93%
95/102
94%
34/36
3.312
 
 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.nio.charset.Charset;
 22  
 import java.util.BitSet;
 23  
 
 24  
 import org.apache.commons.codec.Charsets;
 25  
 import org.apache.commons.codec.DecoderException;
 26  
 import org.apache.commons.codec.EncoderException;
 27  
 import org.apache.commons.codec.StringDecoder;
 28  
 import org.apache.commons.codec.StringEncoder;
 29  
 
 30  
 /**
 31  
  * Similar to the Quoted-Printable content-transfer-encoding defined in
 32  
  * <a href="http://www.ietf.org/rfc/rfc1521.txt">RFC 1521</a> and designed to allow text containing mostly ASCII
 33  
  * characters to be decipherable on an ASCII terminal without decoding.
 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  
  * This class is conditionally thread-safe.
 40  
  * The instance field {@link #encodeBlanks} is mutable {@link #setEncodeBlanks(boolean)}
 41  
  * but is not volatile, and accesses are not synchronised.
 42  
  * If an instance of the class is shared between threads, the caller needs to ensure that suitable synchronisation
 43  
  * is used to ensure safe publication of the value between threads, and must not invoke
 44  
  * {@link #setEncodeBlanks(boolean)} after initial setup.
 45  
  *
 46  
  * @see <a href="http://www.ietf.org/rfc/rfc1522.txt">MIME (Multipurpose Internet Mail Extensions) Part Two: Message
 47  
  *          Header Extensions for Non-ASCII Text</a>
 48  
  *
 49  
  * @since 1.3
 50  
  * @version $Id$
 51  
  */
 52  
 public class QCodec extends RFC1522Codec implements StringEncoder, StringDecoder {
 53  
     /**
 54  
      * The default charset used for string decoding and encoding.
 55  
      */
 56  
     private final Charset charset;
 57  
 
 58  
     /**
 59  
      * BitSet of printable characters as defined in RFC 1522.
 60  
      */
 61  1
     private static final BitSet PRINTABLE_CHARS = new BitSet(256);
 62  
     // Static initializer for printable chars collection
 63  
     static {
 64  
         // alpha characters
 65  1
         PRINTABLE_CHARS.set(' ');
 66  1
         PRINTABLE_CHARS.set('!');
 67  1
         PRINTABLE_CHARS.set('"');
 68  1
         PRINTABLE_CHARS.set('#');
 69  1
         PRINTABLE_CHARS.set('$');
 70  1
         PRINTABLE_CHARS.set('%');
 71  1
         PRINTABLE_CHARS.set('&');
 72  1
         PRINTABLE_CHARS.set('\'');
 73  1
         PRINTABLE_CHARS.set('(');
 74  1
         PRINTABLE_CHARS.set(')');
 75  1
         PRINTABLE_CHARS.set('*');
 76  1
         PRINTABLE_CHARS.set('+');
 77  1
         PRINTABLE_CHARS.set(',');
 78  1
         PRINTABLE_CHARS.set('-');
 79  1
         PRINTABLE_CHARS.set('.');
 80  1
         PRINTABLE_CHARS.set('/');
 81  11
         for (int i = '0'; i <= '9'; i++) {
 82  10
             PRINTABLE_CHARS.set(i);
 83  
         }
 84  1
         PRINTABLE_CHARS.set(':');
 85  1
         PRINTABLE_CHARS.set(';');
 86  1
         PRINTABLE_CHARS.set('<');
 87  1
         PRINTABLE_CHARS.set('>');
 88  1
         PRINTABLE_CHARS.set('@');
 89  27
         for (int i = 'A'; i <= 'Z'; i++) {
 90  26
             PRINTABLE_CHARS.set(i);
 91  
         }
 92  1
         PRINTABLE_CHARS.set('[');
 93  1
         PRINTABLE_CHARS.set('\\');
 94  1
         PRINTABLE_CHARS.set(']');
 95  1
         PRINTABLE_CHARS.set('^');
 96  1
         PRINTABLE_CHARS.set('`');
 97  27
         for (int i = 'a'; i <= 'z'; i++) {
 98  26
             PRINTABLE_CHARS.set(i);
 99  
         }
 100  1
         PRINTABLE_CHARS.set('{');
 101  1
         PRINTABLE_CHARS.set('|');
 102  1
         PRINTABLE_CHARS.set('}');
 103  1
         PRINTABLE_CHARS.set('~');
 104  1
     }
 105  
 
 106  
     private static final byte BLANK = 32;
 107  
 
 108  
     private static final byte UNDERSCORE = 95;
 109  
 
 110  11
     private boolean encodeBlanks = false;
 111  
 
 112  
     /**
 113  
      * Default constructor.
 114  
      */
 115  
     public QCodec() {
 116  10
         this(Charsets.UTF_8);
 117  10
     }
 118  
 
 119  
     /**
 120  
      * Constructor which allows for the selection of a default charset.
 121  
      *
 122  
      * @param charset
 123  
      *            the default string charset to use.
 124  
      *
 125  
      * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
 126  
      * @since 1.7
 127  
      */
 128  
     public QCodec(final Charset charset) {
 129  11
         super();
 130  11
         this.charset = charset;
 131  11
     }
 132  
 
 133  
     /**
 134  
      * Constructor which allows for the selection of a default charset.
 135  
      *
 136  
      * @param charsetName
 137  
      *            the charset to use.
 138  
      * @throws java.nio.charset.UnsupportedCharsetException
 139  
      *             If the named charset is unavailable
 140  
      * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
 141  
      * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
 142  
      */
 143  
     public QCodec(final String charsetName) {
 144  2
         this(Charset.forName(charsetName));
 145  1
     }
 146  
 
 147  
     @Override
 148  
     protected String getEncoding() {
 149  16
         return "Q";
 150  
     }
 151  
 
 152  
     @Override
 153  
     protected byte[] doEncoding(final byte[] bytes) {
 154  10
         if (bytes == null) {
 155  1
             return null;
 156  
         }
 157  9
         final byte[] data = QuotedPrintableCodec.encodeQuotedPrintable(PRINTABLE_CHARS, bytes);
 158  9
         if (this.encodeBlanks) {
 159  24
             for (int i = 0; i < data.length; i++) {
 160  23
                 if (data[i] == BLANK) {
 161  3
                     data[i] = UNDERSCORE;
 162  
                 }
 163  
             }
 164  
         }
 165  9
         return data;
 166  
     }
 167  
 
 168  
     @Override
 169  
     protected byte[] doDecoding(final byte[] bytes) throws DecoderException {
 170  8
         if (bytes == null) {
 171  1
             return null;
 172  
         }
 173  7
         boolean hasUnderscores = false;
 174  174
         for (final byte b : bytes) {
 175  168
             if (b == UNDERSCORE) {
 176  1
                 hasUnderscores = true;
 177  1
                 break;
 178  
             }
 179  
         }
 180  7
         if (hasUnderscores) {
 181  1
             final byte[] tmp = new byte[bytes.length];
 182  24
             for (int i = 0; i < bytes.length; i++) {
 183  23
                 final byte b = bytes[i];
 184  23
                 if (b != UNDERSCORE) {
 185  20
                     tmp[i] = b;
 186  
                 } else {
 187  3
                     tmp[i] = BLANK;
 188  
                 }
 189  
             }
 190  1
             return QuotedPrintableCodec.decodeQuotedPrintable(tmp);
 191  
         }
 192  6
         return QuotedPrintableCodec.decodeQuotedPrintable(bytes);
 193  
     }
 194  
 
 195  
     /**
 196  
      * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
 197  
      *
 198  
      * @param str
 199  
      *            string to convert to quoted-printable form
 200  
      * @param charset
 201  
      *            the charset for str
 202  
      * @return quoted-printable string
 203  
      * @throws EncoderException
 204  
      *             thrown if a failure condition is encountered during the encoding process.
 205  
      * @since 1.7
 206  
      */
 207  
     public String encode(final String str, final Charset charset) throws EncoderException {
 208  9
         if (str == null) {
 209  0
             return null;
 210  
         }
 211  9
         return encodeText(str, charset);
 212  
     }
 213  
 
 214  
     /**
 215  
      * Encodes a string into its quoted-printable form using the specified charset. Unsafe characters are escaped.
 216  
      *
 217  
      * @param str
 218  
      *            string to convert to quoted-printable form
 219  
      * @param charset
 220  
      *            the charset for str
 221  
      * @return quoted-printable string
 222  
      * @throws EncoderException
 223  
      *             thrown if a failure condition is encountered during the encoding process.
 224  
      */
 225  
     public String encode(final String str, final String charset) throws EncoderException {
 226  1
         if (str == null) {
 227  1
             return null;
 228  
         }
 229  
         try {
 230  0
             return encodeText(str, charset);
 231  0
         } catch (final UnsupportedEncodingException e) {
 232  0
             throw new EncoderException(e.getMessage(), e);
 233  
         }
 234  
     }
 235  
 
 236  
     /**
 237  
      * Encodes a string into its quoted-printable form using the default charset. Unsafe characters are escaped.
 238  
      *
 239  
      * @param str
 240  
      *            string to convert to quoted-printable form
 241  
      * @return quoted-printable string
 242  
      * @throws EncoderException
 243  
      *             thrown if a failure condition is encountered during the encoding process.
 244  
      */
 245  
     @Override
 246  
     public String encode(final String str) throws EncoderException {
 247  10
         if (str == null) {
 248  1
             return null;
 249  
         }
 250  9
         return encode(str, getCharset());
 251  
     }
 252  
 
 253  
     /**
 254  
      * Decodes a quoted-printable string into its original form. Escaped characters are converted back to their original
 255  
      * representation.
 256  
      *
 257  
      * @param str
 258  
      *            quoted-printable string to convert into its original form
 259  
      * @return original string
 260  
      * @throws DecoderException
 261  
      *             A decoder exception is thrown if a failure condition is encountered during the decode process.
 262  
      */
 263  
     @Override
 264  
     public String decode(final String str) throws DecoderException {
 265  9
         if (str == null) {
 266  2
             return null;
 267  
         }
 268  
         try {
 269  7
             return decodeText(str);
 270  0
         } catch (final UnsupportedEncodingException e) {
 271  0
             throw new DecoderException(e.getMessage(), e);
 272  
         }
 273  
     }
 274  
 
 275  
     /**
 276  
      * Encodes an object into its quoted-printable form using the default charset. Unsafe characters are escaped.
 277  
      *
 278  
      * @param obj
 279  
      *            object to convert to quoted-printable form
 280  
      * @return quoted-printable object
 281  
      * @throws EncoderException
 282  
      *             thrown if a failure condition is encountered during the encoding process.
 283  
      */
 284  
     @Override
 285  
     public Object encode(final Object obj) throws EncoderException {
 286  3
         if (obj == null) {
 287  1
             return null;
 288  2
         } else if (obj instanceof String) {
 289  1
             return encode((String) obj);
 290  
         } else {
 291  1
             throw new EncoderException("Objects of type " +
 292  
                   obj.getClass().getName() +
 293  
                   " cannot be encoded using Q codec");
 294  
         }
 295  
     }
 296  
 
 297  
     /**
 298  
      * Decodes a quoted-printable object into its original form. Escaped characters are converted back to their original
 299  
      * representation.
 300  
      *
 301  
      * @param obj
 302  
      *            quoted-printable object to convert into its original form
 303  
      * @return original object
 304  
      * @throws DecoderException
 305  
      *             Thrown if the argument is not a <code>String</code>. Thrown if a failure condition is encountered
 306  
      *             during the decode process.
 307  
      */
 308  
     @Override
 309  
     public Object decode(final Object obj) throws DecoderException {
 310  3
         if (obj == null) {
 311  1
             return null;
 312  2
         } else if (obj instanceof String) {
 313  1
             return decode((String) obj);
 314  
         } else {
 315  1
             throw new DecoderException("Objects of type " +
 316  
                   obj.getClass().getName() +
 317  
                   " cannot be decoded using Q codec");
 318  
         }
 319  
     }
 320  
 
 321  
     /**
 322  
      * Gets the default charset name used for string decoding and encoding.
 323  
      *
 324  
      * @return the default charset name
 325  
      * @since 1.7
 326  
      */
 327  
     public Charset getCharset() {
 328  9
         return this.charset;
 329  
     }
 330  
 
 331  
     /**
 332  
      * Gets the default charset name used for string decoding and encoding.
 333  
      *
 334  
      * @return the default charset name
 335  
      */
 336  
     public String getDefaultCharset() {
 337  0
         return this.charset.name();
 338  
     }
 339  
 
 340  
     /**
 341  
      * Tests if optional transformation of SPACE characters is to be used
 342  
      *
 343  
      * @return {@code true} if SPACE characters are to be transformed, {@code false} otherwise
 344  
      */
 345  
     public boolean isEncodeBlanks() {
 346  2
         return this.encodeBlanks;
 347  
     }
 348  
 
 349  
     /**
 350  
      * Defines whether optional transformation of SPACE characters is to be used
 351  
      *
 352  
      * @param b
 353  
      *            {@code true} if SPACE characters are to be transformed, {@code false} otherwise
 354  
      */
 355  
     public void setEncodeBlanks(final boolean b) {
 356  4
         this.encodeBlanks = b;
 357  4
     }
 358  
 }