This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-fileupload.git
commit bc597d653d417a0b872b27458e4f42c89c00c1b7 Author: Gary Gregory <[email protected]> AuthorDate: Sun Apr 2 12:04:51 2023 -0400 Replace org.apache.commons.fileupload2.util.mime.Base64Decoder with java.util.Base64 --- src/changes/changes.xml | 1 + .../fileupload2/util/mime/Base64Decoder.java | 151 ------------------- .../commons/fileupload2/util/mime/MimeUtility.java | 3 +- .../util/mime/Base64DecoderTestCase.java | 162 --------------------- 4 files changed, 3 insertions(+), 314 deletions(-) diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 1c2cc94..76fd66b 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -71,6 +71,7 @@ The <action> type attribute can be add,update,fix,remove. <action dev="ggregory" type="remove" due-to="Gary Gregory">Remove deprecated FileUploadBase.getFileName(Map).</action> <action dev="ggregory" type="remove" due-to="Gary Gregory">Remove deprecated FileUploadBase.getHeader(Map, String).</action> <action dev="ggregory" type="remove" due-to="Gary Gregory">Remove deprecated FileUploadBase.parseHeaders(String).</action> + <action dev="ggregory" type="remove" due-to="Gary Gregory">Replace org.apache.commons.fileupload2.util.mime.Base64Decoder with java.util.Base64.</action> <!-- UPDATE --> <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">Bump actions/cache from 2.1.6 to 3.0.8 #128, #140.</action> <action dev="ggregory" type="update" due-to="Dependabot, Gary Gregory">Bump actions/checkout from 2.3.4 to 3.0.2 #125.</action> diff --git a/src/main/java/org/apache/commons/fileupload2/util/mime/Base64Decoder.java b/src/main/java/org/apache/commons/fileupload2/util/mime/Base64Decoder.java deleted file mode 100644 index 5222e8f..0000000 --- a/src/main/java/org/apache/commons/fileupload2/util/mime/Base64Decoder.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * 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.fileupload2.util.mime; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.Arrays; - -/** - * @since 1.3 - */ -final class Base64Decoder { - - /** - * Decoding table value for invalid bytes. - */ - private static final byte INVALID_BYTE = -1; // must be outside range 0-63 - - /** - * Decoding table value for padding bytes, so can detect PAD after conversion. - */ - private static final int PAD_BYTE = -2; // must be outside range 0-63 - - /** - * Mask to treat byte as unsigned integer. - */ - private static final int MASK_BYTE_UNSIGNED = 0xFF; - - /** - * Number of bytes per encoded chunk - 4 6bit bytes produce 3 8bit bytes on output. - */ - private static final int INPUT_BYTES_PER_CHUNK = 4; - - /** - * Set up the encoding table. - */ - private static final byte[] ENCODING_TABLE = { - (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', - (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', - (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', - (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', - (byte) '7', (byte) '8', (byte) '9', - (byte) '+', (byte) '/' - }; - - /** - * The padding byte. - */ - private static final byte PADDING = (byte) '='; - - /** - * Set up the decoding table; this is indexed by a byte converted to an unsigned int, - * so must be at least as large as the number of different byte values, - * positive and negative and zero. - */ - private static final byte[] DECODING_TABLE = new byte[Byte.MAX_VALUE - Byte.MIN_VALUE + 1]; - - static { - // Initialize as all invalid characters - Arrays.fill(DECODING_TABLE, INVALID_BYTE); - // set up valid characters - for (int i = 0; i < ENCODING_TABLE.length; i++) { - DECODING_TABLE[ENCODING_TABLE[i]] = (byte) i; - } - // Allow pad byte to be easily detected after conversion - DECODING_TABLE[PADDING] = PAD_BYTE; - } - - /** - * Decode the base 64 encoded byte data writing it to the given output stream, - * whitespace characters will be ignored. - * - * @param data the buffer containing the Base64-encoded data - * @param out the output stream to hold the decoded bytes - * - * @return the number of bytes produced. - * @throws IOException thrown when the padding is incorrect or the input is truncated. - */ - public static int decode(final byte[] data, final OutputStream out) throws IOException { - int outLen = 0; - final byte[] cache = new byte[INPUT_BYTES_PER_CHUNK]; - int cachedBytes = 0; - - for (final byte b : data) { - final byte d = DECODING_TABLE[MASK_BYTE_UNSIGNED & b]; - if (d == INVALID_BYTE) { - continue; // Ignore invalid bytes - } - cache[cachedBytes++] = d; - if (cachedBytes == INPUT_BYTES_PER_CHUNK) { - // CHECKSTYLE IGNORE MagicNumber FOR NEXT 4 LINES - final byte b1 = cache[0]; - final byte b2 = cache[1]; - final byte b3 = cache[2]; - final byte b4 = cache[3]; - if (b1 == PAD_BYTE || b2 == PAD_BYTE) { - throw new IOException("Invalid Base64 input: incorrect padding, first two bytes cannot be padding"); - } - // Convert 4 6-bit bytes to 3 8-bit bytes - // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE - out.write((b1 << 2) | (b2 >> 4)); // 6 bits of b1 plus 2 bits of b2 - outLen++; - if (b3 != PAD_BYTE) { - // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE - out.write((b2 << 4) | (b3 >> 2)); // 4 bits of b2 plus 4 bits of b3 - outLen++; - if (b4 != PAD_BYTE) { - // CHECKSTYLE IGNORE MagicNumber FOR NEXT 1 LINE - out.write((b3 << 6) | b4); // 2 bits of b3 plus 6 bits of b4 - outLen++; - } - } else if (b4 != PAD_BYTE) { // if byte 3 is pad, byte 4 must be pad too - throw new // line wrap to avoid 120 char limit - IOException("Invalid Base64 input: incorrect padding, 4th byte must be padding if 3rd byte is"); - } - cachedBytes = 0; - } - } - // Check for anything left over - if (cachedBytes != 0) { - throw new IOException("Invalid Base64 input: truncated"); - } - return outLen; - } - - /** - * Hidden constructor, this class must not be instantiated. - */ - private Base64Decoder() { - // do nothing - } -} diff --git a/src/main/java/org/apache/commons/fileupload2/util/mime/MimeUtility.java b/src/main/java/org/apache/commons/fileupload2/util/mime/MimeUtility.java index 9f56fec..9f61f6f 100644 --- a/src/main/java/org/apache/commons/fileupload2/util/mime/MimeUtility.java +++ b/src/main/java/org/apache/commons/fileupload2/util/mime/MimeUtility.java @@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -230,7 +231,7 @@ public final class MimeUtility { // Base64 encoded? if (encoding.equals(BASE64_ENCODING_MARKER)) { - Base64Decoder.decode(encodedData, out); + out.write(Base64.getMimeDecoder().decode(encodedData)); } else if (encoding.equals(QUOTEDPRINTABLE_ENCODING_MARKER)) { // maybe quoted printable. QuotedPrintableDecoder.decode(encodedData, out); } else { diff --git a/src/test/java/org/apache/commons/fileupload2/util/mime/Base64DecoderTestCase.java b/src/test/java/org/apache/commons/fileupload2/util/mime/Base64DecoderTestCase.java deleted file mode 100644 index ead3263..0000000 --- a/src/test/java/org/apache/commons/fileupload2/util/mime/Base64DecoderTestCase.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * 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.fileupload2.util.mime; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import org.junit.jupiter.api.Test; - -/** - * @since 1.3 - */ -public final class Base64DecoderTestCase { - - private static void assertEncoded(final String clearText, final String encoded) throws Exception { - final byte[] expected = clearText.getBytes(StandardCharsets.US_ASCII); - - final ByteArrayOutputStream out = new ByteArrayOutputStream(encoded.length()); - final byte[] encodedData = encoded.getBytes(StandardCharsets.US_ASCII); - Base64Decoder.decode(encodedData, out); - final byte[] actual = out.toByteArray(); - - assertArrayEquals(expected, actual); - } - - private static void assertIOException(final String messageText, final String encoded) { - final ByteArrayOutputStream out = new ByteArrayOutputStream(encoded.length()); - final byte[] encodedData = encoded.getBytes(StandardCharsets.US_ASCII); - try { - Base64Decoder.decode(encodedData, out); - fail("Expected IOException"); - } catch (final IOException e) { - final String em = e.getMessage(); - assertTrue(em.contains(messageText), "Expected to find " + messageText + " in '" + em + "'"); - } - } - - // This input causes java.lang.ArrayIndexOutOfBoundsException: 1 - // in the Java 6 method DatatypeConverter.parseBase64Binary(String) - // currently reported as truncated (the last chunk consists just of '=') - @Test - public void badLength() throws Exception { - assertIOException("truncated", "Zm8=="); - } - - @Test - public void badPadding() throws Exception { - assertIOException("incorrect padding, 4th byte", "Zg=a"); - } - - @Test - public void badPaddingLeading1() throws Exception { - assertIOException("incorrect padding, first two bytes cannot be padding", "=A=="); - } - - @Test - public void badPaddingLeading2() throws Exception { - assertIOException("incorrect padding, first two bytes cannot be padding", "===="); - } - - // If there are valid trailing Base64 chars, complain - @Test - public void decodeTrailing1() throws Exception { - assertIOException("truncated", "Zm9vYmFy1"); - } - - // If there are valid trailing Base64 chars, complain - @Test - public void decodeTrailing2() throws Exception { - assertIOException("truncated", "Zm9vYmFy12"); - } - - // If there are valid trailing Base64 chars, complain - @Test - public void decodeTrailing3() throws Exception { - assertIOException("truncated", "Zm9vYmFy123"); - } - - @Test - public void decodeTrailingJunk() throws Exception { - assertEncoded("foobar", "Zm9vYmFy!!!"); - } - - /** - * Test our decode with pad character in the middle. - * Continues provided that the padding is in the correct place, - * i.e. concatenated valid strings decode OK. - */ - @Test - public void decodeWithInnerPad() throws Exception { - assertEncoded("Hello WorldHello World", "SGVsbG8gV29ybGQ=SGVsbG8gV29ybGQ="); - } - - // These inputs cause java.lang.ArrayIndexOutOfBoundsException - // in the Java 6 method DatatypeConverter.parseBase64Binary(String) - // The non-ASCII characters should just be ignored - @Test - public void nonASCIIcharacter() throws Exception { - assertEncoded("f", "Zg=À="); // A-grave - assertEncoded("f", "Zg=\u0100="); - } - - /** - * Ignores non-BASE64 bytes. - */ - @Test - public void nonBase64Bytes() throws Exception { - assertEncoded("Hello World", "S?G!V%sbG 8g\rV\t\n29ybGQ*="); - } - - /** - * Tests RFC 4648 section 10 test vectors. - * <ul> - * <li>BASE64("") = ""</li> - * <li>BASE64("f") = "Zg=="</li> - * <li>BASE64("fo") = "Zm8="</li> - * <li>BASE64("foo") = "Zm9v"</li> - * <li>BASE64("foob") = "Zm9vYg=="</li> - * <li>BASE64("fooba") = "Zm9vYmE="</li> - * <li>BASE64("foobar") = "Zm9vYmFy"</li> - * </ul> - * - * @see <a href="http://tools.ietf.org/html/rfc4648">http://tools.ietf.org/html/rfc4648</a> - */ - @Test - public void rfc4648Section10Decode() throws Exception { - assertEncoded("", ""); - assertEncoded("f", "Zg=="); - assertEncoded("fo", "Zm8="); - assertEncoded("foo", "Zm9v"); - assertEncoded("foob", "Zm9vYg=="); - assertEncoded("fooba", "Zm9vYmE="); - assertEncoded("foobar", "Zm9vYmFy"); - } - - @Test - public void truncatedString() { - final byte[] x = {'n'}; - assertThrows(IOException.class, () -> Base64Decoder.decode(x, new ByteArrayOutputStream())); - } - -}
