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