From bb6acd30db9a9f2a88627ffb0ec85f58837eab1e Mon Sep 17 00:00:00 2001 From: Nithin U <106614289+NithinU2802@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:19:50 +0530 Subject: [PATCH 1/7] feat: base64 algorithm implementation --- .../com/thealgorithms/conversions/Base64.java | 195 ++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 src/main/java/com/thealgorithms/conversions/Base64.java diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java new file mode 100644 index 000000000000..d6ea76e001b8 --- /dev/null +++ b/src/main/java/com/thealgorithms/conversions/Base64.java @@ -0,0 +1,195 @@ +package com.thealgorithms.conversions; + +import java.util.List; +import java.util.ArrayList; +import java.nio.charset.StandardCharsets; + +/** + * Base64 is a group of binary-to-text encoding schemes that represent binary data + * in an ASCII string format by translating it into a radix-64 representation. + * Each base64 digit represents exactly 6 bits of data. + * + * Base64 encoding is commonly used when there is a need to encode binary data + * that needs to be stored and transferred over media that are designed to deal + * with textual data. + * + * Wikipedia Reference: https://en.wikipedia.org/wiki/Base64 + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + */ + +public final class Base64 { + + // Base64 character set + private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private static final char PADDING_CHAR = '='; + + private Base64() { + + } + + /** + * Encodes the given byte array to a Base64 encoded string. + * + * @param input the byte array to encode + * @return the Base64 encoded string + * @throws IllegalArgumentException if input is null + */ + public static String encode(byte[] input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + if (input.length == 0) { + return ""; + } + + StringBuilder result = new StringBuilder(); + int padding = 0; + + // Process input in groups of 3 bytes + for (int i = 0; i < input.length; i += 3) { + // Get up to 3 bytes + int byte1 = input[i] & 0xFF; + int byte2 = (i + 1 < input.length) ? (input[i + 1] & 0xFF) : 0; + int byte3 = (i + 2 < input.length) ? (input[i + 2] & 0xFF) : 0; + + // Calculate padding needed + if (i + 1 >= input.length) { + padding = 2; + } else if (i + 2 >= input.length) { + padding = 1; + } + + // Combine 3 bytes into a 24-bit number + int combined = (byte1 << 16) | (byte2 << 8) | byte3; + + // Extract four 6-bit groups + result.append(BASE64_CHARS.charAt((combined >> 18) & 0x3F)); + result.append(BASE64_CHARS.charAt((combined >> 12) & 0x3F)); + result.append(BASE64_CHARS.charAt((combined >> 6) & 0x3F)); + result.append(BASE64_CHARS.charAt(combined & 0x3F)); + } + + // Replace padding characters + if (padding > 0) { + result.setLength(result.length() - padding); + for (int i = 0; i < padding; i++) { + result.append(PADDING_CHAR); + } + } + + return result.toString(); + } + + /** + * Encodes the given string to a Base64 encoded string using UTF-8 encoding. + * + * @param input the string to encode + * @return the Base64 encoded string + * @throws IllegalArgumentException if input is null + */ + public static String encode(String input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + return encode(input.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Decodes the given Base64 encoded string to a byte array. + * + * @param input the Base64 encoded string to decode + * @return the decoded byte array + * @throws IllegalArgumentException if input is null or contains invalid Base64 characters + */ + public static byte[] decode(String input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + if (input.isEmpty()) { + return new byte[0]; + } + + // Remove padding for processing + String cleanInput = input.replace("=", ""); + int padding = input.length() - cleanInput.length(); + + // Validate input length + if ((cleanInput.length() % 4) + padding > 4) { + throw new IllegalArgumentException("Invalid Base64 input length"); + } + + List result = new ArrayList<>(); + + // Process input in groups of 4 characters + for (int i = 0; i < cleanInput.length(); i += 4) { + // Get up to 4 characters + int char1 = getBase64Value(cleanInput.charAt(i)); + int char2 = (i + 1 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 1)) : 0; + int char3 = (i + 2 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 2)) : 0; + int char4 = (i + 3 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 3)) : 0; + + // Combine four 6-bit groups into a 24-bit number + int combined = (char1 << 18) | (char2 << 12) | (char3 << 6) | char4; + + // Extract three 8-bit bytes + result.add((byte) ((combined >> 16) & 0xFF)); + if (i + 2 < cleanInput.length() || (i + 2 == cleanInput.length() && padding < 2)) { + result.add((byte) ((combined >> 8) & 0xFF)); + } + if (i + 3 < cleanInput.length() || (i + 3 == cleanInput.length() && padding < 1)) { + result.add((byte) (combined & 0xFF)); + } + } + + // Convert List to byte[] + byte[] resultArray = new byte[result.size()]; + for (int i = 0; i < result.size(); i++) { + resultArray[i] = result.get(i); + } + + return resultArray; + } + + /** + * Decodes the given Base64 encoded string to a string using UTF-8 encoding. + * + * @param input the Base64 encoded string to decode + * @return the decoded string + * @throws IllegalArgumentException if input is null or contains invalid Base64 characters + */ + public static String decodeToString(String input) { + if (input == null) { + throw new IllegalArgumentException("Input cannot be null"); + } + + byte[] decodedBytes = decode(input); + return new String(decodedBytes, StandardCharsets.UTF_8); + } + + /** + * Gets the numeric value of a Base64 character. + * + * @param c the Base64 character + * @return the numeric value (0-63) + * @throws IllegalArgumentException if character is not a valid Base64 character + */ + private static int getBase64Value(char c) { + if (c >= 'A' && c <= 'Z') { + return c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return c - 'a' + 26; + } else if (c >= '0' && c <= '9') { + return c - '0' + 52; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } else { + throw new IllegalArgumentException("Invalid Base64 character: " + c); + } + } +} From 449961b4d39c16673725df01d27e8ab77ec429c2 Mon Sep 17 00:00:00 2001 From: Nithin U <106614289+NithinU2802@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:21:21 +0530 Subject: [PATCH 2/7] feat: test coverage implementation of Base64Test.java --- .../thealgorithms/conversions/Base64Test.java | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 src/test/java/com/thealgorithms/conversions/Base64Test.java diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java new file mode 100644 index 000000000000..2941fc3a6a2e --- /dev/null +++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java @@ -0,0 +1,203 @@ +package com.thealgorithms.conversions; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +/** + * Test cases for Base64 encoding and decoding. + * + * Author: Nithin U. + * Github: https://github.com/NithinU2802 + */ + +class Base64Test { + + @Test + void testBase64Alphabet() { + // Test that all Base64 characters are handled correctly + String allChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + String encoded = Base64.encode(allChars); + String decoded = Base64.decodeToString(encoded); + assertEquals(allChars, decoded); + } + + @ParameterizedTest + @CsvSource({ + "'', ''", + "A, QQ==", + "AB, QUI=", + "ABC, QUJD", + "ABCD, QUJDRA==", + "Hello, SGVsbG8=", + "'Hello World', SGVsbG8gV29ybGQ=", + "'Hello, World!', 'SGVsbG8sIFdvcmxkIQ=='", + "'The quick brown fox jumps over the lazy dog', 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=='", + "123456789, MTIzNDU2Nzg5", + "'Base64 encoding test', 'QmFzZTY0IGVuY29kaW5nIHRlc3Q='" + }) + void testStringEncoding(String input, String expected) { + assertEquals(expected, Base64.encode(input)); + } + + @ParameterizedTest + @CsvSource({ + "'', ''", + "QQ==, A", + "QUI=, AB", + "QUJD, ABC", + "QUJDRA==, ABCD", + "SGVsbG8=, Hello", + "'SGVsbG8gV29ybGQ=', 'Hello World'", + "'SGVsbG8sIFdvcmxkIQ==', 'Hello, World!'", + "'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==', 'The quick brown fox jumps over the lazy dog'", + "MTIzNDU2Nzg5, 123456789", + "'QmFzZTY0IGVuY29kaW5nIHRlc3Q=', 'Base64 encoding test'" + }) + void testStringDecoding(String input, String expected) { + assertEquals(expected, Base64.decodeToString(input)); + } + + @Test + void testByteArrayEncoding() { + byte[] input = {72, 101, 108, 108, 111}; + String expected = "SGVsbG8="; + assertEquals(expected, Base64.encode(input)); + } + + @Test + void testByteArrayDecoding() { + String input = "SGVsbG8="; + byte[] expected = {72, 101, 108, 108, 111}; + assertArrayEquals(expected, Base64.decode(input)); + } + + @Test + void testRoundTripEncoding() { + String[] testStrings = { + "", + "A", + "AB", + "ABC", + "Hello, World!", + "The quick brown fox jumps over the lazy dog", + "1234567890", + "Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?", + "Unicode: வணக்கம்", // Tamil for "Hello" + "Multi-line\nstring\rwith\tdifferent\nwhitespace" + }; + + for (String original : testStrings) { + String encoded = Base64.encode(original); + String decoded = Base64.decodeToString(encoded); + assertEquals(original, decoded, "Round trip failed for: " + original); + } + } + + @Test + void testRoundTripByteArrayEncoding() { + byte[][] testArrays = { + {}, + {0}, + {-1}, + {0, 1, 2, 3, 4, 5}, + {-128, -1, 0, 1, 127}, + {72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33} + }; + + for (byte[] original : testArrays) { + String encoded = Base64.encode(original); + byte[] decoded = Base64.decode(encoded); + assertArrayEquals(original, decoded, "Round trip failed for byte array"); + } + } + + @Test + void testBinaryData() { + // Test with binary data that might contain null bytes + byte[] binaryData = new byte[256]; + for (int i = 0; i < 256; i++) { + binaryData[i] = (byte) i; + } + + String encoded = Base64.encode(binaryData); + byte[] decoded = Base64.decode(encoded); + assertArrayEquals(binaryData, decoded); + } + + @Test + void testNullInputEncoding() { + assertThrows(IllegalArgumentException.class, () -> Base64.encode((String) null)); + assertThrows(IllegalArgumentException.class, () -> Base64.encode((byte[]) null)); + } + + @Test + void testNullInputDecoding() { + assertThrows(IllegalArgumentException.class, () -> Base64.decode(null)); + assertThrows(IllegalArgumentException.class, () -> Base64.decodeToString(null)); + } + + @Test + void testInvalidBase64Characters() { + assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8@")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8#")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8$")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8%")); + } + + @Test + void testPaddingVariations() { + // Test different padding scenarios '=' + assertEquals("A", Base64.decodeToString("QQ==")); + assertEquals("AB", Base64.decodeToString("QUI=")); + assertEquals("ABC", Base64.decodeToString("QUJD")); + } + + @Test + void testPaddingConsistency() { + // Ensure that strings requiring different amounts of padding encode/decode correctly + String[] testCases = {"A", "AB", "ABC", "ABCD", "ABCDE", "ABCDEF"}; + + for (String test : testCases) { + String encoded = Base64.encode(test); + String decoded = Base64.decodeToString(encoded); + assertEquals(test, decoded); + + // Verify padding is correct + int expectedPadding = (3 - (test.length() % 3)) % 3; + int actualPadding = 0; + for (int i = encoded.length() - 1; i >= 0 && encoded.charAt(i) == '='; i--) { + actualPadding++; + } + assertEquals(expectedPadding, actualPadding, "Incorrect padding for: " + test); + } + } + + @Test + void testLargeData() { + // Test with larger data to ensure scalability + StringBuilder largeString = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + largeString.append("This is a test string for Base64 encoding. "); + } + + String original = largeString.toString(); + String encoded = Base64.encode(original); + String decoded = Base64.decodeToString(encoded); + assertEquals(original, decoded); + } + + @Test + void testEmptyAndSingleCharacter() { + // Test edge cases + assertEquals("", Base64.encode("")); + assertEquals("", Base64.decodeToString("")); + + assertEquals("QQ==", Base64.encode("A")); + assertEquals("A", Base64.decodeToString("QQ==")); + } + +} From 301188314545959e29054e3df708ed14a278786a Mon Sep 17 00:00:00 2001 From: Nithin U <106614289+NithinU2802@users.noreply.github.com> Date: Wed, 1 Oct 2025 23:22:05 +0530 Subject: [PATCH 3/7] Update DIRECTORY.md --- DIRECTORY.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 8ae7aea61922..b311b10fa177 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -94,6 +94,7 @@ - 📄 [AnyBaseToAnyBase](src/main/java/com/thealgorithms/conversions/AnyBaseToAnyBase.java) - 📄 [AnyBaseToDecimal](src/main/java/com/thealgorithms/conversions/AnyBaseToDecimal.java) - 📄 [AnytoAny](src/main/java/com/thealgorithms/conversions/AnytoAny.java) + - 📄 [Base64](src/main/java/com/thealgorithms/conversions/Base64.java) - 📄 [BinaryToDecimal](src/main/java/com/thealgorithms/conversions/BinaryToDecimal.java) - 📄 [BinaryToHexadecimal](src/main/java/com/thealgorithms/conversions/BinaryToHexadecimal.java) - 📄 [BinaryToOctal](src/main/java/com/thealgorithms/conversions/BinaryToOctal.java) @@ -839,6 +840,7 @@ - 📄 [AffineConverterTest](src/test/java/com/thealgorithms/conversions/AffineConverterTest.java) - 📄 [AnyBaseToDecimalTest](src/test/java/com/thealgorithms/conversions/AnyBaseToDecimalTest.java) - 📄 [AnytoAnyTest](src/test/java/com/thealgorithms/conversions/AnytoAnyTest.java) + - 📄 [Base64Test](src/test/java/com/thealgorithms/conversions/Base64Test.java) - 📄 [BinaryToDecimalTest](src/test/java/com/thealgorithms/conversions/BinaryToDecimalTest.java) - 📄 [BinaryToHexadecimalTest](src/test/java/com/thealgorithms/conversions/BinaryToHexadecimalTest.java) - 📄 [BinaryToOctalTest](src/test/java/com/thealgorithms/conversions/BinaryToOctalTest.java) From 30ed79aa495479acc00e8368cf0b2e985adea368 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Thu, 2 Oct 2025 13:42:33 +0530 Subject: [PATCH 4/7] fix: build and lint fix update --- .../com/thealgorithms/conversions/Base64.java | 69 +++++++++---------- .../thealgorithms/conversions/Base64Test.java | 47 +++---------- 2 files changed, 42 insertions(+), 74 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java index d6ea76e001b8..e76dc152e2a0 100644 --- a/src/main/java/com/thealgorithms/conversions/Base64.java +++ b/src/main/java/com/thealgorithms/conversions/Base64.java @@ -1,36 +1,35 @@ package com.thealgorithms.conversions; -import java.util.List; -import java.util.ArrayList; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; /** * Base64 is a group of binary-to-text encoding schemes that represent binary data * in an ASCII string format by translating it into a radix-64 representation. * Each base64 digit represents exactly 6 bits of data. - * + * * Base64 encoding is commonly used when there is a need to encode binary data * that needs to be stored and transferred over media that are designed to deal * with textual data. - * + * * Wikipedia Reference: https://en.wikipedia.org/wiki/Base64 * Author: Nithin U. * Github: https://github.com/NithinU2802 */ public final class Base64 { - + // Base64 character set private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; private static final char PADDING_CHAR = '='; - + private Base64() { - } /** * Encodes the given byte array to a Base64 encoded string. - * + * * @param input the byte array to encode * @return the Base64 encoded string * @throws IllegalArgumentException if input is null @@ -39,38 +38,38 @@ public static String encode(byte[] input) { if (input == null) { throw new IllegalArgumentException("Input cannot be null"); } - + if (input.length == 0) { return ""; } - + StringBuilder result = new StringBuilder(); int padding = 0; - + // Process input in groups of 3 bytes for (int i = 0; i < input.length; i += 3) { // Get up to 3 bytes int byte1 = input[i] & 0xFF; int byte2 = (i + 1 < input.length) ? (input[i + 1] & 0xFF) : 0; int byte3 = (i + 2 < input.length) ? (input[i + 2] & 0xFF) : 0; - + // Calculate padding needed if (i + 1 >= input.length) { padding = 2; } else if (i + 2 >= input.length) { padding = 1; } - + // Combine 3 bytes into a 24-bit number int combined = (byte1 << 16) | (byte2 << 8) | byte3; - + // Extract four 6-bit groups result.append(BASE64_CHARS.charAt((combined >> 18) & 0x3F)); result.append(BASE64_CHARS.charAt((combined >> 12) & 0x3F)); result.append(BASE64_CHARS.charAt((combined >> 6) & 0x3F)); result.append(BASE64_CHARS.charAt(combined & 0x3F)); } - + // Replace padding characters if (padding > 0) { result.setLength(result.length() - padding); @@ -78,13 +77,13 @@ public static String encode(byte[] input) { result.append(PADDING_CHAR); } } - + return result.toString(); } - + /** * Encodes the given string to a Base64 encoded string using UTF-8 encoding. - * + * * @param input the string to encode * @return the Base64 encoded string * @throws IllegalArgumentException if input is null @@ -93,13 +92,13 @@ public static String encode(String input) { if (input == null) { throw new IllegalArgumentException("Input cannot be null"); } - + return encode(input.getBytes(StandardCharsets.UTF_8)); } - + /** * Decodes the given Base64 encoded string to a byte array. - * + * * @param input the Base64 encoded string to decode * @return the decoded byte array * @throws IllegalArgumentException if input is null or contains invalid Base64 characters @@ -108,22 +107,22 @@ public static byte[] decode(String input) { if (input == null) { throw new IllegalArgumentException("Input cannot be null"); } - + if (input.isEmpty()) { return new byte[0]; } - + // Remove padding for processing String cleanInput = input.replace("=", ""); int padding = input.length() - cleanInput.length(); - + // Validate input length if ((cleanInput.length() % 4) + padding > 4) { throw new IllegalArgumentException("Invalid Base64 input length"); } - + List result = new ArrayList<>(); - + // Process input in groups of 4 characters for (int i = 0; i < cleanInput.length(); i += 4) { // Get up to 4 characters @@ -131,10 +130,10 @@ public static byte[] decode(String input) { int char2 = (i + 1 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 1)) : 0; int char3 = (i + 2 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 2)) : 0; int char4 = (i + 3 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 3)) : 0; - + // Combine four 6-bit groups into a 24-bit number int combined = (char1 << 18) | (char2 << 12) | (char3 << 6) | char4; - + // Extract three 8-bit bytes result.add((byte) ((combined >> 16) & 0xFF)); if (i + 2 < cleanInput.length() || (i + 2 == cleanInput.length() && padding < 2)) { @@ -144,19 +143,19 @@ public static byte[] decode(String input) { result.add((byte) (combined & 0xFF)); } } - + // Convert List to byte[] byte[] resultArray = new byte[result.size()]; for (int i = 0; i < result.size(); i++) { resultArray[i] = result.get(i); } - + return resultArray; } - + /** * Decodes the given Base64 encoded string to a string using UTF-8 encoding. - * + * * @param input the Base64 encoded string to decode * @return the decoded string * @throws IllegalArgumentException if input is null or contains invalid Base64 characters @@ -165,14 +164,14 @@ public static String decodeToString(String input) { if (input == null) { throw new IllegalArgumentException("Input cannot be null"); } - + byte[] decodedBytes = decode(input); return new String(decodedBytes, StandardCharsets.UTF_8); } - + /** * Gets the numeric value of a Base64 character. - * + * * @param c the Base64 character * @return the numeric value (0-63) * @throws IllegalArgumentException if character is not a valid Base64 character diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java index 2941fc3a6a2e..6bf1382372f9 100644 --- a/src/test/java/com/thealgorithms/conversions/Base64Test.java +++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java @@ -9,7 +9,7 @@ /** * Test cases for Base64 encoding and decoding. - * + * * Author: Nithin U. * Github: https://github.com/NithinU2802 */ @@ -26,37 +26,15 @@ void testBase64Alphabet() { } @ParameterizedTest - @CsvSource({ - "'', ''", - "A, QQ==", - "AB, QUI=", - "ABC, QUJD", - "ABCD, QUJDRA==", - "Hello, SGVsbG8=", - "'Hello World', SGVsbG8gV29ybGQ=", - "'Hello, World!', 'SGVsbG8sIFdvcmxkIQ=='", - "'The quick brown fox jumps over the lazy dog', 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=='", - "123456789, MTIzNDU2Nzg5", - "'Base64 encoding test', 'QmFzZTY0IGVuY29kaW5nIHRlc3Q='" - }) + @CsvSource({"'', ''", "A, QQ==", "AB, QUI=", "ABC, QUJD", "ABCD, QUJDRA==", "Hello, SGVsbG8=", "'Hello World', SGVsbG8gV29ybGQ=", "'Hello, World!', 'SGVsbG8sIFdvcmxkIQ=='", "'The quick brown fox jumps over the lazy dog', 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=='", + "123456789, MTIzNDU2Nzg5", "'Base64 encoding test', 'QmFzZTY0IGVuY29kaW5nIHRlc3Q='"}) void testStringEncoding(String input, String expected) { assertEquals(expected, Base64.encode(input)); } @ParameterizedTest - @CsvSource({ - "'', ''", - "QQ==, A", - "QUI=, AB", - "QUJD, ABC", - "QUJDRA==, ABCD", - "SGVsbG8=, Hello", - "'SGVsbG8gV29ybGQ=', 'Hello World'", - "'SGVsbG8sIFdvcmxkIQ==', 'Hello, World!'", - "'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==', 'The quick brown fox jumps over the lazy dog'", - "MTIzNDU2Nzg5, 123456789", - "'QmFzZTY0IGVuY29kaW5nIHRlc3Q=', 'Base64 encoding test'" - }) + @CsvSource({"'', ''", "QQ==, A", "QUI=, AB", "QUJD, ABC", "QUJDRA==, ABCD", "SGVsbG8=, Hello", "'SGVsbG8gV29ybGQ=', 'Hello World'", "'SGVsbG8sIFdvcmxkIQ==', 'Hello, World!'", "'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==', 'The quick brown fox jumps over the lazy dog'", + "MTIzNDU2Nzg5, 123456789", "'QmFzZTY0IGVuY29kaW5nIHRlc3Q=', 'Base64 encoding test'"}) void testStringDecoding(String input, String expected) { assertEquals(expected, Base64.decodeToString(input)); } @@ -77,19 +55,10 @@ void testByteArrayDecoding() { @Test void testRoundTripEncoding() { - String[] testStrings = { - "", - "A", - "AB", - "ABC", - "Hello, World!", - "The quick brown fox jumps over the lazy dog", - "1234567890", - "Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?", + String[] testStrings = {"", "A", "AB", "ABC", "Hello, World!", "The quick brown fox jumps over the lazy dog", "1234567890", "Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?", "Unicode: வணக்கம்", // Tamil for "Hello" - "Multi-line\nstring\rwith\tdifferent\nwhitespace" - }; - + "Multi-line\nstring\rwith\tdifferent\nwhitespace"}; + for (String original : testStrings) { String encoded = Base64.encode(original); String decoded = Base64.decodeToString(encoded); From c75a6730d3f471c30b165c12cb410554bb4a61c7 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Thu, 2 Oct 2025 13:49:34 +0530 Subject: [PATCH 5/7] patch: linter fix --- .../com/thealgorithms/conversions/Base64.java | 2 +- .../thealgorithms/conversions/Base64Test.java | 29 ++++++++----------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java index e76dc152e2a0..90a8708692f9 100644 --- a/src/main/java/com/thealgorithms/conversions/Base64.java +++ b/src/main/java/com/thealgorithms/conversions/Base64.java @@ -26,7 +26,7 @@ public final class Base64 { private Base64() { } - + /** * Encodes the given byte array to a Base64 encoded string. * diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java index 6bf1382372f9..0741c0f29d0f 100644 --- a/src/test/java/com/thealgorithms/conversions/Base64Test.java +++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; + import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; @@ -28,14 +29,16 @@ void testBase64Alphabet() { @ParameterizedTest @CsvSource({"'', ''", "A, QQ==", "AB, QUI=", "ABC, QUJD", "ABCD, QUJDRA==", "Hello, SGVsbG8=", "'Hello World', SGVsbG8gV29ybGQ=", "'Hello, World!', 'SGVsbG8sIFdvcmxkIQ=='", "'The quick brown fox jumps over the lazy dog', 'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=='", "123456789, MTIzNDU2Nzg5", "'Base64 encoding test', 'QmFzZTY0IGVuY29kaW5nIHRlc3Q='"}) - void testStringEncoding(String input, String expected) { + void + testStringEncoding(String input, String expected) { assertEquals(expected, Base64.encode(input)); } @ParameterizedTest @CsvSource({"'', ''", "QQ==, A", "QUI=, AB", "QUJD, ABC", "QUJDRA==, ABCD", "SGVsbG8=, Hello", "'SGVsbG8gV29ybGQ=', 'Hello World'", "'SGVsbG8sIFdvcmxkIQ==', 'Hello, World!'", "'VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw==', 'The quick brown fox jumps over the lazy dog'", "MTIzNDU2Nzg5, 123456789", "'QmFzZTY0IGVuY29kaW5nIHRlc3Q=', 'Base64 encoding test'"}) - void testStringDecoding(String input, String expected) { + void + testStringDecoding(String input, String expected) { assertEquals(expected, Base64.decodeToString(input)); } @@ -58,7 +61,7 @@ void testRoundTripEncoding() { String[] testStrings = {"", "A", "AB", "ABC", "Hello, World!", "The quick brown fox jumps over the lazy dog", "1234567890", "Special chars: !@#$%^&*()_+-=[]{}|;:,.<>?", "Unicode: வணக்கம்", // Tamil for "Hello" "Multi-line\nstring\rwith\tdifferent\nwhitespace"}; - + for (String original : testStrings) { String encoded = Base64.encode(original); String decoded = Base64.decodeToString(encoded); @@ -68,14 +71,7 @@ void testRoundTripEncoding() { @Test void testRoundTripByteArrayEncoding() { - byte[][] testArrays = { - {}, - {0}, - {-1}, - {0, 1, 2, 3, 4, 5}, - {-128, -1, 0, 1, 127}, - {72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33} - }; + byte[][] testArrays = {{}, {0}, {-1}, {0, 1, 2, 3, 4, 5}, {-128, -1, 0, 1, 127}, {72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33}}; for (byte[] original : testArrays) { String encoded = Base64.encode(original); @@ -91,7 +87,7 @@ void testBinaryData() { for (int i = 0; i < 256; i++) { binaryData[i] = (byte) i; } - + String encoded = Base64.encode(binaryData); byte[] decoded = Base64.decode(encoded); assertArrayEquals(binaryData, decoded); @@ -129,12 +125,12 @@ void testPaddingVariations() { void testPaddingConsistency() { // Ensure that strings requiring different amounts of padding encode/decode correctly String[] testCases = {"A", "AB", "ABC", "ABCD", "ABCDE", "ABCDEF"}; - + for (String test : testCases) { String encoded = Base64.encode(test); String decoded = Base64.decodeToString(encoded); assertEquals(test, decoded); - + // Verify padding is correct int expectedPadding = (3 - (test.length() % 3)) % 3; int actualPadding = 0; @@ -152,7 +148,7 @@ void testLargeData() { for (int i = 0; i < 1000; i++) { largeString.append("This is a test string for Base64 encoding. "); } - + String original = largeString.toString(); String encoded = Base64.encode(original); String decoded = Base64.decodeToString(encoded); @@ -164,9 +160,8 @@ void testEmptyAndSingleCharacter() { // Test edge cases assertEquals("", Base64.encode("")); assertEquals("", Base64.decodeToString("")); - + assertEquals("QQ==", Base64.encode("A")); assertEquals("A", Base64.decodeToString("QQ==")); } - } From 110424649a715caffe97358ca6eaa61f0b1e414c Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Thu, 2 Oct 2025 18:43:03 +0530 Subject: [PATCH 6/7] fix: enforce strict RFC 4648 compliance in Base64 decoding --- .../com/thealgorithms/conversions/Base64.java | 28 ++++++++++--------- .../thealgorithms/conversions/Base64Test.java | 16 +++++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java index 90a8708692f9..5219c4ba7f4e 100644 --- a/src/main/java/com/thealgorithms/conversions/Base64.java +++ b/src/main/java/com/thealgorithms/conversions/Base64.java @@ -112,34 +112,36 @@ public static byte[] decode(String input) { return new byte[0]; } - // Remove padding for processing - String cleanInput = input.replace("=", ""); - int padding = input.length() - cleanInput.length(); + // Strict RFC 4648 compliance: length must be a multiple of 4 + if (input.length() % 4 != 0) { + throw new IllegalArgumentException("Invalid Base64 input length; must be multiple of 4"); + } - // Validate input length - if ((cleanInput.length() % 4) + padding > 4) { - throw new IllegalArgumentException("Invalid Base64 input length"); + // Validate padding: '=' can only appear at the end (last 1 or 2 chars) + int firstPadding = input.indexOf('='); + if (firstPadding != -1 && firstPadding < input.length() - 2) { + throw new IllegalArgumentException("Padding '=' can only appear at the end (last 1 or 2 characters)"); } List result = new ArrayList<>(); // Process input in groups of 4 characters - for (int i = 0; i < cleanInput.length(); i += 4) { + for (int i = 0; i < input.length(); i += 4) { // Get up to 4 characters - int char1 = getBase64Value(cleanInput.charAt(i)); - int char2 = (i + 1 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 1)) : 0; - int char3 = (i + 2 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 2)) : 0; - int char4 = (i + 3 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 3)) : 0; + int char1 = getBase64Value(input.charAt(i)); + int char2 = getBase64Value(input.charAt(i + 1)); + int char3 = input.charAt(i + 2) == '=' ? 0 : getBase64Value(input.charAt(i + 2)); + int char4 = input.charAt(i + 3) == '=' ? 0 : getBase64Value(input.charAt(i + 3)); // Combine four 6-bit groups into a 24-bit number int combined = (char1 << 18) | (char2 << 12) | (char3 << 6) | char4; // Extract three 8-bit bytes result.add((byte) ((combined >> 16) & 0xFF)); - if (i + 2 < cleanInput.length() || (i + 2 == cleanInput.length() && padding < 2)) { + if (input.charAt(i + 2) != '=') { result.add((byte) ((combined >> 8) & 0xFF)); } - if (i + 3 < cleanInput.length() || (i + 3 == cleanInput.length() && padding < 1)) { + if (input.charAt(i + 3) != '=') { result.add((byte) (combined & 0xFF)); } } diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java index 0741c0f29d0f..fbc220c0ca95 100644 --- a/src/test/java/com/thealgorithms/conversions/Base64Test.java +++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java @@ -113,6 +113,22 @@ void testInvalidBase64Characters() { assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8%")); } + @Test + void testInvalidLength() { + // Length must be multiple of 4 + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQ")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQQ")); + } + + @Test + void testInvalidPaddingPosition() { + // '=' can only appear at the end + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=QQ")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=Q=")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("=QQQ")); + } + @Test void testPaddingVariations() { // Test different padding scenarios '=' From c902212d10cef03c2b9e956e538aa888852c3d83 Mon Sep 17 00:00:00 2001 From: NithinU2802 Date: Thu, 2 Oct 2025 18:43:03 +0530 Subject: [PATCH 7/7] fix: enforce strict rfc 4648 compliance in base64 decoding --- .../com/thealgorithms/conversions/Base64.java | 28 ++++++++++--------- .../thealgorithms/conversions/Base64Test.java | 16 +++++++++++ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/thealgorithms/conversions/Base64.java b/src/main/java/com/thealgorithms/conversions/Base64.java index 90a8708692f9..5219c4ba7f4e 100644 --- a/src/main/java/com/thealgorithms/conversions/Base64.java +++ b/src/main/java/com/thealgorithms/conversions/Base64.java @@ -112,34 +112,36 @@ public static byte[] decode(String input) { return new byte[0]; } - // Remove padding for processing - String cleanInput = input.replace("=", ""); - int padding = input.length() - cleanInput.length(); + // Strict RFC 4648 compliance: length must be a multiple of 4 + if (input.length() % 4 != 0) { + throw new IllegalArgumentException("Invalid Base64 input length; must be multiple of 4"); + } - // Validate input length - if ((cleanInput.length() % 4) + padding > 4) { - throw new IllegalArgumentException("Invalid Base64 input length"); + // Validate padding: '=' can only appear at the end (last 1 or 2 chars) + int firstPadding = input.indexOf('='); + if (firstPadding != -1 && firstPadding < input.length() - 2) { + throw new IllegalArgumentException("Padding '=' can only appear at the end (last 1 or 2 characters)"); } List result = new ArrayList<>(); // Process input in groups of 4 characters - for (int i = 0; i < cleanInput.length(); i += 4) { + for (int i = 0; i < input.length(); i += 4) { // Get up to 4 characters - int char1 = getBase64Value(cleanInput.charAt(i)); - int char2 = (i + 1 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 1)) : 0; - int char3 = (i + 2 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 2)) : 0; - int char4 = (i + 3 < cleanInput.length()) ? getBase64Value(cleanInput.charAt(i + 3)) : 0; + int char1 = getBase64Value(input.charAt(i)); + int char2 = getBase64Value(input.charAt(i + 1)); + int char3 = input.charAt(i + 2) == '=' ? 0 : getBase64Value(input.charAt(i + 2)); + int char4 = input.charAt(i + 3) == '=' ? 0 : getBase64Value(input.charAt(i + 3)); // Combine four 6-bit groups into a 24-bit number int combined = (char1 << 18) | (char2 << 12) | (char3 << 6) | char4; // Extract three 8-bit bytes result.add((byte) ((combined >> 16) & 0xFF)); - if (i + 2 < cleanInput.length() || (i + 2 == cleanInput.length() && padding < 2)) { + if (input.charAt(i + 2) != '=') { result.add((byte) ((combined >> 8) & 0xFF)); } - if (i + 3 < cleanInput.length() || (i + 3 == cleanInput.length() && padding < 1)) { + if (input.charAt(i + 3) != '=') { result.add((byte) (combined & 0xFF)); } } diff --git a/src/test/java/com/thealgorithms/conversions/Base64Test.java b/src/test/java/com/thealgorithms/conversions/Base64Test.java index 0741c0f29d0f..fbc220c0ca95 100644 --- a/src/test/java/com/thealgorithms/conversions/Base64Test.java +++ b/src/test/java/com/thealgorithms/conversions/Base64Test.java @@ -113,6 +113,22 @@ void testInvalidBase64Characters() { assertThrows(IllegalArgumentException.class, () -> Base64.decode("SGVsbG8%")); } + @Test + void testInvalidLength() { + // Length must be multiple of 4 + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQ")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("QQQ")); + } + + @Test + void testInvalidPaddingPosition() { + // '=' can only appear at the end + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=QQ")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("Q=Q=")); + assertThrows(IllegalArgumentException.class, () -> Base64.decode("=QQQ")); + } + @Test void testPaddingVariations() { // Test different padding scenarios '='