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