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        public boolean equals(Object it) {
330            if (it == null || getClass() != it.getClass()) {
331                return false;
332            }
333            return equals((TarArchiveEntry) it);
334        }
335    
336        /**
337         * Hashcodes are based on entry names.
338         *
339         * @return the entry hashcode
340         */
341        public int hashCode() {
342            return getName().hashCode();
343        }
344    
345        /**
346         * Determine if the given entry is a descendant of this entry.
347         * Descendancy is determined by the name of the descendant
348         * starting with this entry's name.
349         *
350         * @param desc Entry to be checked as a descendent of this.
351         * @return True if entry is a descendant of this.
352         */
353        public boolean isDescendent(TarArchiveEntry desc) {
354            return desc.getName().startsWith(getName());
355        }
356    
357        /**
358         * Get this entry's name.
359         *
360         * @return This entry's name.
361         */
362        public String getName() {
363            return name.toString();
364        }
365    
366        /**
367         * Set this entry's name.
368         *
369         * @param name This entry's new name.
370         */
371        public void setName(String name) {
372            this.name = normalizeFileName(name, false);
373        }
374    
375        /**
376         * Set the mode for this entry
377         *
378         * @param mode the mode for this entry
379         */
380        public void setMode(int mode) {
381            this.mode = mode;
382        }
383    
384        /**
385         * Get this entry's link name.
386         *
387         * @return This entry's link name.
388         */
389        public String getLinkName() {
390            return linkName.toString();
391        }
392    
393        /**
394         * Set this entry's link name.
395         * 
396         * @param link the link name to use.
397         * 
398         * @since Apache Commons Compress 1.1
399         */
400        public void setLinkName(String link) {
401            this.linkName = link;
402        }
403    
404        /**
405         * Get this entry's user id.
406         *
407         * @return This entry's user id.
408         */
409        public int getUserId() {
410            return userId;
411        }
412    
413        /**
414         * Set this entry's user id.
415         *
416         * @param userId This entry's new user id.
417         */
418        public void setUserId(int userId) {
419            this.userId = userId;
420        }
421    
422        /**
423         * Get this entry's group id.
424         *
425         * @return This entry's group id.
426         */
427        public int getGroupId() {
428            return groupId;
429        }
430    
431        /**
432         * Set this entry's group id.
433         *
434         * @param groupId This entry's new group id.
435         */
436        public void setGroupId(int groupId) {
437            this.groupId = groupId;
438        }
439    
440        /**
441         * Get this entry's user name.
442         *
443         * @return This entry's user name.
444         */
445        public String getUserName() {
446            return userName.toString();
447        }
448    
449        /**
450         * Set this entry's user name.
451         *
452         * @param userName This entry's new user name.
453         */
454        public void setUserName(String userName) {
455            this.userName = userName;
456        }
457    
458        /**
459         * Get this entry's group name.
460         *
461         * @return This entry's group name.
462         */
463        public String getGroupName() {
464            return groupName.toString();
465        }
466    
467        /**
468         * Set this entry's group name.
469         *
470         * @param groupName This entry's new group name.
471         */
472        public void setGroupName(String groupName) {
473            this.groupName = groupName;
474        }
475    
476        /**
477         * Convenience method to set this entry's group and user ids.
478         *
479         * @param userId This entry's new user id.
480         * @param groupId This entry's new group id.
481         */
482        public void setIds(int userId, int groupId) {
483            setUserId(userId);
484            setGroupId(groupId);
485        }
486    
487        /**
488         * Convenience method to set this entry's group and user names.
489         *
490         * @param userName This entry's new user name.
491         * @param groupName This entry's new group name.
492         */
493        public void setNames(String userName, String groupName) {
494            setUserName(userName);
495            setGroupName(groupName);
496        }
497    
498        /**
499         * Set this entry's modification time. The parameter passed
500         * to this method is in "Java time".
501         *
502         * @param time This entry's new modification time.
503         */
504        public void setModTime(long time) {
505            modTime = time / MILLIS_PER_SECOND;
506        }
507    
508        /**
509         * Set this entry's modification time.
510         *
511         * @param time This entry's new modification time.
512         */
513        public void setModTime(Date time) {
514            modTime = time.getTime() / MILLIS_PER_SECOND;
515        }
516    
517        /**
518         * Set this entry's modification time.
519         *
520         * @return time This entry's new modification time.
521         */
522        public Date getModTime() {
523            return new Date(modTime * MILLIS_PER_SECOND);
524        }
525    
526        /** {@inheritDoc} */
527        public Date getLastModifiedDate() {
528            return getModTime();
529        }
530    
531        /**
532         * Get this entry's file.
533         *
534         * @return This entry's file.
535         */
536        public File getFile() {
537            return file;
538        }
539    
540        /**
541         * Get this entry's mode.
542         *
543         * @return This entry's mode.
544         */
545        public int getMode() {
546            return mode;
547        }
548    
549        /**
550         * Get this entry's file size.
551         *
552         * @return This entry's file size.
553         */
554        public long getSize() {
555            return size;
556        }
557    
558        /**
559         * Set this entry's file size.
560         *
561         * @param size This entry's new file size.
562         * @throws IllegalArgumentException if the size is < 0
563         * or > {@link TarConstants#MAXSIZE} (077777777777L).
564         */
565        public void setSize(long size) {
566            if (size > MAXSIZE || size < 0){
567                throw new IllegalArgumentException("Size is out of range: "+size);
568            }
569            this.size = size;
570        }
571    
572        /**
573         * Indicates in case of a sparse file if an extension sparse header
574         * follows.
575         *
576         * @return true if an extension sparse header follows.
577         */
578        public boolean isExtended() {
579            return isExtended;
580        }
581    
582        /**
583         * Get this entry's real file size in case of a sparse file.
584         *
585         * @return This entry's real file size.
586         */
587        public long getRealSize() {
588            return realSize;
589        }
590    
591        /**
592         * Indicate if this entry is a GNU sparse block 
593         *
594         * @return true if this is a sparse extension provided by GNU tar
595         */
596        public boolean isGNUSparse() {
597            return linkFlag == LF_GNUTYPE_SPARSE;
598        }
599    
600        /**
601         * Indicate if this entry is a GNU long name block
602         *
603         * @return true if this is a long name extension provided by GNU tar
604         */
605        public boolean isGNULongNameEntry() {
606            return linkFlag == LF_GNUTYPE_LONGNAME
607                && name.toString().equals(GNU_LONGLINK);
608        }
609    
610        /**
611         * Check if this is a Pax header.
612         * 
613         * @return <code>true</code> if this is a Pax header.
614         * 
615         * @since Apache Commons Compress 1.1
616         */
617        public boolean isPaxHeader(){
618            return linkFlag == LF_PAX_EXTENDED_HEADER_LC
619                || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
620        }
621    
622        /**
623         * Check if this is a Pax header.
624         * 
625         * @return <code>true</code> if this is a Pax header.
626         * 
627         * @since Apache Commons Compress 1.1
628         */
629        public boolean isGlobalPaxHeader(){
630            return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
631        }
632    
633        /**
634         * Return whether or not this entry represents a directory.
635         *
636         * @return True if this entry is a directory.
637         */
638        public boolean isDirectory() {
639            if (file != null) {
640                return file.isDirectory();
641            }
642    
643            if (linkFlag == LF_DIR) {
644                return true;
645            }
646    
647            if (getName().endsWith("/")) {
648                return true;
649            }
650    
651            return false;
652        }
653    
654        /**
655         * Check if this is a "normal file"
656         *
657         * @since Apache Commons Compress 1.2
658         */
659        public boolean isFile() {
660            if (file != null) {
661                return file.isFile();
662            }
663            if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
664                return true;
665            }
666            return !getName().endsWith("/");
667        }
668    
669        /**
670         * Check if this is a symbolic link entry.
671         *
672         * @since Apache Commons Compress 1.2
673         */
674        public boolean isSymbolicLink() {
675            return linkFlag == LF_SYMLINK;
676        }
677    
678        /**
679         * Check if this is a link entry.
680         *
681         * @since Apache Commons Compress 1.2
682         */
683        public boolean isLink() {
684            return linkFlag == LF_LINK;
685        }
686    
687        /**
688         * Check if this is a character device entry.
689         *
690         * @since Apache Commons Compress 1.2
691         */
692        public boolean isCharacterDevice() {
693            return linkFlag == LF_CHR;
694        }
695    
696        /**
697         * Check if this is a block device entry.
698         *
699         * @since Apache Commons Compress 1.2
700         */
701        public boolean isBlockDevice() {
702            return linkFlag == LF_BLK;
703        }
704    
705        /**
706         * Check if this is a FIFO (pipe) entry.
707         *
708         * @since Apache Commons Compress 1.2
709         */
710        public boolean isFIFO() {
711            return linkFlag == LF_FIFO;
712        }
713    
714        /**
715         * If this entry represents a file, and the file is a directory, return
716         * an array of TarEntries for this entry's children.
717         *
718         * @return An array of TarEntry's for this entry's children.
719         */
720        public TarArchiveEntry[] getDirectoryEntries() {
721            if (file == null || !file.isDirectory()) {
722                return new TarArchiveEntry[0];
723            }
724    
725            String[]   list = file.list();
726            TarArchiveEntry[] result = new TarArchiveEntry[list.length];
727    
728            for (int i = 0; i < list.length; ++i) {
729                result[i] = new TarArchiveEntry(new File(file, list[i]));
730            }
731    
732            return result;
733        }
734    
735        /**
736         * Write an entry's header information to a header buffer.
737         *
738         * @param outbuf The tar entry header buffer to fill in.
739         */
740        public void writeEntryHeader(byte[] outbuf) {
741            int offset = 0;
742    
743            offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN);
744            offset = TarUtils.formatOctalBytes(mode, outbuf, offset, MODELEN);
745            offset = TarUtils.formatOctalBytes(userId, outbuf, offset, UIDLEN);
746            offset = TarUtils.formatOctalBytes(groupId, outbuf, offset, GIDLEN);
747            offset = TarUtils.formatLongOctalBytes(size, outbuf, offset, SIZELEN);
748            offset = TarUtils.formatLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
749    
750            int csOffset = offset;
751    
752            for (int c = 0; c < CHKSUMLEN; ++c) {
753                outbuf[offset++] = (byte) ' ';
754            }
755    
756            outbuf[offset++] = linkFlag;
757            offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN);
758            offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
759            offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
760            offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN);
761            offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN);
762            offset = TarUtils.formatOctalBytes(devMajor, outbuf, offset, DEVLEN);
763            offset = TarUtils.formatOctalBytes(devMinor, outbuf, offset, DEVLEN);
764    
765            while (offset < outbuf.length) {
766                outbuf[offset++] = 0;
767            }
768    
769            long chk = TarUtils.computeCheckSum(outbuf);
770    
771            TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
772        }
773    
774        /**
775         * Parse an entry's header information from a header buffer.
776         *
777         * @param header The tar entry header buffer to get information from.
778         */
779        public void parseTarHeader(byte[] header) {
780            int offset = 0;
781    
782            name = TarUtils.parseName(header, offset, NAMELEN);
783            offset += NAMELEN;
784            mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
785            offset += MODELEN;
786            userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
787            offset += UIDLEN;
788            groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
789            offset += GIDLEN;
790            size = TarUtils.parseOctal(header, offset, SIZELEN);
791            offset += SIZELEN;
792            modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
793            offset += MODTIMELEN;
794            offset += CHKSUMLEN;
795            linkFlag = header[offset++];
796            linkName = TarUtils.parseName(header, offset, NAMELEN);
797            offset += NAMELEN;
798            magic = TarUtils.parseName(header, offset, MAGICLEN);
799            offset += MAGICLEN;
800            version = TarUtils.parseName(header, offset, VERSIONLEN);
801            offset += VERSIONLEN;
802            userName = TarUtils.parseName(header, offset, UNAMELEN);
803            offset += UNAMELEN;
804            groupName = TarUtils.parseName(header, offset, GNAMELEN);
805            offset += GNAMELEN;
806            devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
807            offset += DEVLEN;
808            devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
809            offset += DEVLEN;
810    
811            int type = evaluateType(header);
812            switch (type) {
813            case FORMAT_OLDGNU: {
814                offset += ATIMELEN_GNU;
815                offset += CTIMELEN_GNU;
816                offset += OFFSETLEN_GNU;
817                offset += LONGNAMESLEN_GNU;
818                offset += PAD2LEN_GNU;
819                offset += SPARSELEN_GNU;
820                isExtended = TarUtils.parseBoolean(header, offset);
821                offset += ISEXTENDEDLEN_GNU;
822                realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
823                offset += REALSIZELEN_GNU;
824                break;
825            }
826            case FORMAT_POSIX:
827            default: {
828                String prefix = TarUtils.parseName(header, offset, PREFIXLEN);
829                // SunOS tar -E does not add / to directory names, so fix
830                // up to be consistent
831                if (isDirectory() && !name.endsWith("/")){
832                    name = name + "/";
833                }
834                if (prefix.length() > 0){
835                    name = prefix + "/" + name;
836                }
837            }
838            }
839        }
840    
841        /**
842         * Strips Windows' drive letter as well as any leading slashes,
843         * turns path separators into forward slahes.
844         */
845        private static String normalizeFileName(String fileName,
846                                                boolean preserveLeadingSlashes) {
847            String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
848    
849            if (osname != null) {
850    
851                // Strip off drive letters!
852                // REVIEW Would a better check be "(File.separator == '\')"?
853    
854                if (osname.startsWith("windows")) {
855                    if (fileName.length() > 2) {
856                        char ch1 = fileName.charAt(0);
857                        char ch2 = fileName.charAt(1);
858    
859                        if (ch2 == ':'
860                            && ((ch1 >= 'a' && ch1 <= 'z')
861                                || (ch1 >= 'A' && ch1 <= 'Z'))) {
862                            fileName = fileName.substring(2);
863                        }
864                    }
865                } else if (osname.indexOf("netware") > -1) {
866                    int colon = fileName.indexOf(':');
867                    if (colon != -1) {
868                        fileName = fileName.substring(colon + 1);
869                    }
870                }
871            }
872    
873            fileName = fileName.replace(File.separatorChar, '/');
874    
875            // No absolute pathnames
876            // Windows (and Posix?) paths can start with "\\NetworkDrive\",
877            // so we loop on starting /'s.
878            while (!preserveLeadingSlashes && fileName.startsWith("/")) {
879                fileName = fileName.substring(1);
880            }
881            return fileName;
882        }
883    
884        /**
885         * Evaluate an entry's header format from a header buffer.
886         *
887         * @param header The tar entry header buffer to evaluate the format for.
888         * @return format type
889         */
890        private int evaluateType(byte[] header) {
891            final ByteBuffer magic = ByteBuffer.wrap(header, MAGIC_OFFSET, MAGICLEN);
892            if (magic.compareTo(ByteBuffer.wrap(MAGIC_GNU.getBytes())) == 0)
893                return FORMAT_OLDGNU;
894            if (magic.compareTo(ByteBuffer.wrap(MAGIC_POSIX.getBytes())) == 0)
895                return FORMAT_POSIX;
896            return 0;
897        }
898    }
899