@@ -17,15 +17,17 @@ public final class B64Detector {
1717 private static final Pattern CAMEL_CASE = Pattern .compile ("^[a-z]+([A-Z][a-z]+)+$" );
1818 private static final Base64 .Decoder [] STANDARD_AND_URL = { Base64 .getDecoder (), Base64 .getUrlDecoder () };
1919 private static final Base64 .Decoder [] ALL_DECODERS = { Base64 .getDecoder (), Base64 .getUrlDecoder (), Base64 .getMimeDecoder () };
20+ private static final String [] STANDARD_AND_URL_TAGS = { "" , "url" };
21+ private static final String [] ALL_DECODER_TAGS = { "" , "url" , "mime" };
2022
2123 private B64Detector () {
2224 }
2325
2426 /**
25- * Returns the decoded+truncated string if {@code str} looks like intentional Base64, otherwise null.
27+ * Returns a {@link B64Result} if {@code str} looks like intentional Base64, otherwise null.
2628 * All detection thresholds are taken from {@code options}.
2729 */
28- public static String detect (String str , B64DeobfuscateOptions options ) {
30+ public static B64Result detect (String str , B64DeobfuscateOptions options ) {
2931 if (B64FalsePositives .contains (str )) {
3032 return null ;
3133 }
@@ -41,30 +43,26 @@ public static String detect(String str, B64DeobfuscateOptions options) {
4143 if (!BASE64_STANDARD .matcher (str ).matches () && !BASE64_URL_SAFE .matcher (str ).matches ()) {
4244 return null ;
4345 }
44- String decoded = tryDecode (str , options );
45- if (decoded == null ) {
46- return null ;
47- }
48- return truncate (decoded , options .getMaxCommentLength ());
46+ return tryDecode (str , options );
4947 }
5048
51- private static String tryDecode (String str , B64DeobfuscateOptions options ) {
52- String result = attemptDecode (Base64 .getDecoder (), str , options );
49+ private static B64Result tryDecode (String str , B64DeobfuscateOptions options ) {
50+ B64Result result = attemptDecode (Base64 .getDecoder (), str , options , "" );
5351 if (result != null ) {
5452 return result ;
5553 }
56- result = attemptDecode (Base64 .getUrlDecoder (), str , options );
54+ result = attemptDecode (Base64 .getUrlDecoder (), str , options , "url" );
5755 if (result != null ) {
5856 return result ;
5957 }
6058 // MIME decoder ignores embedded whitespace (PEM line-wrapped Base64)
6159 if (str .indexOf ('\n' ) >= 0 || str .indexOf ('\r' ) >= 0 ) {
62- return attemptDecode (Base64 .getMimeDecoder (), str , options );
60+ return attemptDecode (Base64 .getMimeDecoder (), str , options , "mime" );
6361 }
6462 return null ;
6563 }
6664
67- private static String attemptDecode (Base64 .Decoder decoder , String str , B64DeobfuscateOptions options ) {
65+ private static B64Result attemptDecode (Base64 .Decoder decoder , String str , B64DeobfuscateOptions options , String tag ) {
6866 try {
6967 byte [] bytes = decoder .decode (str );
7068 CharsetDecoder utf8 = StandardCharsets .UTF_8 .newDecoder ()
@@ -81,7 +79,7 @@ private static String attemptDecode(Base64.Decoder decoder, String str, B64Deobf
8179 if (minLen > 0 && decoded .length () < minLen ) {
8280 return null ;
8381 }
84- return decoded ;
82+ return new B64Result ( truncate ( decoded , options . getMaxCommentLength ()), tag ) ;
8583 } catch (CharacterCodingException ignored ) {
8684 // decoded bytes are not valid UTF-8
8785 } catch (Exception ignored ) {
@@ -116,25 +114,27 @@ private static boolean isAlphanumeric(String str, double minRatio) {
116114 }
117115
118116 /**
119- * Returns the decoded string if {@code str} is valid Base64 that decodes to valid UTF-8,
117+ * Returns a {@link B64Result} if {@code str} is valid Base64 that decodes to valid UTF-8,
120118 * without applying heuristic filters (no length, printable-ratio, or alphanumeric-ratio checks).
121119 * Used for array-element candidates when at least one sibling passes normal detection.
122120 * Returns null if the charset check fails, Base64 decode throws, or the result is not valid UTF-8.
123121 */
124- public static String decodeIfValid (String str , int maxCommentLength ) {
122+ public static B64Result decodeIfValid (String str , int maxCommentLength ) {
125123 if (!BASE64_STANDARD .matcher (str ).matches () && !BASE64_URL_SAFE .matcher (str ).matches ()) {
126124 return null ;
127125 }
128126 Base64 .Decoder [] decoders = (str .indexOf ('\n' ) >= 0 || str .indexOf ('\r' ) >= 0 )
129127 ? ALL_DECODERS : STANDARD_AND_URL ;
130- for (Base64 .Decoder decoder : decoders ) {
128+ String [] tags = (str .indexOf ('\n' ) >= 0 || str .indexOf ('\r' ) >= 0 )
129+ ? ALL_DECODER_TAGS : STANDARD_AND_URL_TAGS ;
130+ for (int i = 0 ; i < decoders .length ; i ++) {
131131 try {
132- byte [] bytes = decoder .decode (str );
132+ byte [] bytes = decoders [ i ] .decode (str );
133133 CharsetDecoder utf8 = StandardCharsets .UTF_8 .newDecoder ()
134134 .onMalformedInput (CodingErrorAction .REPORT )
135135 .onUnmappableCharacter (CodingErrorAction .REPORT );
136136 String decoded = utf8 .decode (ByteBuffer .wrap (bytes )).toString ();
137- return truncate (decoded , maxCommentLength );
137+ return new B64Result ( truncate (decoded , maxCommentLength ), tags [ i ] );
138138 } catch (Exception ignored ) {
139139 }
140140 }
@@ -147,15 +147,15 @@ public static String decodeIfValid(String str, int maxCommentLength) {
147147 * is strong evidence of intent. Returns null only if the string is not valid Base64.
148148 * Invalid UTF-8 bytes are replaced rather than causing a rejection.
149149 */
150- public static String decodeForced (String str , int maxCommentLength ) {
151- for (Base64 . Decoder decoder : ALL_DECODERS ) {
150+ public static B64Result decodeForced (String str , int maxCommentLength ) {
151+ for (int i = 0 ; i < ALL_DECODERS . length ; i ++ ) {
152152 try {
153- byte [] bytes = decoder .decode (str );
153+ byte [] bytes = ALL_DECODERS [ i ] .decode (str );
154154 CharsetDecoder utf8 = StandardCharsets .UTF_8 .newDecoder ()
155155 .onMalformedInput (CodingErrorAction .REPLACE )
156156 .onUnmappableCharacter (CodingErrorAction .REPLACE );
157157 String decoded = utf8 .decode (ByteBuffer .wrap (bytes )).toString ();
158- return truncate (decoded , maxCommentLength );
158+ return new B64Result ( truncate (decoded , maxCommentLength ), ALL_DECODER_TAGS [ i ] );
159159 } catch (Exception ignored ) {
160160 }
161161 }
0 commit comments