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 ArchiveEntry, TarConstants {
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 it != null && 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     * <p>This method is only useful for entries created from a {@code
655     * File} but not for entries read from an archive.</p>
656     *
657     * @return This entry's file.
658     */
659    public File getFile() {
660        return file;
661    }
662
663    /**
664     * Get this entry's mode.
665     *
666     * @return This entry's mode.
667     */
668    public int getMode() {
669        return mode;
670    }
671
672    /**
673     * Get this entry's file size.
674     *
675     * @return This entry's file size.
676     */
677    @Override
678    public long getSize() {
679        return size;
680    }
681
682    /**
683     * Set this entry's file size.
684     *
685     * @param size This entry's new file size.
686     * @throws IllegalArgumentException if the size is &lt; 0.
687     */
688    public void setSize(final long size) {
689        if (size < 0){
690            throw new IllegalArgumentException("Size is out of range: "+size);
691        }
692        this.size = size;
693    }
694
695    /**
696     * Get this entry's major device number.
697     *
698     * @return This entry's major device number.
699     * @since 1.4
700     */
701    public int getDevMajor() {
702        return devMajor;
703    }
704
705    /**
706     * Set this entry's major device number.
707     *
708     * @param devNo This entry's major device number.
709     * @throws IllegalArgumentException if the devNo is &lt; 0.
710     * @since 1.4
711     */
712    public void setDevMajor(final int devNo) {
713        if (devNo < 0){
714            throw new IllegalArgumentException("Major device number is out of "
715                                               + "range: " + devNo);
716        }
717        this.devMajor = devNo;
718    }
719
720    /**
721     * Get this entry's minor device number.
722     *
723     * @return This entry's minor device number.
724     * @since 1.4
725     */
726    public int getDevMinor() {
727        return devMinor;
728    }
729
730    /**
731     * Set this entry's minor device number.
732     *
733     * @param devNo This entry's minor device number.
734     * @throws IllegalArgumentException if the devNo is &lt; 0.
735     * @since 1.4
736     */
737    public void setDevMinor(final int devNo) {
738        if (devNo < 0){
739            throw new IllegalArgumentException("Minor device number is out of "
740                                               + "range: " + devNo);
741        }
742        this.devMinor = devNo;
743    }
744
745    /**
746     * Indicates in case of an oldgnu sparse file if an extension
747     * sparse header follows.
748     *
749     * @return true if an extension oldgnu sparse header follows.
750     */
751    public boolean isExtended() {
752        return isExtended;
753    }
754
755    /**
756     * Get this entry's real file size in case of a sparse file.
757     *
758     * @return This entry's real file size.
759     */
760    public long getRealSize() {
761        return realSize;
762    }
763
764    /**
765     * Indicate if this entry is a GNU sparse block.
766     *
767     * @return true if this is a sparse extension provided by GNU tar
768     */
769    public boolean isGNUSparse() {
770        return isOldGNUSparse() || isPaxGNUSparse();
771    }
772
773    /**
774     * Indicate if this entry is a GNU or star sparse block using the
775     * oldgnu format.
776     *
777     * @return true if this is a sparse extension provided by GNU tar or star
778     * @since 1.11
779     */
780    public boolean isOldGNUSparse() {
781        return linkFlag == LF_GNUTYPE_SPARSE;
782    }
783
784    /**
785     * Indicate if this entry is a GNU sparse block using one of the
786     * PAX formats.
787     *
788     * @return true if this is a sparse extension provided by GNU tar
789     * @since 1.11
790     */
791    public boolean isPaxGNUSparse() {
792        return paxGNUSparse;
793    }
794
795    /**
796     * Indicate if this entry is a star sparse block using PAX headers.
797     *
798     * @return true if this is a sparse extension provided by star
799     * @since 1.11
800     */
801    public boolean isStarSparse() {
802        return starSparse;
803    }
804
805    /**
806     * Indicate if this entry is a GNU long linkname block
807     *
808     * @return true if this is a long name extension provided by GNU tar
809     */
810    public boolean isGNULongLinkEntry() {
811        return linkFlag == LF_GNUTYPE_LONGLINK;
812    }
813
814    /**
815     * Indicate if this entry is a GNU long name block
816     *
817     * @return true if this is a long name extension provided by GNU tar
818     */
819    public boolean isGNULongNameEntry() {
820        return linkFlag == LF_GNUTYPE_LONGNAME;
821    }
822
823    /**
824     * Check if this is a Pax header.
825     *
826     * @return {@code true} if this is a Pax header.
827     *
828     * @since 1.1
829     *
830     */
831    public boolean isPaxHeader() {
832        return linkFlag == LF_PAX_EXTENDED_HEADER_LC
833            || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
834    }
835
836    /**
837     * Check if this is a Pax header.
838     *
839     * @return {@code true} if this is a Pax header.
840     *
841     * @since 1.1
842     */
843    public boolean isGlobalPaxHeader() {
844        return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
845    }
846
847    /**
848     * Return whether or not this entry represents a directory.
849     *
850     * @return True if this entry is a directory.
851     */
852    @Override
853    public boolean isDirectory() {
854        if (file != null) {
855            return file.isDirectory();
856        }
857
858        if (linkFlag == LF_DIR) {
859            return true;
860        }
861
862        if (!isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/")) {
863            return true;
864        }
865
866        return false;
867    }
868
869    /**
870     * Check if this is a "normal file"
871     *
872     * @since 1.2
873     * @return whether this is a "normal file"
874     */
875    public boolean isFile() {
876        if (file != null) {
877            return file.isFile();
878        }
879        if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
880            return true;
881        }
882        return !getName().endsWith("/");
883    }
884
885    /**
886     * Check if this is a symbolic link entry.
887     *
888     * @since 1.2
889     * @return whether this is a symbolic link
890     */
891    public boolean isSymbolicLink() {
892        return linkFlag == LF_SYMLINK;
893    }
894
895    /**
896     * Check if this is a link entry.
897     *
898     * @since 1.2
899     * @return whether this is a link entry
900     */
901    public boolean isLink() {
902        return linkFlag == LF_LINK;
903    }
904
905    /**
906     * Check if this is a character device entry.
907     *
908     * @since 1.2
909     * @return whether this is a character device
910     */
911    public boolean isCharacterDevice() {
912        return linkFlag == LF_CHR;
913    }
914
915    /**
916     * Check if this is a block device entry.
917     *
918     * @since 1.2
919     * @return whether this is a block device
920     */
921    public boolean isBlockDevice() {
922        return linkFlag == LF_BLK;
923    }
924
925    /**
926     * Check if this is a FIFO (pipe) entry.
927     *
928     * @since 1.2
929     * @return whether this is a FIFO entry
930     */
931    public boolean isFIFO() {
932        return linkFlag == LF_FIFO;
933    }
934
935    /**
936     * Check whether this is a sparse entry.
937     *
938     * @return whether this is a sparse entry
939     * @since 1.11
940     */
941    public boolean isSparse() {
942        return isGNUSparse() || isStarSparse();
943    }
944
945    /**
946     * If this entry represents a file, and the file is a directory, return
947     * an array of TarEntries for this entry's children.
948     *
949     * <p>This method is only useful for entries created from a {@code
950     * File} but not for entries read from an archive.</p>
951     *
952     * @return An array of TarEntry's for this entry's children.
953     */
954    public TarArchiveEntry[] getDirectoryEntries() {
955        if (file == null || !file.isDirectory()) {
956            return EMPTY_TAR_ARCHIVE_ENTRIES;
957        }
958
959        final String[] list = file.list();
960        if (list == null) {
961            return EMPTY_TAR_ARCHIVE_ENTRIES;
962        }
963        final TarArchiveEntry[] result = new TarArchiveEntry[list.length];
964
965        for (int i = 0; i < result.length; ++i) {
966            result[i] = new TarArchiveEntry(new File(file, list[i]));
967        }
968
969        return result;
970    }
971
972    /**
973     * Write an entry's header information to a header buffer.
974     *
975     * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
976     *
977     * @param outbuf The tar entry header buffer to fill in.
978     */
979    public void writeEntryHeader(final byte[] outbuf) {
980        try {
981            writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
982        } catch (final IOException ex) {
983            try {
984                writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
985            } catch (final IOException ex2) {
986                // impossible
987                throw new RuntimeException(ex2); //NOSONAR
988            }
989        }
990    }
991
992    /**
993     * Write an entry's header information to a header buffer.
994     *
995     * @param outbuf The tar entry header buffer to fill in.
996     * @param encoding encoding to use when writing the file name.
997     * @param starMode whether to use the star/GNU tar/BSD tar
998     * extension for numeric fields if their value doesn't fit in the
999     * maximum size of standard tar archives
1000     * @since 1.4
1001     * @throws IOException on error
1002     */
1003    public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding,
1004                                 final boolean starMode) throws IOException {
1005        int offset = 0;
1006
1007        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
1008                                          encoding);
1009        offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
1010        offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
1011                                       starMode);
1012        offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
1013                                       starMode);
1014        offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
1015        offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
1016                                       starMode);
1017
1018        final int csOffset = offset;
1019
1020        for (int c = 0; c < CHKSUMLEN; ++c) {
1021            outbuf[offset++] = (byte) ' ';
1022        }
1023
1024        outbuf[offset++] = linkFlag;
1025        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
1026                                          encoding);
1027        offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
1028        offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
1029        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
1030                                          encoding);
1031        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
1032                                          encoding);
1033        offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
1034                                       starMode);
1035        offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
1036                                       starMode);
1037
1038        while (offset < outbuf.length) {
1039            outbuf[offset++] = 0;
1040        }
1041
1042        final long chk = TarUtils.computeCheckSum(outbuf);
1043
1044        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
1045    }
1046
1047    private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset,
1048                                      final int length, final boolean starMode) {
1049        if (!starMode && (value < 0
1050                          || value >= 1l << 3 * (length - 1))) {
1051            // value doesn't fit into field when written as octal
1052            // number, will be written to PAX header or causes an
1053            // error
1054            return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
1055        }
1056        return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
1057                                                     length);
1058    }
1059
1060    /**
1061     * Parse an entry's header information from a header buffer.
1062     *
1063     * @param header The tar entry header buffer to get information from.
1064     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
1065     */
1066    public void parseTarHeader(final byte[] header) {
1067        try {
1068            parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
1069        } catch (final IOException ex) {
1070            try {
1071                parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
1072            } catch (final IOException ex2) {
1073                // not really possible
1074                throw new RuntimeException(ex2); //NOSONAR
1075            }
1076        }
1077    }
1078
1079    /**
1080     * Parse an entry's header information from a header buffer.
1081     *
1082     * @param header The tar entry header buffer to get information from.
1083     * @param encoding encoding to use for file names
1084     * @since 1.4
1085     * @throws IllegalArgumentException if any of the numeric fields
1086     * have an invalid format
1087     * @throws IOException on error
1088     */
1089    public void parseTarHeader(final byte[] header, final ZipEncoding encoding)
1090        throws IOException {
1091        parseTarHeader(header, encoding, false);
1092    }
1093
1094    private void parseTarHeader(final byte[] header, final ZipEncoding encoding,
1095                                final boolean oldStyle)
1096        throws IOException {
1097        int offset = 0;
1098
1099        name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
1100            : TarUtils.parseName(header, offset, NAMELEN, encoding);
1101        offset += NAMELEN;
1102        mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
1103        offset += MODELEN;
1104        userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
1105        offset += UIDLEN;
1106        groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
1107        offset += GIDLEN;
1108        size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
1109        offset += SIZELEN;
1110        modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
1111        offset += MODTIMELEN;
1112        checkSumOK = TarUtils.verifyCheckSum(header);
1113        offset += CHKSUMLEN;
1114        linkFlag = header[offset++];
1115        linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
1116            : TarUtils.parseName(header, offset, NAMELEN, encoding);
1117        offset += NAMELEN;
1118        magic = TarUtils.parseName(header, offset, MAGICLEN);
1119        offset += MAGICLEN;
1120        version = TarUtils.parseName(header, offset, VERSIONLEN);
1121        offset += VERSIONLEN;
1122        userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
1123            : TarUtils.parseName(header, offset, UNAMELEN, encoding);
1124        offset += UNAMELEN;
1125        groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
1126            : TarUtils.parseName(header, offset, GNAMELEN, encoding);
1127        offset += GNAMELEN;
1128        devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
1129        offset += DEVLEN;
1130        devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
1131        offset += DEVLEN;
1132
1133        final int type = evaluateType(header);
1134        switch (type) {
1135        case FORMAT_OLDGNU: {
1136            offset += ATIMELEN_GNU;
1137            offset += CTIMELEN_GNU;
1138            offset += OFFSETLEN_GNU;
1139            offset += LONGNAMESLEN_GNU;
1140            offset += PAD2LEN_GNU;
1141            offset += SPARSELEN_GNU;
1142            isExtended = TarUtils.parseBoolean(header, offset);
1143            offset += ISEXTENDEDLEN_GNU;
1144            realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
1145            offset += REALSIZELEN_GNU;
1146            break;
1147        }
1148        case FORMAT_XSTAR: {
1149            final String xstarPrefix = oldStyle
1150                ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR)
1151                : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding);
1152            if (xstarPrefix.length() > 0) {
1153                name = xstarPrefix + "/" + name;
1154            }
1155            break;
1156        }
1157        case FORMAT_POSIX:
1158        default: {
1159            final String prefix = oldStyle
1160                ? TarUtils.parseName(header, offset, PREFIXLEN)
1161                : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
1162            // SunOS tar -E does not add / to directory names, so fix
1163            // up to be consistent
1164            if (isDirectory() && !name.endsWith("/")){
1165                name = name + "/";
1166            }
1167            if (prefix.length() > 0){
1168                name = prefix + "/" + name;
1169            }
1170        }
1171        }
1172    }
1173
1174    /**
1175     * Strips Windows' drive letter as well as any leading slashes,
1176     * turns path separators into forward slahes.
1177     */
1178    private static String normalizeFileName(String fileName,
1179                                            final boolean preserveLeadingSlashes) {
1180        final String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
1181
1182        if (osname != null) {
1183
1184            // Strip off drive letters!
1185            // REVIEW Would a better check be "(File.separator == '\')"?
1186
1187            if (osname.startsWith("windows")) {
1188                if (fileName.length() > 2) {
1189                    final char ch1 = fileName.charAt(0);
1190                    final char ch2 = fileName.charAt(1);
1191
1192                    if (ch2 == ':'
1193                        && (ch1 >= 'a' && ch1 <= 'z'
1194                            || ch1 >= 'A' && ch1 <= 'Z')) {
1195                        fileName = fileName.substring(2);
1196                    }
1197                }
1198            } else if (osname.contains("netware")) {
1199                final int colon = fileName.indexOf(':');
1200                if (colon != -1) {
1201                    fileName = fileName.substring(colon + 1);
1202                }
1203            }
1204        }
1205
1206        fileName = fileName.replace(File.separatorChar, '/');
1207
1208        // No absolute pathnames
1209        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
1210        // so we loop on starting /'s.
1211        while (!preserveLeadingSlashes && fileName.startsWith("/")) {
1212            fileName = fileName.substring(1);
1213        }
1214        return fileName;
1215    }
1216
1217    /**
1218     * Evaluate an entry's header format from a header buffer.
1219     *
1220     * @param header The tar entry header buffer to evaluate the format for.
1221     * @return format type
1222     */
1223    private int evaluateType(final byte[] header) {
1224        if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
1225            return FORMAT_OLDGNU;
1226        }
1227        if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
1228            if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET,
1229                                              XSTAR_MAGIC_LEN)) {
1230                return FORMAT_XSTAR;
1231            }
1232            return FORMAT_POSIX;
1233        }
1234        return 0;
1235    }
1236
1237    void fillGNUSparse0xData(final Map<String, String> headers) {
1238        paxGNUSparse = true;
1239        realSize = Integer.parseInt(headers.get("GNU.sparse.size"));
1240        if (headers.containsKey("GNU.sparse.name")) {
1241            // version 0.1
1242            name = headers.get("GNU.sparse.name");
1243        }
1244    }
1245
1246    void fillGNUSparse1xData(final Map<String, String> headers) {
1247        paxGNUSparse = true;
1248        realSize = Integer.parseInt(headers.get("GNU.sparse.realsize"));
1249        name = headers.get("GNU.sparse.name");
1250    }
1251
1252    void fillStarSparseData(final Map<String, String> headers) {
1253        starSparse = true;
1254        if (headers.containsKey("SCHILY.realsize")) {
1255            realSize = Long.parseLong(headers.get("SCHILY.realsize"));
1256        }
1257    }
1258}
1259