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