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