View Javadoc
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          // Generate some number of random files that is likely to be
60          // two or three times the number of available file handles
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]; // 512bytes per file
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          // Clean up
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             // Every single run with different Time Zone should set the same modification time.
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             // zip 2 seconds precision, normalized to UTC
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      * Check group not writable for reproducible archive.
155      *
156      * @throws IOException
157      * @throws ParseException
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 }