View Javadoc
1   /*
2    * The MIT License
3    *
4    * Copyright (c) 2004, The Codehaus
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy of
7    * this software and associated documentation files (the "Software"), to deal in
8    * the Software without restriction, including without limitation the rights to
9    * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10   * of the Software, and to permit persons to whom the Software is furnished to do
11   * so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  package org.codehaus.plexus.archiver.zip;
25  
26  import javax.annotation.Nonnull;
27  
28  import java.io.ByteArrayInputStream;
29  import java.io.ByteArrayOutputStream;
30  import java.io.File;
31  import java.io.IOException;
32  import java.io.InputStream;
33  import java.io.Writer;
34  import java.nio.charset.StandardCharsets;
35  import java.nio.file.Files;
36  import java.nio.file.Path;
37  import java.nio.file.attribute.FileTime;
38  import java.text.DateFormat;
39  import java.text.ParseException;
40  import java.text.SimpleDateFormat;
41  import java.util.Arrays;
42  import java.util.Date;
43  import java.util.Enumeration;
44  import java.util.Map;
45  import java.util.TimeZone;
46  import java.util.zip.ZipEntry;
47  import java.util.zip.ZipInputStream;
48  import java.util.zip.ZipOutputStream;
49  
50  import org.apache.commons.compress.archivers.zip.ExtraFieldUtils;
51  import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
52  import org.apache.commons.compress.archivers.zip.ZipExtraField;
53  import org.apache.commons.compress.archivers.zip.ZipFile;
54  import org.apache.commons.compress.utils.BoundedInputStream;
55  import org.codehaus.plexus.archiver.Archiver;
56  import org.codehaus.plexus.archiver.ArchiverException;
57  import org.codehaus.plexus.archiver.BasePlexusArchiverTest;
58  import org.codehaus.plexus.archiver.UnArchiver;
59  import org.codehaus.plexus.archiver.UnixStat;
60  import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
61  import org.codehaus.plexus.archiver.tar.TarArchiver;
62  import org.codehaus.plexus.archiver.tar.TarFile;
63  import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
64  import org.codehaus.plexus.archiver.util.DefaultArchivedFileSet;
65  import org.codehaus.plexus.archiver.util.DefaultFileSet;
66  import org.codehaus.plexus.archiver.util.Streams;
67  import org.codehaus.plexus.components.io.attributes.FileAttributes;
68  import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributeUtils;
69  import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
70  import org.codehaus.plexus.components.io.attributes.SimpleResourceAttributes;
71  import org.codehaus.plexus.components.io.filemappers.FileMapper;
72  import org.codehaus.plexus.components.io.filemappers.PrefixFileMapper;
73  import org.codehaus.plexus.components.io.functions.InputStreamTransformer;
74  import org.codehaus.plexus.components.io.resources.PlexusIoFileResourceCollection;
75  import org.codehaus.plexus.components.io.resources.PlexusIoResource;
76  import org.codehaus.plexus.components.io.resources.ResourceFactory;
77  import org.codehaus.plexus.util.FileUtils;
78  import org.codehaus.plexus.util.IOUtil;
79  import org.junit.jupiter.api.Disabled;
80  import org.junit.jupiter.api.Test;
81  import org.junit.jupiter.api.condition.DisabledOnOs;
82  import org.junit.jupiter.api.condition.OS;
83  import org.junit.jupiter.api.io.TempDir;
84  
85  import static org.junit.jupiter.api.Assertions.assertEquals;
86  import static org.junit.jupiter.api.Assertions.assertFalse;
87  import static org.junit.jupiter.api.Assertions.assertNotNull;
88  import static org.junit.jupiter.api.Assertions.assertThrows;
89  import static org.junit.jupiter.api.Assertions.assertTrue;
90  import static org.junit.jupiter.api.Assertions.fail;
91  
92  /**
93   * @author Emmanuel Venisse
94   */
95  @SuppressWarnings("OctalInteger")
96  class ZipArchiverTest extends BasePlexusArchiverTest {
97  
98      @TempDir
99      private File tempDir;
100 
101     @Test
102     void testImplicitPermissions() throws IOException {
103         File zipFile = getTestFile("target/output/zip-with-implicit-dirmode.zip");
104 
105         ZipArchiver archiver = getZipArchiver(zipFile);
106 
107         archiver.setDefaultDirectoryMode(0777);
108         archiver.setDirectoryMode(0641);
109         archiver.setFileMode(0222);
110         archiver.addFile(new File("pom.xml"), "fizz/buzz/pom.xml");
111         archiver.setDefaultDirectoryMode(0530);
112         archiver.setDirectoryMode(-1); // Not forced mode
113         archiver.setFileMode(0111);
114         archiver.addFile(new File("pom.xml"), "fazz/bazz/pam.xml");
115         archiver.createArchive();
116 
117         assertTrue(zipFile.exists());
118         ZipFile zf = new ZipFile(zipFile);
119         ZipArchiveEntry fizz = zf.getEntry("fizz/");
120         assertEquals(040641, fizz.getUnixMode());
121         ZipArchiveEntry pom = zf.getEntry("fizz/buzz/pom.xml");
122         assertEquals(0100222, pom.getUnixMode());
123 
124         ZipArchiveEntry fazz = zf.getEntry("fazz/");
125         assertEquals(040530, fazz.getUnixMode());
126         ZipArchiveEntry pam = zf.getEntry("fazz/bazz/pam.xml");
127         assertEquals(0100111, pam.getUnixMode());
128     }
129 
130     @Test
131     @DisabledOnOs(OS.WINDOWS)
132     void testOveriddenPermissions() throws IOException {
133         File zipFile = getTestFile("target/output/zip-with-overriden-modes.zip");
134 
135         ZipArchiver archiver = getZipArchiver(zipFile);
136         archiver.setDefaultDirectoryMode(0777);
137         archiver.setDirectoryMode(0641);
138         archiver.setFileMode(0777);
139         archiver.addDirectory(new File("src/test/resources/symlinks/src"));
140         archiver.createArchive();
141 
142         assertTrue(zipFile.exists());
143         ZipFile zf = new ZipFile(zipFile);
144         ZipArchiveEntry fizz = zf.getEntry("symDir");
145         assertTrue(fizz.isUnixSymlink());
146         ZipArchiveEntry symR = zf.getEntry("symR");
147         assertTrue(symR.isUnixSymlink());
148     }
149 
150     @Test
151     @DisabledOnOs(OS.WINDOWS)
152     void testCreateArchiveWithDetectedModes() throws Exception {
153 
154         String[] executablePaths = {"path/to/executable", "path/to/executable.bat"};
155 
156         String[] confPaths = {"path/to/etc/file", "path/to/etc/file2"};
157 
158         String[] logPaths = {"path/to/logs/log.txt"};
159 
160         int exeMode = 0777;
161         int confMode = 0600;
162         int logMode = 0640;
163 
164         for (String executablePath : executablePaths) {
165             writeFile(tempDir, executablePath, exeMode);
166         }
167 
168         for (String confPath : confPaths) {
169             writeFile(tempDir, confPath, confMode);
170         }
171 
172         for (String logPath : logPaths) {
173             writeFile(tempDir, logPath, logMode);
174         }
175 
176         {
177             Map<String, PlexusIoResourceAttributes> attributesByPath =
178                     PlexusIoResourceAttributeUtils.getFileAttributesByPath(tempDir);
179             for (String path : executablePaths) {
180                 PlexusIoResourceAttributes attrs = attributesByPath.get(path);
181                 if (attrs == null) {
182                     attrs = attributesByPath.get(new File(tempDir, path).getAbsolutePath());
183                 }
184 
185                 assertNotNull(attrs);
186                 assertEquals(exeMode, attrs.getOctalMode(), "Wrong mode for: " + path);
187             }
188 
189             for (String path : confPaths) {
190                 PlexusIoResourceAttributes attrs = attributesByPath.get(path);
191                 if (attrs == null) {
192                     attrs = attributesByPath.get(new File(tempDir, path).getAbsolutePath());
193                 }
194 
195                 assertNotNull(attrs);
196                 assertEquals(confMode, attrs.getOctalMode(), "Wrong mode for: " + path);
197             }
198 
199             for (String path : logPaths) {
200                 PlexusIoResourceAttributes attrs = attributesByPath.get(path);
201                 if (attrs == null) {
202                     attrs = attributesByPath.get(new File(tempDir, path).getAbsolutePath());
203                 }
204 
205                 assertNotNull(attrs);
206                 assertEquals(logMode, attrs.getOctalMode(), "Wrong mode for: " + path);
207             }
208         }
209 
210         File zipFile = getTestFile("target/output/zip-with-modes.zip");
211 
212         ZipArchiver archiver = getZipArchiver(zipFile);
213 
214         archiver.addDirectory(tempDir);
215         archiver.createArchive();
216 
217         assertTrue(zipFile.exists());
218 
219         File zipFile2 = getTestFile("target/output/zip-with-modes-L2.zip");
220 
221         archiver = getZipArchiver();
222         archiver.setDestFile(zipFile2);
223 
224         archiver.addArchivedFileSet(zipFile);
225         archiver.createArchive();
226 
227         ZipFile zf = new ZipFile(zipFile2);
228 
229         for (String path : executablePaths) {
230             ZipArchiveEntry ze = zf.getEntry(path);
231 
232             int mode = ze.getUnixMode() & UnixStat.PERM_MASK;
233 
234             assertEquals(exeMode, mode, "Wrong mode for: " + path);
235         }
236 
237         for (String path : confPaths) {
238             ZipArchiveEntry ze = zf.getEntry(path);
239 
240             int mode = ze.getUnixMode() & UnixStat.PERM_MASK;
241 
242             assertEquals(confMode, mode, "Wrong mode for: " + path);
243         }
244 
245         for (String path : logPaths) {
246             ZipArchiveEntry ze = zf.getEntry(path);
247 
248             int mode = ze.getUnixMode() & UnixStat.PERM_MASK;
249 
250             assertEquals(logMode, mode, "Wrong mode for: " + path);
251         }
252     }
253 
254     @Test
255     void testCreateEmptyArchive() throws Exception {
256         ZipArchiver archiver = getZipArchiver();
257         archiver.setDestFile(getTestFile("target/output/empty.zip"));
258         try {
259             archiver.createArchive();
260 
261             fail("Creating empty archive should throw EmptyArchiveException");
262         } catch (EmptyArchiveException ignore) {
263         }
264     }
265 
266     private ZipArchiver getZipArchiver() {
267         try {
268             return (ZipArchiver) lookup(Archiver.class, "zip");
269         } catch (Exception e) {
270             throw new RuntimeException(e);
271         }
272     }
273 
274     private ZipArchiver getZipArchiver(File destFile) {
275         final ZipArchiver zipArchiver = getZipArchiver();
276         zipArchiver.setDestFile(destFile);
277         return zipArchiver;
278     }
279 
280     private ZipUnArchiver getZipUnArchiver(File testJar) throws Exception {
281         ZipUnArchiver zu = (ZipUnArchiver) lookup(UnArchiver.class, "zip");
282         zu.setSourceFile(testJar);
283         return zu;
284     }
285 
286     private void writeFile(File dir, String fname, int mode) throws IOException, ArchiverException {
287         File file = new File(dir, fname);
288 
289         if (file.getParentFile() != null) {
290             file.getParentFile().mkdirs();
291         }
292 
293         try (Writer writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
294             writer.write("This is a test file.");
295         }
296 
297         ArchiveEntryUtils.chmod(file, mode);
298     }
299 
300     @Test
301     void testCreateArchive() throws Exception {
302         ZipArchiver archiver = newArchiver("archive1.zip");
303 
304         createArchive(archiver);
305     }
306 
307     @Test
308     void testRecompressAddedZips() throws Exception {
309         // check that by default the zip archives are re-compressed
310 
311         final File zipFileRecompress = getTestFile("target/output/recompress-added-zips.zip");
312         final ZipArchiver zipArchiverRecompress = getZipArchiver(zipFileRecompress);
313         zipArchiverRecompress.addDirectory(getTestFile("src/test/jars"));
314         FileUtils.removePath(zipFileRecompress.getPath());
315         zipArchiverRecompress.createArchive();
316 
317         final ZipFile zfRecompress = new ZipFile(zipFileRecompress);
318         assertEquals(ZipEntry.DEFLATED, zfRecompress.getEntry("test.zip").getMethod());
319         assertEquals(ZipEntry.DEFLATED, zfRecompress.getEntry("test.jar").getMethod());
320         assertEquals(ZipEntry.DEFLATED, zfRecompress.getEntry("test.rar").getMethod());
321         assertEquals(ZipEntry.DEFLATED, zfRecompress.getEntry("test.tar.gz").getMethod());
322         zfRecompress.close();
323 
324         // make sure the zip files are not re-compressed when recompressAddedZips is set to false
325 
326         final File zipFileDontRecompress = getTestFile("target/output/dont-recompress-added-zips.zip");
327         ZipArchiver zipArchiver = getZipArchiver(zipFileDontRecompress);
328         zipArchiver.addDirectory(getTestFile("src/test/jars"));
329         zipArchiver.setRecompressAddedZips(false);
330         FileUtils.removePath(zipFileDontRecompress.getPath());
331         zipArchiver.createArchive();
332 
333         final ZipFile zfDontRecompress = new ZipFile(zipFileDontRecompress);
334         final ZipArchiveEntry zipEntry = zfDontRecompress.getEntry("test.zip");
335         final ZipArchiveEntry jarEntry = zfDontRecompress.getEntry("test.jar");
336         final ZipArchiveEntry rarEntry = zfDontRecompress.getEntry("test.rar");
337         final ZipArchiveEntry tarEntry = zfDontRecompress.getEntry("test.tar.gz");
338         // check if only zip files are not compressed...
339         assertEquals(ZipEntry.STORED, zipEntry.getMethod());
340         assertEquals(ZipEntry.STORED, jarEntry.getMethod());
341         assertEquals(ZipEntry.STORED, rarEntry.getMethod());
342         assertEquals(ZipEntry.DEFLATED, tarEntry.getMethod());
343         // ...and no file is corrupted in the process
344         assertTrue(IOUtil.contentEquals(
345                 Files.newInputStream(getTestFile("src/test/jars/test.zip").toPath()),
346                 zfDontRecompress.getInputStream(zipEntry)));
347         assertTrue(IOUtil.contentEquals(
348                 Files.newInputStream(getTestFile("src/test/jars/test.jar").toPath()),
349                 zfDontRecompress.getInputStream(jarEntry)));
350         assertTrue(IOUtil.contentEquals(
351                 Files.newInputStream(getTestFile("src/test/jars/test.rar").toPath()),
352                 zfDontRecompress.getInputStream(rarEntry)));
353         assertTrue(IOUtil.contentEquals(
354                 Files.newInputStream(getTestFile("src/test/jars/test.tar.gz").toPath()),
355                 zfDontRecompress.getInputStream(tarEntry)));
356         zfDontRecompress.close();
357     }
358 
359     @Test
360     void testAddArchivedFileSet() throws Exception {
361         File toBeAdded = new File("src/test/resources/test.zip");
362         DefaultArchivedFileSet sfd = DefaultArchivedFileSet.archivedFileSet(toBeAdded);
363         File zipFIle = getTestFile("target/output/withZip.zip");
364         final ZipArchiver zipArchiver = getZipArchiver(zipFIle);
365         InputStreamTransformer is = new InputStreamTransformer() {
366 
367             @Nonnull
368             public InputStream transform(@Nonnull PlexusIoResource resource, @Nonnull InputStream inputStream)
369                     throws IOException {
370                 return new BoundedInputStream(inputStream, 3);
371             }
372         };
373         sfd.setStreamTransformer(is);
374         PrefixFileMapper mapper = new PrefixFileMapper();
375         mapper.setPrefix("prefix");
376         sfd.setFileMappers(new FileMapper[] {mapper});
377         zipArchiver.addArchivedFileSet(sfd);
378         zipArchiver.createArchive();
379 
380         final ZipUnArchiver zipUnArchiver = getZipUnArchiver(zipFIle);
381         File destFile = new File("target/output/withZip");
382         destFile.mkdirs();
383         zipUnArchiver.setDestFile(destFile);
384         zipUnArchiver.extract();
385         File a3byteFile = new File(
386                 destFile,
387                 "prefixUsers/kristian/lsrc/plexus/plexus-archiver/src/main/java/org/codehaus/plexus/archiver/zip/ZipArchiver.java");
388         assertTrue(a3byteFile.exists());
389         assertTrue(a3byteFile.length() == 3);
390     }
391 
392     @Test
393     void testCreateArchiveWithStreamTransformer() throws IOException {
394         InputStreamTransformer is = new InputStreamTransformer() {
395 
396             @Nonnull
397             @Override
398             public InputStream transform(@Nonnull PlexusIoResource resource, @Nonnull InputStream inputStream)
399                     throws IOException {
400                 return new BoundedInputStream(inputStream, 3);
401             }
402         };
403 
404         final ZipArchiver zipArchiver = getZipArchiver(getTestFile("target/output/all3bytes.zip"));
405         File zipFIle = new File("src/test/resources/test.zip");
406         DefaultArchivedFileSet afs = new DefaultArchivedFileSet(zipFIle);
407         afs.setStreamTransformer(is);
408         afs.setPrefix("azip/");
409         zipArchiver.addArchivedFileSet(afs);
410 
411         DefaultFileSet dfs = new DefaultFileSet(new File("src/test/resources/mjar179"));
412         dfs.setStreamTransformer(is);
413         dfs.setPrefix("mj179/");
414         zipArchiver.addFileSet(dfs);
415 
416         PlexusIoFileResourceCollection files = new PlexusIoFileResourceCollection();
417         files.setBaseDir(new File("src/test/resources"));
418         files.setStreamTransformer(is);
419         files.setPrefix("plexus/");
420         zipArchiver.addResources(files);
421 
422         zipArchiver.createArchive();
423     }
424 
425     private ZipArchiver newArchiver(String name) throws Exception {
426         ZipArchiver archiver = getZipArchiver(getTestFile("target/output/" + name));
427 
428         archiver.setFileMode(0640);
429         archiver.addFile(getTestFile("src/test/resources/manifests/manifest1.mf"), "one.txt");
430         archiver.addFile(getTestFile("src/test/resources/manifests/manifest2.mf"), "two.txt", 0664);
431 
432         // reset default file mode for files included from now on
433         archiver.setFileMode(0400);
434         archiver.setDirectoryMode(0777);
435         archiver.addDirectory(getTestFile("src/test/resources/world-writable/"), "worldwritable/");
436 
437         archiver.setDirectoryMode(0070);
438         archiver.addDirectory(getTestFile("src/test/resources/group-writable/"), "groupwritable/");
439 
440         archiver.setDirectoryMode(0500);
441         archiver.setFileMode(0400);
442         archiver.addDirectory(getTestFile("src"));
443 
444         return archiver;
445     }
446 
447     private void fileModeAssert(int expected, int actual) {
448         assertEquals(Integer.toString(expected, 8), Integer.toString(actual, 8));
449     }
450 
451     private void createArchive(ZipArchiver archiver) throws ArchiverException, IOException {
452         archiver.createArchive();
453 
454         ZipFile zf = new ZipFile(archiver.getDestFile());
455 
456         Enumeration e = zf.getEntries();
457 
458         while (e.hasMoreElements()) {
459             ZipArchiveEntry ze = (ZipArchiveEntry) e.nextElement();
460             if (ze.isDirectory()) {
461                 if (ze.getName().startsWith("worldwritable")) {
462                     fileModeAssert(0777, UnixStat.PERM_MASK & ze.getUnixMode());
463                 } else if (ze.getName().startsWith("groupwritable")) {
464                     fileModeAssert(0070, UnixStat.PERM_MASK & ze.getUnixMode());
465                 } else {
466                     fileModeAssert(0500, UnixStat.PERM_MASK & ze.getUnixMode());
467                 }
468             } else {
469                 if (ze.getName().equals("one.txt")) {
470                     fileModeAssert(0640, UnixStat.PERM_MASK & ze.getUnixMode());
471                 } else if (ze.getName().equals("two.txt")) {
472                     fileModeAssert(0664, UnixStat.PERM_MASK & ze.getUnixMode());
473                 } else if (ze.isUnixSymlink()) {
474                     //         assertEquals( ze.getName(), 0500, UnixStat.PERM_MASK & ze.getUnixMode() );
475                 } else {
476                     fileModeAssert(0400, UnixStat.PERM_MASK & ze.getUnixMode());
477                 }
478             }
479         }
480     }
481 
482     @Test
483     @DisabledOnOs(OS.WINDOWS)
484     void testSymlinkZip() throws Exception {
485         final File zipFile = getTestFile("target/output/pasymlinks.zip");
486         final ZipArchiver zipArchiver = getZipArchiver(zipFile);
487         PlexusIoFileResourceCollection files = new PlexusIoFileResourceCollection();
488         files.setFollowingSymLinks(false);
489         files.setBaseDir(new File("src/test/resources/symlinks"));
490         files.setPrefix("plexus/");
491         zipArchiver.addResources(files);
492         zipArchiver.createArchive();
493         final File output = getTestFile("target/output/unzipped");
494         output.mkdirs();
495         final ZipUnArchiver zipUnArchiver = getZipUnArchiver(zipFile);
496         zipUnArchiver.setDestFile(output);
497         zipUnArchiver.extract();
498         File symDir = new File("target/output/unzipped/plexus/src/symDir");
499         PlexusIoResourceAttributes fa = FileAttributes.uncached(symDir);
500         assertTrue(fa.isSymbolicLink());
501     }
502 
503     @Test
504     @DisabledOnOs(OS.WINDOWS)
505     @SuppressWarnings("ResultOfMethodCallIgnored")
506     void testSymlinkFileSet() throws Exception {
507         final File zipFile = getTestFile("target/output/pasymlinks-fileset.zip");
508         final ZipArchiver zipArchiver = getZipArchiver(zipFile);
509         final DefaultFileSet fs = new DefaultFileSet();
510         fs.setPrefix("bzz/");
511         fs.setDirectory(new File("src/test/resources/symlinks/src"));
512         zipArchiver.addFileSet(fs);
513         zipArchiver.createArchive();
514         final File output = getTestFile("target/output/unzipped/symlFs");
515         output.mkdirs();
516         final ZipUnArchiver zipUnArchiver = getZipUnArchiver(zipFile);
517         zipUnArchiver.setDestFile(output);
518         zipUnArchiver.extract();
519         File symDir = new File(output, "bzz/symDir");
520         PlexusIoResourceAttributes fa = FileAttributes.uncached(symDir);
521         assertTrue(fa.isSymbolicLink());
522     }
523 
524     @Test
525     void testSymlinkArchivedFileSet() throws Exception {
526         final File zipFile = getTestFile("src/test/resources/symlinks/symlinks.zip");
527         final File zipFile2 = getTestFile("target/output/pasymlinks-archivedFileset.zip");
528         final ZipArchiver zipArchiver = getZipArchiver(zipFile2);
529         zipArchiver.addArchivedFileSet(zipFile);
530         zipArchiver.createArchive();
531 
532         final ZipFile cmp1 = new ZipFile(zipFile);
533         final ZipFile cmp2 = new ZipFile(zipFile2);
534         ArchiveFileComparator.assertZipEquals(cmp1, cmp2, "");
535     }
536 
537     /*
538      * Zip archives store file modification times with a granularity of two seconds.
539      * Verify that ZipArchiver rounds up the last modified time.
540      */
541     @Test
542     void testLastModifiedTimeRounding() throws Exception {
543         Path oddSecondsTimestampFile = Files.createTempFile(tempDir.toPath(), "odd-seconds-timestamp", null);
544         // The milliseconds part is set to zero as not all filesystem support timestamp more granular than second.
545         Files.setLastModifiedTime(oddSecondsTimestampFile, FileTime.fromMillis(1534189011_000L));
546         Path evenSecondsTimestampFile = Files.createTempFile(tempDir.toPath(), "even-seconds-timestamp", null);
547         Files.setLastModifiedTime(evenSecondsTimestampFile, FileTime.fromMillis(1534189012_000L));
548 
549         File destFile = getTestFile("target/output/last-modified-time.zip");
550         ZipArchiver archiver = getZipArchiver(destFile);
551         archiver.addFile(oddSecondsTimestampFile.toFile(), "odd-seconds");
552         archiver.addFile(evenSecondsTimestampFile.toFile(), "even-seconds");
553         archiver.createArchive();
554 
555         // verify that the last modified time of the entry is equal or newer than the original file
556         try (ZipFile resultingZipFile = new ZipFile(destFile)) {
557             assertEquals(
558                     1534189012_000L, resultingZipFile.getEntry("odd-seconds").getTime());
559             assertEquals(
560                     1534189012_000L, resultingZipFile.getEntry("even-seconds").getTime());
561 
562             FileTime expected = FileTime.fromMillis(1534189012_000L);
563             assertEquals(expected, resultingZipFile.getEntry("odd-seconds").getLastModifiedTime());
564             assertEquals(expected, resultingZipFile.getEntry("even-seconds").getLastModifiedTime());
565         }
566     }
567 
568     /*
569      */
570     @Test
571     void testForced() throws Exception {
572         ZipArchiver archiver = newArchiver("archive2.zip");
573 
574         assertTrue(archiver.isForced());
575         File f = archiver.getDestFile();
576         if (f.exists()) {
577             FileUtils.fileDelete(f.getPath());
578         }
579         assertFalse(f.exists());
580         createArchive(archiver);
581         long l1 = f.lastModified();
582         assertTrue(f.exists());
583 
584         archiver = newArchiver("archive2.zip");
585         waitUntilNewTimestamp(archiver.getDestFile(), l1);
586         createArchive(archiver);
587         long l2 = f.lastModified();
588         assertTrue(f.exists());
589         assertTrue(l2 > l1);
590 
591         archiver = newArchiver("archive2.zip");
592         assertTrue(archiver.isSupportingForced());
593         archiver.setForced(false);
594         assertFalse(archiver.isForced());
595 
596         createArchive(archiver);
597         long l3 = f.lastModified();
598         assertTrue(f.exists());
599         assertEquals(l2, l3);
600     }
601 
602     // Used to investigate extrafields
603     @Test
604     void testLookAtExtraZipFields_from_macos() throws IOException {
605         InputStream fis = Streams.fileInputStream(new File("src/test/resources/zip-timestamp/macOsZipFile.zip"));
606         ZipInputStream zis = new ZipInputStream(fis);
607         final java.util.zip.ZipEntry evenEntry = zis.getNextEntry();
608         final ZipExtraField[] parse = ExtraFieldUtils.parse(evenEntry.getExtra());
609         System.out.println(Arrays.asList(parse));
610         final java.util.zip.ZipEntry oddEntry = zis.getNextEntry();
611 
612         System.out.println(Arrays.asList(ExtraFieldUtils.parse(oddEntry.getExtra())));
613 
614         System.out.println("oddEntry.getTime() = " + new Date(oddEntry.getTime()).toString());
615         System.out.println("oddEntry.getName() = " + oddEntry.getName());
616         System.out.println("new String(oddEntry.getExtra()) = " + new String(oddEntry.getExtra()));
617         System.out.println("evenEntry.getName() = " + evenEntry.getName());
618         System.out.println("evenEntry.getTime() = " + new Date(evenEntry.getTime()).toString());
619         System.out.println("new String(evenEntry.getExtra()) = " + new String(evenEntry.getExtra()));
620     }
621 
622     // Used to investigate date roundtrip behaviour across zip versions
623     @Test
624     void testZipStuff() throws IOException {
625         ByteArrayOutputStream baos = new ByteArrayOutputStream();
626         ZipOutputStream zos = new ZipOutputStream(baos);
627         // name the file inside the zip  file
628         final File oddFile = new File("src/test/resources/zip-timestamp/file-with-odd-time.txt");
629         final File evenFile = new File("src/test/resources/zip-timestamp/file-with-even-time.txt");
630         final ZipEntry oddZe = new ZipEntry(oddFile.getName());
631         oddZe.setTime(oddFile.lastModified());
632         zos.putNextEntry(oddZe);
633         final ZipEntry evenZe = new ZipEntry(evenFile.getName());
634         evenZe.setTime(evenFile.lastModified());
635         zos.putNextEntry(evenZe);
636         zos.close();
637 
638         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
639         ZipInputStream zipInputStream = new ZipInputStream(bais);
640         final java.util.zip.ZipEntry oddEntry = zipInputStream.getNextEntry();
641         System.out.println("oddEntry.getTime() = " + new Date(oddEntry.getTime()).toString());
642         System.out.println("oddEntry.getName() = " + oddEntry.getName());
643         final java.util.zip.ZipEntry evenEntry = zipInputStream.getNextEntry();
644         System.out.println("evenEntry.getName() = " + evenEntry.getName());
645         System.out.println("evenEntry.getTime() = " + new Date(evenEntry.getTime()).toString());
646     }
647 
648     @Disabled("Junit3 method name is notest")
649     @Test
650     void notestJustThatOne() throws Exception {
651         final File srcDir = new File("src");
652         String[] inc = {"test/java/org/codehaus/plexus/archiver/zip/ZipShortTest.java"};
653         final File zipFile = new File("target/output/zz1.zip");
654 
655         final File zipFile2 = new File("target/output/zz2.zip");
656         ZipArchiver zipArchiver2 = getZipArchiver(zipFile2);
657 
658         // Bugbug: This does not work on 1.8....?
659         zipArchiver2.addArchivedFileSet(zipFile);
660         FileUtils.removePath(zipFile2.getPath());
661         zipArchiver2.createArchive();
662     }
663 
664     @Test
665     void testCreateResourceCollection() throws Exception {
666         final File srcDir = new File("src");
667         final File zipFile = new File("target/output/src.zip");
668         ZipArchiver zipArchiver = getZipArchiver(zipFile);
669         zipArchiver.addDirectory(srcDir, null, FileUtils.getDefaultExcludes());
670         zipArchiver.setEncoding("UTF-8");
671         FileUtils.removePath(zipFile.getPath());
672         zipArchiver.createArchive();
673 
674         final File zipFile2 = new File("target/output/src2.zip");
675         ZipArchiver zipArchiver2 = getZipArchiver(zipFile2);
676         zipArchiver2.addArchivedFileSet(zipFile, "prfx/");
677         zipArchiver2.setEncoding("UTF-8");
678         FileUtils.removePath(zipFile2.getPath());
679         zipArchiver2.createArchive();
680 
681         final ZipFile cmp1 = new ZipFile(zipFile);
682         final ZipFile cmp2 = new ZipFile(zipFile2);
683         ArchiveFileComparator.assertZipEquals(cmp1, cmp2, "prfx/");
684         cmp1.close();
685         cmp2.close();
686     }
687 
688     @Test
689     void testZipNonConcurrentResourceCollection() throws Exception {
690         final File tarFile = getTestFile("target/output/zip-non-concurrent.tar");
691         TarArchiver tarArchiver = (TarArchiver) lookup(Archiver.class, "tar");
692         // Override uId and gId - in standard mode on OS where uId or gId can have long values creation of tar can fail
693         // We can not use posix mode because mTime can have nanoseconds in posix but zip doesn't so assertions can fail
694         tarArchiver.setOverrideUid(100);
695         tarArchiver.setOverrideGid(100);
696         tarArchiver.setDestFile(tarFile);
697         // We're testing concurrency issue so we need large amount of files
698         for (int i = 0; i < 100; i++) {
699             tarArchiver.addFile(getTestFile("src/test/resources/manifests/manifest1.mf"), "manifest1.mf" + i);
700             // Directories are added separately so let's test them too
701             tarArchiver.addFile(
702                     getTestFile("src/test/resources/manifests/manifest2.mf"), "subdir" + i + "/manifest2.mf");
703         }
704         tarArchiver.createArchive();
705 
706         final File zipFile = new File("target/output/zip-non-concurrent.zip");
707         ZipArchiver zipArchive = getZipArchiver(zipFile);
708         zipArchive.addArchivedFileSet(tarFile, "prfx/");
709         zipArchive.setEncoding("UTF-8");
710         zipArchive.createArchive();
711 
712         final TarFile cmp1 = new TarFile(tarFile);
713         final ZipFile cmp2 = new ZipFile(zipFile);
714         ArchiveFileComparator.assertTarZipEquals(cmp1, cmp2, "prfx/");
715         cmp1.close();
716         cmp2.close();
717     }
718 
719     @Test
720     void testDefaultUTF8() throws IOException {
721         final ZipArchiver zipArchiver = getZipArchiver(new File("target/output/utf8-default.zip"));
722         zipArchiver.addDirectory(new File("src/test/resources/miscUtf8"));
723         zipArchiver.createArchive();
724     }
725 
726     @Test
727     void testDefaultUTF8withUTF8() throws IOException {
728         final ZipArchiver zipArchiver = getZipArchiver(new File("target/output/utf8-with_utf.zip"));
729         zipArchiver.setEncoding("UTF-8");
730         zipArchiver.addDirectory(new File("src/test/resources/miscUtf8"));
731         zipArchiver.createArchive();
732     }
733 
734     @Test
735     void testForcedFileModes() throws IOException {
736         File step1file = new File("target/output/forced-file-mode.zip");
737         {
738             final ZipArchiver zipArchiver = getZipArchiver(step1file);
739             zipArchiver.setFileMode(0077);
740             zipArchiver.setDirectoryMode(0007);
741             PlexusIoResourceAttributes attrs = new SimpleResourceAttributes(123, "fred", 22, "filntstones", 0111);
742             PlexusIoResource resource = ResourceFactory.createResource(
743                     new File("src/test/resources/folders/File.txt"), "Test.txt", null, attrs);
744             zipArchiver.addResource(resource, "Test2.txt", 0707);
745             PlexusIoFileResourceCollection files = new PlexusIoFileResourceCollection();
746             files.setBaseDir(new File("src/test/resources/folders"));
747             files.setPrefix("sixsixsix/");
748             zipArchiver.addResources(files);
749 
750             zipArchiver.createArchive();
751 
752             ZipFile zf = new ZipFile(step1file);
753             fileModeAssert(040007, zf.getEntry("sixsixsix/a/").getUnixMode());
754             fileModeAssert(0100077, zf.getEntry("sixsixsix/b/FileInB.txt").getUnixMode());
755             fileModeAssert(0100707, zf.getEntry("Test2.txt").getUnixMode());
756             zf.close();
757         }
758 
759         File Step2file = new File("target/output/forced-file-mode-from-zip.zip");
760         {
761             final ZipArchiver za2 = getZipArchiver(Step2file);
762             za2.setFileMode(0666);
763             za2.setDirectoryMode(0676);
764 
765             PlexusIoZipFileResourceCollection zipSrc = new PlexusIoZipFileResourceCollection();
766             zipSrc.setFile(step1file);
767             zipSrc.setPrefix("zz/");
768             za2.addResources(zipSrc);
769             za2.createArchive();
770             ZipFile zf = new ZipFile(Step2file);
771             fileModeAssert(040676, zf.getEntry("zz/sixsixsix/a/").getUnixMode());
772             fileModeAssert(0100666, zf.getEntry("zz/Test2.txt").getUnixMode());
773             zf.close();
774         }
775 
776         File step3file = new File("target/output/forced-file-mode-from-zip2.zip");
777         {
778             final ZipArchiver za2 = getZipArchiver(step3file);
779             za2.setFileMode(0666);
780             za2.setDirectoryMode(0676);
781 
782             PlexusArchiverZipFileResourceCollection zipSrc = new PlexusArchiverZipFileResourceCollection();
783             zipSrc.setFile(step1file);
784             zipSrc.setPrefix("zz/");
785             za2.addResources(zipSrc);
786             za2.createArchive();
787             ZipFile zf = new ZipFile(Step2file);
788             fileModeAssert(040676, zf.getEntry("zz/sixsixsix/a/").getUnixMode());
789             fileModeAssert(0100666, zf.getEntry("zz/Test2.txt").getUnixMode());
790             zf.close();
791         }
792     }
793 
794     @Test
795     void testFixedEntryModificationTime() throws IOException {
796         final long almostMinDosTime = toLocalTimeZone(315532802000L);
797 
798         final File zipFile = getTestFile("target/output/zip-with-fixed-entry-modification-times.zip");
799         final ZipArchiver archiver = getZipArchiver(zipFile);
800         archiver.setLastModifiedTime(FileTime.fromMillis(almostMinDosTime));
801         archiver.addDirectory(new File("src/test/resources/zip-timestamp"));
802         archiver.createArchive();
803 
804         assertTrue(zipFile.exists());
805         try (final ZipFile zf = new ZipFile(zipFile)) {
806             assertEquals(
807                     almostMinDosTime, zf.getEntry("file-with-even-time.txt").getTime());
808             assertEquals(almostMinDosTime, zf.getEntry("file-with-odd-time.txt").getTime());
809             assertEquals(almostMinDosTime, zf.getEntry("foo/").getTime());
810         }
811     }
812 
813     @Test
814     @DisabledOnOs(OS.WINDOWS)
815     void testNonExistingSymlink() throws Exception {
816         File zipFile = new File("src/test/resources/symlinks/non_existing_symlink.zip");
817         ZipUnArchiver unArchiver = getZipUnArchiver(zipFile);
818         String tmpdir = Files.createTempDirectory("tmpe_extract").toFile().getAbsolutePath();
819         unArchiver.setDestDirectory(new File(tmpdir));
820         ArchiverException exception = assertThrows(ArchiverException.class, unArchiver::extract);
821         assertEquals("Entry is outside of the target directory (entry1)", exception.getMessage());
822     }
823 
824     /**
825      * Takes a timestamp, turns it into a textual representation based on GMT, then translated it into a timestamp in
826      * local timezone. This makes the test independent of the current TimeZone. The reason this is necessary is:
827      * <ul>
828      * <li>ZIP file format does not take timezone into account.</li>
829      * <li>In the process of converting the ZipEntry time from the DOS date format specified by the ZIP file format, the
830      * timestamp is converted to a Java Date object, which DOES depends of the current system TimeZone, therefore
831      * changing the value of the Date object representing that timestamp relative to the local TimeZone.</li>
832      * </ul>
833      *
834      * @param timestamp the epoch time to convert.
835      * @return the timestamp matching the same input date but in the local TZ.
836      */
837     private long toLocalTimeZone(long timestamp) {
838         String dateFormat = "dd-MM-yyyy hh:mm:ss a";
839         DateFormat formatterWithTimeZone = new SimpleDateFormat(dateFormat);
840         formatterWithTimeZone.setTimeZone(TimeZone.getTimeZone("GMT"));
841         String sDate = formatterWithTimeZone.format(new Date(timestamp));
842 
843         DateFormat formatter = new SimpleDateFormat(dateFormat);
844         try {
845             Date dateWithTimeZone = formatter.parse(sDate);
846             return dateWithTimeZone.getTime();
847         } catch (ParseException e) {
848             fail("Date '" + sDate + "' can not be parsed!");
849             return 0L;
850         }
851     }
852 }