Skip to content

Commit 60d3522

Browse files
committed
fix requireValidLength rejecting line-wrapped MIME Base64
1 parent 54e51f1 commit 60d3522

2 files changed

Lines changed: 38 additions & 1 deletion

File tree

src/main/java/jadx/plugins/stringdecoder/B64Detector.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public final class B64Detector {
2222
// Allow embedded \n/\r (PEM/MIME line-wrapped Base64); = padding and trailing newline are optional.
2323
private static final Pattern BASE64_STANDARD = Pattern.compile("^[A-Za-z0-9+/\\n\\r]*=*[\\n\\r]*$");
2424
private static final Pattern BASE64_URL_SAFE = Pattern.compile("^[A-Za-z0-9_\\-\\n\\r]*=*[\\n\\r]*$");
25+
// Line-break chars permitted as PEM/MIME wrapping above; stripped before the length-quantum check.
26+
private static final Pattern LINE_BREAKS = Pattern.compile("[\\n\\r]");
2527
// Identifier shapes — only checked below IDENTIFIER_FILTER_MAX_LEN.
2628
private static final Pattern CAMEL_CASE = Pattern.compile("^[a-z]+([A-Z][a-z]+)+$");
2729
private static final Pattern PASCAL_CASE = Pattern.compile("^[A-Z][a-z]+([A-Z][a-z0-9]+)+$");
@@ -57,7 +59,7 @@ private interface InputFilter {
5759
*/
5860
private static final InputFilter[] INPUT_FILTERS = {
5961
(s, o) -> B64FalsePositives.contains(s),
60-
(s, o) -> o.isRequireValidLength() && s.length() % 4 != 0,
62+
(s, o) -> o.isRequireValidLength() && significantLength(s) % 4 != 0,
6163
(s, o) -> isLikelyIdentifier(s, o),
6264
(s, o) -> !hasValidBase64Charset(s)
6365
};
@@ -116,6 +118,18 @@ private static boolean hasValidBase64Charset(String str) {
116118
return BASE64_STANDARD.matcher(str).matches() || BASE64_URL_SAFE.matcher(str).matches();
117119
}
118120

121+
/**
122+
* Length excluding {@code \n}/{@code \r} — line wraps don't count toward the 4-char Base64
123+
* quantum, so {@code requireValidLength} must ignore them or it rejects line-wrapped strings.
124+
*
125+
* <p>TODO: full MIME also skips spaces/tabs; supporting it means stripping all {@code \s} here
126+
* and relaxing {@link #hasValidBase64Charset}. Only worth it with a solid false-positive
127+
* defense, else prose like {@code "Java Code"} decodes.
128+
*/
129+
private static int significantLength(String str) {
130+
return LINE_BREAKS.matcher(str).replaceAll("").length();
131+
}
132+
119133
private static Variant[] variantsFor(String str) {
120134
boolean hasLineBreaks = str.indexOf('\n') >= 0 || str.indexOf('\r') >= 0;
121135
return hasLineBreaks ? Variant.values() : new Variant[] { Variant.STANDARD, Variant.URL };

src/test/java/jadx/plugins/stringdecoder/B64DetectorTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.nio.charset.CharacterCodingException;
66
import java.nio.charset.CodingErrorAction;
77
import java.nio.charset.StandardCharsets;
8+
import java.util.Base64;
89
import java.util.Map;
910

1011
import static org.assertj.core.api.Assertions.assertThat;
@@ -229,6 +230,28 @@ public void detectRequireValidLengthFilterTest() {
229230
assertThat(r.getDecoded()).isEqualTo("hello");
230231
}
231232

233+
@Test
234+
public void detectLineWrappedMimeB64NotDivisibleByFourTest() {
235+
// One \n makes the raw length 21 (21 % 4 == 1), which the old length filter rejected outright.
236+
// significantLength() ignores the line break, so the 20 real Base64 chars (20 % 4 == 0) now
237+
// pass the default requireValidLength=true filter and decode via the MIME variant.
238+
B64Result r = B64Detector.detect("SGVsbG8s\nIFdvcmxkIQ==", defaultOptions());
239+
assertThat(r).isNotNull();
240+
assertThat(r.commentText()).isEqualTo("b64(mime): Hello, World!");
241+
}
242+
243+
@Test
244+
public void detectSpaceDelimitedMimeB64CurrentlyFailsTest() throws CharacterCodingException {
245+
// A space-delimited blob is genuinely valid MIME Base64 — the MIME decoder skips the space:
246+
String spaceDelimited = "SGVsbG8s IFdvcmxkIQ==";
247+
byte[] bytes = Base64.getMimeDecoder().decode(spaceDelimited);
248+
assertThat(B64Detector.decodeUtf8(bytes, CodingErrorAction.REPORT)).isEqualTo("Hello, World!");
249+
// ...but B64Detector rejects it: the charset patterns permit only \n/\r as whitespace, not spaces.
250+
// Known limitation; flip this to a successful-decode assertion if full-MIME whitespace tolerance
251+
// is ever added (see significantLength()).
252+
assertThat(B64Detector.detect(spaceDelimited, defaultOptions())).isNull();
253+
}
254+
232255
@Test
233256
public void detectMinDecodedLengthFilterTest() {
234257
// "aGVs" decodes to "hel" (3 chars) — below default minDecodedLength=4

0 commit comments

Comments
 (0)