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.io.input.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 = ZipFile.builder().setFile(zipFile).get();
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 = ZipFile.builder().setFile(zipFile).get();
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 = ZipFile.builder().setFile(zipFile2).get();
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) {
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 =
318                 ZipFile.builder().setFile(zipFileRecompress).get();
319         assertEquals(ZipEntry.DEFLATED, zfRecompress.getEntry("test.zip").getMethod());
320         assertEquals(ZipEntry.DEFLATED, zfRecompress.getEntry("test.jar").getMethod());
321         assertEquals(ZipEntry.DEFLATED, zfRecompress.getEntry("test.rar").getMethod());
322         assertEquals(ZipEntry.DEFLATED, zfRecompress.getEntry("test.tar.gz").getMethod());
323         zfRecompress.close();
324 
325         // make sure the zip files are not re-compressed when recompressAddedZips is set to false
326 
327         final File zipFileDontRecompress = getTestFile("target/output/dont-recompress-added-zips.zip");
328         ZipArchiver zipArchiver = getZipArchiver(zipFileDontRecompress);
329         zipArchiver.addDirectory(getTestFile("src/test/jars"));
330         zipArchiver.setRecompressAddedZips(false);
331         FileUtils.removePath(zipFileDontRecompress.getPath());
332         zipArchiver.createArchive();
333 
334         final ZipFile zfDontRecompress =
335                 ZipFile.builder().setFile(zipFileDontRecompress).get();
336         final ZipArchiveEntry zipEntry = zfDontRecompress.getEntry("test.zip");
337         final ZipArchiveEntry jarEntry = zfDontRecompress.getEntry("test.jar");
338         final ZipArchiveEntry rarEntry = zfDontRecompress.getEntry("test.rar");
339         final ZipArchiveEntry tarEntry = zfDontRecompress.getEntry("test.tar.gz");
340         // check if only zip files are not compressed...
341         assertEquals(ZipEntry.STORED, zipEntry.getMethod());
342         assertEquals(ZipEntry.STORED, jarEntry.getMethod());
343         assertEquals(ZipEntry.STORED, rarEntry.getMethod());
344         assertEquals(ZipEntry.DEFLATED, tarEntry.getMethod());
345         // ...and no file is corrupted in the process
346         assertTrue(IOUtil.contentEquals(
347                 Files.newInputStream(getTestFile("src/test/jars/test.zip").toPath()),
348                 zfDontRecompress.getInputStream(zipEntry)));
349         assertTrue(IOUtil.contentEquals(
350                 Files.newInputStream(getTestFile("src/test/jars/test.jar").toPath()),
351                 zfDontRecompress.getInputStream(jarEntry)));
352         assertTrue(IOUtil.contentEquals(
353                 Files.newInputStream(getTestFile("src/test/jars/test.rar").toPath()),
354                 zfDontRecompress.getInputStream(rarEntry)));
355         assertTrue(IOUtil.contentEquals(
356                 Files.newInputStream(getTestFile("src/test/jars/test.tar.gz").toPath()),
357                 zfDontRecompress.getInputStream(tarEntry)));
358         zfDontRecompress.close();
359     }
360 
361     @Test
362     void testAddArchivedFileSet() throws Exception {
363         File toBeAdded = new File("src/test/resources/test.zip");
364         DefaultArchivedFileSet sfd = DefaultArchivedFileSet.archivedFileSet(toBeAdded);
365         File zipFIle = getTestFile("target/output/withZip.zip");
366         final ZipArchiver zipArchiver = getZipArchiver(zipFIle);
367         InputStreamTransformer is = new InputStreamTransformer() {
368 
369             @Nonnull
370             public InputStream transform(@Nonnull PlexusIoResource resource, @Nonnull InputStream inputStream)
371                     throws IOException {
372                 return BoundedInputStream.builder()
373                         .setInputStream(inputStream)
374                         .setMaxCount(3)
375                         .get();
376             }
377         };
378         sfd.setStreamTransformer(is);
379         PrefixFileMapper mapper = new PrefixFileMapper();
380         mapper.setPrefix("prefix");
381         sfd.setFileMappers(new FileMapper[] {mapper});
382         zipArchiver.addArchivedFileSet(sfd);
383         zipArchiver.createArchive();
384 
385         final ZipUnArchiver zipUnArchiver = getZipUnArchiver(zipFIle);
386         File destFile = new File("target/output/withZip");
387         destFile.mkdirs();
388         zipUnArchiver.setDestFile(destFile);
389         zipUnArchiver.extract();
390         File a3byteFile = new File(
391                 destFile,
392                 "prefixUsers/kristian/lsrc/plexus/plexus-archiver/src/main/java/org/codehaus/plexus/archiver/zip/ZipArchiver.java");
393         assertTrue(a3byteFile.exists());
394         assertEquals(3, a3byteFile.length());
395     }
396 
397     @Test
398     void testCreateArchiveWithStreamTransformer() throws IOException {
399         InputStreamTransformer is = new InputStreamTransformer() {
400 
401             @Nonnull
402             @Override
403             public InputStream transform(@Nonnull PlexusIoResource resource, @Nonnull InputStream inputStream)
404                     throws IOException {
405                 return BoundedInputStream.builder()
406                         .setInputStream(inputStream)
407                         .setBufferSizeMax(3)
408                         .get();
409             }
410         };
411 
412         final ZipArchiver zipArchiver = getZipArchiver(getTestFile("target/output/all3bytes.zip"));
413         File zipFIle = new File("src/test/resources/test.zip");
414         DefaultArchivedFileSet afs = new DefaultArchivedFileSet(zipFIle);
415         afs.setStreamTransformer(is);
416         afs.setPrefix("azip/");
417         zipArchiver.addArchivedFileSet(afs);
418 
419         DefaultFileSet dfs = new DefaultFileSet(new File("src/test/resources/mjar179"));
420         dfs.setStreamTransformer(is);
421         dfs.setPrefix("mj179/");
422         zipArchiver.addFileSet(dfs);
423 
424         PlexusIoFileResourceCollection files = new PlexusIoFileResourceCollection();
425         files.setBaseDir(new File("src/test/resources"));
426         files.setStreamTransformer(is);
427         files.setPrefix("plexus/");
428         zipArchiver.addResources(files);
429 
430         zipArchiver.createArchive();
431     }
432 
433     private ZipArchiver newArchiver(String name) {
434         ZipArchiver archiver = getZipArchiver(getTestFile("target/output/" + name));
435 
436         archiver.setFileMode(0640);
437         archiver.addFile(getTestFile("src/test/resources/manifests/manifest1.mf"), "one.txt");
438         archiver.addFile(getTestFile("src/test/resources/manifests/manifest2.mf"), "two.txt", 0664);
439 
440         // reset default file mode for files included from now on
441         archiver.setFileMode(0400);
442         archiver.setDirectoryMode(0777);
443         archiver.addDirectory(getTestFile("src/test/resources/world-writable/"), "worldwritable/");
444 
445         archiver.setDirectoryMode(0070);
446         archiver.addDirectory(getTestFile("src/test/resources/group-writable/"), "groupwritable/");
447 
448         archiver.setDirectoryMode(0500);
449         archiver.setFileMode(0400);
450         archiver.addDirectory(getTestFile("src"));
451 
452         return archiver;
453     }
454 
455     private void fileModeAssert(int expected, int actual) {
456         assertEquals(Integer.toString(expected, 8), Integer.toString(actual, 8));
457     }
458 
459     private void createArchive(ZipArchiver archiver) throws ArchiverException, IOException {
460         archiver.createArchive();
461 
462         ZipFile zf = ZipFile.builder().setFile(archiver.getDestFile()).get();
463 
464         Enumeration<ZipArchiveEntry> e = zf.getEntries();
465 
466         while (e.hasMoreElements()) {
467             ZipArchiveEntry ze = e.nextElement();
468             if (ze.isDirectory()) {
469                 if (ze.getName().startsWith("worldwritable")) {
470                     fileModeAssert(0777, UnixStat.PERM_MASK & ze.getUnixMode());
471                 } else if (ze.getName().startsWith("groupwritable")) {
472                     fileModeAssert(0070, UnixStat.PERM_MASK & ze.getUnixMode());
473                 } else {
474                     fileModeAssert(0500, UnixStat.PERM_MASK & ze.getUnixMode());
475                 }
476             } else {
477                 if (ze.getName().equals("one.txt")) {
478                     fileModeAssert(0640, UnixStat.PERM_MASK & ze.getUnixMode());
479                 } else if (ze.getName().equals("two.txt")) {
480                     fileModeAssert(0664, UnixStat.PERM_MASK & ze.getUnixMode());
481                 } else if (ze.isUnixSymlink()) {
482                     //         assertEquals( ze.getName(), 0500, UnixStat.PERM_MASK & ze.getUnixMode() );
483                 } else {
484                     fileModeAssert(0400, UnixStat.PERM_MASK & ze.getUnixMode());
485                 }
486             }
487         }
488     }
489 
490     @Test
491     @DisabledOnOs(OS.WINDOWS)
492     void testSymlinkZip() throws Exception {
493         final File zipFile = getTestFile("target/output/pasymlinks.zip");
494         final ZipArchiver zipArchiver = getZipArchiver(zipFile);
495         PlexusIoFileResourceCollection files = new PlexusIoFileResourceCollection();
496         files.setFollowingSymLinks(false);
497         files.setBaseDir(new File("src/test/resources/symlinks"));
498         files.setPrefix("plexus/");
499         zipArchiver.addResources(files);
500         zipArchiver.createArchive();
501         final File output = getTestFile("target/output/unzipped");
502         output.mkdirs();
503         final ZipUnArchiver zipUnArchiver = getZipUnArchiver(zipFile);
504         zipUnArchiver.setDestFile(output);
505         zipUnArchiver.extract();
506         File symDir = new File("target/output/unzipped/plexus/src/symDir");
507         PlexusIoResourceAttributes fa = FileAttributes.uncached(symDir);
508         assertTrue(fa.isSymbolicLink());
509     }
510 
511     @Test
512     @DisabledOnOs(OS.WINDOWS)
513     @SuppressWarnings("ResultOfMethodCallIgnored")
514     void testSymlinkFileSet() throws Exception {
515         final File zipFile = getTestFile("target/output/pasymlinks-fileset.zip");
516         final ZipArchiver zipArchiver = getZipArchiver(zipFile);
517         final DefaultFileSet fs = new DefaultFileSet();
518         fs.setPrefix("bzz/");
519         fs.setDirectory(new File("src/test/resources/symlinks/src"));
520         zipArchiver.addFileSet(fs);
521         zipArchiver.createArchive();
522         final File output = getTestFile("target/output/unzipped/symlFs");
523         output.mkdirs();
524         final ZipUnArchiver zipUnArchiver = getZipUnArchiver(zipFile);
525         zipUnArchiver.setDestFile(output);
526         zipUnArchiver.extract();
527         File symDir = new File(output, "bzz/symDir");
528         PlexusIoResourceAttributes fa = FileAttributes.uncached(symDir);
529         assertTrue(fa.isSymbolicLink());
530     }
531 
532     @Test
533     void testSymlinkArchivedFileSet() throws Exception {
534         final File zipFile = getTestFile("src/test/resources/symlinks/symlinks.zip");
535         final File zipFile2 = getTestFile("target/output/pasymlinks-archivedFileset.zip");
536         final ZipArchiver zipArchiver = getZipArchiver(zipFile2);
537         zipArchiver.addArchivedFileSet(zipFile);
538         zipArchiver.createArchive();
539 
540         final ZipFile cmp1 = ZipFile.builder().setFile(zipFile).get();
541         final ZipFile cmp2 = ZipFile.builder().setFile(zipFile2).get();
542         ArchiveFileComparator.assertZipEquals(cmp1, cmp2, "");
543     }
544 
545     /*
546      * Zip archives store file modification times with a granularity of two seconds.
547      * Verify that ZipArchiver rounds up the last modified time.
548      */
549     @Test
550     void testLastModifiedTimeRounding() throws Exception {
551         Path oddSecondsTimestampFile = Files.createTempFile(tempDir.toPath(), "odd-seconds-timestamp", null);
552         // The milliseconds part is set to zero as not all filesystem support timestamp more granular than second.
553         Files.setLastModifiedTime(oddSecondsTimestampFile, FileTime.fromMillis(1534189011_000L));
554         Path evenSecondsTimestampFile = Files.createTempFile(tempDir.toPath(), "even-seconds-timestamp", null);
555         Files.setLastModifiedTime(evenSecondsTimestampFile, FileTime.fromMillis(1534189012_000L));
556 
557         File destFile = getTestFile("target/output/last-modified-time.zip");
558         ZipArchiver archiver = getZipArchiver(destFile);
559         archiver.addFile(oddSecondsTimestampFile.toFile(), "odd-seconds");
560         archiver.addFile(evenSecondsTimestampFile.toFile(), "even-seconds");
561         archiver.createArchive();
562 
563         // verify that the last modified time of the entry is equal or newer than the original file
564         try (ZipFile resultingZipFile = ZipFile.builder().setFile(destFile).get()) {
565             assertEquals(
566                     1534189012_000L, resultingZipFile.getEntry("odd-seconds").getTime());
567             assertEquals(
568                     1534189012_000L, resultingZipFile.getEntry("even-seconds").getTime());
569 
570             FileTime expected = FileTime.fromMillis(1534189012_000L);
571             assertEquals(expected, resultingZipFile.getEntry("odd-seconds").getLastModifiedTime());
572             assertEquals(expected, resultingZipFile.getEntry("even-seconds").getLastModifiedTime());
573         }
574     }
575 
576     /*
577      */
578     @Test
579     void testForced() throws Exception {
580         ZipArchiver archiver = newArchiver("archive2.zip");
581 
582         assertTrue(archiver.isForced());
583         File f = archiver.getDestFile();
584         if (f.exists()) {
585             FileUtils.fileDelete(f.getPath());
586         }
587         assertFalse(f.exists());
588         createArchive(archiver);
589         long l1 = f.lastModified();
590         assertTrue(f.exists());
591 
592         archiver = newArchiver("archive2.zip");
593         waitUntilNewTimestamp(archiver.getDestFile(), l1);
594         createArchive(archiver);
595         long l2 = f.lastModified();
596         assertTrue(f.exists());
597         assertTrue(l2 > l1);
598 
599         archiver = newArchiver("archive2.zip");
600         assertTrue(archiver.isSupportingForced());
601         archiver.setForced(false);
602         assertFalse(archiver.isForced());
603 
604         createArchive(archiver);
605         long l3 = f.lastModified();
606         assertTrue(f.exists());
607         assertEquals(l2, l3);
608     }
609 
610     // Used to investigate extrafields
611     @Test
612     void testLookAtExtraZipFields_from_macos() throws IOException {
613         InputStream fis = Streams.fileInputStream(new File("src/test/resources/zip-timestamp/macOsZipFile.zip"));
614         ZipInputStream zis = new ZipInputStream(fis);
615         final java.util.zip.ZipEntry evenEntry = zis.getNextEntry();
616         final ZipExtraField[] parse = ExtraFieldUtils.parse(evenEntry.getExtra());
617         System.out.println(Arrays.asList(parse));
618         final java.util.zip.ZipEntry oddEntry = zis.getNextEntry();
619 
620         System.out.println(Arrays.asList(ExtraFieldUtils.parse(oddEntry.getExtra())));
621 
622         System.out.println("oddEntry.getTime() = " + new Date(oddEntry.getTime()));
623         System.out.println("oddEntry.getName() = " + oddEntry.getName());
624         System.out.println("new String(oddEntry.getExtra()) = " + new String(oddEntry.getExtra()));
625         System.out.println("evenEntry.getName() = " + evenEntry.getName());
626         System.out.println("evenEntry.getTime() = " + new Date(evenEntry.getTime()));
627         System.out.println("new String(evenEntry.getExtra()) = " + new String(evenEntry.getExtra()));
628     }
629 
630     // Used to investigate date roundtrip behaviour across zip versions
631     @Test
632     void testZipStuff() throws IOException {
633         ByteArrayOutputStream baos = new ByteArrayOutputStream();
634         ZipOutputStream zos = new ZipOutputStream(baos);
635         // name the file inside the zip  file
636         final File oddFile = new File("src/test/resources/zip-timestamp/file-with-odd-time.txt");
637         final File evenFile = new File("src/test/resources/zip-timestamp/file-with-even-time.txt");
638         final ZipEntry oddZe = new ZipEntry(oddFile.getName());
639         oddZe.setTime(oddFile.lastModified());
640         zos.putNextEntry(oddZe);
641         final ZipEntry evenZe = new ZipEntry(evenFile.getName());
642         evenZe.setTime(evenFile.lastModified());
643         zos.putNextEntry(evenZe);
644         zos.close();
645 
646         ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
647         ZipInputStream zipInputStream = new ZipInputStream(bais);
648         final java.util.zip.ZipEntry oddEntry = zipInputStream.getNextEntry();
649         System.out.println("oddEntry.getTime() = " + new Date(oddEntry.getTime()));
650         System.out.println("oddEntry.getName() = " + oddEntry.getName());
651         final java.util.zip.ZipEntry evenEntry = zipInputStream.getNextEntry();
652         System.out.println("evenEntry.getName() = " + evenEntry.getName());
653         System.out.println("evenEntry.getTime() = " + new Date(evenEntry.getTime()));
654     }
655 
656     @Disabled("Junit3 method name is notest")
657     @Test
658     void notestJustThatOne() throws Exception {
659         final File srcDir = new File("src");
660         String[] inc = {"test/java/org/codehaus/plexus/archiver/zip/ZipShortTest.java"};
661         final File zipFile = new File("target/output/zz1.zip");
662 
663         final File zipFile2 = new File("target/output/zz2.zip");
664         ZipArchiver zipArchiver2 = getZipArchiver(zipFile2);
665 
666         // Bugbug: This does not work on 1.8....?
667         zipArchiver2.addArchivedFileSet(zipFile);
668         FileUtils.removePath(zipFile2.getPath());
669         zipArchiver2.createArchive();
670     }
671 
672     @Test
673     void testCreateResourceCollection() throws Exception {
674         final File srcDir = new File("src");
675         final File zipFile = new File("target/output/src.zip");
676         ZipArchiver zipArchiver = getZipArchiver(zipFile);
677         zipArchiver.addDirectory(srcDir, null, FileUtils.getDefaultExcludes());
678         zipArchiver.setEncoding("UTF-8");
679         FileUtils.removePath(zipFile.getPath());
680         zipArchiver.createArchive();
681 
682         final File zipFile2 = new File("target/output/src2.zip");
683         ZipArchiver zipArchiver2 = getZipArchiver(zipFile2);
684         zipArchiver2.addArchivedFileSet(zipFile, "prfx/");
685         zipArchiver2.setEncoding("UTF-8");
686         FileUtils.removePath(zipFile2.getPath());
687         zipArchiver2.createArchive();
688 
689         final ZipFile cmp1 = ZipFile.builder().setFile(zipFile).get();
690         final ZipFile cmp2 = ZipFile.builder().setFile(zipFile2).get();
691         ArchiveFileComparator.assertZipEquals(cmp1, cmp2, "prfx/");
692         cmp1.close();
693         cmp2.close();
694     }
695 
696     @Test
697     void testZipNonConcurrentResourceCollection() throws Exception {
698         final File tarFile = getTestFile("target/output/zip-non-concurrent.tar");
699         TarArchiver tarArchiver = (TarArchiver) lookup(Archiver.class, "tar");
700         // Override uId and gId - in standard mode on OS where uId or gId can have long values creation of tar can fail
701         // We can not use posix mode because mTime can have nanoseconds in posix but zip doesn't so assertions can fail
702         tarArchiver.setOverrideUid(100);
703         tarArchiver.setOverrideGid(100);
704         tarArchiver.setDestFile(tarFile);
705         // We're testing concurrency issue so we need large amount of files
706         for (int i = 0; i < 100; i++) {
707             tarArchiver.addFile(getTestFile("src/test/resources/manifests/manifest1.mf"), "manifest1.mf" + i);
708             // Directories are added separately so let's test them too
709             tarArchiver.addFile(
710                     getTestFile("src/test/resources/manifests/manifest2.mf"), "subdir" + i + "/manifest2.mf");
711         }
712         tarArchiver.createArchive();
713 
714         final File zipFile = new File("target/output/zip-non-concurrent.zip");
715         ZipArchiver zipArchive = getZipArchiver(zipFile);
716         zipArchive.addArchivedFileSet(tarFile, "prfx/");
717         zipArchive.setEncoding("UTF-8");
718         zipArchive.createArchive();
719 
720         final TarFile cmp1 = new TarFile(tarFile);
721         final ZipFile cmp2 = ZipFile.builder().setFile(zipFile).get();
722         ArchiveFileComparator.assertTarZipEquals(cmp1, cmp2, "prfx/");
723         cmp1.close();
724         cmp2.close();
725     }
726 
727     @Test
728     void testDefaultUTF8() throws IOException {
729         final ZipArchiver zipArchiver = getZipArchiver(new File("target/output/utf8-default.zip"));
730         zipArchiver.addDirectory(new File("src/test/resources/miscUtf8"));
731         zipArchiver.createArchive();
732     }
733 
734     @Test
735     void testDefaultUTF8withUTF8() throws IOException {
736         final ZipArchiver zipArchiver = getZipArchiver(new File("target/output/utf8-with_utf.zip"));
737         zipArchiver.setEncoding("UTF-8");
738         zipArchiver.addDirectory(new File("src/test/resources/miscUtf8"));
739         zipArchiver.createArchive();
740     }
741 
742     @Test
743     void testForcedFileModes() throws IOException {
744         File step1file = new File("target/output/forced-file-mode.zip");
745         {
746             final ZipArchiver zipArchiver = getZipArchiver(step1file);
747             zipArchiver.setFileMode(0077);
748             zipArchiver.setDirectoryMode(0007);
749             PlexusIoResourceAttributes attrs = new SimpleResourceAttributes(123, "fred", 22, "filntstones", 0111);
750             PlexusIoResource resource = ResourceFactory.createResource(
751                     new File("src/test/resources/folders/File.txt"), "Test.txt", null, attrs);
752             zipArchiver.addResource(resource, "Test2.txt", 0707);
753             PlexusIoFileResourceCollection files = new PlexusIoFileResourceCollection();
754             files.setBaseDir(new File("src/test/resources/folders"));
755             files.setPrefix("sixsixsix/");
756             zipArchiver.addResources(files);
757 
758             zipArchiver.createArchive();
759 
760             ZipFile zf = ZipFile.builder().setFile(step1file).get();
761             fileModeAssert(040007, zf.getEntry("sixsixsix/a/").getUnixMode());
762             fileModeAssert(0100077, zf.getEntry("sixsixsix/b/FileInB.txt").getUnixMode());
763             fileModeAssert(0100707, zf.getEntry("Test2.txt").getUnixMode());
764             zf.close();
765         }
766 
767         File Step2file = new File("target/output/forced-file-mode-from-zip.zip");
768         {
769             final ZipArchiver za2 = getZipArchiver(Step2file);
770             za2.setFileMode(0666);
771             za2.setDirectoryMode(0676);
772 
773             PlexusIoZipFileResourceCollection zipSrc = new PlexusIoZipFileResourceCollection();
774             zipSrc.setFile(step1file);
775             zipSrc.setPrefix("zz/");
776             za2.addResources(zipSrc);
777             za2.createArchive();
778             ZipFile zf = ZipFile.builder().setFile(Step2file).get();
779             fileModeAssert(040676, zf.getEntry("zz/sixsixsix/a/").getUnixMode());
780             fileModeAssert(0100666, zf.getEntry("zz/Test2.txt").getUnixMode());
781             zf.close();
782         }
783 
784         File step3file = new File("target/output/forced-file-mode-from-zip2.zip");
785         {
786             final ZipArchiver za2 = getZipArchiver(step3file);
787             za2.setFileMode(0666);
788             za2.setDirectoryMode(0676);
789 
790             PlexusArchiverZipFileResourceCollection zipSrc = new PlexusArchiverZipFileResourceCollection();
791             zipSrc.setFile(step1file);
792             zipSrc.setPrefix("zz/");
793             za2.addResources(zipSrc);
794             za2.createArchive();
795             ZipFile zf = ZipFile.builder().setFile(Step2file).get();
796             fileModeAssert(040676, zf.getEntry("zz/sixsixsix/a/").getUnixMode());
797             fileModeAssert(0100666, zf.getEntry("zz/Test2.txt").getUnixMode());
798             zf.close();
799         }
800     }
801 
802     @Test
803     void testFixedEntryModificationTime() throws IOException {
804         final long almostMinDosTime = toLocalTimeZone(315532802000L);
805 
806         final File zipFile = getTestFile("target/output/zip-with-fixed-entry-modification-times.zip");
807         final ZipArchiver archiver = getZipArchiver(zipFile);
808         archiver.setLastModifiedTime(FileTime.fromMillis(almostMinDosTime));
809         archiver.addDirectory(new File("src/test/resources/zip-timestamp"));
810         archiver.createArchive();
811 
812         assertTrue(zipFile.exists());
813         try (final ZipFile zf = ZipFile.builder().setFile(zipFile).get()) {
814             assertEquals(
815                     almostMinDosTime, zf.getEntry("file-with-even-time.txt").getTime());
816             assertEquals(almostMinDosTime, zf.getEntry("file-with-odd-time.txt").getTime());
817             assertEquals(almostMinDosTime, zf.getEntry("foo/").getTime());
818         }
819     }
820 
821     @Test
822     @DisabledOnOs(OS.WINDOWS)
823     void testNonExistingSymlink() throws Exception {
824         File zipFile = new File("src/test/resources/symlinks/non_existing_symlink.zip");
825         ZipUnArchiver unArchiver = getZipUnArchiver(zipFile);
826         String tmpdir = Files.createTempDirectory("tmpe_extract").toFile().getAbsolutePath();
827         unArchiver.setDestDirectory(new File(tmpdir));
828         ArchiverException exception = assertThrows(ArchiverException.class, unArchiver::extract);
829         assertEquals("Entry is outside of the target directory (entry1)", exception.getMessage());
830     }
831 
832     /**
833      * Takes a timestamp, turns it into a textual representation based on GMT, then translated it into a timestamp in
834      * local timezone. This makes the test independent of the current TimeZone. The reason this is necessary is:
835      * <ul>
836      * <li>ZIP file format does not take timezone into account.</li>
837      * <li>In the process of converting the ZipEntry time from the DOS date format specified by the ZIP file format, the
838      * timestamp is converted to a Java Date object, which DOES depends of the current system TimeZone, therefore
839      * changing the value of the Date object representing that timestamp relative to the local TimeZone.</li>
840      * </ul>
841      *
842      * @param timestamp the epoch time to convert.
843      * @return the timestamp matching the same input date but in the local TZ.
844      */
845     private long toLocalTimeZone(long timestamp) {
846         String dateFormat = "dd-MM-yyyy hh:mm:ss a";
847         DateFormat formatterWithTimeZone = new SimpleDateFormat(dateFormat);
848         formatterWithTimeZone.setTimeZone(TimeZone.getTimeZone("GMT"));
849         String sDate = formatterWithTimeZone.format(new Date(timestamp));
850 
851         DateFormat formatter = new SimpleDateFormat(dateFormat);
852         try {
853             Date dateWithTimeZone = formatter.parse(sDate);
854             return dateWithTimeZone.getTime();
855         } catch (ParseException e) {
856             fail("Date '" + sDate + "' can not be parsed!");
857             return 0L;
858         }
859     }
860 }