View Javadoc

    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  package org.apache.commons.fileupload.util.mime;
    18
    19  import java.io.IOException;
    20  import java.io.OutputStream;
    21
    22  /**
    23   * @since 1.3
    24   */
    25  final class QuotedPrintableDecoder {
    26
    27      /**
    28       * The shift value required to create the upper nibble
    29       * from the first of 2 byte values converted from ascii hex.
    30       */
    31      private static final int UPPER_NIBBLE_SHIFT = Byte.SIZE / 2;
    32
    33      /**
    34       * Hidden constructor, this class must not be instantiated.
    35       */
    36      private QuotedPrintableDecoder() {
    37          // do nothing
    38      }
    39
    40      /**
    41       * Decode the encoded byte data writing it to the given output stream.
    42       *
    43       * @param data   The array of byte data to decode.
    44       * @param out    The output stream used to return the decoded data.
    45       *
    46       * @return the number of bytes produced.
    47       * @throws IOException
    48       */
    49      public static int decode(byte[] data, OutputStream out) throws IOException {
    50          int off = 0;
    51          int length = data.length;
    52          int endOffset = off + length;
    53          int bytesWritten = 0;
    54
    55          while (off < endOffset) {
    56              byte ch = data[off++];
    57
    58              // space characters were translated to '_' on encode, so we need to translate them back.
    59              if (ch == '_') {
    60                  out.write(' ');
    61              } else if (ch == '=') {
    62                  // we found an encoded character.  Reduce the 3 char sequence to one.
    63                  // but first, make sure we have two characters to work with.
    64                  if (off + 1 >= endOffset) {
    65                      throw new IOException("Invalid quoted printable encoding; truncated escape sequence");
    66                  }
    67
    68                  byte b1 = data[off++];
    69                  byte b2 = data[off++];
    70
    71                  // we've found an encoded carriage return.  The next char needs to be a newline
    72                  if (b1 == '\r') {
    73                      if (b2 != '\n') {
    74                          throw new IOException("Invalid quoted printable encoding; CR must be followed by LF");
    75                      }
    76                      // this was a soft linebreak inserted by the encoding.  We just toss this away
    77                      // on decode.
    78                  } else {
    79                      // this is a hex pair we need to convert back to a single byte.
    80                      int c1 = hexToBinary(b1);
    81                      int c2 = hexToBinary(b2);
    82                      out.write((c1 << UPPER_NIBBLE_SHIFT) | c2);
    83                      // 3 bytes in, one byte out
    84                      bytesWritten++;
    85                  }
    86              } else {
    87                  // simple character, just write it out.
    88                  out.write(ch);
    89                  bytesWritten++;
    90              }
    91          }
    92
    93          return bytesWritten;
    94      }
    95
    96      /**
    97       * Convert a hex digit to the binary value it represents.
    98       *
    99       * @param b the ascii hex byte to convert (0-0, A-F, a-f)
    100      * @return the int value of the hex byte, 0-15
    101      * @throws IOException if the byte is not a valid hex digit.
    102      */
    103     private static int hexToBinary(final byte b) throws IOException {
    104         // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE
    105         final int i = Character.digit((char) b, 16);
    106         if (i == -1) {
    107             throw new IOException("Invalid quoted printable encoding: not a valid hex digit: " + b);
    108         }
    109         return i;
    110     }
    111
    112 }