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