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