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