Skip to content

Commit 8c800cd

Browse files
skyrisingjaskarth
andauthored
Detect JAR/ZIP files with other file endings (Closes #85) (#128)
This handles for example self-executing JARs packaged as EXE file, which contain the JAR content at the end after the native code. These files are treated like JAR files, meaning any MANIFEST.MF will not be extracted. Co-authored-by: SuperCoder79 <[email protected]>
1 parent 71a6c0b commit 8c800cd

File tree

2 files changed

+56
-37
lines changed

2 files changed

+56
-37
lines changed

src/org/jetbrains/java/decompiler/struct/JarContextSource.java

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Copyright 2000-2022 JetBrains s.r.o. and ForgeFlower contributors Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
22
package org.jetbrains.java.decompiler.struct;
33

4-
import static java.util.Objects.requireNonNull;
5-
64
import org.jetbrains.java.decompiler.main.DecompilerContext;
75
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider;
86
import org.jetbrains.java.decompiler.main.extern.IContextSource;
@@ -13,15 +11,13 @@
1311
import java.io.File;
1412
import java.io.IOException;
1513
import java.io.InputStream;
16-
import java.util.ArrayList;
17-
import java.util.Enumeration;
18-
import java.util.LinkedHashSet;
19-
import java.util.List;
20-
import java.util.Set;
14+
import java.util.*;
2115
import java.util.jar.Manifest;
2216
import java.util.zip.ZipEntry;
2317
import java.util.zip.ZipFile;
2418

19+
import static java.util.Objects.requireNonNull;
20+
2521
final class JarContextSource implements IContextSource, AutoCloseable {
2622
private static final String MANIFEST = "META-INF/MANIFEST.MF";
2723

@@ -30,7 +26,7 @@ final class JarContextSource implements IContextSource, AutoCloseable {
3026
private final String relativePath; // used for nested contexts from DirectoryContextSource
3127
private final File jarFile;
3228
private final ZipFile file;
33-
private boolean isJar;
29+
private final boolean isZip;
3430

3531
@SuppressWarnings("deprecation")
3632
JarContextSource(final IBytecodeProvider legacyProvider, final File archive) throws IOException {
@@ -43,7 +39,7 @@ final class JarContextSource implements IContextSource, AutoCloseable {
4339
this.relativePath = relativePath;
4440
this.jarFile = requireNonNull(archive, "archive");
4541
this.file = new ZipFile(archive);
46-
this.isJar = this.jarFile.getName().endsWith("jar");
42+
this.isZip = this.jarFile.getName().endsWith("zip");
4743
}
4844

4945
@Override
@@ -67,7 +63,7 @@ public Entries getEntries() {
6763
if (name.endsWith(CLASS_SUFFIX)) {
6864
classes.add(Entry.parse(name.substring(0, name.length() - CLASS_SUFFIX.length())));
6965
}
70-
else if (!this.isJar || !name.equalsIgnoreCase(MANIFEST)) {
66+
else if (this.isZip || !name.equalsIgnoreCase(MANIFEST)) {
7167
others.add(Entry.parse(name));
7268
}
7369
}

src/org/jetbrains/java/decompiler/struct/StructContext.java

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
package org.jetbrains.java.decompiler.struct;
33

44
import org.jetbrains.java.decompiler.main.DecompilerContext;
5-
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
65
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider;
76
import org.jetbrains.java.decompiler.main.extern.IContextSource;
87
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
8+
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
99
import org.jetbrains.java.decompiler.struct.gen.generics.GenericMain;
1010
import org.jetbrains.java.decompiler.struct.gen.generics.GenericMethodDescriptor;
1111
import org.jetbrains.java.decompiler.util.DataInputFullStream;
@@ -14,11 +14,11 @@
1414
import java.io.IOException;
1515
import java.io.InputStream;
1616
import java.io.UncheckedIOException;
17-
import java.util.ArrayList;
18-
import java.util.HashMap;
19-
import java.util.List;
20-
import java.util.Map;
21-
import java.util.Objects;
17+
import java.nio.ByteBuffer;
18+
import java.nio.ByteOrder;
19+
import java.nio.channels.SeekableByteChannel;
20+
import java.nio.file.Files;
21+
import java.util.*;
2222
import java.util.concurrent.ConcurrentHashMap;
2323
import java.util.stream.Collectors;
2424

@@ -129,30 +129,53 @@ public void saveContext() {
129129
}
130130
}
131131

132+
private static boolean isJarFile(File file) {
133+
if (!file.isFile()) return false;
134+
String name = file.getName();
135+
if (name.endsWith(".jar") || name.endsWith(".zip")) return true;
136+
if (name.endsWith(".class")) return false;
137+
try (SeekableByteChannel channel = Files.newByteChannel(file.toPath())) {
138+
long size = channel.size();
139+
// The EOCD ZIP record has 22+n bytes depending on the length of the comment.
140+
if (size < 22) return false;
141+
int bufferSize = (int) Math.min(size & ~3, 1024);
142+
channel.position(size - bufferSize);
143+
ByteBuffer buffer = ByteBuffer.allocate(bufferSize).order(ByteOrder.LITTLE_ENDIAN);
144+
int read = 0;
145+
while (read < bufferSize) {
146+
read += channel.read(buffer);
147+
}
148+
buffer.flip();
149+
for (int pos = buffer.limit() - 22; pos >= 0; pos--) {
150+
if (buffer.getInt(pos) == 0x06054b50) {
151+
return true;
152+
}
153+
}
154+
} catch (IOException e) {
155+
DecompilerContext.getLogger().writeMessage("Could not determine if " + file + " contains a JAR file", IFernflowerLogger.Severity.WARN, e);
156+
}
157+
return false;
158+
}
159+
132160
public void addSpace(File file, boolean isOwn) {
133161
if (file.isDirectory()) {
134162
addSpace(new DirectoryContextSource(this.legacyProvider, file), isOwn);
135-
} else {
136-
final String name = file.getName();
137-
if (name.endsWith(".jar") || name.endsWith(".zip")) {
138-
if (file.isFile()) {
139-
// archive
140-
try {
141-
addSpace(new JarContextSource(this.legacyProvider, file), isOwn);
142-
} catch (final IOException ex) {
143-
final String message = "Invalid archive " + file;
144-
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.ERROR, ex);
145-
throw new UncheckedIOException(message, ex);
146-
}
147-
}
148-
} else {
149-
try {
150-
addSpace(new SingleFileContextSource(this.legacyProvider, file), isOwn);
151-
} catch (final IOException ex) {
152-
final String message = "Invalid file " + file;
153-
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.ERROR, ex);
154-
throw new UncheckedIOException(message, ex);
155-
}
163+
} else if (isJarFile(file)) {
164+
// archive
165+
try {
166+
addSpace(new JarContextSource(this.legacyProvider, file), isOwn);
167+
} catch (final IOException ex) {
168+
final String message = "Invalid archive " + file;
169+
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.ERROR, ex);
170+
throw new UncheckedIOException(message, ex);
171+
}
172+
} else if (file.isFile()) {
173+
try {
174+
addSpace(new SingleFileContextSource(this.legacyProvider, file), isOwn);
175+
} catch (final IOException ex) {
176+
final String message = "Invalid file " + file;
177+
DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.ERROR, ex);
178+
throw new UncheckedIOException(message, ex);
156179
}
157180
}
158181
}

0 commit comments

Comments
 (0)