Skip to content

Commit 4e153db

Browse files
authored
fix: Base64 decoding to discard newline characters (#1941)
* fix: Base64 decoding to discard newline characters * adding test case for "+" character and new line character * test case with the slash character
1 parent 31e847a commit 4e153db

File tree

2 files changed

+83
-2
lines changed

2 files changed

+83
-2
lines changed

google-http-client/src/main/java/com/google/api/client/util/Base64.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626
*/
2727
@Deprecated
2828
public class Base64 {
29+
// Guava's Base64 (https://datatracker.ietf.org/doc/html/rfc4648#section-4) decoders. When
30+
// decoding, they discard the new line character so that the behavior matches what we had with
31+
// Apache Commons Codec's decodeBase64.
32+
// The 2nd argument of the withSeparator method, "64", does not have any effect in decoding.
33+
private static final BaseEncoding BASE64_DECODER = BaseEncoding.base64().withSeparator("\n", 64);
34+
private static final BaseEncoding BASE64URL_DECODER =
35+
BaseEncoding.base64Url().withSeparator("\n", 64);
2936

3037
/**
3138
* Encodes binary data using the base64 algorithm but does not chunk the output.
@@ -92,6 +99,9 @@ public static byte[] decodeBase64(byte[] base64Data) {
9299
* Decodes a Base64 String into octets. Note that this method handles both URL-safe and
93100
* non-URL-safe base 64 encoded strings.
94101
*
102+
* <p>For the compatibility with the old version that used Apache Commons Coded's decodeBase64,
103+
* this method discards new line characters and trailing whitespaces.
104+
*
95105
* @param base64String String containing Base64 data or {@code null} for {@code null} result
96106
* @return Array containing decoded data or {@code null} for {@code null} input
97107
*/
@@ -100,10 +110,10 @@ public static byte[] decodeBase64(String base64String) {
100110
return null;
101111
}
102112
try {
103-
return BaseEncoding.base64().decode(base64String);
113+
return BASE64_DECODER.decode(base64String);
104114
} catch (IllegalArgumentException e) {
105115
if (e.getCause() instanceof DecodingException) {
106-
return BaseEncoding.base64Url().decode(base64String.trim());
116+
return BASE64URL_DECODER.decode(base64String.trim());
107117
}
108118
throw e;
109119
}

google-http-client/src/test/java/com/google/api/client/util/Base64Test.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.api.client.util;
1616

17+
import static com.google.common.truth.Truth.assertThat;
18+
1719
import java.nio.charset.StandardCharsets;
1820
import junit.framework.TestCase;
1921

@@ -62,4 +64,73 @@ public void test_encodeBase64URLSafe_withNull_shouldReturnNull() {
6264
public void test_encodeBase64_withNull_shouldReturnNull() {
6365
assertNull(Base64.encodeBase64(null));
6466
}
67+
68+
public void test_decodeBase64_newline_character_invalid_length() {
69+
// The RFC 4648 (https://datatracker.ietf.org/doc/html/rfc4648#section-3.3) states that a
70+
// specification referring to the Base64 encoding may state that it ignores characters outside
71+
// the base alphabet.
72+
73+
// In Base64 encoding, 3 characters (24 bits) are converted to 4 of 6-bits, each of which is
74+
// converted to a byte (a character).
75+
// Base64encode("abc") => "YWJj" (4 characters)
76+
// Base64encode("def") => "ZGVm" (4 characters)
77+
// Adding a new line character between them. This should be discarded.
78+
String encodedString = "YWJj\nZGVm";
79+
80+
// This is a reference implementation by Apache Commons Codec. It discards the new line
81+
// characters.
82+
// assertEquals(
83+
// "abcdef",
84+
// new String(
85+
// org.apache.commons.codec.binary.Base64.decodeBase64(encodedString),
86+
// StandardCharsets.UTF_8));
87+
88+
// This is our implementation. Before the
89+
// https://github.com/googleapis/google-http-java-client/pull/1941/, it was throwing
90+
// IllegalArgumentException("Invalid length 9").
91+
assertEquals("abcdef", new String(Base64.decodeBase64(encodedString), StandardCharsets.UTF_8));
92+
}
93+
94+
public void test_decodeBase64_newline_character() {
95+
// In Base64 encoding, 2 characters (16 bits) are converted to 3 of 6-bits plus the padding
96+
// character ('=").
97+
// Base64encode("ab") => "YWI=" (3 characters + padding character)
98+
// Adding a new line character that should be discarded between them
99+
String encodedString = "YW\nI=";
100+
101+
// This is a reference implementation by Apache Commons Codec. It discards the new line
102+
// characters.
103+
// assertEquals(
104+
// "ab",
105+
// new String(
106+
// org.apache.commons.codec.binary.Base64.decodeBase64(encodedString),
107+
// StandardCharsets.UTF_8));
108+
109+
// This is our implementation. Before the fix
110+
// https://github.com/googleapis/google-http-java-client/pull/1941/, it was throwing
111+
// IllegalArgumentException("Unrecognized character: 0xa").
112+
assertEquals("ab", new String(Base64.decodeBase64(encodedString), StandardCharsets.UTF_8));
113+
}
114+
115+
public void test_decodeBase64_plus_and_newline_characters() {
116+
// The plus sign is 62 in the Base64 table. So it's a valid character in encoded strings.
117+
// https://datatracker.ietf.org/doc/html/rfc4648#section-4
118+
String encodedString = "+\nw==";
119+
120+
byte[] actual = Base64.decodeBase64(encodedString);
121+
// Before the fix https://github.com/googleapis/google-http-java-client/pull/1941/, it was
122+
// throwing IllegalArgumentException("Unrecognized character: +").
123+
assertThat(actual).isEqualTo(new byte[] {(byte) 0xfb});
124+
}
125+
126+
public void test_decodeBase64_slash_and_newline_characters() {
127+
// The slash sign is 63 in the Base64 table. So it's a valid character in encoded strings.
128+
// https://datatracker.ietf.org/doc/html/rfc4648#section-4
129+
String encodedString = "/\nw==";
130+
131+
byte[] actual = Base64.decodeBase64(encodedString);
132+
// Before the fix https://github.com/googleapis/google-http-java-client/pull/1941/, it was
133+
// throwing IllegalArgumentException("Unrecognized character: /").
134+
assertThat(actual).isEqualTo(new byte[] {(byte) 0xff});
135+
}
65136
}

0 commit comments

Comments
 (0)