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