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