1 package org.codehaus.plexus.archiver.jar;
2
3 import java.io.File;
4 import java.io.IOException;
5 import java.io.OutputStream;
6 import java.nio.file.Files;
7 import java.nio.file.Path;
8 import java.nio.file.attribute.FileTime;
9 import java.text.ParseException;
10 import java.text.SimpleDateFormat;
11 import java.util.Enumeration;
12 import java.util.Random;
13 import java.util.TimeZone;
14 import java.util.zip.ZipEntry;
15 import java.util.zip.ZipFile;
16
17 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
18 import org.junit.jupiter.api.Test;
19 import org.junit.jupiter.api.io.TempDir;
20
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22
23 class JarArchiverTest extends BaseJarArchiverTest {
24
25 @TempDir
26 private Path tempDir;
27
28 @Test
29 void createManifestOnlyJar() throws Exception {
30 File jarFile = Files.createTempFile(tempDir, "JarArchiverTest.", ".jar").toFile();
31
32 JarArchiver archiver = getJarArchiver();
33 archiver.setDestFile(jarFile);
34
35 Manifest manifest = new Manifest();
36 Manifest.Attribute attribute =
37 new Manifest.Attribute("Main-Class", getClass().getName());
38
39 manifest.addConfiguredAttribute(attribute);
40
41 archiver.addConfiguredManifest(manifest);
42
43 archiver.createArchive();
44 }
45
46 @Test
47 void nonCompressed() throws Exception {
48 File jarFile = new File("target/output/jarArchiveNonCompressed.jar");
49
50 JarArchiver archiver = getJarArchiver();
51 archiver.setDestFile(jarFile);
52 archiver.setCompress(false);
53 archiver.addDirectory(new File("src/test/resources/mjar179"));
54 archiver.createArchive();
55 }
56
57 @Test
58 void veryLargeJar() throws Exception {
59
60
61 Random rand = new Random();
62 for (int i = 0; i < 45000; i++) {
63 Path path = tempDir.resolve("file" + i);
64 try (OutputStream out = Files.newOutputStream(path)) {
65 byte[] data = new byte[512];
66 rand.nextBytes(data);
67 out.write(data);
68 out.flush();
69 }
70 }
71
72 File jarFile = new File("target/output/veryLargeJar.jar");
73
74 JarArchiver archiver = getJarArchiver();
75 archiver.setDestFile(jarFile);
76 archiver.addDirectory(tempDir.toFile());
77 archiver.createArchive();
78
79 Files.delete(jarFile.toPath());
80 }
81
82 @Test
83 void reproducibleBuild() throws Exception {
84 String[] tzList = {
85 "America/Managua",
86 "America/New_York",
87 "America/Buenos_Aires",
88 "America/Sao_Paulo",
89 "America/Los_Angeles",
90 "Africa/Cairo",
91 "Africa/Lagos",
92 "Africa/Nairobi",
93 "Europe/Lisbon",
94 "Europe/Madrid",
95 "Europe/Moscow",
96 "Europe/Oslo",
97 "Australia/Sydney",
98 "Asia/Tokyo",
99 "Asia/Singapore",
100 "Asia/Qatar",
101 "Asia/Seoul",
102 "Atlantic/Bermuda",
103 "UTC",
104 "GMT",
105 "Etc/GMT-14"
106 };
107 for (String tzId : tzList) {
108
109 createReproducibleBuild(tzId);
110 }
111 }
112
113 private void createReproducibleBuild(String timeZoneId) throws IOException, ManifestException, ParseException {
114 final TimeZone defaultTz = TimeZone.getDefault();
115 TimeZone.setDefault(TimeZone.getTimeZone(timeZoneId));
116 try {
117 String tzName = timeZoneId.substring(timeZoneId.lastIndexOf('/') + 1);
118 Path jarFile = Files.createTempFile(tempDir, "JarArchiverTest-" + tzName + "-", ".jar");
119
120 Manifest manifest = new Manifest();
121 Manifest.Attribute attribute = new Manifest.Attribute("Main-Class", "com.example.app.Main");
122 manifest.addConfiguredAttribute(attribute);
123
124 JarArchiver archiver = getJarArchiver();
125 archiver.setDestFile(jarFile.toFile());
126
127 SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
128 long parsedTime = isoFormat.parse("2038-01-19T03:14:08Z").getTime();
129 FileTime lastModTime = FileTime.fromMillis(parsedTime);
130
131 archiver.configureReproducibleBuild(lastModTime);
132
133 archiver.addConfiguredManifest(manifest);
134 archiver.addDirectory(new File("src/test/resources/java-classes"));
135
136 archiver.createArchive();
137
138
139 long expectedTime = normalizeLastModifiedTime(parsedTime - (parsedTime % 2000));
140 try (ZipFile zip = new ZipFile(jarFile.toFile())) {
141 Enumeration<? extends ZipEntry> entries = zip.entries();
142 while (entries.hasMoreElements()) {
143 ZipEntry entry = entries.nextElement();
144 long time = entry.getTime();
145 assertEquals(expectedTime, time, "last modification time does not match");
146 }
147 }
148 } finally {
149 TimeZone.setDefault(defaultTz);
150 }
151 }
152
153
154
155
156
157
158
159 @Test
160 void reproducibleUmask() throws Exception {
161 Path jarFile = Files.createTempFile(tempDir, "JarArchiverTest-umask", ".jar");
162
163 JarArchiver archiver = getJarArchiver();
164 archiver.setDestFile(jarFile.toFile());
165
166 SimpleDateFormat isoFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");
167 long parsedTime = isoFormat.parse("2038-01-19T03:14:08Z").getTime();
168 FileTime lastModTime = FileTime.fromMillis(parsedTime);
169
170 archiver.configureReproducibleBuild(lastModTime);
171
172 archiver.addDirectory(new File("src/test/resources/java-classes"));
173 archiver.addFile(new File("src/test/resources/world-writable/foo.txt"), "addFile.txt");
174
175 archiver.createArchive();
176
177 try (org.apache.commons.compress.archivers.zip.ZipFile zip =
178 org.apache.commons.compress.archivers.zip.ZipFile.builder()
179 .setFile(jarFile.toFile())
180 .get()) {
181 Enumeration<? extends ZipArchiveEntry> entries = zip.getEntries();
182 while (entries.hasMoreElements()) {
183 ZipArchiveEntry entry = entries.nextElement();
184 int mode = entry.getUnixMode();
185 assertEquals(
186 0,
187 mode & 0_020,
188 entry.getName() + " group should not be writable in reproducible mode: "
189 + Integer.toOctalString(mode));
190 }
191 }
192 }
193
194 @Override
195 protected JarArchiver getJarArchiver() {
196 return new JarArchiver();
197 }
198 }