Skip to content Skip to sidebar Skip to footer

How To Make Android Base64.decode Reliable Throws Exception (or Returning Null) Given An Invalid Base64 Encoded String?

In Java SE, if given an invalid base64 encoded String, the decoder will throw exception. Java SE // java.util.Base64 Base64.Decoder decoder = Base64.getDecoder(); String input = '�

Solution 1:

Initially, I plan to use Apache isBase64, to validate the input before passing into Base64.decode.

https://github.com/apache/commons-codec/blob/master/src/main/java/org/apache/commons/codec/binary/Base64.java

Later, I notice the logic is not strict enough. They do not check for the following rules mentioned in https://stackoverflow.com/a/8571544/72437

  • Check that the length is a multiple of 4 characters
  • Check that every character is in the set A-Z, a-z, 0-9, +, / except for padding at the end which is 0, 1 or 2 '=' characters

Hence, I modify Apache isBase64 with the above rules. Note, for simplicity, I do not accept white space as valid base64 in my usage purpose.

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */package org.apache.commons.codec.binary;

import java.nio.charset.Charset;

importstatic com.yocto.wenote.Constants.UTF_8;

/**
 * Provides Base64 encoding and decoding as defined by <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
 *
 * <p>
 * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose
 * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein.
 * </p>
 * <p>
 * The class can be parameterized in the following manner with various constructors:
 * </p>
 * <ul>
 * <li>URL-safe mode: Default off.</li>
 * <li>Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of
 * 4 in the encoded data.
 * <li>Line separator: Default is CRLF ("\r\n")</li>
 * </ul>
 * <p>
 * The URL-safe parameter is only applied to encode operations. Decoding seamlessly handles both modes.
 * </p>
 * <p>
 * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only
 * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252,
 * UTF-8, etc).
 * </p>
 * <p>
 * This class is thread-safe.
 * </p>
 *
 * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>
 * @since 1.0
 */publicclassBase64 {
    /**
     * Byte used to pad output.
     */protectedstaticfinalbytePAD_DEFAULT='='; // Allow static access to default/**
     * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified
     * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64
     * alphabet but fall within the bounds of the array are translated to -1.
     *
     * Thanks to "commons" project in ws.apache.org for this code.
     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
     */privatestaticfinalbyte[] DECODE_TABLE = {
            //   0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 00-0f
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10-1f
            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, // 20-2f + /52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, // 30-3f 0-9
            -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, // 40-4f A-O15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, // 50-5f P-Z
            -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 60-6f a-o41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51// 70-7a p-z
    };

    /**
     * Returns whether or not the <code>octet</code> is in the base 64 alphabet.
     *
     * @param octet
     *            The value to test
     * @return <code>true</code> if the value is defined in the the base 64 alphabet, <code>false</code> otherwise.
     * @since 1.4
     */staticbooleanisBase64(finalbyte octet) {
        return (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1);
    }

    privatestaticbooleanisPadding(finalbyte octet) {
        returnoctet== PAD_DEFAULT;
    }

    /**
     * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the
     * method treats whitespace as valid.
     *
     * @param base64
     *            String to test
     * @return <code>true</code> if all characters in the String are valid characters in the Base64 alphabet or if
     *         the String is empty; <code>false</code>, otherwise
     *  @since 1.5
     */publicstaticbooleanisBase64(final String base64) {
        return isBase64(getBytesUtf8(base64));
    }

    /**
     * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the
     * method treats whitespace as valid.
     *
     * @param arrayOctet
     *            byte array to test
     * @return <code>true</code> if all bytes are valid characters in the Base64 alphabet or if the byte array is empty;
     *         <code>false</code>, otherwise
     * @since 1.5
     */privatestaticbooleanisBase64(finalbyte[] arrayOctet) {
        finalintlength= arrayOctet.length;

        // Check that the length is a multiple of 4 charactersif (length%4 != 0) {
            returnfalse;
        }

        // Check that every character is in the set A-Z, a-z, 0-9, +, / except for padding at the// end which is 0, 1 or 2 '=' characters.finalintend= Math.max(0, length-2);

        for (inti=0; i < end; i++) {
            if (!isBase64(arrayOctet[i])) {
                returnfalse;
            }
        }

        booleanpadding=false;

        for (inti= end; i < arrayOctet.length; i++) {
            byteoctet= arrayOctet[i];
            if (padding) {
                if (!isPadding(octet)) {
                    returnfalse;
                }
            } else {
                if (isPadding(octet)) {
                    padding = true;
                } elseif (!isBase64(octet)) {
                    returnfalse;
                }
            }
        }

        returntrue;
    }

    /**
     * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte
     * array.
     *
     * @param string
     *            the String to encode, may be <code>null</code>
     * @return encoded bytes, or <code>null</code> if the input string was <code>null</code>
     * @throws NullPointerException
     *             Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is
     *             required by the Java platform specification.
     * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException
     * @see <a href="http://download.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html">Standard charsets</a>
     * @see #getBytesUnchecked(String, String)
     */privatestaticbyte[] getBytesUtf8(final String string) {
        return getBytes(string, UTF_8);
    }

    /**
     * Calls {@link String#getBytes(Charset)}
     *
     * @param string
     *            The string to encode (if null, return null).
     * @param charset
     *            The {@link Charset} to encode the <code>String</code>
     * @return the encoded bytes
     */privatestaticbyte[] getBytes(final String string, final Charset charset) {
        if (string == null) {
            returnnull;
        }
        return string.getBytes(charset);
    }
}

Post a Comment for "How To Make Android Base64.decode Reliable Throws Exception (or Returning Null) Given An Invalid Base64 Encoded String?"