Skip to content

Commit 420a211

Browse files
committed
Added unit test for 'patching' JVM deceiving zips.
1 parent ff7a008 commit 420a211

File tree

4 files changed

+103
-33
lines changed

4 files changed

+103
-33
lines changed

src/main/java/software/coley/llzip/strategy/JavaZipWriterStategy.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
import java.io.IOException;
99
import java.io.OutputStream;
10-
import java.util.Optional;
1110
import java.util.zip.ZipEntry;
1211
import java.util.zip.ZipOutputStream;
1312

@@ -23,9 +22,10 @@ public class JavaZipWriterStategy implements ZipWriterStrategy {
2322
public void write(ZipArchive archive, OutputStream os) throws IOException {
2423
try (ZipOutputStream zos = new ZipOutputStream(os)) {
2524
for (LocalFileHeader fileHeader : archive.getLocalFiles()) {
26-
String name = Optional.ofNullable(fileHeader.getLinkedDirectoryFileHeader())
27-
.map(CentralDirectoryFileHeader::getFileName)
28-
.orElse(fileHeader.getFileName());
25+
CentralDirectoryFileHeader linked = fileHeader.getLinkedDirectoryFileHeader();
26+
if (linked == null)
27+
continue;
28+
String name = linked.getFileName();
2929
zos.putNextEntry(new ZipEntry(name));
3030
zos.write(ZipCompressions.decompress(fileHeader));
3131
zos.closeEntry();

src/test/java/software/coley/llzip/CompressionTests.java

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package software.coley.llzip;
22

33
import org.junit.jupiter.api.Test;
4-
import org.objectweb.asm.ClassReader;
5-
import org.objectweb.asm.ClassVisitor;
6-
import org.objectweb.asm.MethodVisitor;
7-
import org.objectweb.asm.Opcodes;
84
import software.coley.llzip.part.CentralDirectoryFileHeader;
95
import software.coley.llzip.part.LocalFileHeader;
106
import software.coley.llzip.strategy.Decompressor;
@@ -30,7 +26,7 @@ public void testDeflateStandardJar() {
3026
LocalFileHeader localFileHeader = zip.getLocalFiles().get(0);
3127
assertEquals("Hello.class", localFileHeader.getFileName());
3228
byte[] classData = localFileHeader.decompress(new DeflateDecompressor());
33-
assertDefinesString(classData, "Hello world!");
29+
Utils.assertDefinesString(classData, "Hello world!");
3430
} catch (IOException ex) {
3531
fail(ex);
3632
}
@@ -61,13 +57,13 @@ public void testDeflateJvmJar() {
6157
assertEquals("Hello.class", redHerringCentralDir.getFileName());
6258
assertNull( redHerringCentralDir.getLinkedFileHeader(), "The red herring central directory got linked");
6359
byte[] redHerringClassData = zip.getLocalFiles().get(1).decompress(new DeflateDecompressor());
64-
assertDefinesString(redHerringClassData, "Hello world!");
60+
Utils.assertDefinesString(redHerringClassData, "Hello world!");
6561
// The real class that gets run by the JVM
6662
CentralDirectoryFileHeader jvmCentralDir = zip.getCentralDirectories().get(3);
6763
assertEquals("Hello.class/", jvmCentralDir.getFileName());
6864
assertNotEquals("Hello.class/", jvmCentralDir.getLinkedFileHeader().getFileName());
6965
byte[] classData = jvmCentralDir.getLinkedFileHeader().decompress(new DeflateDecompressor());
70-
assertDefinesString(classData, "The secret code is: ROSE");
66+
Utils.assertDefinesString(classData, "The secret code is: ROSE");
7167
} catch (IOException ex) {
7268
fail(ex);
7369
}
@@ -84,35 +80,15 @@ public void testDeflateJvmJarWithGarbageHeader() {
8480
CentralDirectoryFileHeader redHerringCentralDir = zip.getCentralDirectories().get(1);
8581
assertEquals("Hello\t.class", redHerringCentralDir.getFileName());
8682
byte[] redHerringClassData = redHerringCentralDir.getLinkedFileHeader().decompress(new DeflateDecompressor());
87-
assertDefinesString(redHerringClassData, "Hello world!");
83+
Utils.assertDefinesString(redHerringClassData, "Hello world!");
8884
// The real class that gets run by the JVM
8985
CentralDirectoryFileHeader jvmCentralDir = zip.getCentralDirectories().get(0);
9086
assertEquals("Hello.class/", jvmCentralDir.getFileName());
9187
assertNotEquals("Hello.class/", jvmCentralDir.getLinkedFileHeader().getFileName());
9288
byte[] classData = jvmCentralDir.getLinkedFileHeader().decompress(new DeflateDecompressor());
93-
assertDefinesString(classData, "The secret code is: ROSE");
89+
Utils.assertDefinesString(classData, "The secret code is: ROSE");
9490
} catch (IOException ex) {
9591
fail(ex);
9692
}
9793
}
98-
99-
100-
101-
public static void assertDefinesString(byte[] code, String target) {
102-
boolean[] visited = new boolean[1];
103-
ClassReader cr = new ClassReader(code);
104-
cr.accept(new ClassVisitor(Opcodes.ASM9) {
105-
@Override
106-
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
107-
return new MethodVisitor(Opcodes.ASM9) {
108-
@Override
109-
public void visitLdcInsn(Object value) {
110-
visited[0] = true;
111-
assertEquals(target, value);
112-
}
113-
};
114-
}
115-
}, ClassReader.SKIP_FRAMES);
116-
assertTrue(visited[0], "The entry did not visit any LDC constants");
117-
}
11894
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package software.coley.llzip;
2+
3+
import org.junit.jupiter.api.Test;
4+
import software.coley.llzip.strategy.JavaZipWriterStategy;
5+
6+
import java.io.ByteArrayInputStream;
7+
import java.io.ByteArrayOutputStream;
8+
import java.io.IOException;
9+
import java.nio.file.Files;
10+
import java.nio.file.Paths;
11+
import java.util.zip.ZipEntry;
12+
import java.util.zip.ZipInputStream;
13+
14+
import static org.junit.jupiter.api.Assertions.fail;
15+
16+
/**
17+
* Tests for patching the <i>"trick"</i> jars, and making them compatible with standard Java ZIP apis.
18+
*
19+
* @author Matt Coley
20+
*/
21+
public class PatchingTests {
22+
@Test
23+
public void testTrickJarPatched() {
24+
try {
25+
byte[] data = Files.readAllBytes(Paths.get("src/test/resources/hello-trick-garbagehead.jar"));
26+
// Parse the zip with LL-Java zip, then write back using std java apis
27+
// in order to create a std java complaint jar.
28+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
29+
ZipArchive zip = ZipIO.readJvm(data);
30+
new JavaZipWriterStategy().write(zip, baos);
31+
byte[] fixed = baos.toByteArray();
32+
// Validate the new jar bytes can be read and show the true file contents.
33+
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(fixed))) {
34+
ZipEntry entry;
35+
while ((entry = zis.getNextEntry()) != null) {
36+
if (entry.getName().contains("Hello.class")) {
37+
byte[] buffer = new byte[1024];
38+
baos = new ByteArrayOutputStream();
39+
int len = 0;
40+
while ((len = zis.read(buffer)) > 0)
41+
baos.write(buffer, 0, len);
42+
// Now check if the secret code is found.
43+
// If not, we extracted the wrong class.
44+
Utils.assertDefinesString(baos.toByteArray(), "The secret code is: ROSE");
45+
}
46+
}
47+
}
48+
} catch (IOException ex) {
49+
fail(ex);
50+
}
51+
}
52+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package software.coley.llzip;
2+
3+
import org.objectweb.asm.ClassReader;
4+
import org.objectweb.asm.ClassVisitor;
5+
import org.objectweb.asm.MethodVisitor;
6+
import org.objectweb.asm.Opcodes;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
import static org.junit.jupiter.api.Assertions.assertTrue;
10+
11+
/**
12+
* Utilities for zip tests.
13+
*
14+
* @author Matt Coley
15+
*/
16+
public class Utils {
17+
/**
18+
* Asserts the string has been found and is the <b>ONLY</b> matching string in the class.
19+
*
20+
* @param code
21+
* Class bytecode.
22+
* @param target
23+
* String instance to look for.
24+
*/
25+
public static void assertDefinesString(byte[] code, String target) {
26+
boolean[] visited = new boolean[1];
27+
ClassReader cr = new ClassReader(code);
28+
cr.accept(new ClassVisitor(Opcodes.ASM9) {
29+
@Override
30+
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
31+
return new MethodVisitor(Opcodes.ASM9) {
32+
@Override
33+
public void visitLdcInsn(Object value) {
34+
visited[0] = true;
35+
assertEquals(target, value);
36+
}
37+
};
38+
}
39+
}, ClassReader.SKIP_FRAMES);
40+
assertTrue(visited[0], "The entry did not visit any LDC constants");
41+
}
42+
}

0 commit comments

Comments
 (0)