-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathPartParseTests.java
216 lines (197 loc) · 7.75 KB
/
PartParseTests.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
package software.coley.lljzip;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import software.coley.lljzip.format.compression.ZipCompressions;
import software.coley.lljzip.format.model.LocalFileHeader;
import software.coley.lljzip.format.model.ZipArchive;
import software.coley.lljzip.format.model.ZipPart;
import software.coley.lljzip.format.read.ForwardScanZipReader;
import software.coley.lljzip.util.MemorySegmentUtil;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* Parse tests for {@link ZipArchive}.
* Ensures all the {@link ZipPart}s of the archive are read as expected.
*
* @author Matt Coley
*/
public class PartParseTests {
@ParameterizedTest
@ValueSource(strings = {
"src/test/resources/sample-code-7z.zip", // ZIP made from 7z
"src/test/resources/sample-code-windows.zip", // ZIP made from windows built in 'send to zip'
})
public void testStandardCodeZip(String path) {
try {
ZipArchive zip = ZipIO.readStandard(Paths.get(path));
assertNotNull(zip);
// Each code zip contains these files
assertTrue(hasFile(zip, "ClassFile.java"));
assertTrue(hasFile(zip, "ClassMember.java"));
assertTrue(hasFile(zip, "ConstPool.java"));
assertTrue(hasFile(zip, "Descriptor.java"));
assertTrue(hasFile(zip, "Field.java"));
assertTrue(hasFile(zip, "Method.java"));
} catch (IOException ex) {
fail(ex);
}
}
@ParameterizedTest
@ValueSource(strings = {
"hello.jar",
"hello-secret.jar",
"hello-secret-0-length-locals.jar",
"hello-secret-junkheader.jar",
})
public void testHello(String name) {
try {
Path data = Paths.get("src/test/resources/" + name);
ZipArchive zipStd = ZipIO.readStandard(data);
ZipArchive zipJvm = ZipIO.readJvm(data);
assertNotNull(zipStd);
assertNotNull(zipJvm);
assertEquals(zipJvm, zipJvm);
// The 'hello' jars has a manifest and single class to run itself when invoked via 'java -jar'
assertTrue(hasFile(zipStd, "META-INF/MANIFEST.MF"));
assertTrue(hasFile(zipStd, "Hello.class"));
} catch (IOException ex) {
fail(ex);
}
}
@ParameterizedTest
@ValueSource(strings = {
"hello-concat.jar",
"hello-concat-junkheader.jar",
"hello-merged.jar",
"hello-merged-junkheader.jar",
})
public void testConcatAndMerged(String name) {
try {
Path path = Paths.get("src/test/resources/" + name);
ZipArchive zipStd = ZipIO.readStandard(path);
ZipArchive zipJvm = ZipIO.readJvm(path);
assertNotNull(zipStd);
assertNotNull(zipJvm);
assertNotEquals(zipJvm, zipStd);
assertNotEquals(zipJvm.getEnd(), zipStd.getEnd());
assertTrue(hasFile(zipJvm, "META-INF/MANIFEST.MF"));
assertTrue(hasFile(zipJvm, "Hello.class"));
// Assert that the standard ZIP reader read the 'first' version of the class
// and the JVM reader read the 'second' version of the class.
LocalFileHeader stdHello = zipStd.getLocalFileByName("Hello.class");
LocalFileHeader jvmHello = zipJvm.getLocalFileByName("Hello.class");
assertNotEquals(stdHello, jvmHello);
String stdHelloRaw = MemorySegmentUtil.toString(ZipCompressions.decompress(stdHello));
String jvmHelloRaw = MemorySegmentUtil.toString(ZipCompressions.decompress(jvmHello));
assertFalse(stdHelloRaw.isEmpty());
assertFalse(jvmHelloRaw.isEmpty());
assertTrue(stdHelloRaw.contains("Hello world"));
assertTrue(jvmHelloRaw.contains("The secret code is: ROSE"));
} catch (IOException ex) {
fail(ex);
}
}
@ParameterizedTest
@ValueSource(strings = {
"hello-end-declares-0-entries.jar",
"hello-end-declares-0-entries-0-offset.jar",
"hello-junk-dir-length.jar",
"hello-junk-eocd.jar",
"hello-junk-local-length.jar",
"hello-total-junk.jar",
"hello-total-junk-large.jar",
"hello-wrong-local-compression.jar",
"hello-zeroed-locals.jar",
"hello-concat.jar",
"hello-concat-junkheader.jar",
"hello-merged.jar",
"hello-merged-fake-empty.jar",
"hello-merged-junkheader.jar",
"hello-secret-0-length-locals.jar",
"hello-secret-junkheader.jar",
"hello-secret-trailing-slash.jar",
"hello-secret-trailing-slash-0-length-locals.jar",
"hello-txt-stored.jar",
"hello-txt-type-0.jar",
})
public void testJvmCanRecoverData(String name) {
try {
Path path = Paths.get("src/test/resources/" + name);
ZipArchive zip = ZipIO.readJvm(path);
List<LocalFileHeader> localFiles = zip.getNameFilteredLocalFiles(n -> n.contains(".class"));
assertEquals(1, localFiles.size(), "More than 1 class");
byte[] decompressed = MemorySegmentUtil.toByteArray(ZipCompressions.decompress(localFiles.get(0)));
String decompressedStr = new String(decompressed);
assertDoesNotThrow(() -> {
ClassWriter cw = new ClassWriter(0);
ClassReader cr = new ClassReader(decompressed);
cr.accept(cw, 0);
}, "Failed to read class, must have failed to decompress");
assertTrue(decompressedStr.contains("Hello world") || decompressedStr.contains("The secret code is: ROSE"));
} catch (IOException ex) {
fail(ex);
}
}
@Test
public void testLocalHeaderDetectMismatch() {
Path path = Paths.get("src/test/resources/hello-secret-0-length-locals.jar");
try {
// The 'standard' strategy does not adopt CEN values when reading local entries.
// The 'jvm' strategy does.
ZipArchive zipStd = ZipIO.readStandard(path);
assertNotNull(zipStd);
LocalFileHeader hello = zipStd.getLocalFileByName("Hello.class");
assertNotNull(hello);
assertEquals(0, hello.getFileData().byteSize()); // Should be empty
// The local file header says the contents are 0 bytes, but the central header has the real length
assertTrue(hello.hasDifferentValuesThanCentralDirectoryHeader());
// The solution to differing values is to adopt values in the reader strategy
ZipArchive zipStdAndAdopt = ZipIO.read(path, new ForwardScanZipReader() {
@Override
public void postProcessLocalFileHeader(@Nonnull LocalFileHeader file) {
assertDoesNotThrow(() -> file.adoptLinkedCentralDirectoryValues());
}
});
LocalFileHeader helloAdopted = zipStdAndAdopt.getLocalFileByName("Hello.class");
assertFalse(helloAdopted.hasDifferentValuesThanCentralDirectoryHeader());
assertNotEquals(0, helloAdopted.getFileData().byteSize()); // Should have data
// The JVM strategy copies most properties, except for size.
ZipArchive zipJvm = ZipIO.readJvm(path);
helloAdopted = zipJvm.getLocalFileByName("Hello.class");
assertNotEquals(0, helloAdopted.getFileData().byteSize()); // Should have data, even if not sourced from values in the CEN
} catch (IOException ex) {
fail(ex);
}
}
@Test
public void testEndInLocalDataDesc() {
// The non-class files have been zeroed out in this sample, but we don't care.
// The main point is that the "bin/3" and "bin/4" red-herring headers are properly dealt with,
// and we can see that there are 639 class files in this jar file.
try (ZipArchive archive = ZipIO.readJvm(Paths.get("src/test/resources/end-in-local-data-desc.jar"))) {
assertEquals(639, archive.getNameFilteredLocalFiles(f -> f.endsWith(".class")).size());
} catch (IOException ex) {
fail(ex);
}
}
@Test
public void testMergedFakeEmpty() {
try (ZipArchive zipJvm = ZipIO.readJvm(Paths.get("src/test/resources/hello-merged-fake-empty.jar"))) {
assertNotNull(zipJvm);
assertTrue(hasFile(zipJvm, "META-INF/MANIFEST.MF"));
assertTrue(hasFile(zipJvm, "Hello.class/")); // has trailing slash in class name
} catch (IOException ex) {
fail(ex);
}
}
private static boolean hasFile(ZipArchive zip, String name) {
return !zip.getNameFilteredLocalFiles(name::equals).isEmpty();
}
}