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