Skip to content

Commit 13106f8

Browse files
committed
Expand test suite
1 parent 011fd3a commit 13106f8

21 files changed

+110
-100
lines changed
Lines changed: 7 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
package software.coley.llzip;
22

33
import org.junit.jupiter.api.Test;
4-
import software.coley.llzip.part.CentralDirectoryFileHeader;
5-
import software.coley.llzip.part.LocalFileHeader;
6-
import software.coley.llzip.strategy.Decompressor;
7-
import software.coley.llzip.strategy.DeflateDecompressor;
4+
import software.coley.llzip.format.model.LocalFileHeader;
5+
import software.coley.llzip.format.compression.Decompressor;
6+
import software.coley.llzip.format.compression.DeflateDecompressor;
7+
import software.coley.llzip.format.model.ZipArchive;
88
import software.coley.llzip.util.ByteData;
99
import software.coley.llzip.util.ByteDataUtil;
1010

1111
import java.io.IOException;
1212
import java.nio.file.Paths;
1313

14-
import static org.junit.jupiter.api.Assertions.*;
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
import static org.junit.jupiter.api.Assertions.fail;
1516

1617
/**
1718
* Tests for {@link LocalFileHeader#decompress(Decompressor)}.
@@ -20,7 +21,7 @@
2021
*/
2122
public class CompressionTests {
2223
@Test
23-
public void testDeflateStandardJar() {
24+
public void testDeflate() {
2425
try {
2526
ZipArchive zip = ZipIO.readStandard(Paths.get("src/test/resources/hello.jar"));
2627
LocalFileHeader localFileHeader = zip.getLocalFiles().get(0);
@@ -31,62 +32,4 @@ public void testDeflateStandardJar() {
3132
fail(ex);
3233
}
3334
}
34-
35-
@Test
36-
public void testDeflateJvmJar() {
37-
try {
38-
// When parsed there should be these sections in order
39-
/////// MISSING: LocalFileHeader - Not read by JVM
40-
/////// MISSING: LocalFileHeader - Not read by JVM
41-
/////// MISSING: LocalFileHeader - Not read by JVM
42-
// 0 = {CentralDirectoryFileHeader} offset=642
43-
// 1 = {CentralDirectoryFileHeader} offset=735
44-
// 2 = {CentralDirectoryFileHeader} offset=826
45-
/////// MISSING: EndOfCentralDirectory - Not read by JVM <--- This is where most tools read from, but not the JVM!
46-
// 3 = {LocalFileHeader} offset=950 <--- name=<blank>, message=The secret code is: ROSE
47-
// 4 = {LocalFileHeader} offset=1243 <--- name=<blank>, message=Hello world!
48-
// 5 = {LocalFileHeader} offset=1523
49-
// 6 = {CentralDirectoryFileHeader} offset=1592 <--- name="Hello.class/" file=ROSE
50-
// 7 = {CentralDirectoryFileHeader} offset=1650 <--- name="Hello.class " file=Hello world!
51-
// 8 = {CentralDirectoryFileHeader} offset=1708
52-
// 9 = {EndOfCentralDirectory} offset=1774
53-
ZipArchive zip = ZipIO.readJvm(Paths.get("src/test/resources/hello-trick.jar"));
54-
// The red herring class that most zip tools see
55-
CentralDirectoryFileHeader redHerringCentralDir = zip.getCentralDirectories().get(0);
56-
assertEquals("Hello.class", redHerringCentralDir.getFileNameAsString());
57-
assertNull( redHerringCentralDir.getLinkedFileHeader(), "The red herring central directory got linked");
58-
ByteData redHerringClassData = zip.getLocalFiles().get(1).decompress(new DeflateDecompressor());
59-
Utils.assertDefinesString(redHerringClassData, "Hello world!");
60-
// The real class that gets run by the JVM
61-
CentralDirectoryFileHeader jvmCentralDir = zip.getCentralDirectories().get(3);
62-
assertEquals("Hello.class/", jvmCentralDir.getFileNameAsString());
63-
assertNotEquals("Hello.class/", jvmCentralDir.getLinkedFileHeader().getFileName());
64-
ByteData classData = jvmCentralDir.getLinkedFileHeader().decompress(new DeflateDecompressor());
65-
Utils.assertDefinesString(classData, "The secret code is: ROSE");
66-
} catch (IOException ex) {
67-
fail(ex);
68-
}
69-
}
70-
71-
@Test
72-
public void testDeflateJvmJarWithGarbageHeader() {
73-
try {
74-
// The trick jar is similar to the above, but with extra garbage at the beginning of the file.
75-
// This test shows that garbage bytes in the beginning of the file can be bypassed.
76-
ZipArchive zip = ZipIO.readJvm(Paths.get("src/test/resources/hello-trick-garbagehead.jar"));
77-
// The red herring class that most zip tools see
78-
CentralDirectoryFileHeader redHerringCentralDir = zip.getCentralDirectories().get(1);
79-
assertEquals("Hello\t.class", redHerringCentralDir.getFileNameAsString());
80-
ByteData redHerringClassData = redHerringCentralDir.getLinkedFileHeader().decompress(new DeflateDecompressor());
81-
Utils.assertDefinesString(redHerringClassData, "Hello world!");
82-
// The real class that gets run by the JVM
83-
CentralDirectoryFileHeader jvmCentralDir = zip.getCentralDirectories().get(0);
84-
assertEquals("Hello.class/", jvmCentralDir.getFileNameAsString());
85-
assertNotEquals("Hello.class/", jvmCentralDir.getLinkedFileHeader().getFileName());
86-
ByteData classData = jvmCentralDir.getLinkedFileHeader().decompress(new DeflateDecompressor());
87-
Utils.assertDefinesString(classData, "The secret code is: ROSE");
88-
} catch (IOException ex) {
89-
fail(ex);
90-
}
91-
}
9235
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public class LargeZipTests {
1414
@Test
1515
public void testLargeStrings() {
1616
try {
17-
ZipIO.readStandard(Paths.get("src/test/resources/large-strings.zip"));
17+
ZipIO.readStandard(Paths.get("src/test/resources/sample-long-name.zip"));
1818
} catch (IOException error) {
1919
Assertions.fail(error);
2020
}

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

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
import org.junit.jupiter.api.Test;
44
import org.junit.jupiter.params.ParameterizedTest;
55
import org.junit.jupiter.params.provider.ValueSource;
6-
import software.coley.llzip.part.ZipPart;
6+
import software.coley.llzip.format.compression.ZipCompressions;
7+
import software.coley.llzip.format.model.LocalFileHeader;
8+
import software.coley.llzip.format.model.ZipArchive;
9+
import software.coley.llzip.format.model.ZipPart;
10+
import software.coley.llzip.format.read.JvmZipReaderStrategy;
11+
import software.coley.llzip.util.ByteDataUtil;
712

813
import java.io.IOException;
14+
import java.nio.file.Path;
915
import java.nio.file.Paths;
1016

1117
import static org.junit.jupiter.api.Assertions.*;
@@ -19,13 +25,14 @@
1925
public class PartParseTests {
2026
@ParameterizedTest
2127
@ValueSource(strings = {
22-
"src/test/resources/code-windows.zip",
23-
"src/test/resources/code-7z.zip",
28+
"src/test/resources/sample-code-7z.zip", // ZIP made from 7z
29+
"src/test/resources/sample-code-windows.zip", // ZIP made from windows built in 'send to zip'
2430
})
2531
public void testStandardCodeZip(String path) {
2632
try {
2733
ZipArchive zip = ZipIO.readStandard(Paths.get(path));
2834
assertNotNull(zip);
35+
2936
// Each code zip contains these files
3037
assertTrue(hasFile(zip, "ClassFile.java"));
3138
assertTrue(hasFile(zip, "ClassMember.java"));
@@ -38,53 +45,100 @@ public void testStandardCodeZip(String path) {
3845
}
3946
}
4047

41-
@Test
42-
public void testStandardJar() {
48+
@ParameterizedTest
49+
@ValueSource(strings = {
50+
"hello.jar",
51+
"hello-secret.jar",
52+
"hello-secret-0-length-locals.jar",
53+
"hello-secret-junkheader.jar",
54+
})
55+
public void testHello(String name) {
4356
try {
44-
ZipArchive zip = ZipIO.readStandard(Paths.get("src/test/resources/hello.jar"));
45-
assertNotNull(zip);
46-
// The 'hello' jar has a manifest and single class to run itself when invoked via 'java -jar'
47-
assertTrue(hasFile(zip, "META-INF/MANIFEST.MF"));
48-
assertTrue(hasFile(zip, "Hello.class"));
57+
Path data = Paths.get("src/test/resources/" + name);
58+
ZipArchive zipStd = ZipIO.readStandard(data);
59+
ZipArchive zipJvm = ZipIO.readJvm(data);
60+
assertNotNull(zipStd);
61+
assertNotNull(zipJvm);
62+
assertEquals(zipJvm, zipJvm);
63+
64+
// The 'hello' jars has a manifest and single class to run itself when invoked via 'java -jar'
65+
assertTrue(hasFile(zipStd, "META-INF/MANIFEST.MF"));
66+
assertTrue(hasFile(zipStd, "Hello.class"));
67+
} catch (IOException ex) {
68+
fail(ex);
69+
}
70+
}
71+
72+
@ParameterizedTest
73+
@ValueSource(strings = {
74+
"hello-concat.jar",
75+
"hello-concat-junkheader.jar",
76+
"hello-merged.jar",
77+
"hello-merged-junkheader.jar",
78+
})
79+
public void testConcatAndMerged(String name) {
80+
try {
81+
Path path = Paths.get("src/test/resources/" + name);
82+
ZipArchive zipStd = ZipIO.readStandard(path);
83+
ZipArchive zipJvm = ZipIO.readJvm(path);
84+
assertNotNull(zipStd);
85+
assertNotNull(zipJvm);
86+
assertNotEquals(zipJvm, zipStd);
87+
assertNotEquals(zipJvm.getEnd(), zipStd.getEnd());
88+
assertTrue(hasFile(zipJvm, "META-INF/MANIFEST.MF"));
89+
assertTrue(hasFile(zipJvm, "Hello.class"));
90+
LocalFileHeader stdHello = zipStd.getLocalFileByName("Hello.class");
91+
LocalFileHeader jvmHello = zipJvm.getLocalFileByName("Hello.class");
92+
String stdHelloRaw = ByteDataUtil.toString(ZipCompressions.decompress(stdHello));
93+
String jvmHelloRaw = ByteDataUtil.toString(ZipCompressions.decompress(jvmHello));
94+
assertTrue(stdHelloRaw.contains("Hello world"));
95+
assertTrue(jvmHelloRaw.contains("The secret code is: ROSE"));
4996
} catch (IOException ex) {
5097
fail(ex);
5198
}
5299
}
53100

54101
@Test
55-
public void testJvmStandardJar() {
102+
public void testLocalHeaderDetectMismatch() {
103+
Path path = Paths.get("src/test/resources/hello-secret-0-length-locals.jar");
104+
56105
try {
57-
// Even with edge case parsing being the focus, the JVM reader should be able to handle this jar fine.
58-
ZipArchive zip = ZipIO.readJvm(Paths.get("src/test/resources/hello.jar"));
59-
assertNotNull(zip);
60-
// The 'hello' jar has a manifest and single class to run itself when invoked via 'java -jar'
61-
assertTrue(hasFile(zip, "META-INF/MANIFEST.MF"));
62-
assertTrue(hasFile(zip, "Hello.class"));
106+
ZipArchive zipJvm = ZipIO.readJvm(path);
107+
assertNotNull(zipJvm);
108+
109+
LocalFileHeader hello = zipJvm.getLocalFileByName("Hello.class");
110+
assertNotNull(hello);
111+
112+
// The local file header says the contents are 0 bytes, but the central header has the real length
113+
assertTrue(hello.hasDifferentValuesThanCentralDirectoryHeader());
114+
115+
// The solution to differing values is to adopt values in the reader strategy
116+
zipJvm = ZipIO.read(path, new JvmZipReaderStrategy() {
117+
@Override
118+
public void postProcessLocalFileHeader(LocalFileHeader file) {
119+
file.adoptLinkedCentralDirectoryValues();
120+
}
121+
});
122+
hello = zipJvm.getLocalFileByName("Hello.class");
123+
assertFalse(hello.hasDifferentValuesThanCentralDirectoryHeader());
63124
} catch (IOException ex) {
64125
fail(ex);
65126
}
66127
}
67128

68129
@Test
69-
public void testJvmTrickJar() {
130+
public void testMergedFakeEmpty() {
70131
try {
71-
ZipArchive zip = ZipIO.readJvm(Paths.get("src/test/resources/hello-trick.jar"));
72-
assertNotNull(zip);
73-
// The 'hello' jar has a manifest and single class to run itself when invoked via 'java -jar'
74-
assertTrue(hasFile(zip, "META-INF/MANIFEST.MF"));
75-
// There are two classes with deceiving names in the trick jar
76-
// - The central directory names are authoritative in Java.
77-
// - The local file names are ignored, so they can be anything, even `\0`
78-
assertTrue(hasFile(zip, "Hello.class/"));
79-
assertTrue(hasFile(zip, "Hello.class\1"));
132+
ZipArchive zipJvm = ZipIO.readJvm(Paths.get("src/test/resources/hello-merged-fake-empty.jar"));
133+
assertNotNull(zipJvm);
134+
assertTrue(hasFile(zipJvm, "META-INF/MANIFEST.MF"));
135+
assertTrue(hasFile(zipJvm, "Hello.class/")); // has trailing slash in class name
80136
} catch (IOException ex) {
81137
fail(ex);
82138
}
83139
}
84140

85141
private static boolean hasFile(ZipArchive zip, String name) {
86-
return zip.getCentralDirectories().stream()
87-
.anyMatch(cdfh -> cdfh.getLinkedFileHeader() != null &&
88-
cdfh.getFileNameAsString().equals(name));
142+
return !zip.getNameFilteredLocalFiles(name::equals).isEmpty();
89143
}
90144
}

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

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

3-
import org.junit.jupiter.api.Test;
4-
import software.coley.llzip.strategy.JavaZipWriterStrategy;
3+
import org.junit.jupiter.params.ParameterizedTest;
4+
import org.junit.jupiter.params.provider.ValueSource;
5+
import software.coley.llzip.format.model.ZipArchive;
6+
import software.coley.llzip.format.write.JavaZipWriterStrategy;
57

68
import java.io.ByteArrayInputStream;
79
import java.io.ByteArrayOutputStream;
@@ -18,15 +20,23 @@
1820
* @author Matt Coley
1921
*/
2022
public class PatchingTests {
21-
@Test
22-
public void testTrickJarPatched() {
23+
@ParameterizedTest
24+
@ValueSource(strings = {
25+
"hello-concat.jar",
26+
"hello-concat-junkheader.jar",
27+
"hello-merged.jar",
28+
"hello-merged-fake-empty.jar",
29+
"hello-merged-junkheader.jar",
30+
})
31+
public void testTrickJarPatched(String name) {
2332
try {
2433
// Parse the zip with LL-Java zip, then write back using std java apis
2534
// in order to create a std java complaint jar.
2635
ByteArrayOutputStream baos = new ByteArrayOutputStream();
27-
ZipArchive zip = ZipIO.readJvm(Paths.get("src/test/resources/hello-trick-garbagehead.jar"));
36+
ZipArchive zip = ZipIO.readJvm(Paths.get("src/test/resources/" + name));
2837
new JavaZipWriterStrategy().write(zip, baos);
2938
byte[] fixed = baos.toByteArray();
39+
3040
// Validate the new jar bytes can be read and show the true file contents.
3141
try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(fixed))) {
3242
ZipEntry entry;
@@ -37,6 +47,7 @@ public void testTrickJarPatched() {
3747
int len = 0;
3848
while ((len = zis.read(buffer)) > 0)
3949
baos.write(buffer, 0, len);
50+
4051
// Now check if the secret code is found.
4152
// If not, we extracted the wrong class.
4253
Utils.assertDefinesString(baos.toByteArray(), "The secret code is: ROSE");
1.51 KB
Binary file not shown.
1.42 KB
Binary file not shown.
1.1 KB
Binary file not shown.
1.32 KB
Binary file not shown.
1.22 KB
Binary file not shown.
755 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)