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.tar;
25  
26  import java.io.BufferedInputStream;
27  import java.io.File;
28  import java.io.IOException;
29  import java.io.Writer;
30  import java.nio.charset.StandardCharsets;
31  import java.nio.file.Files;
32  import java.util.Enumeration;
33  import java.util.LinkedHashMap;
34  import java.util.Map;
35  
36  import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
37  import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
38  import org.codehaus.plexus.archiver.Archiver;
39  import org.codehaus.plexus.archiver.ArchiverException;
40  import org.codehaus.plexus.archiver.TestSupport;
41  import org.codehaus.plexus.archiver.UnixStat;
42  import org.codehaus.plexus.archiver.bzip2.BZip2Compressor;
43  import org.codehaus.plexus.archiver.exceptions.EmptyArchiveException;
44  import org.codehaus.plexus.archiver.gzip.GZipCompressor;
45  import org.codehaus.plexus.archiver.util.ArchiveEntryUtils;
46  import org.codehaus.plexus.archiver.util.Compressor;
47  import org.codehaus.plexus.archiver.util.DefaultArchivedFileSet;
48  import org.codehaus.plexus.archiver.zip.ArchiveFileComparator;
49  import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributeUtils;
50  import org.codehaus.plexus.components.io.attributes.PlexusIoResourceAttributes;
51  import org.codehaus.plexus.util.FileUtils;
52  import org.codehaus.plexus.util.IOUtil;
53  import org.junit.jupiter.api.Test;
54  import org.junit.jupiter.api.condition.DisabledOnOs;
55  import org.junit.jupiter.api.condition.OS;
56  import org.junit.jupiter.api.io.TempDir;
57  
58  import static org.codehaus.plexus.archiver.util.Streams.bufferedInputStream;
59  import static org.codehaus.plexus.components.io.resources.ResourceFactory.createResource;
60  import static org.junit.jupiter.api.Assertions.assertEquals;
61  import static org.junit.jupiter.api.Assertions.assertNotNull;
62  import static org.junit.jupiter.api.Assertions.assertTrue;
63  import static org.junit.jupiter.api.Assertions.fail;
64  
65  /**
66   * @author Emmanuel Venisse
67   */
68  class TarArchiverTest extends TestSupport {
69      @TempDir
70      private File tempDir;
71  
72      @Test
73      @DisabledOnOs(OS.WINDOWS)
74      void testCreateArchiveWithDetectedModes() throws Exception {
75  
76          String[] executablePaths = {"path/to/executable", "path/to/executable.bat"};
77  
78          String[] confPaths = {"path/to/etc/file", "path/to/etc/file2"};
79  
80          String[] logPaths = {"path/to/logs/log.txt"};
81  
82          int exeMode = 0777;
83          int confMode = 0600;
84          int logMode = 0640;
85  
86          for (String executablePath : executablePaths) {
87              writeFile(tempDir, executablePath, exeMode);
88          }
89  
90          for (String confPath : confPaths) {
91              writeFile(tempDir, confPath, confMode);
92          }
93  
94          for (String logPath : logPaths) {
95              writeFile(tempDir, logPath, logMode);
96          }
97  
98          {
99              Map attributesByPath = PlexusIoResourceAttributeUtils.getFileAttributesByPath(tempDir);
100             for (String path : executablePaths) {
101                 PlexusIoResourceAttributes attrs = (PlexusIoResourceAttributes) attributesByPath.get(path);
102                 if (attrs == null) {
103                     attrs = (PlexusIoResourceAttributes)
104                             attributesByPath.get(new File(tempDir, path).getAbsolutePath());
105                 }
106 
107                 assertNotNull(attrs);
108                 assertEquals(exeMode, attrs.getOctalMode(), "Wrong mode for: " + path);
109             }
110 
111             for (String path : confPaths) {
112                 PlexusIoResourceAttributes attrs = (PlexusIoResourceAttributes) attributesByPath.get(path);
113                 if (attrs == null) {
114                     attrs = (PlexusIoResourceAttributes)
115                             attributesByPath.get(new File(tempDir, path).getAbsolutePath());
116                 }
117 
118                 assertNotNull(attrs);
119                 assertEquals(confMode, attrs.getOctalMode(), "Wrong mode for: " + path);
120             }
121 
122             for (String path : logPaths) {
123                 PlexusIoResourceAttributes attrs = (PlexusIoResourceAttributes) attributesByPath.get(path);
124                 if (attrs == null) {
125                     attrs = (PlexusIoResourceAttributes)
126                             attributesByPath.get(new File(tempDir, path).getAbsolutePath());
127                 }
128 
129                 assertNotNull(attrs);
130                 assertEquals(logMode, attrs.getOctalMode(), "Wrong mode for: " + path);
131             }
132         }
133 
134         File tarFile = getTestFile("target/output/tar-with-modes.tar");
135 
136         TarArchiver archiver = getPosixTarArchiver();
137         archiver.setDestFile(tarFile);
138 
139         archiver.addDirectory(tempDir);
140         archiver.createArchive();
141 
142         assertTrue(tarFile.exists());
143 
144         File tarFile2 = getTestFile("target/output/tar-with-modes-L2.tar");
145 
146         archiver = getPosixTarArchiver();
147         archiver.setDestFile(tarFile2);
148 
149         archiver.addArchivedFileSet(tarFile);
150         archiver.createArchive();
151 
152         TarFile tf = new TarFile(tarFile2);
153 
154         Map<String, TarArchiveEntry> entriesByPath = new LinkedHashMap<String, TarArchiveEntry>();
155         for (Enumeration e = tf.getEntries(); e.hasMoreElements(); ) {
156             TarArchiveEntry te = (TarArchiveEntry) e.nextElement();
157             entriesByPath.put(te.getName(), te);
158         }
159 
160         for (String path : executablePaths) {
161             TarArchiveEntry te = entriesByPath.get(path);
162 
163             int mode = te.getMode() & UnixStat.PERM_MASK;
164 
165             assertEquals(exeMode, mode, "Wrong mode for: " + path);
166         }
167 
168         for (String path : confPaths) {
169             TarArchiveEntry te = entriesByPath.get(path);
170 
171             int mode = te.getMode() & UnixStat.PERM_MASK;
172 
173             assertEquals(confMode, mode, "Wrong mode for: " + path);
174         }
175 
176         for (String path : logPaths) {
177             TarArchiveEntry te = entriesByPath.get(path);
178 
179             int mode = te.getMode() & UnixStat.PERM_MASK;
180 
181             assertEquals(logMode, mode, "Wrong mode for: " + path);
182         }
183     }
184 
185     @Test
186     void testCreateEmptyArchive() throws Exception {
187         TarArchiver archiver = getPosixTarArchiver();
188         archiver.setDestFile(getTestFile("target/output/empty.tar"));
189         try {
190             archiver.createArchive();
191 
192             fail("Creating empty archive should throw EmptyArchiveException");
193         } catch (EmptyArchiveException ignore) {
194         }
195     }
196 
197     @Test
198     void testUnicode() throws Exception {
199         File tmpDir = getTestFile("src/test/resources/utf8");
200         TarArchiver archiver = getPosixTarArchiver();
201         File tarFile = getTestFile("target/output/tar-with-longFileName.tar");
202         archiver.setDestFile(tarFile);
203         archiver.setLongfile(TarLongFileMode.posix); // Todo: should be gnu. But will fail with high userod
204         archiver.addDirectory(tmpDir);
205         archiver.createArchive();
206         assertTrue(tarFile.exists());
207     }
208 
209     private void writeFile(File dir, String fname, int mode) throws IOException, ArchiverException {
210         File file = new File(dir, fname);
211 
212         if (file.getParentFile() != null) {
213             file.getParentFile().mkdirs();
214         }
215 
216         try (Writer writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
217             writer.write("This is a test file.");
218         }
219 
220         ArchiveEntryUtils.chmod(file, mode);
221     }
222 
223     @Test
224     void testCreateArchive() throws Exception {
225         createArchive(0500, new int[] {0400, 0640, 0664});
226         createArchive(0500, new int[] {0400, 0640, 0664});
227     }
228 
229     void createArchive(final int directoryMode, final int fileModes[]) throws Exception {
230         int defaultFileMode = fileModes[0];
231         int oneFileMode = fileModes[1];
232         int twoFileMode = fileModes[2];
233 
234         TarArchiver archiver = getPosixTarArchiver();
235 
236         archiver.setDirectoryMode(directoryMode);
237 
238         archiver.setFileMode(defaultFileMode);
239 
240         archiver.addDirectory(getTestFile("src/main"));
241         archiver.setFileMode(oneFileMode);
242 
243         archiver.addFile(getTestFile("src/test/resources/manifests/manifest1.mf"), "one.txt");
244         archiver.addFile(getTestFile("src/test/resources/manifests/manifest2.mf"), "two.txt", twoFileMode);
245         archiver.setDestFile(getTestFile("target/output/archive.tar"));
246 
247         archiver.addSymlink("link_to_test_destinaton", "../test_destination/");
248 
249         archiver.createArchive();
250 
251         TarArchiveInputStream tis;
252 
253         tis = new TarArchiveInputStream(
254                 bufferedInputStream(Files.newInputStream(archiver.getDestFile().toPath())));
255         TarArchiveEntry te;
256 
257         while ((te = tis.getNextTarEntry()) != null) {
258             if (te.isDirectory()) {
259                 assertEquals(
260                         directoryMode,
261                         te.getMode() & UnixStat.PERM_MASK,
262                         "un-expected tar-entry mode for [te.name=" + te.getName() + "]");
263             } else if (te.isSymbolicLink()) {
264                 assertEquals("../test_destination/", te.getLinkName());
265                 assertEquals("link_to_test_destinaton", te.getName());
266                 assertEquals(0640, te.getMode() & UnixStat.PERM_MASK);
267             } else {
268                 if (te.getName().equals("one.txt")) {
269                     assertEquals(oneFileMode, te.getMode() & UnixStat.PERM_MASK);
270                 } else if (te.getName().equals("two.txt")) {
271                     assertEquals(twoFileMode, te.getMode() & UnixStat.PERM_MASK);
272                 } else {
273                     assertEquals(
274                             defaultFileMode,
275                             te.getMode() & UnixStat.PERM_MASK,
276                             "un-expected tar-entry mode for [te.name=" + te.getName() + "]");
277                 }
278             }
279         }
280         IOUtil.close(tis);
281     }
282 
283     @Test
284     void testCreateArchiveWithJiustASymlink() throws Exception {
285         TarArchiver archiver = getPosixTarArchiver();
286 
287         archiver.setDirectoryMode(0500);
288 
289         archiver.setFileMode(0400);
290 
291         archiver.setFileMode(0640);
292 
293         archiver.setDestFile(getTestFile("target/output/symlinkarchive.tar"));
294 
295         archiver.addSymlink("link_to_test_destinaton", "../test_destination/");
296 
297         archiver.createArchive();
298 
299         TarArchiveInputStream tis;
300 
301         tis = new TarArchiveInputStream(new BufferedInputStream(
302                 Files.newInputStream(archiver.getDestFile().toPath())));
303         TarArchiveEntry te;
304 
305         while ((te = tis.getNextTarEntry()) != null) {
306             if (te.isDirectory()) {
307                 assertEquals(
308                         0500,
309                         te.getMode() & UnixStat.PERM_MASK,
310                         "un-expected tar-entry mode for [te.name=" + te.getName() + "]");
311             } else if (te.isSymbolicLink()) {
312                 assertEquals("../test_destination/", te.getLinkName());
313                 assertEquals("link_to_test_destinaton", te.getName());
314                 assertEquals(0640, te.getMode() & UnixStat.PERM_MASK);
315             } else {
316                 assertEquals(
317                         0400,
318                         te.getMode() & UnixStat.PERM_MASK,
319                         "un-expected tar-entry mode for [te.name=" + te.getName() + "]");
320             }
321         }
322         tis.close();
323     }
324 
325     private TarArchiver getPosixTarArchiver() throws Exception {
326         TarArchiver archiver = (TarArchiver) lookup(Archiver.class, "tar");
327         archiver.setLongfile(TarLongFileMode.posix);
328         return archiver;
329     }
330 
331     private class TarHandler {
332 
333         File createTarFile() throws Exception {
334             final File srcDir = new File("src");
335             final File tarFile = new File("target/output/src.tar");
336             TarArchiver tarArchiver = getPosixTarArchiver();
337             tarArchiver.setDestFile(tarFile);
338             tarArchiver.addDirectory(srcDir, null, FileUtils.getDefaultExcludes());
339             FileUtils.removePath(tarFile.getPath());
340             tarArchiver.createArchive();
341             return tarFile;
342         }
343 
344         File createTarfile2(File tarFile) throws Exception {
345             final File tarFile2 = new File("target/output/src2.tar");
346             TarArchiver tarArchiver2 = getPosixTarArchiver();
347             tarArchiver2.setDestFile(tarFile2);
348             tarArchiver2.addArchivedFileSet(tarFile, "prfx/");
349             FileUtils.removePath(tarFile2.getPath());
350             tarArchiver2.createArchive();
351             return tarFile2;
352         }
353 
354         TarFile newTarFile(File tarFile) {
355             return new TarFile(tarFile);
356         }
357     }
358 
359     private class GZipTarHandler extends TarHandler {
360 
361         @Override
362         File createTarFile() throws Exception {
363             File file = super.createTarFile();
364             File compressedFile = new File(file.getPath() + ".gz");
365             Compressor compressor = new GZipCompressor();
366             compressor.setSource(createResource(file, file.getName()));
367             compressor.setDestFile(compressedFile);
368             compressor.compress();
369             compressor.close();
370             return compressedFile;
371         }
372 
373         @Override
374         TarFile newTarFile(File tarFile) {
375             return new GZipTarFile(tarFile);
376         }
377     }
378 
379     private class BZip2TarHandler extends TarHandler {
380 
381         @Override
382         File createTarFile() throws Exception {
383             File file = super.createTarFile();
384             File compressedFile = new File(file.getPath() + ".bz2");
385             Compressor compressor = new BZip2Compressor();
386             compressor.setSource(createResource(file));
387             compressor.setDestFile(compressedFile);
388             compressor.compress();
389             compressor.close();
390             return compressedFile;
391         }
392 
393         @Override
394         TarFile newTarFile(File tarFile) {
395             return new BZip2TarFile(tarFile);
396         }
397     }
398 
399     @Test
400     void testUncompressedResourceCollection() throws Exception {
401         testCreateResourceCollection(new TarHandler());
402     }
403 
404     @Test
405     void testGzipCompressedResourceCollection() throws Exception {
406         testCreateResourceCollection(new GZipTarHandler());
407     }
408 
409     @Test
410     void testGzipFIleHandleLeak() throws Exception {
411         GZipTarHandler tarHandler = new GZipTarHandler();
412         final File tarFile = tarHandler.createTarFile();
413         final File tarFile2 = tarHandler.createTarfile2(tarFile);
414         final TarFile cmp1 = tarHandler.newTarFile(tarFile);
415         final TarFile cmp2 = new TarFile(tarFile2);
416         ArchiveFileComparator.forEachTarArchiveEntry(cmp1, ze1 -> assertNotNull(ze1.getName()));
417         cmp1.close();
418         cmp2.close();
419     }
420 
421     @Test
422     void testBzip2CompressedResourceCollection() throws Exception {
423         testCreateResourceCollection(new BZip2TarHandler());
424     }
425 
426     @Test
427     void testTarFileNotClosingInputStream() throws Exception {
428         // Supposedly not closing the stream according to yjp.
429         TarHandler tarHandler = new BZip2TarHandler();
430         final File fileName = tarHandler.createTarFile();
431         final TarFile tarFile = tarHandler.newTarFile(fileName);
432         tarFile.getEntries();
433         tarFile.close();
434     }
435 
436     private void testCreateResourceCollection(TarHandler tarHandler) throws Exception {
437         final File tarFile = tarHandler.createTarFile();
438         final File tarFile2 = tarHandler.createTarfile2(tarFile);
439         final TarFile cmp1 = tarHandler.newTarFile(tarFile);
440         final TarFile cmp2 = new TarFile(tarFile2);
441         ArchiveFileComparator.assertTarEquals(cmp1, cmp2, "prfx/");
442         cmp1.close();
443         cmp2.close();
444     }
445 
446     @Test
447     void testSymlinkArchivedFileSet() throws Exception {
448         final File tarFile = getTestFile("src/test/resources/symlinks/symlinks.tar");
449         final File tarFile2 = getTestFile("target/output/pasymlinks-archivedFileset.tar");
450         final TarArchiver tarArchiver = getPosixTarArchiver();
451         tarArchiver.setDestFile(tarFile2);
452         DefaultArchivedFileSet archivedFileSet =
453                 DefaultArchivedFileSet.archivedFileSet(tarFile).usingDefaultExcludes(false);
454         tarArchiver.addArchivedFileSet(archivedFileSet);
455         tarArchiver.createArchive();
456 
457         final TarFile cmp1 = new TarFile(tarFile);
458         final TarFile cmp2 = new TarFile(tarFile2);
459         ArchiveFileComparator.assertTarEquals(cmp1, cmp2, "");
460     }
461 }