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