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