View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.commons.compress.archivers.tar;
21  
22  import static java.nio.charset.StandardCharsets.UTF_8;
23  import static org.apache.commons.compress.AbstractTest.getFile;
24  import static org.apache.commons.compress.AbstractTest.getPath;
25  import static org.junit.jupiter.api.Assertions.assertEquals;
26  import static org.junit.jupiter.api.Assertions.assertNotEquals;
27  import static org.junit.jupiter.api.Assertions.assertNotNull;
28  import static org.junit.jupiter.api.Assertions.assertNull;
29  import static org.junit.jupiter.api.Assertions.assertThrows;
30  import static org.junit.jupiter.api.Assertions.assertTrue;
31  import static org.junit.jupiter.api.Assumptions.assumeTrue;
32  
33  import java.io.ByteArrayInputStream;
34  import java.io.ByteArrayOutputStream;
35  import java.io.File;
36  import java.io.IOException;
37  import java.nio.charset.StandardCharsets;
38  import java.nio.file.Files;
39  import java.nio.file.attribute.FileTime;
40  import java.time.Instant;
41  import java.util.Arrays;
42  import java.util.Collections;
43  import java.util.List;
44  import java.util.Random;
45  
46  import org.apache.commons.compress.AbstractTest;
47  import org.apache.commons.compress.archivers.zip.ZipEncodingHelper;
48  import org.apache.commons.lang3.StringUtils;
49  import org.apache.commons.lang3.SystemProperties;
50  import org.junit.jupiter.api.Test;
51  import org.junit.jupiter.api.condition.EnabledOnOs;
52  
53  class TarArchiveEntryTest implements TarConstants {
54  
55      private static final String OS = StringUtils.toRootLowerCase(SystemProperties.getOsName());
56      private static final String ROOT = OS.startsWith("windows") || OS.startsWith("netware") ? "C:\\" : "/";
57  
58      private void assertGnuMagic(final TarArchiveEntry t) {
59          assertEquals(MAGIC_GNU + VERSION_GNU_SPACE, readMagic(t));
60      }
61  
62      private void assertPosixMagic(final TarArchiveEntry t) {
63          assertEquals(MAGIC_POSIX + VERSION_POSIX, readMagic(t));
64      }
65  
66      private TarArchiveEntry createEntryForTimeTests() {
67          final TarArchiveEntry entry = new TarArchiveEntry("./times.txt");
68          entry.addPaxHeader("size", "1");
69          entry.addPaxHeader("mtime", "1647221103.5998539");
70          entry.addPaxHeader("atime", "1647221460.7069272");
71          entry.addPaxHeader("ctime", "1647221339.7005053");
72          entry.addPaxHeader("LIBARCHIVE.creationtime", "1647221340.7235090");
73          return entry;
74      }
75  
76      private String readMagic(final TarArchiveEntry t) {
77          final byte[] buf = new byte[512];
78          t.writeEntryHeader(buf);
79          return new String(buf, MAGIC_OFFSET, MAGICLEN + VERSIONLEN);
80      }
81  
82      @Test
83      void testExtraPaxHeaders() throws IOException {
84          final ByteArrayOutputStream bos = new ByteArrayOutputStream();
85          TarArchiveEntry entry = new TarArchiveEntry("./weasels");
86          try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
87              entry.addPaxHeader("APACHE.mustelida", "true");
88              entry.addPaxHeader("SCHILY.xattr.user.org.apache.weasels", "maximum weasels");
89              entry.addPaxHeader("size", "1");
90              assertEquals(2, entry.getExtraPaxHeaders().size(), "extra header count");
91              assertEquals("true", entry.getExtraPaxHeader("APACHE.mustelida"), "APACHE.mustelida");
92              assertEquals("maximum weasels", entry.getExtraPaxHeader("SCHILY.xattr.user.org.apache.weasels"), "SCHILY.xattr.user.org.apache.weasels");
93              assertEquals(entry.getSize(), 1, "size");
94  
95              tos.putArchiveEntry(entry);
96              tos.write('W');
97              tos.closeArchiveEntry();
98          }
99          assertNotEquals(0, entry.getExtraPaxHeaders().size(), "should have extra headers before clear");
100         entry.clearExtraPaxHeaders();
101         assertEquals(0, entry.getExtraPaxHeaders().size(), "extra headers should be empty after clear");
102         try (TarArchiveInputStream tis = new TarArchiveInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
103             entry = tis.getNextTarEntry();
104             assertNotNull(entry, "couldn't get entry");
105 
106             assertEquals(2, entry.getExtraPaxHeaders().size(), "extra header count");
107             assertEquals("true", entry.getExtraPaxHeader("APACHE.mustelida"), "APACHE.mustelida");
108             assertEquals("maximum weasels", entry.getExtraPaxHeader("SCHILY.xattr.user.org.apache.weasels"), "user.org.apache.weasels");
109 
110             assertEquals('W', tis.read());
111             assertTrue(tis.read() < 0, "should be at end of entry");
112 
113             assertNull(tis.getNextTarEntry(), "should be at end of file");
114         }
115     }
116 
117     /**
118      * JIRA issue SANDBOX-284
119      *
120      * @see "https://issues.apache.org/jira/browse/SANDBOX-284"
121      */
122     @Test
123     void testFileSystemRoot() {
124         final TarArchiveEntry t = new TarArchiveEntry(new File(ROOT));
125         assertEquals("/", t.getName());
126         assertEquals(TarConstants.LF_DIR, t.getLinkFlag());
127     }
128 
129     @Test
130     void testGetFileFromNonFileEntry() {
131         final TarArchiveEntry entry = new TarArchiveEntry("test.txt");
132         assertNull(entry.getFile());
133         assertNull(entry.getPath());
134     }
135 
136     @Test
137     void testGetOrderedSparseHeadersRejectsOverlappingStructs() throws Exception {
138         final TarArchiveEntry te = new TarArchiveEntry("test");
139         te.fillStarSparseData(Collections.singletonMap("SCHILY.realsize", "201"));
140         te.setSparseHeaders(Arrays.asList(new TarArchiveStructSparse(10, 5), new TarArchiveStructSparse(12, 1)));
141         assertThrows(IOException.class, () -> te.getOrderedSparseHeaders());
142     }
143 
144     @Test
145     void testGetOrderedSparseHeadersRejectsStructsPointingBeyondOutputEntry() throws Exception {
146         final TarArchiveEntry te = new TarArchiveEntry("test");
147         te.setSparseHeaders(Arrays.asList(new TarArchiveStructSparse(200, 2)));
148         te.fillStarSparseData(Collections.singletonMap("SCHILY.realsize", "201"));
149         assertThrows(IOException.class, () -> te.getOrderedSparseHeaders());
150     }
151 
152     @Test
153     void testGetOrderedSparseHeadersRejectsStructsWithReallyBigNumbers() throws Exception {
154         final TarArchiveEntry te = new TarArchiveEntry("test");
155         te.fillStarSparseData(Collections.singletonMap("SCHILY.realsize", String.valueOf(Long.MAX_VALUE)));
156         te.setSparseHeaders(Arrays.asList(new TarArchiveStructSparse(Long.MAX_VALUE, 2)));
157         assertThrows(IOException.class, () -> te.getOrderedSparseHeaders());
158     }
159 
160     @Test
161     void testGetOrderedSparseHeadersSortsAndFiltersSparseStructs() throws Exception {
162         final TarArchiveEntry te = new TarArchiveEntry("test");
163         // hacky way to set realSize
164         te.fillStarSparseData(Collections.singletonMap("SCHILY.realsize", "201"));
165         te.setSparseHeaders(Arrays.asList(new TarArchiveStructSparse(10, 2), new TarArchiveStructSparse(20, 0), new TarArchiveStructSparse(15, 1),
166                 new TarArchiveStructSparse(0, 0)));
167         final List<TarArchiveStructSparse> strs = te.getOrderedSparseHeaders();
168         assertEquals(3, strs.size());
169         assertEquals(10, strs.get(0).getOffset());
170         assertEquals(15, strs.get(1).getOffset());
171         assertEquals(20, strs.get(2).getOffset());
172     }
173 
174     @Test
175     void testLinkFlagConstructor() {
176         final TarArchiveEntry t = new TarArchiveEntry("/foo", LF_GNUTYPE_LONGNAME);
177         assertGnuMagic(t);
178         assertEquals("foo", t.getName());
179         assertEquals(TarConstants.LF_GNUTYPE_LONGNAME, t.getLinkFlag());
180     }
181 
182     @Test
183     void testLinkFlagConstructorWithFileFlag() {
184         final TarArchiveEntry t = new TarArchiveEntry("/foo", LF_NORMAL);
185         assertPosixMagic(t);
186         assertEquals("foo", t.getName());
187         assertEquals(TarConstants.LF_NORMAL, t.getLinkFlag());
188     }
189 
190     @Test
191     void testLinkFlagConstructorWithPreserve() {
192         final TarArchiveEntry t = new TarArchiveEntry("/foo", LF_GNUTYPE_LONGNAME, true);
193         assertGnuMagic(t);
194         assertEquals("/foo", t.getName());
195         assertEquals(TarConstants.LF_GNUTYPE_LONGNAME, t.getLinkFlag());
196     }
197 
198     @Test
199     @EnabledOnOs(org.junit.jupiter.api.condition.OS.LINUX)
200     void testLinuxFileInformationFromFile() throws IOException {
201         final TarArchiveEntry entry = new TarArchiveEntry(getFile("test1.xml"));
202         assertNotEquals(0, entry.getLongUserId());
203         assertNotEquals(0, entry.getLongGroupId());
204         assertNotEquals("", entry.getUserName());
205     }
206 
207     @Test
208     @EnabledOnOs(org.junit.jupiter.api.condition.OS.LINUX)
209     void testLinuxFileInformationFromPath() throws IOException {
210         final TarArchiveEntry entry = new TarArchiveEntry(getPath("test1.xml"));
211         assertNotEquals(0, entry.getLongUserId());
212         assertNotEquals(0, entry.getLongGroupId());
213         assertNotEquals("", entry.getUserName());
214     }
215 
216     @Test
217     void testMaxFileSize() {
218         final TarArchiveEntry t = new TarArchiveEntry("");
219         t.setSize(0);
220         t.setSize(1);
221         assertThrows(IllegalArgumentException.class, () -> t.setSize(-1));
222         t.setSize(077777777777L);
223         t.setSize(0100000000000L);
224     }
225 
226     @Test
227     void testNegativeOffsetInConstructorNotAllowed() {
228         // @formatter:off
229         final byte[] entryContent = (
230             "test1.xml\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
231             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
232             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
233             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
234             + "\u00000000644\u00000000765\u00000000765\u000000000001142\u000010716545626\u0000012260\u0000 0\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
235             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
236             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
237             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
238             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
239             + "\u0000ustar  \u0000tcurdt\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
240             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000tcurdt\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
241             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
242             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
243             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
244             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
245             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
246             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
247             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
248             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
249             + "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000").getBytes(UTF_8);
250         // @formatter:on
251         assertThrows(IllegalArgumentException.class,
252                 () -> new TarArchiveEntry(entryContent, ZipEncodingHelper.getZipEncoding(StandardCharsets.ISO_8859_1.name()), false, -1));
253     }
254 
255     @Test
256     void testNegativeOffsetInSetterNotAllowed() {
257         assertThrows(IllegalArgumentException.class, () -> new TarArchiveEntry("test").setDataOffset(-1));
258     }
259 
260     @Test
261     void testPaxTimeFieldsForInvalidValues() {
262         final String[] headerNames = { "LIBARCHIVE.creationtime", "atime", "mtime", "ctime" };
263         // @formatter:off
264         final String[] testValues = {
265                 // Generate a number with a very large integer or fractional component
266                 new Random().nextLong() + "." + String.join("",
267                         Collections.nCopies(15000, String.valueOf(Long.MAX_VALUE))),
268                 // These two examples use the exponent notation
269                 "9e9999999",
270                 "9E9999999",
271                 // These examples are out of range for java.time.Instant
272                 String.valueOf(Long.MAX_VALUE),
273                 String.valueOf(Long.MIN_VALUE)
274         };
275         // @formatter:on
276 
277         final TarArchiveEntry entry = new TarArchiveEntry("test.txt");
278         for (final String name : headerNames) {
279             for (final String value : testValues) {
280                 final Exception exp = assertThrows(IllegalArgumentException.class, () -> entry.addPaxHeader(name, value));
281                 assertTrue(exp.getCause().getMessage().startsWith("Corrupted PAX header. Time field value is invalid"));
282             }
283         }
284     }
285 
286     @Test
287     void testPreservesDriveSpecOnWindowsAndNetwareIfAskedTo() {
288         assumeTrue("C:\\".equals(ROOT));
289         TarArchiveEntry t = new TarArchiveEntry(ROOT + "foo.txt", true);
290         assertEquals("C:/foo.txt", t.getName());
291         assertEquals(TarConstants.LF_NORMAL, t.getLinkFlag());
292         t = new TarArchiveEntry(ROOT + "foo.txt", LF_GNUTYPE_LONGNAME, true);
293         assertEquals("C:/foo.txt", t.getName());
294         assertEquals(TarConstants.LF_GNUTYPE_LONGNAME, t.getLinkFlag());
295     }
296 
297     @Test
298     void testShouldNotWriteTimePaxHeadersByDefault() throws IOException {
299         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
300         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
301             final TarArchiveEntry entry = createEntryForTimeTests();
302             tos.putArchiveEntry(entry);
303             tos.write('W');
304             tos.closeArchiveEntry();
305         }
306         try (TarArchiveInputStream tis = new TarArchiveInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
307             final TarArchiveEntry entry = tis.getNextTarEntry();
308             assertNotNull(entry, "couldn't get entry");
309 
310             assertEquals(0, entry.getExtraPaxHeaders().size(), "extra header count");
311             assertNull(entry.getExtraPaxHeader("mtime"), "mtime");
312             assertNull(entry.getExtraPaxHeader("atime"), "atime");
313             assertNull(entry.getExtraPaxHeader("ctime"), "ctime");
314             assertNull(entry.getExtraPaxHeader("LIBARCHIVE.creationtime"), "birthtime");
315             assertEquals(toFileTime("2022-03-14T01:25:03Z"), entry.getLastModifiedTime(), "mtime");
316             assertNull(entry.getLastAccessTime(), "atime");
317             assertNull(entry.getStatusChangeTime(), "ctime");
318             assertNull(entry.getCreationTime(), "birthtime");
319 
320             assertEquals('W', tis.read());
321             assertTrue(tis.read() < 0, "should be at end of entry");
322 
323             assertNull(tis.getNextTarEntry(), "should be at end of file");
324         }
325     }
326 
327     @Test
328     void testShouldParseTimePaxHeadersAndNotCountAsExtraPaxHeaders() {
329         final TarArchiveEntry entry = createEntryForTimeTests();
330         assertEquals(0, entry.getExtraPaxHeaders().size(), "extra header count");
331         assertNull(entry.getExtraPaxHeader("size"), "size");
332         assertNull(entry.getExtraPaxHeader("mtime"), "mtime");
333         assertNull(entry.getExtraPaxHeader("atime"), "atime");
334         assertNull(entry.getExtraPaxHeader("ctime"), "ctime");
335         assertNull(entry.getExtraPaxHeader("LIBARCHIVE.creationtime"), "birthtime");
336         assertEquals(entry.getSize(), 1, "size");
337         assertEquals(toFileTime("2022-03-14T01:25:03.599853900Z"), entry.getLastModifiedTime(), "mtime");
338         assertEquals(toFileTime("2022-03-14T01:31:00.706927200Z"), entry.getLastAccessTime(), "atime");
339         assertEquals(toFileTime("2022-03-14T01:28:59.700505300Z"), entry.getStatusChangeTime(), "ctime");
340         assertEquals(toFileTime("2022-03-14T01:29:00.723509000Z"), entry.getCreationTime(), "birthtime");
341     }
342 
343     @Test
344     void testShouldWriteTimesAsPaxHeadersForPosixMode() throws IOException {
345         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
346         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
347             final TarArchiveEntry entry = createEntryForTimeTests();
348             tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
349             tos.putArchiveEntry(entry);
350             tos.write('W');
351             tos.closeArchiveEntry();
352         }
353         try (TarArchiveInputStream tis = new TarArchiveInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
354             final TarArchiveEntry entry = tis.getNextTarEntry();
355             assertNotNull(entry, "couldn't get entry");
356 
357             assertEquals(0, entry.getExtraPaxHeaders().size(), "extra header count");
358             assertNull(entry.getExtraPaxHeader("mtime"), "mtime");
359             assertNull(entry.getExtraPaxHeader("atime"), "atime");
360             assertNull(entry.getExtraPaxHeader("ctime"), "ctime");
361             assertNull(entry.getExtraPaxHeader("LIBARCHIVE.creationtime"), "birthtime");
362             assertEquals(toFileTime("2022-03-14T01:25:03.599853900Z"), entry.getLastModifiedTime(), "mtime");
363             assertEquals(toFileTime("2022-03-14T01:31:00.706927200Z"), entry.getLastAccessTime(), "atime");
364             assertEquals(toFileTime("2022-03-14T01:28:59.700505300Z"), entry.getStatusChangeTime(), "ctime");
365             assertEquals(toFileTime("2022-03-14T01:29:00.723509000Z"), entry.getCreationTime(), "birthtime");
366 
367             assertEquals('W', tis.read());
368             assertTrue(tis.read() < 0, "should be at end of entry");
369 
370             assertNull(tis.getNextTarEntry(), "should be at end of file");
371         }
372     }
373 
374     @Test
375     void testShouldWriteTimesAsPaxHeadersForPosixModeAndCreationTimeShouldBeUsedAsCtime() throws IOException {
376         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
377         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
378             final TarArchiveEntry entry = createEntryForTimeTests();
379             entry.setStatusChangeTime(null);
380             tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
381             tos.putArchiveEntry(entry);
382             tos.write('W');
383             tos.closeArchiveEntry();
384         }
385         try (TarArchiveInputStream tis = new TarArchiveInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
386             final TarArchiveEntry entry = tis.getNextTarEntry();
387             assertNotNull(entry, "couldn't get entry");
388 
389             assertEquals(0, entry.getExtraPaxHeaders().size(), "extra header count");
390             assertNull(entry.getExtraPaxHeader("mtime"), "mtime");
391             assertNull(entry.getExtraPaxHeader("atime"), "atime");
392             assertNull(entry.getExtraPaxHeader("ctime"), "ctime");
393             assertNull(entry.getExtraPaxHeader("LIBARCHIVE.creationtime"), "birthtime");
394             assertEquals(toFileTime("2022-03-14T01:25:03.599853900Z"), entry.getLastModifiedTime(), "mtime");
395             assertEquals(toFileTime("2022-03-14T01:31:00.706927200Z"), entry.getLastAccessTime(), "atime");
396             assertEquals(toFileTime("2022-03-14T01:29:00.723509000Z"), entry.getStatusChangeTime(), "ctime");
397             assertEquals(toFileTime("2022-03-14T01:29:00.723509000Z"), entry.getCreationTime(), "birthtime");
398 
399             assertEquals('W', tis.read());
400             assertTrue(tis.read() < 0, "should be at end of entry");
401 
402             assertNull(tis.getNextTarEntry(), "should be at end of file");
403         }
404     }
405 
406     @Test
407     void testShouldWriteTimesForStarMode() throws IOException {
408         final ByteArrayOutputStream bos = new ByteArrayOutputStream();
409         try (TarArchiveOutputStream tos = new TarArchiveOutputStream(bos)) {
410             final TarArchiveEntry entry = createEntryForTimeTests();
411             tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
412             tos.putArchiveEntry(entry);
413             tos.write('W');
414             tos.closeArchiveEntry();
415         }
416         try (TarArchiveInputStream tis = new TarArchiveInputStream(new ByteArrayInputStream(bos.toByteArray()))) {
417             final TarArchiveEntry entry = tis.getNextTarEntry();
418             assertNotNull(entry, "couldn't get entry");
419 
420             assertEquals(0, entry.getExtraPaxHeaders().size(), "extra header count");
421             assertNull(entry.getExtraPaxHeader("mtime"), "mtime");
422             assertNull(entry.getExtraPaxHeader("atime"), "atime");
423             assertNull(entry.getExtraPaxHeader("ctime"), "ctime");
424             assertNull(entry.getExtraPaxHeader("LIBARCHIVE.creationtime"), "birthtime");
425             assertEquals(toFileTime("2022-03-14T01:25:03Z"), entry.getLastModifiedTime(), "mtime");
426             assertEquals(toFileTime("2022-03-14T01:31:00Z"), entry.getLastAccessTime(), "atime");
427             assertEquals(toFileTime("2022-03-14T01:28:59Z"), entry.getStatusChangeTime(), "ctime");
428             assertNull(entry.getCreationTime(), "birthtime");
429 
430             assertEquals('W', tis.read());
431             assertTrue(tis.read() < 0, "should be at end of entry");
432 
433             assertNull(tis.getNextTarEntry(), "should be at end of file");
434         }
435     }
436 
437     @Test
438     void testTarFileWithFSRoot() throws IOException {
439         final File f = File.createTempFile("taetest", ".tar");
440         TarArchiveEntry entry = new TarArchiveEntry(new File(ROOT));
441         try {
442             try (TarArchiveOutputStream tout = new TarArchiveOutputStream(Files.newOutputStream(f.toPath()))) {
443                 tout.putArchiveEntry(entry);
444                 tout.closeArchiveEntry();
445                 entry = new TarArchiveEntry(new File(new File(ROOT), "foo.txt"));
446                 entry.setSize(6);
447                 tout.putArchiveEntry(entry);
448                 tout.write(new byte[] { 'h', 'e', 'l', 'l', 'o', ' ' });
449                 tout.closeArchiveEntry();
450                 entry = new TarArchiveEntry(new File(new File(ROOT), "bar.txt").getAbsolutePath());
451                 entry.setSize(5);
452                 tout.putArchiveEntry(entry);
453                 tout.write(new byte[] { 'w', 'o', 'r', 'l', 'd' });
454                 tout.closeArchiveEntry();
455                 entry = new TarArchiveEntry("dummy");
456                 entry.setName(new File(new File(ROOT), "baz.txt").getAbsolutePath());
457                 entry.setSize(1);
458                 tout.putArchiveEntry(entry);
459                 tout.write(new byte[] { '!' });
460                 tout.closeArchiveEntry();
461             }
462             try (TarArchiveInputStream tin = new TarArchiveInputStream(Files.newInputStream(f.toPath()))) {
463                 // tin.setDebug(true);
464                 entry = tin.getNextTarEntry();
465                 assertNotNull(entry);
466                 assertEquals("/", entry.getName());
467                 assertEquals(TarConstants.LF_DIR, entry.getLinkFlag());
468                 assertTrue(entry.isCheckSumOK());
469                 assertTrue(entry.isTypeFlagUstar());
470                 entry = tin.getNextTarEntry();
471                 assertNotNull(entry);
472                 assertEquals("foo.txt", entry.getName());
473                 assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
474                 assertTrue(entry.isCheckSumOK());
475                 assertTrue(entry.isTypeFlagUstar());
476                 entry = tin.getNextTarEntry();
477                 assertNotNull(entry);
478                 assertEquals("bar.txt", entry.getName());
479                 assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
480                 assertTrue(entry.isCheckSumOK());
481                 assertTrue(entry.isTypeFlagUstar());
482                 entry = tin.getNextTarEntry();
483                 assertNotNull(entry);
484                 assertEquals("baz.txt", entry.getName());
485                 assertEquals(TarConstants.LF_NORMAL, entry.getLinkFlag());
486                 assertTrue(entry.isCheckSumOK());
487                 assertTrue(entry.isTypeFlagUstar());
488             }
489         } finally {
490             AbstractTest.forceDelete(f);
491         }
492     }
493 
494     @Test
495     @EnabledOnOs(org.junit.jupiter.api.condition.OS.WINDOWS)
496     void testWindowsFileInformationFromFile() throws IOException {
497         final TarArchiveEntry entry = new TarArchiveEntry(getFile("test1.xml"));
498         assertNotEquals("", entry.getUserName());
499         assertTrue(entry.isTypeFlagUstar());
500     }
501 
502     @Test
503     @EnabledOnOs(org.junit.jupiter.api.condition.OS.WINDOWS)
504     void testWindowsFileInformationFromPath() throws IOException {
505         final TarArchiveEntry entry = new TarArchiveEntry(getPath("test1.xml"));
506         assertNotEquals("", entry.getUserName());
507         assertTrue(entry.isTypeFlagUstar());
508     }
509 
510     private FileTime toFileTime(final String text) {
511         return FileTime.from(Instant.parse(text));
512     }
513 }