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