提交 c47e9cdd 编写于 作者: S Skylot

fix: allow to load Spring Boot jar (#1066)

上级 8dd76420
......@@ -128,7 +128,10 @@ public final class ResourcesLoader {
return;
}
if (FileUtils.isZipFile(file)) {
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> addEntry(list, file, entry));
ZipSecurity.visitZipEntries(file, (zipFile, entry) -> {
addEntry(list, file, entry);
return null;
});
} else {
addResourceFile(list, file);
}
......
package jadx.plugins.input.javaconvert;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import org.objectweb.asm.ClassReader;
public class AsmUtils {
public static String getNameFromClassFile(Path file) throws IOException {
try (InputStream in = Files.newInputStream(file)) {
return getClassFullName(new ClassReader(in));
}
}
public static String getNameFromClassFile(byte[] content) throws IOException {
return getClassFullName(new ClassReader(content));
}
private static String getClassFullName(ClassReader classReader) {
return classReader.getClassName();
}
}
package jadx.plugins.input.javaconvert;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
......@@ -9,13 +9,14 @@ import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.attribute.FileTime;
import java.util.List;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.objectweb.asm.ClassReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -41,7 +42,7 @@ public class JavaConvertLoader {
try {
convertJar(result, path);
} catch (Exception e) {
LOG.error("Failed to convert file: " + path.toAbsolutePath(), e);
LOG.error("Failed to convert file: {}", path.toAbsolutePath(), e);
}
});
}
......@@ -59,7 +60,7 @@ public class JavaConvertLoader {
Path jarFile = Files.createTempFile("jadx-", ".jar");
try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(jarFile))) {
for (Path file : clsFiles) {
String clsName = getNameFromClassFile(file);
String clsName = AsmUtils.getNameFromClassFile(file);
if (clsName == null || !ZipSecurity.isValidZipEntryName(clsName)) {
throw new IOException("Can't read class name from file: " + file);
}
......@@ -74,13 +75,6 @@ public class JavaConvertLoader {
}
}
public static String getNameFromClassFile(Path file) throws IOException {
try (InputStream in = Files.newInputStream(file)) {
ClassReader classReader = new ClassReader(in);
return classReader.getClassName();
}
}
private static void processAars(List<Path> input, ConvertResult result) {
PathMatcher aarMatcher = FileSystems.getDefault().getPathMatcher("glob:**.aar");
input.stream()
......@@ -100,6 +94,68 @@ public class JavaConvertLoader {
}
private static void convertJar(ConvertResult result, Path path) throws Exception {
if (repackAndConvertJar(result, path)) {
return;
}
convertSimpleJar(result, path);
}
private static boolean repackAndConvertJar(ConvertResult result, Path path) throws Exception {
// check if jar need a full repackage
Boolean repackNeeded = ZipSecurity.visitZipEntries(path.toFile(), (zipFile, zipEntry) -> {
String entryName = zipEntry.getName();
if (zipEntry.isDirectory()) {
if (entryName.equals("BOOT-INF/")) {
return true; // Spring Boot jar
}
if (entryName.equals("META-INF/versions/")) {
return true; // exclude duplicated classes
}
}
if (entryName.endsWith(".jar")) {
return true; // contains sub jars
}
if (entryName.endsWith("module-info.class")) {
return true; // need to exclude module files
}
return null;
});
if (!Objects.equals(repackNeeded, Boolean.TRUE)) {
return false;
}
Path jarFile = Files.createTempFile("jadx-classes-", ".jar");
result.addTempPath(jarFile);
try (JarOutputStream jo = new JarOutputStream(Files.newOutputStream(jarFile))) {
ZipSecurity.readZipEntries(path.toFile(), (entry, in) -> {
try {
String entryName = entry.getName();
if (entryName.endsWith(".class")) {
if (entryName.endsWith("module-info.class")
|| entryName.startsWith("META-INF/versions/")) {
return;
}
byte[] clsFileContent = inputStreamToByteArray(in);
String clsName = AsmUtils.getNameFromClassFile(clsFileContent);
if (clsName == null || !ZipSecurity.isValidZipEntryName(clsName)) {
throw new IOException("Can't read class name from file: " + entryName);
}
addJarEntry(jo, clsName + ".class", clsFileContent, entry.getLastModifiedTime());
} else if (entryName.endsWith(".jar")) {
Path tempJar = saveInputStreamToFile(in, ".jar");
result.addTempPath(tempJar);
convertJar(result, tempJar);
}
} catch (Exception e) {
LOG.error("Failed to process jar entry: {} in {}", entry, path, e);
}
});
}
convertSimpleJar(result, jarFile);
return true;
}
private static void convertSimpleJar(ConvertResult result, Path path) throws Exception {
Path tempDirectory = Files.createTempDirectory("jadx-");
result.addTempPath(tempDirectory);
......@@ -119,18 +175,24 @@ public class JavaConvertLoader {
}
}
public static void addFileToJar(JarOutputStream jar, Path source, String entryName) throws IOException {
try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(source))) {
JarEntry entry = new JarEntry(entryName);
entry.setTime(Files.getLastModifiedTime(source, LinkOption.NOFOLLOW_LINKS).toMillis());
jar.putNextEntry(entry);
private static void addFileToJar(JarOutputStream jar, Path source, String entryName) throws IOException {
byte[] fileContent = Files.readAllBytes(source);
FileTime lastModifiedTime = Files.getLastModifiedTime(source, LinkOption.NOFOLLOW_LINKS);
addJarEntry(jar, entryName, fileContent, lastModifiedTime);
}
copyStream(in, jar);
jar.closeEntry();
private static void addJarEntry(JarOutputStream jar, String entryName, byte[] content,
FileTime modTime) throws IOException {
JarEntry entry = new JarEntry(entryName);
if (modTime != null) {
entry.setTime(modTime.toMillis());
}
jar.putNextEntry(entry);
jar.write(content);
jar.closeEntry();
}
public static void copyStream(InputStream input, OutputStream output) throws IOException {
private static void copyStream(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[8 * 1024];
while (true) {
int count = input.read(buffer);
......@@ -141,6 +203,13 @@ public class JavaConvertLoader {
}
}
private static byte[] inputStreamToByteArray(InputStream input) throws IOException {
try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {
copyStream(input, output);
return output.toByteArray();
}
}
private static Path saveInputStreamToFile(InputStream in, String suffix) throws IOException {
Path tempJar = Files.createTempFile("jadx-temp-", suffix);
try (OutputStream out = Files.newOutputStream(tempJar)) {
......
......@@ -6,9 +6,11 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -86,14 +88,22 @@ public class ZipSecurity {
return new BufferedInputStream(limited);
}
public static void visitZipEntries(File file, BiConsumer<ZipFile, ZipEntry> visitor) {
/**
* Visit valid entries in zip file.
* Return not null value from visitor to stop iteration.
*/
@Nullable
public static <R> R visitZipEntries(File file, BiFunction<ZipFile, ZipEntry, R> visitor) {
try (ZipFile zip = new ZipFile(file)) {
Enumeration<? extends ZipEntry> entries = zip.entries();
int entriesProcessed = 0;
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (!entry.isDirectory() && isValidZipEntry(entry)) {
visitor.accept(zip, entry);
if (isValidZipEntry(entry)) {
R result = visitor.apply(zip, entry);
if (result != null) {
return result;
}
entriesProcessed++;
if (entriesProcessed > MAX_ENTRIES_COUNT) {
throw new IllegalStateException("Zip entries count limit exceeded: " + MAX_ENTRIES_COUNT
......@@ -104,15 +114,19 @@ public class ZipSecurity {
} catch (Exception e) {
throw new RuntimeException("Failed to process zip file: " + file.getAbsolutePath(), e);
}
return null;
}
public static void readZipEntries(File file, BiConsumer<ZipEntry, InputStream> visitor) {
visitZipEntries(file, (zip, entry) -> {
try (InputStream in = getInputStreamForEntry(zip, entry)) {
visitor.accept(entry, in);
} catch (Exception e) {
throw new RuntimeException("Error process zip entry: " + entry.getName());
if (!entry.isDirectory()) {
try (InputStream in = getInputStreamForEntry(zip, entry)) {
visitor.accept(entry, in);
} catch (Exception e) {
throw new RuntimeException("Error process zip entry: " + entry.getName());
}
}
return null;
});
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册