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  package org.apache.commons.compress.archivers.tar;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.UncheckedIOException;
24  import java.math.BigDecimal;
25  import java.nio.file.DirectoryStream;
26  import java.nio.file.Files;
27  import java.nio.file.LinkOption;
28  import java.nio.file.Path;
29  import java.nio.file.attribute.BasicFileAttributes;
30  import java.nio.file.attribute.DosFileAttributes;
31  import java.nio.file.attribute.FileTime;
32  import java.nio.file.attribute.PosixFileAttributes;
33  import java.time.DateTimeException;
34  import java.time.Instant;
35  import java.util.ArrayList;
36  import java.util.Collections;
37  import java.util.Comparator;
38  import java.util.Date;
39  import java.util.HashMap;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.Objects;
43  import java.util.Set;
44  import java.util.regex.Pattern;
45  import java.util.stream.Collectors;
46  
47  import org.apache.commons.compress.archivers.ArchiveEntry;
48  import org.apache.commons.compress.archivers.EntryStreamOffsets;
49  import org.apache.commons.compress.archivers.zip.ZipEncoding;
50  import org.apache.commons.compress.utils.ArchiveUtils;
51  import org.apache.commons.compress.utils.IOUtils;
52  import org.apache.commons.compress.utils.ParsingUtils;
53  import org.apache.commons.io.file.attribute.FileTimes;
54  import org.apache.commons.lang3.StringUtils;
55  import org.apache.commons.lang3.SystemProperties;
56  
57  /**
58   * An entry in a <a href="https://www.gnu.org/software/tar/manual/html_node/Standard.html">Tar archive</a>.
59   * It consists of the entry's header, as well as the entry's File. Entries can be instantiated in one of three
60   * ways, depending on how they are to be used.
61   * <p>
62   * TarEntries that are created from the header bytes read from an archive are instantiated with the {@link TarArchiveEntry#TarArchiveEntry(byte[])} constructor.
63   * These entries will be used when extracting from or listing the contents of an archive. These entries have their header filled in using the header bytes. They
64   * also set the File to null, since they reference an archive entry not a file.
65   * </p>
66   * <p>
67   * TarEntries that are created from Files that are to be written into an archive are instantiated with the {@link TarArchiveEntry#TarArchiveEntry(File)} or
68   * {@link TarArchiveEntry#TarArchiveEntry(Path)} constructor. These entries have their header filled in using the File's information. They also keep a reference
69   * to the File for convenience when writing entries.
70   * </p>
71   * <p>
72   * Finally, TarEntries can be constructed from nothing but a name. This allows the programmer to construct the entry by hand, for instance when only an
73   * InputStream is available for writing to the archive, and the header information is constructed from other information. In this case the header fields are set
74   * to defaults and the File is set to null.
75   * </p>
76   * <p>
77   * The C structure for a Tar Entry's header is:
78   * </p>
79   * <pre>
80   * struct header {
81   *   char name[100];     // TarConstants.NAMELEN    - offset   0
82   *   char mode[8];       // TarConstants.MODELEN    - offset 100
83   *   char uid[8];        // TarConstants.UIDLEN     - offset 108
84   *   char gid[8];        // TarConstants.GIDLEN     - offset 116
85   *   char size[12];      // TarConstants.SIZELEN    - offset 124
86   *   char mtime[12];     // TarConstants.MODTIMELEN - offset 136
87   *   char chksum[8];     // TarConstants.CHKSUMLEN  - offset 148
88   *   char linkflag[1];   //                         - offset 156
89   *   char linkname[100]; // TarConstants.NAMELEN    - offset 157
90   *   // The following fields are only present in new-style POSIX tar archives:
91   *   char magic[6];      // TarConstants.MAGICLEN   - offset 257
92   *   char version[2];    // TarConstants.VERSIONLEN - offset 263
93   *   char uname[32];     // TarConstants.UNAMELEN   - offset 265
94   *   char gname[32];     // TarConstants.GNAMELEN   - offset 297
95   *   char devmajor[8];   // TarConstants.DEVLEN     - offset 329
96   *   char devminor[8];   // TarConstants.DEVLEN     - offset 337
97   *   char prefix[155];   // TarConstants.PREFIXLEN  - offset 345
98   *   // Used if "name" field is not long enough to hold the path
99   *   char pad[12];       // NULs                    - offset 500
100  * } header;
101  * </pre>
102  * <p>
103  * All unused bytes are set to null. New-style GNU tar files are slightly different from the above. For values of size larger than 077777777777L (11 7s) or uid
104  * and gid larger than 07777777L (7 7s) the sign bit of the first byte is set, and the rest of the field is the binary representation of the number. See
105  * {@link TarUtils#parseOctalOrBinary(byte[], int, int)}.
106  * <p>
107  * The C structure for a old GNU Tar Entry's header is:
108  * </p>
109  * <pre>
110  * struct oldgnu_header {
111  *   char unused_pad1[345]; // TarConstants.PAD1LEN_GNU       - offset 0
112  *   char atime[12];        // TarConstants.ATIMELEN_GNU      - offset 345
113  *   char ctime[12];        // TarConstants.CTIMELEN_GNU      - offset 357
114  *   char offset[12];       // TarConstants.OFFSETLEN_GNU     - offset 369
115  *   char longnames[4];     // TarConstants.LONGNAMESLEN_GNU  - offset 381
116  *   char unused_pad2;      // TarConstants.PAD2LEN_GNU       - offset 385
117  *   struct sparse sp[4];   // TarConstants.SPARSELEN_GNU     - offset 386
118  *   char isextended;       // TarConstants.ISEXTENDEDLEN_GNU - offset 482
119  *   char realsize[12];     // TarConstants.REALSIZELEN_GNU   - offset 483
120  *   char unused_pad[17];   // TarConstants.PAD3LEN_GNU       - offset 495
121  * };
122  * </pre>
123  * <p>
124  * Whereas, "struct sparse" is:
125  * </p>
126  * <pre>
127  * struct sparse {
128  *   char offset[12];   // offset 0
129  *   char numbytes[12]; // offset 12
130  * };
131  * </pre>
132  * <p>
133  * The C structure for a xstar (Jörg Schilling star) Tar Entry's header is:
134  * </p>
135  * <pre>
136  * struct star_header {
137  *   char name[100];     // offset   0
138  *   char mode[8];       // offset 100
139  *   char uid[8];        // offset 108
140  *   char gid[8];        // offset 116
141  *   char size[12];      // offset 124
142  *   char mtime[12];     // offset 136
143  *   char chksum[8];     // offset 148
144  *   char typeflag;      // offset 156
145  *   char linkname[100]; // offset 157
146  *   char magic[6];      // offset 257
147  *   char version[2];    // offset 263
148  *   char uname[32];     // offset 265
149  *   char gname[32];     // offset 297
150  *   char devmajor[8];   // offset 329
151  *   char devminor[8];   // offset 337
152  *   char prefix[131];   // offset 345
153  *   char atime[12];     // offset 476
154  *   char ctime[12];     // offset 488
155  *   char mfill[8];      // offset 500
156  *   char xmagic[4];     // offset 508  "tar\0"
157  * };
158  * </pre>
159  * <p>
160  * which is identical to new-style POSIX up to the first 130 bytes of the prefix.
161  * </p>
162  * <p>
163  * The C structure for the xstar-specific parts of a xstar Tar Entry's header is:
164  * </p>
165  * <pre>
166  * struct xstar_in_header {
167  *   char fill[345];         // offset 0     Everything before t_prefix
168  *   char prefix[1];         // offset 345   Prefix for t_name
169  *   char fill2;             // offset 346
170  *   char fill3[8];          // offset 347
171  *   char isextended;        // offset 355
172  *   struct sparse sp[SIH];  // offset 356   8 x 12
173  *   char realsize[12];      // offset 452   Real size for sparse data
174  *   char offset[12];        // offset 464   Offset for multivolume data
175  *   char atime[12];         // offset 476
176  *   char ctime[12];         // offset 488
177  *   char mfill[8];          // offset 500
178  *   char xmagic[4];         // offset 508   "tar\0"
179  * };
180  * </pre>
181  *
182  * @NotThreadSafe
183  */
184 public class TarArchiveEntry implements ArchiveEntry, TarConstants, EntryStreamOffsets {
185 
186     private static final TarArchiveEntry[] EMPTY_TAR_ARCHIVE_ENTRY_ARRAY = {};
187 
188     /**
189      * Value used to indicate unknown mode, user/groupids, device numbers and modTime when parsing a file in lenient mode and the archive contains illegal
190      * fields.
191      *
192      * @since 1.19
193      */
194     public static final long UNKNOWN = -1L;
195 
196     /** Maximum length of a user's name in the tar file */
197     public static final int MAX_NAMELEN = 31;
198 
199     /** Default permissions bits for directories */
200     public static final int DEFAULT_DIR_MODE = 040755;
201 
202     /** Default permissions bits for files */
203     public static final int DEFAULT_FILE_MODE = 0100644;
204 
205     /**
206      * Convert millis to seconds
207      *
208      * @deprecated Unused.
209      */
210     @Deprecated
211     public static final int MILLIS_PER_SECOND = 1000;
212 
213     /**
214      * Regular expression pattern for validating values in pax extended header file time fields. These fields contain two numeric values (seconds and sub-second
215      * values) as per this definition: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_05
216      * <p>
217      * Since they are parsed into long values, maximum length of each is the same as Long.MAX_VALUE which is 19 digits.
218      * </p>
219      */
220     private static final Pattern PAX_EXTENDED_HEADER_FILE_TIMES_PATTERN = Pattern.compile("-?\\d{1,19}(?:\\.\\d{1,19})?");
221 
222     private static FileTime fileTimeFromOptionalSeconds(final long seconds) {
223         return seconds <= 0 ? null : FileTimes.fromUnixTime(seconds);
224     }
225 
226     /**
227      * Strips Windows' drive letter as well as any leading slashes, turns path separators into forward slashes.
228      */
229     private static String normalizeFileName(String fileName, final boolean preserveAbsolutePath) {
230         if (!preserveAbsolutePath) {
231             final String property = SystemProperties.getOsName();
232             if (property != null) {
233                 final String osName = StringUtils.toRootLowerCase(property);
234 
235                 // Strip off drive letters!
236                 // REVIEW Would a better check be "(File.separator == '\')"?
237 
238                 if (osName.startsWith("windows")) {
239                     if (fileName.length() > 2) {
240                         final char ch1 = fileName.charAt(0);
241                         final char ch2 = fileName.charAt(1);
242 
243                         if (ch2 == ':' && (ch1 >= 'a' && ch1 <= 'z' || ch1 >= 'A' && ch1 <= 'Z')) {
244                             fileName = fileName.substring(2);
245                         }
246                     }
247                 } else if (osName.contains("netware")) {
248                     final int colon = fileName.indexOf(':');
249                     if (colon != -1) {
250                         fileName = fileName.substring(colon + 1);
251                     }
252                 }
253             }
254         }
255 
256         fileName = fileName.replace(File.separatorChar, '/');
257 
258         // No absolute pathnames
259         // Windows (and Posix?) paths can start with "\\NetworkDrive\",
260         // so we loop on starting /'s.
261         while (!preserveAbsolutePath && fileName.startsWith("/")) {
262             fileName = fileName.substring(1);
263         }
264         return fileName;
265     }
266 
267     private static Instant parseInstantFromDecimalSeconds(final String value) throws IOException {
268         // Validate field values to prevent denial of service attacks with BigDecimal values (see JDK-6560193)
269         if (!PAX_EXTENDED_HEADER_FILE_TIMES_PATTERN.matcher(value).matches()) {
270             throw new IOException("Corrupted PAX header. Time field value is invalid '" + value + "'");
271         }
272 
273         final BigDecimal epochSeconds = new BigDecimal(value);
274         final long seconds = epochSeconds.longValue();
275         final long nanos = epochSeconds.remainder(BigDecimal.ONE).movePointRight(9).longValue();
276         try {
277             return Instant.ofEpochSecond(seconds, nanos);
278         } catch (DateTimeException | ArithmeticException e) {
279             // DateTimeException: Thrown if the instant exceeds the maximum or minimum instant.
280             // ArithmeticException: Thrown if numeric overflow occurs.
281             throw new IOException("Corrupted PAX header. Time field value is invalid '" + value + "'", e);
282         }
283     }
284 
285     /** The entry's name. */
286     private String name = "";
287 
288     /** Whether to allow leading slashes or drive names inside the name */
289     private final boolean preserveAbsolutePath;
290 
291     /** The entry's permission mode. */
292     private int mode;
293 
294     /** The entry's user id. */
295     private long userId;
296 
297     /** The entry's group id. */
298     private long groupId;
299 
300     /** The entry's size. */
301     private long size;
302 
303     /**
304      * The entry's modification time. Corresponds to the POSIX {@code mtime} attribute.
305      */
306     private FileTime mTime;
307 
308     /**
309      * The entry's status change time. Corresponds to the POSIX {@code ctime} attribute.
310      *
311      * @since 1.22
312      */
313     private FileTime cTime;
314 
315     /**
316      * The entry's last access time. Corresponds to the POSIX {@code atime} attribute.
317      *
318      * @since 1.22
319      */
320     private FileTime aTime;
321 
322     /**
323      * The entry's creation time. Corresponds to the POSIX {@code birthtime} attribute.
324      *
325      * @since 1.22
326      */
327     private FileTime birthTime;
328 
329     /** If the header checksum is reasonably correct. */
330     private boolean checkSumOK;
331 
332     /** The entry's link flag. */
333     private byte linkFlag;
334 
335     /** The entry's link name. */
336     private String linkName = "";
337 
338     /** The entry's magic tag. */
339     private String magic = MAGIC_POSIX;
340 
341     /** The version of the format */
342     private String version = VERSION_POSIX;
343 
344     /** The entry's user name. */
345     private String userName;
346 
347     /** The entry's group name. */
348     private String groupName = "";
349 
350     /** The entry's major device number. */
351     private int devMajor;
352 
353     /** The entry's minor device number. */
354     private int devMinor;
355 
356     /** The sparse headers in tar */
357     private List<TarArchiveStructSparse> sparseHeaders;
358 
359     /** If an extension sparse header follows. */
360     private boolean isExtended;
361 
362     /** The entry's real size in case of a sparse file. */
363     private long realSize;
364 
365     /** Is this entry a GNU sparse entry using one of the PAX formats? */
366     private boolean paxGNUSparse;
367 
368     /**
369      * is this entry a GNU sparse entry using 1.X PAX formats? the sparse headers of 1.x PAX Format is stored in file data block
370      */
371     private boolean paxGNU1XSparse;
372 
373     /** Is this entry a star sparse entry using the PAX header? */
374     private boolean starSparse;
375 
376     /** The entry's file reference */
377     private final Path file;
378 
379     /** The entry's file linkOptions */
380     private final LinkOption[] linkOptions;
381 
382     /** Extra, user supplied pax headers */
383     private final Map<String, String> extraPaxHeaders = new HashMap<>();
384 
385     private long dataOffset = OFFSET_UNKNOWN;
386 
387     /**
388      * Constructs an empty entry and prepares the header values.
389      */
390     private TarArchiveEntry(final boolean preserveAbsolutePath) {
391         String user = SystemProperties.getUserName("");
392         if (user.length() > MAX_NAMELEN) {
393             user = user.substring(0, MAX_NAMELEN);
394         }
395         this.userName = user;
396         this.file = null;
397         this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS;
398         this.preserveAbsolutePath = preserveAbsolutePath;
399     }
400 
401     /**
402      * Constructs an entry from an archive's header bytes. File is set to null.
403      *
404      * @param headerBuf The header bytes from a tar archive entry.
405      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
406      */
407     public TarArchiveEntry(final byte[] headerBuf) {
408         this(false);
409         parseTarHeader(headerBuf);
410     }
411 
412     /**
413      * Constructs an entry from an archive's header bytes. File is set to null.
414      *
415      * @param headerBuf The header bytes from a tar archive entry.
416      * @param encoding  encoding to use for file names
417      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
418      * @throws IOException              on error
419      * @since 1.4
420      */
421     public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding) throws IOException {
422         this(headerBuf, encoding, false);
423     }
424 
425     /**
426      * Constructs an entry from an archive's header bytes. File is set to null.
427      *
428      * @param headerBuf The header bytes from a tar archive entry.
429      * @param encoding  encoding to use for file names
430      * @param lenient   when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
431      *                  {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
432      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
433      * @throws IOException              on error
434      * @since 1.19
435      */
436     public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient) throws IOException {
437         this(Collections.emptyMap(), headerBuf, encoding, lenient);
438     }
439 
440     /**
441      * Constructs an entry from an archive's header bytes for random access tar. File is set to null.
442      *
443      * @param headerBuf  the header bytes from a tar archive entry.
444      * @param encoding   encoding to use for file names.
445      * @param lenient    when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
446      *                   {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
447      * @param dataOffset position of the entry data in the random access file.
448      * @throws IllegalArgumentException if any of the numeric fields have an invalid format.
449      * @throws IOException              on error.
450      * @since 1.21
451      */
452     public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient, final long dataOffset) throws IOException {
453         this(headerBuf, encoding, lenient);
454         setDataOffset(dataOffset);
455     }
456 
457     /**
458      * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file. The name is set from the normalized
459      * file path.
460      * <p>
461      * The entry's name will be the value of the {@code file}'s path with all file separators replaced by forward slashes and leading slashes as well as Windows
462      * drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
463      * </p>
464      * <p>
465      * Note: Since 1.21 this internally uses the same code as the TarArchiveEntry constructors with a {@link Path} as parameter. But all thrown exceptions are
466      * ignored. If handling those exceptions is needed consider switching to the path constructors.
467      * </p>
468      *
469      * @param file The file that the entry represents.
470      */
471     public TarArchiveEntry(final File file) {
472         this(file, file.getPath());
473     }
474 
475     /**
476      * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file.
477      * <p>
478      * The entry's name will be the value of the {@code fileName} argument with all file separators replaced by forward slashes and leading slashes as well as
479      * Windows drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
480      * </p>
481      * <p>
482      * Note: Since 1.21 this internally uses the same code as the TarArchiveEntry constructors with a {@link Path} as parameter. But all thrown exceptions are
483      * ignored. If handling those exceptions is needed consider switching to the path constructors.
484      * </p>
485      *
486      * @param file     The file that the entry represents.
487      * @param fileName the name to be used for the entry.
488      */
489     public TarArchiveEntry(final File file, final String fileName) {
490         final String normalizedName = normalizeFileName(fileName, false);
491         this.file = file.toPath();
492         this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS;
493         try {
494             readFileMode(this.file, normalizedName);
495         } catch (final IOException e) {
496             // Ignore exceptions from NIO for backwards compatibility
497             // Fallback to get size of file if it's no directory to the old file api
498             if (!file.isDirectory()) {
499                 this.size = file.length();
500             }
501         }
502         this.userName = "";
503         try {
504             readOsSpecificProperties(this.file);
505         } catch (final IOException e) {
506             // Ignore exceptions from NIO for backwards compatibility
507             // Fallback to get the last modified date of the file from the old file api
508             this.mTime = FileTime.fromMillis(file.lastModified());
509         }
510         preserveAbsolutePath = false;
511     }
512 
513     /**
514      * Constructs an entry from an archive's header bytes. File is set to null.
515      *
516      * @param globalPaxHeaders the parsed global PAX headers, or null if this is the first one.
517      * @param headerBuf        The header bytes from a tar archive entry.
518      * @param encoding         encoding to use for file names
519      * @param lenient          when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
520      *                         {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
521      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
522      * @throws IOException              on error
523      * @since 1.22
524      */
525     public TarArchiveEntry(final Map<String, String> globalPaxHeaders, final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient)
526             throws IOException {
527         this(false);
528         parseTarHeader(globalPaxHeaders, headerBuf, encoding, false, lenient);
529     }
530 
531     /**
532      * Constructs an entry from an archive's header bytes for random access tar. File is set to null.
533      *
534      * @param globalPaxHeaders the parsed global PAX headers, or null if this is the first one.
535      * @param headerBuf        the header bytes from a tar archive entry.
536      * @param encoding         encoding to use for file names.
537      * @param lenient          when set to true illegal values for group/userid, mode, device numbers and timestamp will be ignored and the fields set to
538      *                         {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead.
539      * @param dataOffset       position of the entry data in the random access file.
540      * @throws IllegalArgumentException if any of the numeric fields have an invalid format.
541      * @throws IOException              on error.
542      * @since 1.22
543      */
544     public TarArchiveEntry(final Map<String, String> globalPaxHeaders, final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient,
545             final long dataOffset) throws IOException {
546         this(globalPaxHeaders, headerBuf, encoding, lenient);
547         setDataOffset(dataOffset);
548     }
549 
550     /**
551      * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file. The name is set from the normalized
552      * file path.
553      * <p>
554      * The entry's name will be the value of the {@code file}'s path with all file separators replaced by forward slashes and leading slashes as well as Windows
555      * drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
556      * </p>
557      *
558      * @param file The file that the entry represents.
559      * @throws IOException if an I/O error occurs
560      * @since 1.21
561      */
562     public TarArchiveEntry(final Path file) throws IOException {
563         this(file, file.toString());
564     }
565 
566     /**
567      * Constructs an entry for a file. File is set to file, and the header is constructed from information from the file.
568      * <p>
569      * The entry's name will be the value of the {@code fileName} argument with all file separators replaced by forward slashes and leading slashes as well as
570      * Windows drive letters stripped. The name will end in a slash if the {@code file} represents a directory.
571      * </p>
572      *
573      * @param file        The file that the entry represents.
574      * @param fileName    the name to be used for the entry.
575      * @param linkOptions options indicating how symbolic links are handled.
576      * @throws IOException if an I/O error occurs
577      * @since 1.21
578      */
579     public TarArchiveEntry(final Path file, final String fileName, final LinkOption... linkOptions) throws IOException {
580         final String normalizedName = normalizeFileName(fileName, false);
581         this.file = file;
582         this.linkOptions = linkOptions == null ? IOUtils.EMPTY_LINK_OPTIONS : linkOptions;
583         readFileMode(file, normalizedName, linkOptions);
584         this.userName = "";
585         readOsSpecificProperties(file);
586         preserveAbsolutePath = false;
587     }
588 
589     /**
590      * Constructs an entry with only a name. This allows the programmer to construct the entry's header "by hand". File is set to null.
591      * <p>
592      * The entry's name will be the value of the {@code name} argument with all file separators replaced by forward slashes and leading slashes as well as
593      * Windows drive letters stripped.
594      * </p>
595      *
596      * @param name the entry name
597      */
598     public TarArchiveEntry(final String name) {
599         this(name, false);
600     }
601 
602     /**
603      * Constructs an entry with only a name. This allows the programmer to construct the entry's header "by hand". File is set to null.
604      * <p>
605      * The entry's name will be the value of the {@code name} argument with all file separators replaced by forward slashes. Leading slashes and Windows drive
606      * letters are stripped if {@code preserveAbsolutePath} is {@code false}.
607      * </p>
608      *
609      * @param name                 the entry name
610      * @param preserveAbsolutePath whether to allow leading slashes or drive letters in the name.
611      * @since 1.1
612      */
613     public TarArchiveEntry(String name, final boolean preserveAbsolutePath) {
614         this(preserveAbsolutePath);
615         name = normalizeFileName(name, preserveAbsolutePath);
616         final boolean isDir = name.endsWith("/");
617         this.name = name;
618         this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
619         this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
620         this.mTime = FileTime.from(Instant.now());
621         this.userName = "";
622     }
623 
624     /**
625      * Constructs an entry with a name and a link flag.
626      * <p>
627      * The entry's name will be the value of the {@code name} argument with all file separators replaced by forward slashes and leading slashes as well as
628      * Windows drive letters stripped.
629      * </p>
630      *
631      * @param name     the entry name
632      * @param linkFlag the entry link flag.
633      */
634     public TarArchiveEntry(final String name, final byte linkFlag) {
635         this(name, linkFlag, false);
636     }
637 
638     /**
639      * Constructs an entry with a name and a link flag.
640      * <p>
641      * The entry's name will be the value of the {@code name} argument with all file separators replaced by forward slashes. Leading slashes and Windows drive
642      * letters are stripped if {@code preserveAbsolutePath} is {@code false}.
643      * </p>
644      *
645      * @param name                 the entry name
646      * @param linkFlag             the entry link flag.
647      * @param preserveAbsolutePath whether to allow leading slashes or drive letters in the name.
648      * @since 1.5
649      */
650     public TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveAbsolutePath) {
651         this(name, preserveAbsolutePath);
652         this.linkFlag = linkFlag;
653         if (linkFlag == LF_GNUTYPE_LONGNAME) {
654             magic = MAGIC_GNU;
655             version = VERSION_GNU_SPACE;
656         }
657     }
658 
659     /**
660      * Adds a PAX header to this entry. If the header corresponds to an existing field in the entry, that field will be set; otherwise the header will be added
661      * to the extraPaxHeaders Map
662      *
663      * @param name  The full name of the header to set.
664      * @param value value of header.
665      * @since 1.15
666      */
667     public void addPaxHeader(final String name, final String value) {
668         try {
669             processPaxHeader(name, value);
670         } catch (final IOException ex) {
671             throw new IllegalArgumentException("Invalid input", ex);
672         }
673     }
674 
675     /**
676      * Clears all extra PAX headers.
677      *
678      * @since 1.15
679      */
680     public void clearExtraPaxHeaders() {
681         extraPaxHeaders.clear();
682     }
683 
684     /**
685      * Determine if the two entries are equal. Equality is determined by the header names being equal.
686      *
687      * @param it Entry to be checked for equality.
688      * @return True if the entries are equal.
689      */
690     @Override
691     public boolean equals(final Object it) {
692         if (it == null || getClass() != it.getClass()) {
693             return false;
694         }
695         return equals((TarArchiveEntry) it);
696     }
697 
698     /**
699      * Determine if the two entries are equal. Equality is determined by the header names being equal.
700      *
701      * @param it Entry to be checked for equality.
702      * @return True if the entries are equal.
703      */
704     public boolean equals(final TarArchiveEntry it) {
705         return it != null && getName().equals(it.getName());
706     }
707 
708     /**
709      * Evaluates an entry's header format from a header buffer.
710      *
711      * @param header The tar entry header buffer to evaluate the format for.
712      * @return format type
713      */
714     private int evaluateType(final Map<String, String> globalPaxHeaders, final byte[] header) {
715         if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
716             return FORMAT_OLDGNU;
717         }
718         if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
719             if (isXstar(globalPaxHeaders, header)) {
720                 return FORMAT_XSTAR;
721             }
722             return FORMAT_POSIX;
723         }
724         return 0;
725     }
726 
727     private int fill(final byte value, final int offset, final byte[] outbuf, final int length) {
728         for (int i = 0; i < length; i++) {
729             outbuf[offset + i] = value;
730         }
731         return offset + length;
732     }
733 
734     private int fill(final int value, final int offset, final byte[] outbuf, final int length) {
735         return fill((byte) value, offset, outbuf, length);
736     }
737 
738     void fillGNUSparse0xData(final Map<String, String> headers) throws IOException {
739         paxGNUSparse = true;
740         realSize = ParsingUtils.parseIntValue(headers.get(TarGnuSparseKeys.SIZE));
741         if (headers.containsKey(TarGnuSparseKeys.NAME)) {
742             // version 0.1
743             name = headers.get(TarGnuSparseKeys.NAME);
744         }
745     }
746 
747     void fillGNUSparse1xData(final Map<String, String> headers) throws IOException {
748         paxGNUSparse = true;
749         paxGNU1XSparse = true;
750         if (headers.containsKey(TarGnuSparseKeys.NAME)) {
751             name = headers.get(TarGnuSparseKeys.NAME);
752         }
753         if (headers.containsKey(TarGnuSparseKeys.REALSIZE)) {
754             realSize = ParsingUtils.parseIntValue(headers.get(TarGnuSparseKeys.REALSIZE));
755         }
756     }
757 
758     void fillStarSparseData(final Map<String, String> headers) throws IOException {
759         starSparse = true;
760         if (headers.containsKey("SCHILY.realsize")) {
761             realSize = ParsingUtils.parseLongValue(headers.get("SCHILY.realsize"));
762         }
763     }
764 
765     /**
766      * Gets this entry's creation time.
767      *
768      * @return This entry's computed creation time.
769      * @since 1.22
770      */
771     public FileTime getCreationTime() {
772         return birthTime;
773     }
774 
775     /**
776      * {@inheritDoc}
777      *
778      * @since 1.21
779      */
780     @Override
781     public long getDataOffset() {
782         return dataOffset;
783     }
784 
785     /**
786      * Gets this entry's major device number.
787      *
788      * @return This entry's major device number.
789      * @since 1.4
790      */
791     public int getDevMajor() {
792         return devMajor;
793     }
794 
795     /**
796      * Gets this entry's minor device number.
797      *
798      * @return This entry's minor device number.
799      * @since 1.4
800      */
801     public int getDevMinor() {
802         return devMinor;
803     }
804 
805     /**
806      * If this entry represents a file, and the file is a directory, return an array of TarEntries for this entry's children.
807      * <p>
808      * This method is only useful for entries created from a {@code
809      * File} or {@code Path} but not for entries read from an archive.
810      * </p>
811      *
812      * @return An array of TarEntry's for this entry's children.
813      */
814     public TarArchiveEntry[] getDirectoryEntries() {
815         if (file == null || !isDirectory()) {
816             return EMPTY_TAR_ARCHIVE_ENTRY_ARRAY;
817         }
818         final List<TarArchiveEntry> entries = new ArrayList<>();
819         try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(file)) {
820             for (final Path p : dirStream) {
821                 entries.add(new TarArchiveEntry(p));
822             }
823         } catch (final IOException e) {
824             return EMPTY_TAR_ARCHIVE_ENTRY_ARRAY;
825         }
826         return entries.toArray(EMPTY_TAR_ARCHIVE_ENTRY_ARRAY);
827     }
828 
829     /**
830      * Gets named extra PAX header
831      *
832      * @param name The full name of an extended PAX header to retrieve
833      * @return The value of the header, if any.
834      * @since 1.15
835      */
836     public String getExtraPaxHeader(final String name) {
837         return extraPaxHeaders.get(name);
838     }
839 
840     /**
841      * Gets extra PAX Headers
842      *
843      * @return read-only map containing any extra PAX Headers
844      * @since 1.15
845      */
846     public Map<String, String> getExtraPaxHeaders() {
847         return Collections.unmodifiableMap(extraPaxHeaders);
848     }
849 
850     /**
851      * Gets this entry's file.
852      * <p>
853      * This method is only useful for entries created from a {@code
854      * File} or {@code Path} but not for entries read from an archive.
855      * </p>
856      *
857      * @return this entry's file or null if the entry was not created from a file.
858      */
859     public File getFile() {
860         return file != null ? file.toFile() : null;
861     }
862 
863     /**
864      * Gets this entry's group id.
865      *
866      * @return This entry's group id.
867      * @deprecated use #getLongGroupId instead as group ids can be bigger than {@link Integer#MAX_VALUE}
868      */
869     @Deprecated
870     public int getGroupId() {
871         return (int) (groupId & 0xffffffff);
872     }
873 
874     /**
875      * Gets this entry's group name.
876      *
877      * @return This entry's group name.
878      */
879     public String getGroupName() {
880         return groupName;
881     }
882 
883     /**
884      * Gets this entry's last access time.
885      *
886      * @return This entry's last access time.
887      * @since 1.22
888      */
889     public FileTime getLastAccessTime() {
890         return aTime;
891     }
892 
893     /**
894      * Gets this entry's modification time. This is equivalent to {@link TarArchiveEntry#getLastModifiedTime()}, but precision is truncated to milliseconds.
895      *
896      * @return This entry's modification time.
897      * @see TarArchiveEntry#getLastModifiedTime()
898      */
899     @Override
900     public Date getLastModifiedDate() {
901         return getModTime();
902     }
903 
904     /**
905      * Gets this entry's modification time.
906      *
907      * @return This entry's modification time.
908      * @since 1.22
909      */
910     public FileTime getLastModifiedTime() {
911         return mTime;
912     }
913 
914     /**
915      * Gets this entry's link flag.
916      *
917      * @return this entry's link flag.
918      * @since 1.23
919      */
920     public byte getLinkFlag() {
921         return linkFlag;
922     }
923 
924     /**
925      * Gets this entry's link name.
926      *
927      * @return This entry's link name.
928      */
929     public String getLinkName() {
930         return linkName;
931     }
932 
933     /**
934      * Gets this entry's group id.
935      *
936      * @return This entry's group id.
937      * @since 1.10
938      */
939     public long getLongGroupId() {
940         return groupId;
941     }
942 
943     /**
944      * Gets this entry's user id.
945      *
946      * @return This entry's user id.
947      * @since 1.10
948      */
949     public long getLongUserId() {
950         return userId;
951     }
952 
953     /**
954      * Gets this entry's mode.
955      *
956      * @return This entry's mode.
957      */
958     public int getMode() {
959         return mode;
960     }
961 
962     /**
963      * Gets this entry's modification time. This is equivalent to {@link TarArchiveEntry#getLastModifiedTime()}, but precision is truncated to milliseconds.
964      *
965      * @return This entry's modification time.
966      * @see TarArchiveEntry#getLastModifiedTime()
967      */
968     public Date getModTime() {
969         return FileTimes.toDate(mTime);
970     }
971 
972     /**
973      * Gets this entry's name.
974      * <p>
975      * This method returns the raw name as it is stored inside of the archive.
976      * </p>
977      *
978      * @return This entry's name.
979      */
980     @Override
981     public String getName() {
982         return name;
983     }
984 
985     /**
986      * Gets this entry's sparse headers ordered by offset with all empty sparse sections at the start filtered out.
987      *
988      * @return immutable list of this entry's sparse headers, never null
989      * @throws IOException if the list of sparse headers contains blocks that overlap
990      * @since 1.21
991      */
992     public List<TarArchiveStructSparse> getOrderedSparseHeaders() throws IOException {
993         if (sparseHeaders == null || sparseHeaders.isEmpty()) {
994             return Collections.emptyList();
995         }
996         final List<TarArchiveStructSparse> orderedAndFiltered = sparseHeaders.stream().filter(s -> s.getOffset() > 0 || s.getNumbytes() > 0)
997                 .sorted(Comparator.comparingLong(TarArchiveStructSparse::getOffset)).collect(Collectors.toList());
998         final int numberOfHeaders = orderedAndFiltered.size();
999         for (int i = 0; i < numberOfHeaders; i++) {
1000             final TarArchiveStructSparse str = orderedAndFiltered.get(i);
1001             if (i + 1 < numberOfHeaders && str.getOffset() + str.getNumbytes() > orderedAndFiltered.get(i + 1).getOffset()) {
1002                 throw new IOException("Corrupted TAR archive. Sparse blocks for " + getName() + " overlap each other.");
1003             }
1004             if (str.getOffset() + str.getNumbytes() < 0) {
1005                 // integer overflow?
1006                 throw new IOException("Unreadable TAR archive. Offset and numbytes for sparse block in " + getName() + " too large.");
1007             }
1008         }
1009         if (!orderedAndFiltered.isEmpty()) {
1010             final TarArchiveStructSparse last = orderedAndFiltered.get(numberOfHeaders - 1);
1011             if (last.getOffset() + last.getNumbytes() > getRealSize()) {
1012                 throw new IOException("Corrupted TAR archive. Sparse block extends beyond real size of the entry");
1013             }
1014         }
1015         return orderedAndFiltered;
1016     }
1017 
1018     /**
1019      * Gets this entry's file.
1020      * <p>
1021      * This method is only useful for entries created from a {@code
1022      * File} or {@code Path} but not for entries read from an archive.
1023      * </p>
1024      *
1025      * @return this entry's file or null if the entry was not created from a file.
1026      * @since 1.21
1027      */
1028     public Path getPath() {
1029         return file;
1030     }
1031 
1032     /**
1033      * Gets this entry's real file size in case of a sparse file.
1034      * <p>
1035      * This is the size a file would take on disk if the entry was expanded.
1036      * </p>
1037      * <p>
1038      * If the file is not a sparse file, return size instead of realSize.
1039      * </p>
1040      *
1041      * @return This entry's real file size, if the file is not a sparse file, return size instead of realSize.
1042      */
1043     public long getRealSize() {
1044         if (!isSparse()) {
1045             return getSize();
1046         }
1047         return realSize;
1048     }
1049 
1050     /**
1051      * Gets this entry's file size.
1052      * <p>
1053      * This is the size the entry's data uses inside the archive. Usually this is the same as {@link #getRealSize}, but it doesn't take the "holes" into account
1054      * when the entry represents a sparse file.
1055      * </p>
1056      *
1057      * @return This entry's file size.
1058      */
1059     @Override
1060     public long getSize() {
1061         return size;
1062     }
1063 
1064     /**
1065      * Gets this entry's sparse headers
1066      *
1067      * @return This entry's sparse headers
1068      * @since 1.20
1069      */
1070     public List<TarArchiveStructSparse> getSparseHeaders() {
1071         return sparseHeaders;
1072     }
1073 
1074     /**
1075      * Gets this entry's status change time.
1076      *
1077      * @return This entry's status change time.
1078      * @since 1.22
1079      */
1080     public FileTime getStatusChangeTime() {
1081         return cTime;
1082     }
1083 
1084     /**
1085      * Gets this entry's user id.
1086      *
1087      * @return This entry's user id.
1088      * @deprecated use #getLongUserId instead as user ids can be bigger than {@link Integer#MAX_VALUE}
1089      */
1090     @Deprecated
1091     public int getUserId() {
1092         return (int) (userId & 0xffffffff);
1093     }
1094 
1095     /**
1096      * Gets this entry's user name.
1097      *
1098      * @return This entry's user name.
1099      */
1100     public String getUserName() {
1101         return userName;
1102     }
1103 
1104     /**
1105      * Hash codes are based on entry names.
1106      *
1107      * @return the entry hash code
1108      */
1109     @Override
1110     public int hashCode() {
1111         return getName().hashCode();
1112     }
1113 
1114     /**
1115      * Tests whether this is a block device entry.
1116      *
1117      * @return whether this is a block device
1118      * @since 1.2
1119      */
1120     public boolean isBlockDevice() {
1121         return linkFlag == LF_BLK;
1122     }
1123 
1124     /**
1125      * Tests whether this is a character device entry.
1126      *
1127      * @return whether this is a character device
1128      * @since 1.2
1129      */
1130     public boolean isCharacterDevice() {
1131         return linkFlag == LF_CHR;
1132     }
1133 
1134     /**
1135      * Tests whether this entry's checksum status.
1136      *
1137      * @return if the header checksum is reasonably correct
1138      * @see TarUtils#verifyCheckSum(byte[])
1139      * @since 1.5
1140      */
1141     public boolean isCheckSumOK() {
1142         return checkSumOK;
1143     }
1144 
1145     /**
1146      * Tests whether the given entry is a descendant of this entry. Descendancy is determined by the name of the descendant starting with this entry's name.
1147      *
1148      * @param desc Entry to be checked as a descendant of this.
1149      * @return True if entry is a descendant of this.
1150      */
1151     public boolean isDescendent(final TarArchiveEntry desc) {
1152         return desc.getName().startsWith(getName());
1153     }
1154 
1155     /**
1156      * Tests whether or not this entry represents a directory.
1157      *
1158      * @return True if this entry is a directory.
1159      */
1160     @Override
1161     public boolean isDirectory() {
1162         if (file != null) {
1163             return Files.isDirectory(file, linkOptions);
1164         }
1165         if (linkFlag == LF_DIR) {
1166             return true;
1167         }
1168         return !isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/");
1169     }
1170 
1171     /**
1172      * Tests whether in case of an oldgnu sparse file if an extension sparse header follows.
1173      *
1174      * @return true if an extension oldgnu sparse header follows.
1175      */
1176     public boolean isExtended() {
1177         return isExtended;
1178     }
1179 
1180     /**
1181      * Tests whether this is a FIFO (pipe) entry.
1182      *
1183      * @return whether this is a FIFO entry
1184      * @since 1.2
1185      */
1186     public boolean isFIFO() {
1187         return linkFlag == LF_FIFO;
1188     }
1189 
1190     /**
1191      * Tests whether this is a "normal file"
1192      *
1193      * @return whether this is a "normal file"
1194      * @since 1.2
1195      */
1196     public boolean isFile() {
1197         if (file != null) {
1198             return Files.isRegularFile(file, linkOptions);
1199         }
1200         if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
1201             return true;
1202         }
1203         return linkFlag != LF_DIR && !getName().endsWith("/");
1204     }
1205 
1206     /**
1207      * Tests whether this is a Pax header.
1208      *
1209      * @return {@code true} if this is a Pax header.
1210      * @since 1.1
1211      */
1212     public boolean isGlobalPaxHeader() {
1213         return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
1214     }
1215 
1216     /**
1217      * Tests whether this entry is a GNU long linkname block
1218      *
1219      * @return true if this is a long name extension provided by GNU tar
1220      */
1221     public boolean isGNULongLinkEntry() {
1222         return linkFlag == LF_GNUTYPE_LONGLINK;
1223     }
1224 
1225     /**
1226      * Tests whether this entry is a GNU long name block
1227      *
1228      * @return true if this is a long name extension provided by GNU tar
1229      */
1230     public boolean isGNULongNameEntry() {
1231         return linkFlag == LF_GNUTYPE_LONGNAME;
1232     }
1233 
1234     /**
1235      * Tests whether this entry is a GNU sparse block.
1236      *
1237      * @return true if this is a sparse extension provided by GNU tar
1238      */
1239     public boolean isGNUSparse() {
1240         return isOldGNUSparse() || isPaxGNUSparse();
1241     }
1242 
1243     private boolean isInvalidPrefix(final byte[] header) {
1244         // prefix[130] is guaranteed to be '\0' with XSTAR/XUSTAR
1245         if (header[XSTAR_PREFIX_OFFSET + 130] != 0) {
1246             // except when typeflag is 'M'
1247             if (header[LF_OFFSET] != LF_MULTIVOLUME) {
1248                 return true;
1249             }
1250             // We come only here if we try to read in a GNU/xstar/xustar multivolume archive starting past volume #0
1251             // As of 1.22, commons-compress does not support multivolume tar archives.
1252             // If/when it does, this should work as intended.
1253             if ((header[XSTAR_MULTIVOLUME_OFFSET] & 0x80) == 0 && header[XSTAR_MULTIVOLUME_OFFSET + 11] != ' ') {
1254                 return true;
1255             }
1256         }
1257         return false;
1258     }
1259 
1260     private boolean isInvalidXtarTime(final byte[] buffer, final int offset, final int length) {
1261         // If atime[0]...atime[10] or ctime[0]...ctime[10] is not a POSIX octal number it cannot be 'xstar'.
1262         if ((buffer[offset] & 0x80) == 0) {
1263             final int lastIndex = length - 1;
1264             for (int i = 0; i < lastIndex; i++) {
1265                 final byte b = buffer[offset + i];
1266                 if (b < '0' || b > '7') {
1267                     return true;
1268                 }
1269             }
1270             // Check for both POSIX compliant end of number characters if not using base 256
1271             final byte b = buffer[offset + lastIndex];
1272             if (b != ' ' && b != 0) {
1273                 return true;
1274             }
1275         }
1276         return false;
1277     }
1278 
1279     /**
1280      * Tests whether this is a link entry.
1281      *
1282      * @return whether this is a link entry
1283      * @since 1.2
1284      */
1285     public boolean isLink() {
1286         return linkFlag == LF_LINK;
1287     }
1288 
1289     /**
1290      * Tests whether this entry is a GNU or star sparse block using the oldgnu format.
1291      *
1292      * @return true if this is a sparse extension provided by GNU tar or star
1293      * @since 1.11
1294      */
1295     public boolean isOldGNUSparse() {
1296         return linkFlag == LF_GNUTYPE_SPARSE;
1297     }
1298 
1299     /**
1300      * Tests whether this entry is a sparse file with 1.X PAX Format or not
1301      *
1302      * @return True if this entry is a sparse file with 1.X PAX Format
1303      * @since 1.20
1304      */
1305     public boolean isPaxGNU1XSparse() {
1306         return paxGNU1XSparse;
1307     }
1308 
1309     /**
1310      * Tests whether this entry is a GNU sparse block using one of the PAX formats.
1311      *
1312      * @return true if this is a sparse extension provided by GNU tar
1313      * @since 1.11
1314      */
1315     public boolean isPaxGNUSparse() {
1316         return paxGNUSparse;
1317     }
1318 
1319     /**
1320      * Tests whether this is a Pax header.
1321      *
1322      * @return {@code true} if this is a Pax header.
1323      * @since 1.1
1324      */
1325     public boolean isPaxHeader() {
1326         return linkFlag == LF_PAX_EXTENDED_HEADER_LC || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
1327     }
1328 
1329     /**
1330      * Tests whether this is a sparse entry.
1331      *
1332      * @return whether this is a sparse entry
1333      * @since 1.11
1334      */
1335     public boolean isSparse() {
1336         return isGNUSparse() || isStarSparse();
1337     }
1338 
1339     /**
1340      * Tests whether this entry is a star sparse block using PAX headers.
1341      *
1342      * @return true if this is a sparse extension provided by star
1343      * @since 1.11
1344      */
1345     public boolean isStarSparse() {
1346         return starSparse;
1347     }
1348 
1349     /**
1350      * {@inheritDoc}
1351      *
1352      * @since 1.21
1353      */
1354     @Override
1355     public boolean isStreamContiguous() {
1356         return true;
1357     }
1358 
1359     /**
1360      * Tests whether this is a symbolic link entry.
1361      *
1362      * @return whether this is a symbolic link
1363      * @since 1.2
1364      */
1365     public boolean isSymbolicLink() {
1366         return linkFlag == LF_SYMLINK;
1367     }
1368 
1369     /**
1370      * Tests whether the <a href="https://www.mkssoftware.com/docs/man4/tar.4.asp">type flag</a> contains a valid USTAR value.
1371      *
1372      * <pre>
1373      * Type Flag    File Type
1374      * 0 or null    Regular file
1375      * 1            Link to another file already archived
1376      * 2            Symbolic link
1377      * 3            Character special device
1378      * 4            Block special device
1379      * 5            Directory
1380      * 6            FIFO special file
1381      * 7            Reserved
1382      * A-Z          Available for custom usage
1383      * </pre>
1384      *
1385      * @return whether the type flag contains a valid USTAR value.
1386      */
1387     boolean isTypeFlagUstar() {
1388         return linkFlag == 0 || linkFlag >= '0' && linkFlag <= '7' || linkFlag >= 'A' && linkFlag <= 'Z';
1389     }
1390 
1391     /**
1392      * Tests whether the given header is in XSTAR / XUSTAR format.
1393      *
1394      * Use the same logic found in star version 1.6 in {@code header.c}, function {@code isxmagic(TCB *ptb)}.
1395      */
1396     private boolean isXstar(final Map<String, String> globalPaxHeaders, final byte[] header) {
1397         // Check if this is XSTAR
1398         if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET, XSTAR_MAGIC_LEN)) {
1399             return true;
1400         }
1401         //
1402         // If SCHILY.archtype is present in the global PAX header, we can use it to identify the type of archive.
1403         //
1404         // Possible values for XSTAR: - xustar: 'xstar' format without "tar" signature at header offset 508. - exustar: 'xustar' format variant that always
1405         // includes x-headers and g-headers.
1406         //
1407         final String archType = globalPaxHeaders.get("SCHILY.archtype");
1408         if (archType != null) {
1409             return "xustar".equals(archType) || "exustar".equals(archType);
1410         }
1411         // Check if this is XUSTAR
1412         if (isInvalidPrefix(header) || isInvalidXtarTime(header, XSTAR_ATIME_OFFSET, ATIMELEN_XSTAR)
1413                 || isInvalidXtarTime(header, XSTAR_CTIME_OFFSET, CTIMELEN_XSTAR)) {
1414             return false;
1415         }
1416         return true;
1417     }
1418 
1419     private long parseOctalOrBinary(final byte[] header, final int offset, final int length, final boolean lenient) {
1420         if (lenient) {
1421             try {
1422                 return TarUtils.parseOctalOrBinary(header, offset, length);
1423             } catch (final IllegalArgumentException ex) { // NOSONAR
1424                 return UNKNOWN;
1425             }
1426         }
1427         return TarUtils.parseOctalOrBinary(header, offset, length);
1428     }
1429 
1430     /**
1431      * Parses an entry's header information from a header buffer.
1432      *
1433      * @param header The tar entry header buffer to get information from.
1434      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
1435      */
1436     public void parseTarHeader(final byte[] header) {
1437         try {
1438             parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
1439         } catch (final IOException ex) { // NOSONAR
1440             try {
1441                 parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true, false);
1442             } catch (final IOException ex2) {
1443                 // not really possible
1444                 throw new UncheckedIOException(ex2); // NOSONAR
1445             }
1446         }
1447     }
1448 
1449     /**
1450      * Parse an entry's header information from a header buffer.
1451      *
1452      * @param header   The tar entry header buffer to get information from.
1453      * @param encoding encoding to use for file names
1454      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
1455      * @throws IOException              on error
1456      * @since 1.4
1457      */
1458     public void parseTarHeader(final byte[] header, final ZipEncoding encoding) throws IOException {
1459         parseTarHeader(header, encoding, false, false);
1460     }
1461 
1462     private void parseTarHeader(final byte[] header, final ZipEncoding encoding, final boolean oldStyle, final boolean lenient) throws IOException {
1463         parseTarHeader(Collections.emptyMap(), header, encoding, oldStyle, lenient);
1464     }
1465 
1466     private void parseTarHeader(final Map<String, String> globalPaxHeaders, final byte[] header, final ZipEncoding encoding, final boolean oldStyle,
1467             final boolean lenient) throws IOException {
1468         try {
1469             parseUstarHeaderBlock(globalPaxHeaders, header, encoding, oldStyle, lenient);
1470         } catch (final IllegalArgumentException ex) {
1471             throw new IOException("Corrupted TAR archive.", ex);
1472         }
1473     }
1474 
1475     private int parseTarHeaderBlock(final byte[] header, final ZipEncoding encoding, final boolean oldStyle, final boolean lenient, int offset)
1476             throws IOException {
1477         name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) : TarUtils.parseName(header, offset, NAMELEN, encoding);
1478         offset += NAMELEN;
1479         mode = (int) parseOctalOrBinary(header, offset, MODELEN, lenient);
1480         offset += MODELEN;
1481         userId = (int) parseOctalOrBinary(header, offset, UIDLEN, lenient);
1482         offset += UIDLEN;
1483         groupId = (int) parseOctalOrBinary(header, offset, GIDLEN, lenient);
1484         offset += GIDLEN;
1485         size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
1486         if (size < 0) {
1487             throw new IOException("broken archive, entry with negative size");
1488         }
1489         offset += SIZELEN;
1490         mTime = FileTimes.fromUnixTime(parseOctalOrBinary(header, offset, MODTIMELEN, lenient));
1491         offset += MODTIMELEN;
1492         checkSumOK = TarUtils.verifyCheckSum(header);
1493         offset += CHKSUMLEN;
1494         linkFlag = header[offset++];
1495         linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) : TarUtils.parseName(header, offset, NAMELEN, encoding);
1496         return offset;
1497     }
1498 
1499     private void parseUstarHeaderBlock(final Map<String, String> globalPaxHeaders, final byte[] header, final ZipEncoding encoding, final boolean oldStyle,
1500             final boolean lenient) throws IOException {
1501         int offset = 0;
1502         offset = parseTarHeaderBlock(header, encoding, oldStyle, lenient, offset);
1503         offset += NAMELEN;
1504         magic = TarUtils.parseName(header, offset, MAGICLEN);
1505         offset += MAGICLEN;
1506         version = TarUtils.parseName(header, offset, VERSIONLEN);
1507         offset += VERSIONLEN;
1508         userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN) : TarUtils.parseName(header, offset, UNAMELEN, encoding);
1509         offset += UNAMELEN;
1510         groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN) : TarUtils.parseName(header, offset, GNAMELEN, encoding);
1511         offset += GNAMELEN;
1512         if (linkFlag == LF_CHR || linkFlag == LF_BLK) {
1513             devMajor = (int) parseOctalOrBinary(header, offset, DEVLEN, lenient);
1514             offset += DEVLEN;
1515             devMinor = (int) parseOctalOrBinary(header, offset, DEVLEN, lenient);
1516             offset += DEVLEN;
1517         } else {
1518             offset += 2 * DEVLEN;
1519         }
1520         final int type = evaluateType(globalPaxHeaders, header);
1521         switch (type) {
1522         case FORMAT_OLDGNU: {
1523             aTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, ATIMELEN_GNU, lenient));
1524             offset += ATIMELEN_GNU;
1525             cTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, CTIMELEN_GNU, lenient));
1526             offset += CTIMELEN_GNU;
1527             offset += OFFSETLEN_GNU;
1528             offset += LONGNAMESLEN_GNU;
1529             offset += PAD2LEN_GNU;
1530             sparseHeaders = new ArrayList<>(TarUtils.readSparseStructs(header, offset, SPARSE_HEADERS_IN_OLDGNU_HEADER));
1531             offset += SPARSELEN_GNU;
1532             isExtended = TarUtils.parseBoolean(header, offset);
1533             offset += ISEXTENDEDLEN_GNU;
1534             realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
1535             offset += REALSIZELEN_GNU; // NOSONAR - assignment as documentation
1536             break;
1537         }
1538         case FORMAT_XSTAR: {
1539             final String xstarPrefix = oldStyle ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR)
1540                     : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding);
1541             offset += PREFIXLEN_XSTAR;
1542             if (!xstarPrefix.isEmpty()) {
1543                 name = xstarPrefix + "/" + name;
1544             }
1545             aTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, ATIMELEN_XSTAR, lenient));
1546             offset += ATIMELEN_XSTAR;
1547             cTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, CTIMELEN_XSTAR, lenient));
1548             offset += CTIMELEN_XSTAR; // NOSONAR - assignment as documentation
1549             break;
1550         }
1551         case FORMAT_POSIX:
1552         default: {
1553             final String prefix = oldStyle ? TarUtils.parseName(header, offset, PREFIXLEN) : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
1554             offset += PREFIXLEN; // NOSONAR - assignment as documentation
1555             // SunOS tar -E does not add / to directory names, so fix up to be consistent
1556             if (isDirectory() && !name.endsWith("/")) {
1557                 name += "/";
1558             }
1559             if (!prefix.isEmpty()) {
1560                 name = prefix + "/" + name;
1561             }
1562         }
1563         }
1564     }
1565 
1566     /**
1567      * Processes one pax header, using the entries extraPaxHeaders map as source for extra headers used when handling entries for sparse files.
1568      *
1569      * @param key
1570      * @param val
1571      * @since 1.15
1572      */
1573     private void processPaxHeader(final String key, final String val) throws IOException {
1574         processPaxHeader(key, val, extraPaxHeaders);
1575     }
1576 
1577     /**
1578      * Processes one pax header, using the supplied map as source for extra headers to be used when handling entries for sparse files
1579      *
1580      * @param key     the header name.
1581      * @param val     the header value.
1582      * @param headers map of headers used for dealing with sparse file.
1583      * @throws NumberFormatException if encountered errors when parsing the numbers
1584      * @since 1.15
1585      */
1586     private void processPaxHeader(final String key, final String val, final Map<String, String> headers) throws IOException {
1587         /*
1588          * The following headers are defined for Pax. charset: cannot use these without changing TarArchiveEntry fields mtime atime ctime
1589          * LIBARCHIVE.creationtime comment gid, gname linkpath size uid,uname SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
1590          *
1591          * GNU sparse files use additional members, we use GNU.sparse.size to detect the 0.0 and 0.1 versions and GNU.sparse.realsize for 1.0.
1592          *
1593          * star files use additional members of which we use SCHILY.filetype in order to detect star sparse files.
1594          *
1595          * If called from addExtraPaxHeader, these additional headers must be already present.
1596          */
1597         switch (key) {
1598         case "path":
1599             setName(val);
1600             break;
1601         case "linkpath":
1602             setLinkName(val);
1603             break;
1604         case "gid":
1605             setGroupId(ParsingUtils.parseLongValue(val));
1606             break;
1607         case "gname":
1608             setGroupName(val);
1609             break;
1610         case "uid":
1611             setUserId(ParsingUtils.parseLongValue(val));
1612             break;
1613         case "uname":
1614             setUserName(val);
1615             break;
1616         case "size":
1617             final long size = ParsingUtils.parseLongValue(val);
1618             if (size < 0) {
1619                 throw new IOException("Corrupted TAR archive. Entry size is negative");
1620             }
1621             setSize(size);
1622             break;
1623         case "mtime":
1624             setLastModifiedTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
1625             break;
1626         case "atime":
1627             setLastAccessTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
1628             break;
1629         case "ctime":
1630             setStatusChangeTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
1631             break;
1632         case "LIBARCHIVE.creationtime":
1633             setCreationTime(FileTime.from(parseInstantFromDecimalSeconds(val)));
1634             break;
1635         case "SCHILY.devminor":
1636             final int devMinor = ParsingUtils.parseIntValue(val);
1637             if (devMinor < 0) {
1638                 throw new IOException("Corrupted TAR archive. Dev-Minor is negative");
1639             }
1640             setDevMinor(devMinor);
1641             break;
1642         case "SCHILY.devmajor":
1643             final int devMajor = ParsingUtils.parseIntValue(val);
1644             if (devMajor < 0) {
1645                 throw new IOException("Corrupted TAR archive. Dev-Major is negative");
1646             }
1647             setDevMajor(devMajor);
1648             break;
1649         case TarGnuSparseKeys.SIZE:
1650             fillGNUSparse0xData(headers);
1651             break;
1652         case TarGnuSparseKeys.REALSIZE:
1653             fillGNUSparse1xData(headers);
1654             break;
1655         case "SCHILY.filetype":
1656             if ("sparse".equals(val)) {
1657                 fillStarSparseData(headers);
1658             }
1659             break;
1660         default:
1661             extraPaxHeaders.put(key, val);
1662         }
1663     }
1664 
1665     private void readFileMode(final Path path, final String normalizedName, final LinkOption... options) throws IOException {
1666         if (Files.isDirectory(path, options)) {
1667             this.mode = DEFAULT_DIR_MODE;
1668             this.linkFlag = LF_DIR;
1669 
1670             final int nameLength = normalizedName.length();
1671             if (nameLength == 0 || normalizedName.charAt(nameLength - 1) != '/') {
1672                 this.name = normalizedName + "/";
1673             } else {
1674                 this.name = normalizedName;
1675             }
1676         } else {
1677             this.mode = DEFAULT_FILE_MODE;
1678             this.linkFlag = LF_NORMAL;
1679             this.name = normalizedName;
1680             this.size = Files.size(path);
1681         }
1682     }
1683 
1684     private void readOsSpecificProperties(final Path path, final LinkOption... options) throws IOException {
1685         final Set<String> availableAttributeViews = path.getFileSystem().supportedFileAttributeViews();
1686         if (availableAttributeViews.contains("posix")) {
1687             final PosixFileAttributes posixFileAttributes = Files.readAttributes(path, PosixFileAttributes.class, options);
1688             setLastModifiedTime(posixFileAttributes.lastModifiedTime());
1689             setCreationTime(posixFileAttributes.creationTime());
1690             setLastAccessTime(posixFileAttributes.lastAccessTime());
1691             this.userName = posixFileAttributes.owner().getName();
1692             this.groupName = posixFileAttributes.group().getName();
1693             if (availableAttributeViews.contains("unix")) {
1694                 this.userId = ((Number) Files.getAttribute(path, "unix:uid", options)).longValue();
1695                 this.groupId = ((Number) Files.getAttribute(path, "unix:gid", options)).longValue();
1696                 try {
1697                     setStatusChangeTime((FileTime) Files.getAttribute(path, "unix:ctime", options));
1698                 } catch (final IllegalArgumentException ignored) {
1699                     // ctime is not supported
1700                 }
1701             }
1702         } else {
1703             if (availableAttributeViews.contains("dos")) {
1704                 final DosFileAttributes dosFileAttributes = Files.readAttributes(path, DosFileAttributes.class, options);
1705                 setLastModifiedTime(dosFileAttributes.lastModifiedTime());
1706                 setCreationTime(dosFileAttributes.creationTime());
1707                 setLastAccessTime(dosFileAttributes.lastAccessTime());
1708             } else {
1709                 final BasicFileAttributes basicFileAttributes = Files.readAttributes(path, BasicFileAttributes.class, options);
1710                 setLastModifiedTime(basicFileAttributes.lastModifiedTime());
1711                 setCreationTime(basicFileAttributes.creationTime());
1712                 setLastAccessTime(basicFileAttributes.lastAccessTime());
1713             }
1714             this.userName = Files.getOwner(path, options).getName();
1715         }
1716     }
1717 
1718     /**
1719      * Sets this entry's creation time.
1720      *
1721      * @param birthTime This entry's new creation time.
1722      * @since 1.22
1723      */
1724     public void setCreationTime(final FileTime birthTime) {
1725         this.birthTime = birthTime;
1726     }
1727 
1728     /**
1729      * Sets the offset of the data for the tar entry.
1730      *
1731      * @param dataOffset the position of the data in the tar.
1732      * @since 1.21
1733      */
1734     public void setDataOffset(final long dataOffset) {
1735         if (dataOffset < 0) {
1736             throw new IllegalArgumentException("The offset cannot be smaller than 0");
1737         }
1738         this.dataOffset = dataOffset;
1739     }
1740 
1741     /**
1742      * Sets this entry's major device number.
1743      *
1744      * @param devNo This entry's major device number.
1745      * @throws IllegalArgumentException if the devNo is &lt; 0.
1746      * @since 1.4
1747      */
1748     public void setDevMajor(final int devNo) {
1749         if (devNo < 0) {
1750             throw new IllegalArgumentException("Major device number is out of range: " + devNo);
1751         }
1752         this.devMajor = devNo;
1753     }
1754 
1755     /**
1756      * Sets this entry's minor device number.
1757      *
1758      * @param devNo This entry's minor device number.
1759      * @throws IllegalArgumentException if the devNo is &lt; 0.
1760      * @since 1.4
1761      */
1762     public void setDevMinor(final int devNo) {
1763         if (devNo < 0) {
1764             throw new IllegalArgumentException("Minor device number is out of range: " + devNo);
1765         }
1766         this.devMinor = devNo;
1767     }
1768 
1769     /**
1770      * Sets this entry's group id.
1771      *
1772      * @param groupId This entry's new group id.
1773      */
1774     public void setGroupId(final int groupId) {
1775         setGroupId((long) groupId);
1776     }
1777 
1778     /**
1779      * Sets this entry's group id.
1780      *
1781      * @param groupId This entry's new group id.
1782      * @since 1.10
1783      */
1784     public void setGroupId(final long groupId) {
1785         this.groupId = groupId;
1786     }
1787 
1788     /**
1789      * Sets this entry's group name.
1790      *
1791      * @param groupName This entry's new group name.
1792      */
1793     public void setGroupName(final String groupName) {
1794         this.groupName = groupName;
1795     }
1796 
1797     /**
1798      * Convenience method to set this entry's group and user ids.
1799      *
1800      * @param userId  This entry's new user id.
1801      * @param groupId This entry's new group id.
1802      */
1803     public void setIds(final int userId, final int groupId) {
1804         setUserId(userId);
1805         setGroupId(groupId);
1806     }
1807 
1808     /**
1809      * Sets this entry's last access time.
1810      *
1811      * @param time This entry's new last access time.
1812      * @since 1.22
1813      */
1814     public void setLastAccessTime(final FileTime time) {
1815         aTime = time;
1816     }
1817 
1818     /**
1819      * Sets this entry's modification time.
1820      *
1821      * @param time This entry's new modification time.
1822      * @since 1.22
1823      */
1824     public void setLastModifiedTime(final FileTime time) {
1825         mTime = Objects.requireNonNull(time, "time");
1826     }
1827 
1828     /**
1829      * Sets this entry's link name.
1830      *
1831      * @param link the link name to use.
1832      * @since 1.1
1833      */
1834     public void setLinkName(final String link) {
1835         this.linkName = link;
1836     }
1837 
1838     /**
1839      * Sets the mode for this entry
1840      *
1841      * @param mode the mode for this entry
1842      */
1843     public void setMode(final int mode) {
1844         this.mode = mode;
1845     }
1846 
1847     /**
1848      * Sets this entry's modification time.
1849      *
1850      * @param time This entry's new modification time.
1851      * @see TarArchiveEntry#setLastModifiedTime(FileTime)
1852      */
1853     public void setModTime(final Date time) {
1854         setLastModifiedTime(FileTimes.toFileTime(time));
1855     }
1856 
1857     /**
1858      * Sets this entry's modification time.
1859      *
1860      * @param time This entry's new modification time.
1861      * @since 1.21
1862      * @see TarArchiveEntry#setLastModifiedTime(FileTime)
1863      */
1864     public void setModTime(final FileTime time) {
1865         setLastModifiedTime(time);
1866     }
1867 
1868     /**
1869      * Sets this entry's modification time. The parameter passed to this method is in "Java time".
1870      *
1871      * @param time This entry's new modification time.
1872      * @see TarArchiveEntry#setLastModifiedTime(FileTime)
1873      */
1874     public void setModTime(final long time) {
1875         setLastModifiedTime(FileTime.fromMillis(time));
1876     }
1877 
1878     /**
1879      * Sets this entry's name.
1880      *
1881      * @param name This entry's new name.
1882      */
1883     public void setName(final String name) {
1884         this.name = normalizeFileName(name, this.preserveAbsolutePath);
1885     }
1886 
1887     /**
1888      * Convenience method to set this entry's group and user names.
1889      *
1890      * @param userName  This entry's new user name.
1891      * @param groupName This entry's new group name.
1892      */
1893     public void setNames(final String userName, final String groupName) {
1894         setUserName(userName);
1895         setGroupName(groupName);
1896     }
1897 
1898     /**
1899      * Sets this entry's file size.
1900      *
1901      * @param size This entry's new file size.
1902      * @throws IllegalArgumentException if the size is &lt; 0.
1903      */
1904     public void setSize(final long size) {
1905         if (size < 0) {
1906             throw new IllegalArgumentException("Size is out of range: " + size);
1907         }
1908         this.size = size;
1909     }
1910 
1911     /**
1912      * Sets this entry's sparse headers
1913      *
1914      * @param sparseHeaders The new sparse headers
1915      * @since 1.20
1916      */
1917     public void setSparseHeaders(final List<TarArchiveStructSparse> sparseHeaders) {
1918         this.sparseHeaders = sparseHeaders;
1919     }
1920 
1921     /**
1922      * Sets this entry's status change time.
1923      *
1924      * @param time This entry's new status change time.
1925      * @since 1.22
1926      */
1927     public void setStatusChangeTime(final FileTime time) {
1928         cTime = time;
1929     }
1930 
1931     /**
1932      * Sets this entry's user id.
1933      *
1934      * @param userId This entry's new user id.
1935      */
1936     public void setUserId(final int userId) {
1937         setUserId((long) userId);
1938     }
1939 
1940     /**
1941      * Sets this entry's user id.
1942      *
1943      * @param userId This entry's new user id.
1944      * @since 1.10
1945      */
1946     public void setUserId(final long userId) {
1947         this.userId = userId;
1948     }
1949 
1950     /**
1951      * Sets this entry's user name.
1952      *
1953      * @param userName This entry's new user name.
1954      */
1955     public void setUserName(final String userName) {
1956         this.userName = userName;
1957     }
1958 
1959     /**
1960      * Converts this instance to a String useful for debugging.
1961      *
1962      * @since 1.28.0
1963      */
1964     @Override
1965     public String toString() {
1966         return getClass().getSimpleName() + "[" + name + "]";
1967     }
1968 
1969     /**
1970      * Update the entry using a map of pax headers.
1971      *
1972      * @param headers
1973      * @since 1.15
1974      */
1975     void updateEntryFromPaxHeaders(final Map<String, String> headers) throws IOException {
1976         for (final Map.Entry<String, String> ent : headers.entrySet()) {
1977             processPaxHeader(ent.getKey(), ent.getValue(), headers);
1978         }
1979     }
1980 
1981     /**
1982      * Writes an entry's header information to a header buffer.
1983      * <p>
1984      * This method does not use the star/GNU tar/BSD tar extensions.
1985      * </p>
1986      *
1987      * @param outbuf The tar entry header buffer to fill in.
1988      */
1989     public void writeEntryHeader(final byte[] outbuf) {
1990         try {
1991             writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
1992         } catch (final IOException ex) { // NOSONAR
1993             try {
1994                 writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
1995             } catch (final IOException ex2) {
1996                 // impossible
1997                 throw new UncheckedIOException(ex2); // NOSONAR
1998             }
1999         }
2000     }
2001 
2002     /**
2003      * Writes an entry's header information to a header buffer.
2004      *
2005      * @param outbuf   The tar entry header buffer to fill in.
2006      * @param encoding encoding to use when writing the file name.
2007      * @param starMode whether to use the star/GNU tar/BSD tar extension for numeric fields if their value doesn't fit in the maximum size of standard tar
2008      *                 archives
2009      * @throws IOException on error
2010      * @since 1.4
2011      */
2012     public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding, final boolean starMode) throws IOException {
2013         int offset = 0;
2014         offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN, encoding);
2015         offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
2016         offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN, starMode);
2017         offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN, starMode);
2018         offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
2019         final FileTime fileTime = mTime;
2020         offset = writeEntryHeaderField(FileTimes.toUnixTime(fileTime), outbuf, offset, MODTIMELEN, starMode);
2021         final int csOffset = offset;
2022         offset = fill((byte) ' ', offset, outbuf, CHKSUMLEN);
2023         outbuf[offset++] = linkFlag;
2024         offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN, encoding);
2025         offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
2026         offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
2027         offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN, encoding);
2028         offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN, encoding);
2029         offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN, starMode);
2030         offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN, starMode);
2031         if (starMode) {
2032             // skip prefix
2033             offset = fill(0, offset, outbuf, PREFIXLEN_XSTAR);
2034             offset = writeEntryHeaderOptionalTimeField(aTime, offset, outbuf, ATIMELEN_XSTAR);
2035             offset = writeEntryHeaderOptionalTimeField(cTime, offset, outbuf, CTIMELEN_XSTAR);
2036             // 8-byte fill
2037             offset = fill(0, offset, outbuf, 8);
2038             // Do not write MAGIC_XSTAR because it causes issues with some TAR tools
2039             // This makes it effectively XUSTAR, which guarantees compatibility with USTAR
2040             offset = fill(0, offset, outbuf, XSTAR_MAGIC_LEN);
2041         }
2042         offset = fill(0, offset, outbuf, outbuf.length - offset); // NOSONAR - assignment as documentation
2043         final long chk = TarUtils.computeCheckSum(outbuf);
2044         TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
2045     }
2046 
2047     private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset, final int length, final boolean starMode) {
2048         if (!starMode && (value < 0 || value >= 1L << 3 * (length - 1))) {
2049             // value doesn't fit into field when written as octal
2050             // number, will be written to PAX header or causes an
2051             // error
2052             return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
2053         }
2054         return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset, length);
2055     }
2056 
2057     private int writeEntryHeaderOptionalTimeField(final FileTime time, int offset, final byte[] outbuf, final int fieldLength) {
2058         if (time != null) {
2059             offset = writeEntryHeaderField(FileTimes.toUnixTime(time), outbuf, offset, fieldLength, true);
2060         } else {
2061             offset = fill(0, offset, outbuf, fieldLength);
2062         }
2063         return offset;
2064     }
2065 
2066 }