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