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