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.util.Collections;
024import java.util.Date;
025import java.util.HashMap;
026import java.util.Locale;
027import java.util.Map;
028import org.apache.commons.compress.archivers.ArchiveEntry;
029import org.apache.commons.compress.archivers.zip.ZipEncoding;
030import org.apache.commons.compress.utils.ArchiveUtils;
031
032/**
033 * This class represents an entry in a Tar archive. It consists
034 * of the entry's header, as well as the entry's File. Entries
035 * can be instantiated in one of three ways, depending on how
036 * they are to be used.
037 * <p>
038 * TarEntries that are created from the header bytes read from
039 * an archive are instantiated with the TarEntry( byte[] )
040 * constructor. These entries will be used when extracting from
041 * or listing the contents of an archive. These entries have their
042 * header filled in using the header bytes. They also set the File
043 * to null, since they reference an archive entry not a file.
044 * <p>
045 * TarEntries that are created from Files that are to be written
046 * into an archive are instantiated with the TarEntry( File )
047 * constructor. These entries have their header filled in using
048 * the File's information. They also keep a reference to the File
049 * for convenience when writing entries.
050 * <p>
051 * Finally, TarEntries can be constructed from nothing but a name.
052 * This allows the programmer to construct the entry by hand, for
053 * instance when only an InputStream is available for writing to
054 * the archive, and the header information is constructed from
055 * other information. In this case the header fields are set to
056 * defaults and the File is set to null.
057 *
058 * <p>
059 * The C structure for a Tar Entry's header is:
060 * <pre>
061 * struct header {
062 * char name[100];     // TarConstants.NAMELEN    - offset   0
063 * char mode[8];       // TarConstants.MODELEN    - offset 100
064 * char uid[8];        // TarConstants.UIDLEN     - offset 108
065 * char gid[8];        // TarConstants.GIDLEN     - offset 116
066 * char size[12];      // TarConstants.SIZELEN    - offset 124
067 * char mtime[12];     // TarConstants.MODTIMELEN - offset 136
068 * char chksum[8];     // TarConstants.CHKSUMLEN  - offset 148
069 * char linkflag[1];   //                         - offset 156
070 * char linkname[100]; // TarConstants.NAMELEN    - offset 157
071 * The following fields are only present in new-style POSIX tar archives:
072 * char magic[6];      // TarConstants.MAGICLEN   - offset 257
073 * char version[2];    // TarConstants.VERSIONLEN - offset 263
074 * char uname[32];     // TarConstants.UNAMELEN   - offset 265
075 * char gname[32];     // TarConstants.GNAMELEN   - offset 297
076 * char devmajor[8];   // TarConstants.DEVLEN     - offset 329
077 * char devminor[8];   // TarConstants.DEVLEN     - offset 337
078 * char prefix[155];   // TarConstants.PREFIXLEN  - offset 345
079 * // Used if "name" field is not long enough to hold the path
080 * char pad[12];       // NULs                    - offset 500
081 * } header;
082 * All unused bytes are set to null.
083 * New-style GNU tar files are slightly different from the above.
084 * For values of size larger than 077777777777L (11 7s)
085 * or uid and gid larger than 07777777L (7 7s)
086 * the sign bit of the first byte is set, and the rest of the
087 * field is the binary representation of the number.
088 * See TarUtils.parseOctalOrBinary.
089 * </pre>
090 *
091 * <p>
092 * The C structure for a old GNU Tar Entry's header is:
093 * <pre>
094 * struct oldgnu_header {
095 * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU       - offset 0
096 * char atime[12];        // TarConstants.ATIMELEN_GNU      - offset 345
097 * char ctime[12];        // TarConstants.CTIMELEN_GNU      - offset 357
098 * char offset[12];       // TarConstants.OFFSETLEN_GNU     - offset 369
099 * char longnames[4];     // TarConstants.LONGNAMESLEN_GNU  - offset 381
100 * char unused_pad2;      // TarConstants.PAD2LEN_GNU       - offset 385
101 * struct sparse sp[4];   // TarConstants.SPARSELEN_GNU     - offset 386
102 * char isextended;       // TarConstants.ISEXTENDEDLEN_GNU - offset 482
103 * char realsize[12];     // TarConstants.REALSIZELEN_GNU   - offset 483
104 * char unused_pad[17];   // TarConstants.PAD3LEN_GNU       - offset 495
105 * };
106 * </pre>
107 * Whereas, "struct sparse" is:
108 * <pre>
109 * struct sparse {
110 * char offset[12];   // offset 0
111 * char numbytes[12]; // offset 12
112 * };
113 * </pre>
114 *
115 * <p>
116 * The C structure for a xstar (Jörg Schilling star) Tar Entry's header is:
117 * <pre>
118 * struct star_header {
119 *  char name[100];             // offset   0
120 *  char mode[8];               // offset 100
121 *  char uid[8];                // offset 108
122 *  char gid[8];                // offset 116
123 *  char size[12];              // offset 124
124 *  char mtime[12];             // offset 136
125 *  char chksum[8];             // offset 148
126 *  char typeflag;              // offset 156
127 *  char linkname[100];         // offset 157
128 *  char magic[6];              // offset 257
129 *  char version[2];            // offset 263
130 *  char uname[32];             // offset 265
131 *  char gname[32];             // offset 297
132 *  char devmajor[8];           // offset 329
133 *  char devminor[8];           // offset 337
134 *  char prefix[131];           // offset 345
135 *  char atime[12];             // offset 476
136 *  char ctime[12];             // offset 488
137 *  char mfill[8];              // offset 500
138 *  char xmagic[4];             // offset 508  "tar"
139 * };
140 * </pre>
141 * <p>which is identical to new-style POSIX up to the first 130 bytes of the prefix.</p>
142 *
143 * @NotThreadSafe
144 */
145
146public class TarArchiveEntry implements ArchiveEntry, TarConstants {
147    private static final TarArchiveEntry[] EMPTY_TAR_ARCHIVE_ENTRIES = new TarArchiveEntry[0];
148
149    /** The entry's name. */
150    private String name = "";
151
152    /** Whether to enforce leading slashes on the name */
153    private boolean preserveLeadingSlashes;
154
155    /** The entry's permission mode. */
156    private int mode;
157
158    /** The entry's user id. */
159    private long userId = 0;
160
161    /** The entry's group id. */
162    private long groupId = 0;
163
164    /** The entry's size. */
165    private long size = 0;
166
167    /** The entry's modification time. */
168    private long modTime;
169
170    /** If the header checksum is reasonably correct. */
171    private boolean checkSumOK;
172
173    /** The entry's link flag. */
174    private byte linkFlag;
175
176    /** The entry's link name. */
177    private String linkName = "";
178
179    /** The entry's magic tag. */
180    private String magic = MAGIC_POSIX;
181    /** The version of the format */
182    private String version = VERSION_POSIX;
183
184    /** The entry's user name. */
185    private String userName;
186
187    /** The entry's group name. */
188    private String groupName = "";
189
190    /** The entry's major device number. */
191    private int devMajor = 0;
192
193    /** The entry's minor device number. */
194    private int devMinor = 0;
195
196    /** If an extension sparse header follows. */
197    private boolean isExtended;
198
199    /** The entry's real size in case of a sparse file. */
200    private long realSize;
201
202    /** is this entry a GNU sparse entry using one of the PAX formats? */
203    private boolean paxGNUSparse;
204
205    /** is this entry a star sparse entry using the PAX header? */
206    private boolean starSparse;
207
208    /** The entry's file reference */
209    private final File file;
210
211    /** Extra, user supplied pax headers     */
212    private final Map<String,String> extraPaxHeaders = new HashMap<>();
213
214    /** Maximum length of a user's name in the tar file */
215    public static final int MAX_NAMELEN = 31;
216
217    /** Default permissions bits for directories */
218    public static final int DEFAULT_DIR_MODE = 040755;
219
220    /** Default permissions bits for files */
221    public static final int DEFAULT_FILE_MODE = 0100644;
222
223    /** Convert millis to seconds */
224    public static final int MILLIS_PER_SECOND = 1000;
225
226
227    /**
228     * Construct an empty entry and prepares the header values.
229     */
230    private TarArchiveEntry() {
231        String user = System.getProperty("user.name", "");
232
233        if (user.length() > MAX_NAMELEN) {
234            user = user.substring(0, MAX_NAMELEN);
235        }
236
237        this.userName = user;
238        this.file = null;
239    }
240
241    /**
242     * Construct an entry with only a name. This allows the programmer
243     * to construct the entry's header "by hand". File is set to null.
244     *
245     * @param name the entry name
246     */
247    public TarArchiveEntry(final String name) {
248        this(name, false);
249    }
250
251    /**
252     * Construct an entry with only a name. This allows the programmer
253     * to construct the entry's header "by hand". File is set to null.
254     *
255     * @param name the entry name
256     * @param preserveLeadingSlashes whether to allow leading slashes
257     * in the name.
258     *
259     * @since 1.1
260     */
261    public TarArchiveEntry(String name, final boolean preserveLeadingSlashes) {
262        this();
263
264        this.preserveLeadingSlashes = preserveLeadingSlashes;
265
266        name = normalizeFileName(name, preserveLeadingSlashes);
267        final boolean isDir = name.endsWith("/");
268
269        this.name = name;
270        this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
271        this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
272        this.modTime = new Date().getTime() / MILLIS_PER_SECOND;
273        this.userName = "";
274    }
275
276    /**
277     * Construct an entry with a name and a link flag.
278     *
279     * @param name the entry name
280     * @param linkFlag the entry link flag.
281     */
282    public TarArchiveEntry(final String name, final byte linkFlag) {
283        this(name, linkFlag, false);
284    }
285
286    /**
287     * Construct an entry with a name and a link flag.
288     *
289     * @param name the entry name
290     * @param linkFlag the entry link flag.
291     * @param preserveLeadingSlashes whether to allow leading slashes
292     * in the name.
293     *
294     * @since 1.5
295     */
296    public TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveLeadingSlashes) {
297        this(name, preserveLeadingSlashes);
298        this.linkFlag = linkFlag;
299        if (linkFlag == LF_GNUTYPE_LONGNAME) {
300            magic = MAGIC_GNU;
301            version = VERSION_GNU_SPACE;
302        }
303    }
304
305    /**
306     * Construct an entry for a file. File is set to file, and the
307     * header is constructed from information from the file.
308     * The name is set from the normalized file path.
309     *
310     * @param file The file that the entry represents.
311     */
312    public TarArchiveEntry(final File file) {
313        this(file, file.getPath());
314    }
315
316    /**
317     * Construct an entry for a file. File is set to file, and the
318     * header is constructed from information from the file.
319     *
320     * @param file The file that the entry represents.
321     * @param fileName the name to be used for the entry.
322     */
323    public TarArchiveEntry(final File file, final String fileName) {
324        final String normalizedName = normalizeFileName(fileName, false);
325        this.file = file;
326
327        if (file.isDirectory()) {
328            this.mode = DEFAULT_DIR_MODE;
329            this.linkFlag = LF_DIR;
330
331            final int nameLength = normalizedName.length();
332            if (nameLength == 0 || normalizedName.charAt(nameLength - 1) != '/') {
333                this.name = normalizedName + "/";
334            } else {
335                this.name = normalizedName;
336            }
337        } else {
338            this.mode = DEFAULT_FILE_MODE;
339            this.linkFlag = LF_NORMAL;
340            this.size = file.length();
341            this.name = normalizedName;
342        }
343
344        this.modTime = file.lastModified() / MILLIS_PER_SECOND;
345        this.userName = "";
346    }
347
348    /**
349     * Construct an entry from an archive's header bytes. File is set
350     * to null.
351     *
352     * @param headerBuf The header bytes from a tar archive entry.
353     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
354     */
355    public TarArchiveEntry(final byte[] headerBuf) {
356        this();
357        parseTarHeader(headerBuf);
358    }
359
360    /**
361     * Construct an entry from an archive's header bytes. File is set
362     * to null.
363     *
364     * @param headerBuf The header bytes from a tar archive entry.
365     * @param encoding encoding to use for file names
366     * @since 1.4
367     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
368     * @throws IOException on error
369     */
370    public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding)
371        throws IOException {
372        this();
373        parseTarHeader(headerBuf, encoding);
374    }
375
376    /**
377     * Determine if the two entries are equal. Equality is determined
378     * by the header names being equal.
379     *
380     * @param it Entry to be checked for equality.
381     * @return True if the entries are equal.
382     */
383    public boolean equals(final TarArchiveEntry it) {
384        return it != null && getName().equals(it.getName());
385    }
386
387    /**
388     * Determine if the two entries are equal. Equality is determined
389     * by the header names being equal.
390     *
391     * @param it Entry to be checked for equality.
392     * @return True if the entries are equal.
393     */
394    @Override
395    public boolean equals(final Object it) {
396        if (it == null || getClass() != it.getClass()) {
397            return false;
398        }
399        return equals((TarArchiveEntry) it);
400    }
401
402    /**
403     * Hashcodes are based on entry names.
404     *
405     * @return the entry hashcode
406     */
407    @Override
408    public int hashCode() {
409        return getName().hashCode();
410    }
411
412    /**
413     * Determine if the given entry is a descendant of this entry.
414     * Descendancy is determined by the name of the descendant
415     * starting with this entry's name.
416     *
417     * @param desc Entry to be checked as a descendent of this.
418     * @return True if entry is a descendant of this.
419     */
420    public boolean isDescendent(final TarArchiveEntry desc) {
421        return desc.getName().startsWith(getName());
422    }
423
424    /**
425     * Get this entry's name.
426     *
427     * @return This entry's name.
428     */
429    @Override
430    public String getName() {
431        return name;
432    }
433
434    /**
435     * Set this entry's name.
436     *
437     * @param name This entry's new name.
438     */
439    public void setName(final String name) {
440        this.name = normalizeFileName(name, this.preserveLeadingSlashes);
441    }
442
443    /**
444     * Set the mode for this entry
445     *
446     * @param mode the mode for this entry
447     */
448    public void setMode(final int mode) {
449        this.mode = mode;
450    }
451
452    /**
453     * Get this entry's link name.
454     *
455     * @return This entry's link name.
456     */
457    public String getLinkName() {
458        return linkName;
459    }
460
461    /**
462     * Set this entry's link name.
463     *
464     * @param link the link name to use.
465     *
466     * @since 1.1
467     */
468    public void setLinkName(final String link) {
469        this.linkName = link;
470    }
471
472    /**
473     * Get this entry's user id.
474     *
475     * @return This entry's user id.
476     * @deprecated use #getLongUserId instead as user ids can be
477     * bigger than {@link Integer#MAX_VALUE}
478     */
479    @Deprecated
480    public int getUserId() {
481        return (int) (userId & 0xffffffff);
482    }
483
484    /**
485     * Set this entry's user id.
486     *
487     * @param userId This entry's new user id.
488     */
489    public void setUserId(final int userId) {
490        setUserId((long) userId);
491    }
492
493    /**
494     * Get this entry's user id.
495     *
496     * @return This entry's user id.
497     * @since 1.10
498     */
499    public long getLongUserId() {
500        return userId;
501    }
502
503    /**
504     * Set this entry's user id.
505     *
506     * @param userId This entry's new user id.
507     * @since 1.10
508     */
509    public void setUserId(final long userId) {
510        this.userId = userId;
511    }
512
513    /**
514     * Get this entry's group id.
515     *
516     * @return This entry's group id.
517     * @deprecated use #getLongGroupId instead as group ids can be
518     * bigger than {@link Integer#MAX_VALUE}
519     */
520    @Deprecated
521    public int getGroupId() {
522        return (int) (groupId & 0xffffffff);
523    }
524
525    /**
526     * Set this entry's group id.
527     *
528     * @param groupId This entry's new group id.
529     */
530    public void setGroupId(final int groupId) {
531        setGroupId((long) groupId);
532    }
533
534    /**
535     * Get this entry's group id.
536     *
537     * @since 1.10
538     * @return This entry's group id.
539     */
540    public long getLongGroupId() {
541        return groupId;
542    }
543
544    /**
545     * Set this entry's group id.
546     *
547     * @since 1.10
548     * @param groupId This entry's new group id.
549     */
550    public void setGroupId(final long groupId) {
551        this.groupId = groupId;
552    }
553
554    /**
555     * Get this entry's user name.
556     *
557     * @return This entry's user name.
558     */
559    public String getUserName() {
560        return userName;
561    }
562
563    /**
564     * Set this entry's user name.
565     *
566     * @param userName This entry's new user name.
567     */
568    public void setUserName(final String userName) {
569        this.userName = userName;
570    }
571
572    /**
573     * Get this entry's group name.
574     *
575     * @return This entry's group name.
576     */
577    public String getGroupName() {
578        return groupName;
579    }
580
581    /**
582     * Set this entry's group name.
583     *
584     * @param groupName This entry's new group name.
585     */
586    public void setGroupName(final String groupName) {
587        this.groupName = groupName;
588    }
589
590    /**
591     * Convenience method to set this entry's group and user ids.
592     *
593     * @param userId This entry's new user id.
594     * @param groupId This entry's new group id.
595     */
596    public void setIds(final int userId, final int groupId) {
597        setUserId(userId);
598        setGroupId(groupId);
599    }
600
601    /**
602     * Convenience method to set this entry's group and user names.
603     *
604     * @param userName This entry's new user name.
605     * @param groupName This entry's new group name.
606     */
607    public void setNames(final String userName, final String groupName) {
608        setUserName(userName);
609        setGroupName(groupName);
610    }
611
612    /**
613     * Set this entry's modification time. The parameter passed
614     * to this method is in "Java time".
615     *
616     * @param time This entry's new modification time.
617     */
618    public void setModTime(final long time) {
619        modTime = time / MILLIS_PER_SECOND;
620    }
621
622    /**
623     * Set this entry's modification time.
624     *
625     * @param time This entry's new modification time.
626     */
627    public void setModTime(final Date time) {
628        modTime = time.getTime() / MILLIS_PER_SECOND;
629    }
630
631    /**
632     * Set this entry's modification time.
633     *
634     * @return time This entry's new modification time.
635     */
636    public Date getModTime() {
637        return new Date(modTime * MILLIS_PER_SECOND);
638    }
639
640    @Override
641    public Date getLastModifiedDate() {
642        return getModTime();
643    }
644
645    /**
646     * Get this entry's checksum status.
647     *
648     * @return if the header checksum is reasonably correct
649     * @see TarUtils#verifyCheckSum(byte[])
650     * @since 1.5
651     */
652    public boolean isCheckSumOK() {
653        return checkSumOK;
654    }
655
656    /**
657     * Get this entry's file.
658     *
659     * <p>This method is only useful for entries created from a {@code
660     * File} but not for entries read from an archive.</p>
661     *
662     * @return This entry's file.
663     */
664    public File getFile() {
665        return file;
666    }
667
668    /**
669     * Get this entry's mode.
670     *
671     * @return This entry's mode.
672     */
673    public int getMode() {
674        return mode;
675    }
676
677    /**
678     * Get this entry's file size.
679     *
680     * @return This entry's file size.
681     */
682    @Override
683    public long getSize() {
684        return size;
685    }
686
687    /**
688     * Set this entry's file size.
689     *
690     * @param size This entry's new file size.
691     * @throws IllegalArgumentException if the size is &lt; 0.
692     */
693    public void setSize(final long size) {
694        if (size < 0){
695            throw new IllegalArgumentException("Size is out of range: "+size);
696        }
697        this.size = size;
698    }
699
700    /**
701     * Get this entry's major device number.
702     *
703     * @return This entry's major device number.
704     * @since 1.4
705     */
706    public int getDevMajor() {
707        return devMajor;
708    }
709
710    /**
711     * Set this entry's major device number.
712     *
713     * @param devNo This entry's major device number.
714     * @throws IllegalArgumentException if the devNo is &lt; 0.
715     * @since 1.4
716     */
717    public void setDevMajor(final int devNo) {
718        if (devNo < 0){
719            throw new IllegalArgumentException("Major device number is out of "
720                                               + "range: " + devNo);
721        }
722        this.devMajor = devNo;
723    }
724
725    /**
726     * Get this entry's minor device number.
727     *
728     * @return This entry's minor device number.
729     * @since 1.4
730     */
731    public int getDevMinor() {
732        return devMinor;
733    }
734
735    /**
736     * Set this entry's minor device number.
737     *
738     * @param devNo This entry's minor device number.
739     * @throws IllegalArgumentException if the devNo is &lt; 0.
740     * @since 1.4
741     */
742    public void setDevMinor(final int devNo) {
743        if (devNo < 0){
744            throw new IllegalArgumentException("Minor device number is out of "
745                                               + "range: " + devNo);
746        }
747        this.devMinor = devNo;
748    }
749
750    /**
751     * Indicates in case of an oldgnu sparse file if an extension
752     * sparse header follows.
753     *
754     * @return true if an extension oldgnu sparse header follows.
755     */
756    public boolean isExtended() {
757        return isExtended;
758    }
759
760    /**
761     * Get this entry's real file size in case of a sparse file.
762     *
763     * @return This entry's real file size.
764     */
765    public long getRealSize() {
766        return realSize;
767    }
768
769    /**
770     * Indicate if this entry is a GNU sparse block.
771     *
772     * @return true if this is a sparse extension provided by GNU tar
773     */
774    public boolean isGNUSparse() {
775        return isOldGNUSparse() || isPaxGNUSparse();
776    }
777
778    /**
779     * Indicate if this entry is a GNU or star sparse block using the
780     * oldgnu format.
781     *
782     * @return true if this is a sparse extension provided by GNU tar or star
783     * @since 1.11
784     */
785    public boolean isOldGNUSparse() {
786        return linkFlag == LF_GNUTYPE_SPARSE;
787    }
788
789    /**
790     * Indicate if this entry is a GNU sparse block using one of the
791     * PAX formats.
792     *
793     * @return true if this is a sparse extension provided by GNU tar
794     * @since 1.11
795     */
796    public boolean isPaxGNUSparse() {
797        return paxGNUSparse;
798    }
799
800    /**
801     * Indicate if this entry is a star sparse block using PAX headers.
802     *
803     * @return true if this is a sparse extension provided by star
804     * @since 1.11
805     */
806    public boolean isStarSparse() {
807        return starSparse;
808    }
809
810    /**
811     * Indicate if this entry is a GNU long linkname block
812     *
813     * @return true if this is a long name extension provided by GNU tar
814     */
815    public boolean isGNULongLinkEntry() {
816        return linkFlag == LF_GNUTYPE_LONGLINK;
817    }
818
819    /**
820     * Indicate if this entry is a GNU long name block
821     *
822     * @return true if this is a long name extension provided by GNU tar
823     */
824    public boolean isGNULongNameEntry() {
825        return linkFlag == LF_GNUTYPE_LONGNAME;
826    }
827
828    /**
829     * Check if this is a Pax header.
830     *
831     * @return {@code true} if this is a Pax header.
832     *
833     * @since 1.1
834     *
835     */
836    public boolean isPaxHeader() {
837        return linkFlag == LF_PAX_EXTENDED_HEADER_LC
838            || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
839    }
840
841    /**
842     * Check if this is a Pax header.
843     *
844     * @return {@code true} if this is a Pax header.
845     *
846     * @since 1.1
847     */
848    public boolean isGlobalPaxHeader() {
849        return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
850    }
851
852    /**
853     * Return whether or not this entry represents a directory.
854     *
855     * @return True if this entry is a directory.
856     */
857    @Override
858    public boolean isDirectory() {
859        if (file != null) {
860            return file.isDirectory();
861        }
862
863        if (linkFlag == LF_DIR) {
864            return true;
865        }
866
867        if (!isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/")) {
868            return true;
869        }
870
871        return false;
872    }
873
874    /**
875     * Check if this is a "normal file"
876     *
877     * @since 1.2
878     * @return whether this is a "normal file"
879     */
880    public boolean isFile() {
881        if (file != null) {
882            return file.isFile();
883        }
884        if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
885            return true;
886        }
887        return !getName().endsWith("/");
888    }
889
890    /**
891     * Check if this is a symbolic link entry.
892     *
893     * @since 1.2
894     * @return whether this is a symbolic link
895     */
896    public boolean isSymbolicLink() {
897        return linkFlag == LF_SYMLINK;
898    }
899
900    /**
901     * Check if this is a link entry.
902     *
903     * @since 1.2
904     * @return whether this is a link entry
905     */
906    public boolean isLink() {
907        return linkFlag == LF_LINK;
908    }
909
910    /**
911     * Check if this is a character device entry.
912     *
913     * @since 1.2
914     * @return whether this is a character device
915     */
916    public boolean isCharacterDevice() {
917        return linkFlag == LF_CHR;
918    }
919
920    /**
921     * Check if this is a block device entry.
922     *
923     * @since 1.2
924     * @return whether this is a block device
925     */
926    public boolean isBlockDevice() {
927        return linkFlag == LF_BLK;
928    }
929
930    /**
931     * Check if this is a FIFO (pipe) entry.
932     *
933     * @since 1.2
934     * @return whether this is a FIFO entry
935     */
936    public boolean isFIFO() {
937        return linkFlag == LF_FIFO;
938    }
939
940    /**
941     * Check whether this is a sparse entry.
942     *
943     * @return whether this is a sparse entry
944     * @since 1.11
945     */
946    public boolean isSparse() {
947        return isGNUSparse() || isStarSparse();
948    }
949
950    /**
951     * get extra PAX Headers
952     * @return read-only map containing any extra PAX Headers
953     * @since 1.15
954     */
955    public Map<String, String> getExtraPaxHeaders() {
956        return Collections.unmodifiableMap(extraPaxHeaders);
957    }
958
959    /**
960     * clear all extra PAX headers.
961     * @since 1.15
962     */
963    public void clearExtraPaxHeaders() {
964        extraPaxHeaders.clear();
965    }
966
967    /**
968     * add a PAX header to this entry. If the header corresponds to an existing field in the entry,
969     * that field will be set; otherwise the header will be added to the extraPaxHeaders Map
970     * @param name  The full name of the header to set.
971     * @param value value of header.
972     * @since 1.15
973     */
974    public void addPaxHeader(String name,String value) {
975         processPaxHeader(name,value);
976    }
977
978    /**
979     * get named extra PAX header
980     * @param name The full name of an extended PAX header to retrieve
981     * @return The value of the header, if any.
982     * @since 1.15
983     */
984    public String getExtraPaxHeader(String name) {
985        return extraPaxHeaders.get(name);
986    }
987
988    /**
989     * Update the entry using a map of pax headers.
990     * @param headers
991     * @since 1.15
992     */
993    void updateEntryFromPaxHeaders(Map<String, String> headers) {
994        for (final Map.Entry<String, String> ent : headers.entrySet()) {
995            final String key = ent.getKey();
996            final String val = ent.getValue();
997            processPaxHeader(key, val, headers);
998        }
999    }
1000
1001    /**
1002     * process one pax header, using the entries extraPaxHeaders map as source for extra headers
1003     * used when handling entries for sparse files.
1004     * @param key
1005     * @param val
1006     * @since 1.15
1007     */
1008    private void processPaxHeader(String key, String val) {
1009        processPaxHeader(key,val,extraPaxHeaders);
1010    }
1011
1012    /**
1013     * Process one pax header, using the supplied map as source for extra headers to be used when handling
1014     * entries for sparse files
1015     *
1016     * @param key  the header name.
1017     * @param val  the header value.
1018     * @param headers  map of headers used for dealing with sparse file.
1019     * @since 1.15
1020     */
1021    private void processPaxHeader(String key, String val, Map<String, String> headers) {
1022    /*
1023     * The following headers are defined for Pax.
1024     * atime, ctime, charset: cannot use these without changing TarArchiveEntry fields
1025     * mtime
1026     * comment
1027     * gid, gname
1028     * linkpath
1029     * size
1030     * uid,uname
1031     * SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
1032     *
1033     * GNU sparse files use additional members, we use
1034     * GNU.sparse.size to detect the 0.0 and 0.1 versions and
1035     * GNU.sparse.realsize for 1.0.
1036     *
1037     * star files use additional members of which we use
1038     * SCHILY.filetype in order to detect star sparse files.
1039     *
1040     * If called from addExtraPaxHeader, these additional headers must be already present .
1041     */
1042        switch (key) {
1043            case "path":
1044                setName(val);
1045                break;
1046            case "linkpath":
1047                setLinkName(val);
1048                break;
1049            case "gid":
1050                setGroupId(Long.parseLong(val));
1051                break;
1052            case "gname":
1053                setGroupName(val);
1054                break;
1055            case "uid":
1056                setUserId(Long.parseLong(val));
1057                break;
1058            case "uname":
1059                setUserName(val);
1060                break;
1061            case "size":
1062                setSize(Long.parseLong(val));
1063                break;
1064            case "mtime":
1065                setModTime((long) (Double.parseDouble(val) * 1000));
1066                break;
1067            case "SCHILY.devminor":
1068                setDevMinor(Integer.parseInt(val));
1069                break;
1070            case "SCHILY.devmajor":
1071                setDevMajor(Integer.parseInt(val));
1072                break;
1073            case "GNU.sparse.size":
1074                fillGNUSparse0xData(headers);
1075                break;
1076            case "GNU.sparse.realsize":
1077                fillGNUSparse1xData(headers);
1078                break;
1079            case "SCHILY.filetype":
1080                if ("sparse".equals(val)) {
1081                    fillStarSparseData(headers);
1082                }
1083                break;
1084            default:
1085                extraPaxHeaders.put(key,val);
1086        }
1087    }
1088
1089
1090
1091    /**
1092     * If this entry represents a file, and the file is a directory, return
1093     * an array of TarEntries for this entry's children.
1094     *
1095     * <p>This method is only useful for entries created from a {@code
1096     * File} but not for entries read from an archive.</p>
1097     *
1098     * @return An array of TarEntry's for this entry's children.
1099     */
1100    public TarArchiveEntry[] getDirectoryEntries() {
1101        if (file == null || !file.isDirectory()) {
1102            return EMPTY_TAR_ARCHIVE_ENTRIES;
1103        }
1104
1105        final String[] list = file.list();
1106        if (list == null) {
1107            return EMPTY_TAR_ARCHIVE_ENTRIES;
1108        }
1109        final TarArchiveEntry[] result = new TarArchiveEntry[list.length];
1110
1111        for (int i = 0; i < result.length; ++i) {
1112            result[i] = new TarArchiveEntry(new File(file, list[i]));
1113        }
1114
1115        return result;
1116    }
1117
1118    /**
1119     * Write an entry's header information to a header buffer.
1120     *
1121     * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
1122     *
1123     * @param outbuf The tar entry header buffer to fill in.
1124     */
1125    public void writeEntryHeader(final byte[] outbuf) {
1126        try {
1127            writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
1128        } catch (final IOException ex) {
1129            try {
1130                writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
1131            } catch (final IOException ex2) {
1132                // impossible
1133                throw new RuntimeException(ex2); //NOSONAR
1134            }
1135        }
1136    }
1137
1138    /**
1139     * Write an entry's header information to a header buffer.
1140     *
1141     * @param outbuf The tar entry header buffer to fill in.
1142     * @param encoding encoding to use when writing the file name.
1143     * @param starMode whether to use the star/GNU tar/BSD tar
1144     * extension for numeric fields if their value doesn't fit in the
1145     * maximum size of standard tar archives
1146     * @since 1.4
1147     * @throws IOException on error
1148     */
1149    public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding,
1150                                 final boolean starMode) throws IOException {
1151        int offset = 0;
1152
1153        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
1154                                          encoding);
1155        offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
1156        offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
1157                                       starMode);
1158        offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
1159                                       starMode);
1160        offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
1161        offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
1162                                       starMode);
1163
1164        final int csOffset = offset;
1165
1166        for (int c = 0; c < CHKSUMLEN; ++c) {
1167            outbuf[offset++] = (byte) ' ';
1168        }
1169
1170        outbuf[offset++] = linkFlag;
1171        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
1172                                          encoding);
1173        offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
1174        offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
1175        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
1176                                          encoding);
1177        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
1178                                          encoding);
1179        offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
1180                                       starMode);
1181        offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
1182                                       starMode);
1183
1184        while (offset < outbuf.length) {
1185            outbuf[offset++] = 0;
1186        }
1187
1188        final long chk = TarUtils.computeCheckSum(outbuf);
1189
1190        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
1191    }
1192
1193    private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset,
1194                                      final int length, final boolean starMode) {
1195        if (!starMode && (value < 0
1196                          || value >= 1l << 3 * (length - 1))) {
1197            // value doesn't fit into field when written as octal
1198            // number, will be written to PAX header or causes an
1199            // error
1200            return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
1201        }
1202        return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
1203                                                     length);
1204    }
1205
1206    /**
1207     * Parse an entry's header information from a header buffer.
1208     *
1209     * @param header The tar entry header buffer to get information from.
1210     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
1211     */
1212    public void parseTarHeader(final byte[] header) {
1213        try {
1214            parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
1215        } catch (final IOException ex) {
1216            try {
1217                parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
1218            } catch (final IOException ex2) {
1219                // not really possible
1220                throw new RuntimeException(ex2); //NOSONAR
1221            }
1222        }
1223    }
1224
1225    /**
1226     * Parse an entry's header information from a header buffer.
1227     *
1228     * @param header The tar entry header buffer to get information from.
1229     * @param encoding encoding to use for file names
1230     * @since 1.4
1231     * @throws IllegalArgumentException if any of the numeric fields
1232     * have an invalid format
1233     * @throws IOException on error
1234     */
1235    public void parseTarHeader(final byte[] header, final ZipEncoding encoding)
1236        throws IOException {
1237        parseTarHeader(header, encoding, false);
1238    }
1239
1240    private void parseTarHeader(final byte[] header, final ZipEncoding encoding,
1241                                final boolean oldStyle)
1242        throws IOException {
1243        int offset = 0;
1244
1245        name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
1246            : TarUtils.parseName(header, offset, NAMELEN, encoding);
1247        offset += NAMELEN;
1248        mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
1249        offset += MODELEN;
1250        userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
1251        offset += UIDLEN;
1252        groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
1253        offset += GIDLEN;
1254        size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
1255        offset += SIZELEN;
1256        modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
1257        offset += MODTIMELEN;
1258        checkSumOK = TarUtils.verifyCheckSum(header);
1259        offset += CHKSUMLEN;
1260        linkFlag = header[offset++];
1261        linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
1262            : TarUtils.parseName(header, offset, NAMELEN, encoding);
1263        offset += NAMELEN;
1264        magic = TarUtils.parseName(header, offset, MAGICLEN);
1265        offset += MAGICLEN;
1266        version = TarUtils.parseName(header, offset, VERSIONLEN);
1267        offset += VERSIONLEN;
1268        userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
1269            : TarUtils.parseName(header, offset, UNAMELEN, encoding);
1270        offset += UNAMELEN;
1271        groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
1272            : TarUtils.parseName(header, offset, GNAMELEN, encoding);
1273        offset += GNAMELEN;
1274        devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
1275        offset += DEVLEN;
1276        devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
1277        offset += DEVLEN;
1278
1279        final int type = evaluateType(header);
1280        switch (type) {
1281        case FORMAT_OLDGNU: {
1282            offset += ATIMELEN_GNU;
1283            offset += CTIMELEN_GNU;
1284            offset += OFFSETLEN_GNU;
1285            offset += LONGNAMESLEN_GNU;
1286            offset += PAD2LEN_GNU;
1287            offset += SPARSELEN_GNU;
1288            isExtended = TarUtils.parseBoolean(header, offset);
1289            offset += ISEXTENDEDLEN_GNU;
1290            realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
1291            offset += REALSIZELEN_GNU; // NOSONAR - assignment as documentation
1292            break;
1293        }
1294        case FORMAT_XSTAR: {
1295            final String xstarPrefix = oldStyle
1296                ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR)
1297                : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding);
1298            if (xstarPrefix.length() > 0) {
1299                name = xstarPrefix + "/" + name;
1300            }
1301            break;
1302        }
1303        case FORMAT_POSIX:
1304        default: {
1305            final String prefix = oldStyle
1306                ? TarUtils.parseName(header, offset, PREFIXLEN)
1307                : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
1308            // SunOS tar -E does not add / to directory names, so fix
1309            // up to be consistent
1310            if (isDirectory() && !name.endsWith("/")){
1311                name = name + "/";
1312            }
1313            if (prefix.length() > 0){
1314                name = prefix + "/" + name;
1315            }
1316        }
1317        }
1318    }
1319
1320    /**
1321     * Strips Windows' drive letter as well as any leading slashes,
1322     * turns path separators into forward slahes.
1323     */
1324    private static String normalizeFileName(String fileName,
1325                                            final boolean preserveLeadingSlashes) {
1326        final String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
1327
1328        if (osname != null) {
1329
1330            // Strip off drive letters!
1331            // REVIEW Would a better check be "(File.separator == '\')"?
1332
1333            if (osname.startsWith("windows")) {
1334                if (fileName.length() > 2) {
1335                    final char ch1 = fileName.charAt(0);
1336                    final char ch2 = fileName.charAt(1);
1337
1338                    if (ch2 == ':'
1339                        && (ch1 >= 'a' && ch1 <= 'z'
1340                            || ch1 >= 'A' && ch1 <= 'Z')) {
1341                        fileName = fileName.substring(2);
1342                    }
1343                }
1344            } else if (osname.contains("netware")) {
1345                final int colon = fileName.indexOf(':');
1346                if (colon != -1) {
1347                    fileName = fileName.substring(colon + 1);
1348                }
1349            }
1350        }
1351
1352        fileName = fileName.replace(File.separatorChar, '/');
1353
1354        // No absolute pathnames
1355        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
1356        // so we loop on starting /'s.
1357        while (!preserveLeadingSlashes && fileName.startsWith("/")) {
1358            fileName = fileName.substring(1);
1359        }
1360        return fileName;
1361    }
1362
1363    /**
1364     * Evaluate an entry's header format from a header buffer.
1365     *
1366     * @param header The tar entry header buffer to evaluate the format for.
1367     * @return format type
1368     */
1369    private int evaluateType(final byte[] header) {
1370        if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
1371            return FORMAT_OLDGNU;
1372        }
1373        if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
1374            if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET,
1375                                              XSTAR_MAGIC_LEN)) {
1376                return FORMAT_XSTAR;
1377            }
1378            return FORMAT_POSIX;
1379        }
1380        return 0;
1381    }
1382
1383    void fillGNUSparse0xData(final Map<String, String> headers) {
1384        paxGNUSparse = true;
1385        realSize = Integer.parseInt(headers.get("GNU.sparse.size"));
1386        if (headers.containsKey("GNU.sparse.name")) {
1387            // version 0.1
1388            name = headers.get("GNU.sparse.name");
1389        }
1390    }
1391
1392    void fillGNUSparse1xData(final Map<String, String> headers) {
1393        paxGNUSparse = true;
1394        realSize = Integer.parseInt(headers.get("GNU.sparse.realsize"));
1395        name = headers.get("GNU.sparse.name");
1396    }
1397
1398    void fillStarSparseData(final Map<String, String> headers) {
1399        starSparse = true;
1400        if (headers.containsKey("SCHILY.realsize")) {
1401            realSize = Long.parseLong(headers.get("SCHILY.realsize"));
1402        }
1403    }
1404}
1405