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     * @throws IOException on error
322     */
323    public TarArchiveEntry(byte[] headerBuf, ZipEncoding encoding)
324        throws IOException {
325        this();
326        parseTarHeader(headerBuf, encoding);
327    }
328
329    /**
330     * Determine if the two entries are equal. Equality is determined
331     * by the header names being equal.
332     *
333     * @param it Entry to be checked for equality.
334     * @return True if the entries are equal.
335     */
336    public boolean equals(TarArchiveEntry it) {
337        return getName().equals(it.getName());
338    }
339
340    /**
341     * Determine if the two entries are equal. Equality is determined
342     * by the header names being equal.
343     *
344     * @param it Entry to be checked for equality.
345     * @return True if the entries are equal.
346     */
347    @Override
348    public boolean equals(Object it) {
349        if (it == null || getClass() != it.getClass()) {
350            return false;
351        }
352        return equals((TarArchiveEntry) it);
353    }
354
355    /**
356     * Hashcodes are based on entry names.
357     *
358     * @return the entry hashcode
359     */
360    @Override
361    public int hashCode() {
362        return getName().hashCode();
363    }
364
365    /**
366     * Determine if the given entry is a descendant of this entry.
367     * Descendancy is determined by the name of the descendant
368     * starting with this entry's name.
369     *
370     * @param desc Entry to be checked as a descendent of this.
371     * @return True if entry is a descendant of this.
372     */
373    public boolean isDescendent(TarArchiveEntry desc) {
374        return desc.getName().startsWith(getName());
375    }
376
377    /**
378     * Get this entry's name.
379     *
380     * @return This entry's name.
381     */
382    public String getName() {
383        return name;
384    }
385
386    /**
387     * Set this entry's name.
388     *
389     * @param name This entry's new name.
390     */
391    public void setName(String name) {
392        this.name = normalizeFileName(name, false);
393    }
394
395    /**
396     * Set the mode for this entry
397     *
398     * @param mode the mode for this entry
399     */
400    public void setMode(int mode) {
401        this.mode = mode;
402    }
403
404    /**
405     * Get this entry's link name.
406     *
407     * @return This entry's link name.
408     */
409    public String getLinkName() {
410        return linkName;
411    }
412
413    /**
414     * Set this entry's link name.
415     * 
416     * @param link the link name to use.
417     * 
418     * @since 1.1
419     */
420    public void setLinkName(String link) {
421        this.linkName = link;
422    }
423
424    /**
425     * Get this entry's user id.
426     *
427     * @return This entry's user id.
428     * @deprecated use #getLongUserId instead as user ids can be
429     * bigger than {@link Integer#MAX_VALUE}
430     */
431    @Deprecated
432    public int getUserId() {
433        return (int) (userId & 0xffffffff);
434    }
435
436    /**
437     * Set this entry's user id.
438     *
439     * @param userId This entry's new user id.
440     */
441    public void setUserId(int userId) {
442        setUserId((long) userId);
443    }
444
445    /**
446     * Get this entry's user id.
447     *
448     * @return This entry's user id.
449     * @since 1.10
450     */
451    public long getLongUserId() {
452        return userId;
453    }
454
455    /**
456     * Set this entry's user id.
457     *
458     * @param userId This entry's new user id.
459     * @since 1.10
460     */
461    public void setUserId(long userId) {
462        this.userId = userId;
463    }
464
465    /**
466     * Get this entry's group id.
467     *
468     * @return This entry's group id.
469     * @deprecated use #getLongGroupId instead as group ids can be
470     * bigger than {@link Integer#MAX_VALUE}
471     */
472    @Deprecated
473    public int getGroupId() {
474        return (int) (groupId & 0xffffffff);
475    }
476
477    /**
478     * Set this entry's group id.
479     *
480     * @param groupId This entry's new group id.
481     */
482    public void setGroupId(int groupId) {
483        setGroupId((long) groupId);
484    }
485
486    /**
487     * Get this entry's group id.
488     *
489     * @since 1.10
490     * @return This entry's group id.
491     */
492    public long getLongGroupId() {
493        return groupId;
494    }
495
496    /**
497     * Set this entry's group id.
498     *
499     * @since 1.10
500     * @param groupId This entry's new group id.
501     */
502    public void setGroupId(long groupId) {
503        this.groupId = groupId;
504    }
505
506    /**
507     * Get this entry's user name.
508     *
509     * @return This entry's user name.
510     */
511    public String getUserName() {
512        return userName;
513    }
514
515    /**
516     * Set this entry's user name.
517     *
518     * @param userName This entry's new user name.
519     */
520    public void setUserName(String userName) {
521        this.userName = userName;
522    }
523
524    /**
525     * Get this entry's group name.
526     *
527     * @return This entry's group name.
528     */
529    public String getGroupName() {
530        return groupName;
531    }
532
533    /**
534     * Set this entry's group name.
535     *
536     * @param groupName This entry's new group name.
537     */
538    public void setGroupName(String groupName) {
539        this.groupName = groupName;
540    }
541
542    /**
543     * Convenience method to set this entry's group and user ids.
544     *
545     * @param userId This entry's new user id.
546     * @param groupId This entry's new group id.
547     */
548    public void setIds(int userId, int groupId) {
549        setUserId(userId);
550        setGroupId(groupId);
551    }
552
553    /**
554     * Convenience method to set this entry's group and user names.
555     *
556     * @param userName This entry's new user name.
557     * @param groupName This entry's new group name.
558     */
559    public void setNames(String userName, String groupName) {
560        setUserName(userName);
561        setGroupName(groupName);
562    }
563
564    /**
565     * Set this entry's modification time. The parameter passed
566     * to this method is in "Java time".
567     *
568     * @param time This entry's new modification time.
569     */
570    public void setModTime(long time) {
571        modTime = time / MILLIS_PER_SECOND;
572    }
573
574    /**
575     * Set this entry's modification time.
576     *
577     * @param time This entry's new modification time.
578     */
579    public void setModTime(Date time) {
580        modTime = time.getTime() / MILLIS_PER_SECOND;
581    }
582
583    /**
584     * Set this entry's modification time.
585     *
586     * @return time This entry's new modification time.
587     */
588    public Date getModTime() {
589        return new Date(modTime * MILLIS_PER_SECOND);
590    }
591
592    public Date getLastModifiedDate() {
593        return getModTime();
594    }
595
596    /**
597     * Get this entry's checksum status.
598     *
599     * @return if the header checksum is reasonably correct
600     * @see TarUtils#verifyCheckSum(byte[])
601     * @since 1.5
602     */
603    public boolean isCheckSumOK() {
604        return checkSumOK;
605    }
606
607    /**
608     * Get this entry's file.
609     *
610     * @return This entry's file.
611     */
612    public File getFile() {
613        return file;
614    }
615
616    /**
617     * Get this entry's mode.
618     *
619     * @return This entry's mode.
620     */
621    public int getMode() {
622        return mode;
623    }
624
625    /**
626     * Get this entry's file size.
627     *
628     * @return This entry's file size.
629     */
630    public long getSize() {
631        return size;
632    }
633
634    /**
635     * Set this entry's file size.
636     *
637     * @param size This entry's new file size.
638     * @throws IllegalArgumentException if the size is &lt; 0.
639     */
640    public void setSize(long size) {
641        if (size < 0){
642            throw new IllegalArgumentException("Size is out of range: "+size);
643        }
644        this.size = size;
645    }
646
647    /**
648     * Get this entry's major device number.
649     *
650     * @return This entry's major device number.
651     * @since 1.4
652     */
653    public int getDevMajor() {
654        return devMajor;
655    }
656
657    /**
658     * Set this entry's major device number.
659     *
660     * @param devNo This entry's major device number.
661     * @throws IllegalArgumentException if the devNo is &lt; 0.
662     * @since 1.4
663     */
664    public void setDevMajor(int devNo) {
665        if (devNo < 0){
666            throw new IllegalArgumentException("Major device number is out of "
667                                               + "range: " + devNo);
668        }
669        this.devMajor = devNo;
670    }
671
672    /**
673     * Get this entry's minor device number.
674     *
675     * @return This entry's minor device number.
676     * @since 1.4
677     */
678    public int getDevMinor() {
679        return devMinor;
680    }
681
682    /**
683     * Set this entry's minor device number.
684     *
685     * @param devNo This entry's minor device number.
686     * @throws IllegalArgumentException if the devNo is &lt; 0.
687     * @since 1.4
688     */
689    public void setDevMinor(int devNo) {
690        if (devNo < 0){
691            throw new IllegalArgumentException("Minor device number is out of "
692                                               + "range: " + devNo);
693        }
694        this.devMinor = devNo;
695    }
696
697    /**
698     * Indicates in case of a sparse file if an extension sparse header
699     * follows.
700     *
701     * @return true if an extension sparse header follows.
702     */
703    public boolean isExtended() {
704        return isExtended;
705    }
706
707    /**
708     * Get this entry's real file size in case of a sparse file.
709     *
710     * @return This entry's real file size.
711     */
712    public long getRealSize() {
713        return realSize;
714    }
715
716    /**
717     * Indicate if this entry is a GNU sparse block 
718     *
719     * @return true if this is a sparse extension provided by GNU tar
720     */
721    public boolean isGNUSparse() {
722        return linkFlag == LF_GNUTYPE_SPARSE;
723    }
724
725    /**
726     * Indicate if this entry is a GNU long linkname block
727     *
728     * @return true if this is a long name extension provided by GNU tar
729     */
730    public boolean isGNULongLinkEntry() {
731        return linkFlag == LF_GNUTYPE_LONGLINK
732            && name.equals(GNU_LONGLINK);
733    }
734
735    /**
736     * Indicate if this entry is a GNU long name block
737     *
738     * @return true if this is a long name extension provided by GNU tar
739     */
740    public boolean isGNULongNameEntry() {
741        return linkFlag == LF_GNUTYPE_LONGNAME
742            && name.equals(GNU_LONGLINK);
743    }
744
745    /**
746     * Check if this is a Pax header.
747     * 
748     * @return {@code true} if this is a Pax header.
749     * 
750     * @since 1.1
751     * 
752     */
753    public boolean isPaxHeader(){
754        return linkFlag == LF_PAX_EXTENDED_HEADER_LC
755            || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
756    }
757
758    /**
759     * Check if this is a Pax header.
760     * 
761     * @return {@code true} if this is a Pax header.
762     * 
763     * @since 1.1
764     */
765    public boolean isGlobalPaxHeader(){
766        return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
767    }
768
769    /**
770     * Return whether or not this entry represents a directory.
771     *
772     * @return True if this entry is a directory.
773     */
774    public boolean isDirectory() {
775        if (file != null) {
776            return file.isDirectory();
777        }
778
779        if (linkFlag == LF_DIR) {
780            return true;
781        }
782
783        if (getName().endsWith("/")) {
784            return true;
785        }
786
787        return false;
788    }
789
790    /**
791     * Check if this is a "normal file"
792     *
793     * @since 1.2
794     * @return whether this is a "normal file"
795     */
796    public boolean isFile() {
797        if (file != null) {
798            return file.isFile();
799        }
800        if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
801            return true;
802        }
803        return !getName().endsWith("/");
804    }
805
806    /**
807     * Check if this is a symbolic link entry.
808     *
809     * @since 1.2
810     * @return whether this is a symbolic link
811     */
812    public boolean isSymbolicLink() {
813        return linkFlag == LF_SYMLINK;
814    }
815
816    /**
817     * Check if this is a link entry.
818     *
819     * @since 1.2
820     * @return whether this is a link entry
821     */
822    public boolean isLink() {
823        return linkFlag == LF_LINK;
824    }
825
826    /**
827     * Check if this is a character device entry.
828     *
829     * @since 1.2
830     * @return whether this is a character device
831     */
832    public boolean isCharacterDevice() {
833        return linkFlag == LF_CHR;
834    }
835
836    /**
837     * Check if this is a block device entry.
838     *
839     * @since 1.2
840     * @return whether this is a block device
841     */
842    public boolean isBlockDevice() {
843        return linkFlag == LF_BLK;
844    }
845
846    /**
847     * Check if this is a FIFO (pipe) entry.
848     *
849     * @since 1.2
850     * @return whether this is a FIFO entry
851     */
852    public boolean isFIFO() {
853        return linkFlag == LF_FIFO;
854    }
855
856    /**
857     * If this entry represents a file, and the file is a directory, return
858     * an array of TarEntries for this entry's children.
859     *
860     * @return An array of TarEntry's for this entry's children.
861     */
862    public TarArchiveEntry[] getDirectoryEntries() {
863        if (file == null || !file.isDirectory()) {
864            return new TarArchiveEntry[0];
865        }
866
867        String[]   list = file.list();
868        TarArchiveEntry[] result = new TarArchiveEntry[list.length];
869
870        for (int i = 0; i < list.length; ++i) {
871            result[i] = new TarArchiveEntry(new File(file, list[i]));
872        }
873
874        return result;
875    }
876
877    /**
878     * Write an entry's header information to a header buffer.
879     *
880     * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
881     *
882     * @param outbuf The tar entry header buffer to fill in.
883     */
884    public void writeEntryHeader(byte[] outbuf) {
885        try {
886            writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
887        } catch (IOException ex) {
888            try {
889                writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
890            } catch (IOException ex2) {
891                // impossible
892                throw new RuntimeException(ex2);
893            }
894        }
895    }
896
897    /**
898     * Write an entry's header information to a header buffer.
899     *
900     * @param outbuf The tar entry header buffer to fill in.
901     * @param encoding encoding to use when writing the file name.
902     * @param starMode whether to use the star/GNU tar/BSD tar
903     * extension for numeric fields if their value doesn't fit in the
904     * maximum size of standard tar archives
905     * @since 1.4
906     * @throws IOException on error
907     */
908    public void writeEntryHeader(byte[] outbuf, ZipEncoding encoding,
909                                 boolean starMode) throws IOException {
910        int offset = 0;
911
912        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
913                                          encoding);
914        offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
915        offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
916                                       starMode);
917        offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
918                                       starMode);
919        offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
920        offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
921                                       starMode);
922
923        int csOffset = offset;
924
925        for (int c = 0; c < CHKSUMLEN; ++c) {
926            outbuf[offset++] = (byte) ' ';
927        }
928
929        outbuf[offset++] = linkFlag;
930        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
931                                          encoding);
932        offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
933        offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
934        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
935                                          encoding);
936        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
937                                          encoding);
938        offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
939                                       starMode);
940        offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
941                                       starMode);
942
943        while (offset < outbuf.length) {
944            outbuf[offset++] = 0;
945        }
946
947        long chk = TarUtils.computeCheckSum(outbuf);
948
949        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
950    }
951
952    private int writeEntryHeaderField(long value, byte[] outbuf, int offset,
953                                      int length, boolean starMode) {
954        if (!starMode && (value < 0
955                          || value >= 1l << 3 * (length - 1))) {
956            // value doesn't fit into field when written as octal
957            // number, will be written to PAX header or causes an
958            // error
959            return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
960        }
961        return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
962                                                     length);
963    }
964
965    /**
966     * Parse an entry's header information from a header buffer.
967     *
968     * @param header The tar entry header buffer to get information from.
969     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
970     */
971    public void parseTarHeader(byte[] header) {
972        try {
973            parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
974        } catch (IOException ex) {
975            try {
976                parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
977            } catch (IOException ex2) {
978                // not really possible
979                throw new RuntimeException(ex2);
980            }
981        }
982    }
983
984    /**
985     * Parse an entry's header information from a header buffer.
986     *
987     * @param header The tar entry header buffer to get information from.
988     * @param encoding encoding to use for file names
989     * @since 1.4
990     * @throws IllegalArgumentException if any of the numeric fields
991     * have an invalid format
992     * @throws IOException on error
993     */
994    public void parseTarHeader(byte[] header, ZipEncoding encoding)
995        throws IOException {
996        parseTarHeader(header, encoding, false);
997    }
998
999    private void parseTarHeader(byte[] header, ZipEncoding encoding,
1000                                final boolean oldStyle)
1001        throws IOException {
1002        int offset = 0;
1003
1004        name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
1005            : TarUtils.parseName(header, offset, NAMELEN, encoding);
1006        offset += NAMELEN;
1007        mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
1008        offset += MODELEN;
1009        userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
1010        offset += UIDLEN;
1011        groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
1012        offset += GIDLEN;
1013        size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
1014        offset += SIZELEN;
1015        modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
1016        offset += MODTIMELEN;
1017        checkSumOK = TarUtils.verifyCheckSum(header);
1018        offset += CHKSUMLEN;
1019        linkFlag = header[offset++];
1020        linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
1021            : TarUtils.parseName(header, offset, NAMELEN, encoding);
1022        offset += NAMELEN;
1023        magic = TarUtils.parseName(header, offset, MAGICLEN);
1024        offset += MAGICLEN;
1025        version = TarUtils.parseName(header, offset, VERSIONLEN);
1026        offset += VERSIONLEN;
1027        userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
1028            : TarUtils.parseName(header, offset, UNAMELEN, encoding);
1029        offset += UNAMELEN;
1030        groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
1031            : TarUtils.parseName(header, offset, GNAMELEN, encoding);
1032        offset += GNAMELEN;
1033        devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
1034        offset += DEVLEN;
1035        devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
1036        offset += DEVLEN;
1037
1038        int type = evaluateType(header);
1039        switch (type) {
1040        case FORMAT_OLDGNU: {
1041            offset += ATIMELEN_GNU;
1042            offset += CTIMELEN_GNU;
1043            offset += OFFSETLEN_GNU;
1044            offset += LONGNAMESLEN_GNU;
1045            offset += PAD2LEN_GNU;
1046            offset += SPARSELEN_GNU;
1047            isExtended = TarUtils.parseBoolean(header, offset);
1048            offset += ISEXTENDEDLEN_GNU;
1049            realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
1050            offset += REALSIZELEN_GNU;
1051            break;
1052        }
1053        case FORMAT_POSIX:
1054        default: {
1055            String prefix = oldStyle
1056                ? TarUtils.parseName(header, offset, PREFIXLEN)
1057                : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
1058            // SunOS tar -E does not add / to directory names, so fix
1059            // up to be consistent
1060            if (isDirectory() && !name.endsWith("/")){
1061                name = name + "/";
1062            }
1063            if (prefix.length() > 0){
1064                name = prefix + "/" + name;
1065            }
1066        }
1067        }
1068    }
1069
1070    /**
1071     * Strips Windows' drive letter as well as any leading slashes,
1072     * turns path separators into forward slahes.
1073     */
1074    private static String normalizeFileName(String fileName,
1075                                            boolean preserveLeadingSlashes) {
1076        String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
1077
1078        if (osname != null) {
1079
1080            // Strip off drive letters!
1081            // REVIEW Would a better check be "(File.separator == '\')"?
1082
1083            if (osname.startsWith("windows")) {
1084                if (fileName.length() > 2) {
1085                    char ch1 = fileName.charAt(0);
1086                    char ch2 = fileName.charAt(1);
1087
1088                    if (ch2 == ':'
1089                        && (ch1 >= 'a' && ch1 <= 'z'
1090                            || ch1 >= 'A' && ch1 <= 'Z')) {
1091                        fileName = fileName.substring(2);
1092                    }
1093                }
1094            } else if (osname.contains("netware")) {
1095                int colon = fileName.indexOf(':');
1096                if (colon != -1) {
1097                    fileName = fileName.substring(colon + 1);
1098                }
1099            }
1100        }
1101
1102        fileName = fileName.replace(File.separatorChar, '/');
1103
1104        // No absolute pathnames
1105        // Windows (and Posix?) paths can start with "\\NetworkDrive\",
1106        // so we loop on starting /'s.
1107        while (!preserveLeadingSlashes && fileName.startsWith("/")) {
1108            fileName = fileName.substring(1);
1109        }
1110        return fileName;
1111    }
1112
1113    /**
1114     * Evaluate an entry's header format from a header buffer.
1115     *
1116     * @param header The tar entry header buffer to evaluate the format for.
1117     * @return format type
1118     */
1119    private int evaluateType(byte[] header) {
1120        if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
1121            return FORMAT_OLDGNU;
1122        }
1123        if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
1124            return FORMAT_POSIX;
1125        }
1126        return 0;
1127    }
1128}
1129