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.io.IOException;
023    import java.util.Date;
024    import java.util.Locale;
025    
026    import org.apache.commons.compress.archivers.ArchiveEntry;
027    import org.apache.commons.compress.archivers.zip.ZipEncoding;
028    import 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    
116    public 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 Commons Compress 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        /** {@inheritDoc} */
545        public Date getLastModifiedDate() {
546            return getModTime();
547        }
548    
549        /**
550         * Get this entry's checksum status.
551         *
552         * @return if the header checksum is reasonably correct
553         * @see TarUtils#verifyCheckSum(byte[])
554         * @since 1.5
555         */
556        public boolean isCheckSumOK() {
557            return checkSumOK;
558        }
559    
560        /**
561         * Get this entry's file.
562         *
563         * @return This entry's file.
564         */
565        public File getFile() {
566            return file;
567        }
568    
569        /**
570         * Get this entry's mode.
571         *
572         * @return This entry's mode.
573         */
574        public int getMode() {
575            return mode;
576        }
577    
578        /**
579         * Get this entry's file size.
580         *
581         * @return This entry's file size.
582         */
583        public long getSize() {
584            return size;
585        }
586    
587        /**
588         * Set this entry's file size.
589         *
590         * @param size This entry's new file size.
591         * @throws IllegalArgumentException if the size is &lt; 0.
592         */
593        public void setSize(long size) {
594            if (size < 0){
595                throw new IllegalArgumentException("Size is out of range: "+size);
596            }
597            this.size = size;
598        }
599    
600        /**
601         * Get this entry's major device number.
602         *
603         * @return This entry's major device number.
604         * @since 1.4
605         */
606        public int getDevMajor() {
607            return devMajor;
608        }
609    
610        /**
611         * Set this entry's major device number.
612         *
613         * @param devNo This entry's major device number.
614         * @throws IllegalArgumentException if the devNo is &lt; 0.
615         * @since 1.4
616         */
617        public void setDevMajor(int devNo) {
618            if (devNo < 0){
619                throw new IllegalArgumentException("Major device number is out of "
620                                                   + "range: " + devNo);
621            }
622            this.devMajor = devNo;
623        }
624    
625        /**
626         * Get this entry's minor device number.
627         *
628         * @return This entry's minor device number.
629         * @since 1.4
630         */
631        public int getDevMinor() {
632            return devMinor;
633        }
634    
635        /**
636         * Set this entry's minor device number.
637         *
638         * @param devNo This entry's minor device number.
639         * @throws IllegalArgumentException if the devNo is &lt; 0.
640         * @since 1.4
641         */
642        public void setDevMinor(int devNo) {
643            if (devNo < 0){
644                throw new IllegalArgumentException("Minor device number is out of "
645                                                   + "range: " + devNo);
646            }
647            this.devMinor = devNo;
648        }
649    
650        /**
651         * Indicates in case of a sparse file if an extension sparse header
652         * follows.
653         *
654         * @return true if an extension sparse header follows.
655         */
656        public boolean isExtended() {
657            return isExtended;
658        }
659    
660        /**
661         * Get this entry's real file size in case of a sparse file.
662         *
663         * @return This entry's real file size.
664         */
665        public long getRealSize() {
666            return realSize;
667        }
668    
669        /**
670         * Indicate if this entry is a GNU sparse block 
671         *
672         * @return true if this is a sparse extension provided by GNU tar
673         */
674        public boolean isGNUSparse() {
675            return linkFlag == LF_GNUTYPE_SPARSE;
676        }
677    
678        /**
679         * Indicate if this entry is a GNU long name block
680         *
681         * @return true if this is a long name extension provided by GNU tar
682         */
683        public boolean isGNULongNameEntry() {
684            return linkFlag == LF_GNUTYPE_LONGNAME
685                && name.equals(GNU_LONGLINK);
686        }
687    
688        /**
689         * Check if this is a Pax header.
690         * 
691         * @return {@code true} if this is a Pax header.
692         * 
693         * @since 1.1
694         * 
695         */
696        public boolean isPaxHeader(){
697            return linkFlag == LF_PAX_EXTENDED_HEADER_LC
698                || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
699        }
700    
701        /**
702         * Check if this is a Pax header.
703         * 
704         * @return {@code true} if this is a Pax header.
705         * 
706         * @since 1.1
707         */
708        public boolean isGlobalPaxHeader(){
709            return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
710        }
711    
712        /**
713         * Return whether or not this entry represents a directory.
714         *
715         * @return True if this entry is a directory.
716         */
717        public boolean isDirectory() {
718            if (file != null) {
719                return file.isDirectory();
720            }
721    
722            if (linkFlag == LF_DIR) {
723                return true;
724            }
725    
726            if (getName().endsWith("/")) {
727                return true;
728            }
729    
730            return false;
731        }
732    
733        /**
734         * Check if this is a "normal file"
735         *
736         * @since 1.2
737         */
738        public boolean isFile() {
739            if (file != null) {
740                return file.isFile();
741            }
742            if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
743                return true;
744            }
745            return !getName().endsWith("/");
746        }
747    
748        /**
749         * Check if this is a symbolic link entry.
750         *
751         * @since 1.2
752         */
753        public boolean isSymbolicLink() {
754            return linkFlag == LF_SYMLINK;
755        }
756    
757        /**
758         * Check if this is a link entry.
759         *
760         * @since 1.2
761         */
762        public boolean isLink() {
763            return linkFlag == LF_LINK;
764        }
765    
766        /**
767         * Check if this is a character device entry.
768         *
769         * @since 1.2
770         */
771        public boolean isCharacterDevice() {
772            return linkFlag == LF_CHR;
773        }
774    
775        /**
776         * Check if this is a block device entry.
777         *
778         * @since 1.2
779         */
780        public boolean isBlockDevice() {
781            return linkFlag == LF_BLK;
782        }
783    
784        /**
785         * Check if this is a FIFO (pipe) entry.
786         *
787         * @since 1.2
788         */
789        public boolean isFIFO() {
790            return linkFlag == LF_FIFO;
791        }
792    
793        /**
794         * If this entry represents a file, and the file is a directory, return
795         * an array of TarEntries for this entry's children.
796         *
797         * @return An array of TarEntry's for this entry's children.
798         */
799        public TarArchiveEntry[] getDirectoryEntries() {
800            if (file == null || !file.isDirectory()) {
801                return new TarArchiveEntry[0];
802            }
803    
804            String[]   list = file.list();
805            TarArchiveEntry[] result = new TarArchiveEntry[list.length];
806    
807            for (int i = 0; i < list.length; ++i) {
808                result[i] = new TarArchiveEntry(new File(file, list[i]));
809            }
810    
811            return result;
812        }
813    
814        /**
815         * Write an entry's header information to a header buffer.
816         *
817         * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
818         *
819         * @param outbuf The tar entry header buffer to fill in.
820         */
821        public void writeEntryHeader(byte[] outbuf) {
822            try {
823                writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
824            } catch (IOException ex) {
825                try {
826                    writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
827                } catch (IOException ex2) {
828                    // impossible
829                    throw new RuntimeException(ex2);
830                }
831            }
832        }
833    
834        /**
835         * Write an entry's header information to a header buffer.
836         *
837         * @param outbuf The tar entry header buffer to fill in.
838         * @param encoding encoding to use when writing the file name.
839         * @param starMode whether to use the star/GNU tar/BSD tar
840         * extension for numeric fields if their value doesn't fit in the
841         * maximum size of standard tar archives
842         * @since 1.4
843         */
844        public void writeEntryHeader(byte[] outbuf, ZipEncoding encoding,
845                                     boolean starMode) throws IOException {
846            int offset = 0;
847    
848            offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
849                                              encoding);
850            offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
851            offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
852                                           starMode);
853            offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
854                                           starMode);
855            offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
856            offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
857                                           starMode);
858    
859            int csOffset = offset;
860    
861            for (int c = 0; c < CHKSUMLEN; ++c) {
862                outbuf[offset++] = (byte) ' ';
863            }
864    
865            outbuf[offset++] = linkFlag;
866            offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
867                                              encoding);
868            offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
869            offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
870            offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
871                                              encoding);
872            offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
873                                              encoding);
874            offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
875                                           starMode);
876            offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
877                                           starMode);
878    
879            while (offset < outbuf.length) {
880                outbuf[offset++] = 0;
881            }
882    
883            long chk = TarUtils.computeCheckSum(outbuf);
884    
885            TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
886        }
887    
888        private int writeEntryHeaderField(long value, byte[] outbuf, int offset,
889                                          int length, boolean starMode) {
890            if (!starMode && (value < 0
891                              || value >= (1l << (3 * (length - 1))))) {
892                // value doesn't fit into field when written as octal
893                // number, will be written to PAX header or causes an
894                // error
895                return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
896            }
897            return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
898                                                         length);
899        }
900    
901        /**
902         * Parse an entry's header information from a header buffer.
903         *
904         * @param header The tar entry header buffer to get information from.
905         * @throws IllegalArgumentException if any of the numeric fields have an invalid format
906         */
907        public void parseTarHeader(byte[] header) {
908            try {
909                parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
910            } catch (IOException ex) {
911                try {
912                    parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
913                } catch (IOException ex2) {
914                    // not really possible
915                    throw new RuntimeException(ex2);
916                }
917            }
918        }
919    
920        /**
921         * Parse an entry's header information from a header buffer.
922         *
923         * @param header The tar entry header buffer to get information from.
924         * @param encoding encoding to use for file names
925         * @since Commons Compress 1.4
926         * @throws IllegalArgumentException if any of the numeric fields
927         * have an invalid format
928         */
929        public void parseTarHeader(byte[] header, ZipEncoding encoding)
930            throws IOException {
931            parseTarHeader(header, encoding, false);
932        }
933    
934        private void parseTarHeader(byte[] header, ZipEncoding encoding,
935                                    final boolean oldStyle)
936            throws IOException {
937            int offset = 0;
938    
939            name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
940                : TarUtils.parseName(header, offset, NAMELEN, encoding);
941            offset += NAMELEN;
942            mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
943            offset += MODELEN;
944            userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
945            offset += UIDLEN;
946            groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
947            offset += GIDLEN;
948            size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
949            offset += SIZELEN;
950            modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
951            offset += MODTIMELEN;
952            checkSumOK = TarUtils.verifyCheckSum(header);
953            offset += CHKSUMLEN;
954            linkFlag = header[offset++];
955            linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
956                : TarUtils.parseName(header, offset, NAMELEN, encoding);
957            offset += NAMELEN;
958            magic = TarUtils.parseName(header, offset, MAGICLEN);
959            offset += MAGICLEN;
960            version = TarUtils.parseName(header, offset, VERSIONLEN);
961            offset += VERSIONLEN;
962            userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
963                : TarUtils.parseName(header, offset, UNAMELEN, encoding);
964            offset += UNAMELEN;
965            groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
966                : TarUtils.parseName(header, offset, GNAMELEN, encoding);
967            offset += GNAMELEN;
968            devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
969            offset += DEVLEN;
970            devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
971            offset += DEVLEN;
972    
973            int type = evaluateType(header);
974            switch (type) {
975            case FORMAT_OLDGNU: {
976                offset += ATIMELEN_GNU;
977                offset += CTIMELEN_GNU;
978                offset += OFFSETLEN_GNU;
979                offset += LONGNAMESLEN_GNU;
980                offset += PAD2LEN_GNU;
981                offset += SPARSELEN_GNU;
982                isExtended = TarUtils.parseBoolean(header, offset);
983                offset += ISEXTENDEDLEN_GNU;
984                realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
985                offset += REALSIZELEN_GNU;
986                break;
987            }
988            case FORMAT_POSIX:
989            default: {
990                String prefix = oldStyle
991                    ? TarUtils.parseName(header, offset, PREFIXLEN)
992                    : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
993                // SunOS tar -E does not add / to directory names, so fix
994                // up to be consistent
995                if (isDirectory() && !name.endsWith("/")){
996                    name = name + "/";
997                }
998                if (prefix.length() > 0){
999                    name = prefix + "/" + name;
1000                }
1001            }
1002            }
1003        }
1004    
1005        /**
1006         * Strips Windows' drive letter as well as any leading slashes,
1007         * turns path separators into forward slahes.
1008         */
1009        private static String normalizeFileName(String fileName,
1010                                                boolean preserveLeadingSlashes) {
1011            String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
1012    
1013            if (osname != null) {
1014    
1015                // Strip off drive letters!
1016                // REVIEW Would a better check be "(File.separator == '\')"?
1017    
1018                if (osname.startsWith("windows")) {
1019                    if (fileName.length() > 2) {
1020                        char ch1 = fileName.charAt(0);
1021                        char ch2 = fileName.charAt(1);
1022    
1023                        if (ch2 == ':'
1024                            && ((ch1 >= 'a' && ch1 <= 'z')
1025                                || (ch1 >= 'A' && ch1 <= 'Z'))) {
1026                            fileName = fileName.substring(2);
1027                        }
1028                    }
1029                } else if (osname.indexOf("netware") > -1) {
1030                    int colon = fileName.indexOf(':');
1031                    if (colon != -1) {
1032                        fileName = fileName.substring(colon + 1);
1033                    }
1034                }
1035            }
1036    
1037            fileName = fileName.replace(File.separatorChar, '/');
1038    
1039            // No absolute pathnames
1040            // Windows (and Posix?) paths can start with "\\NetworkDrive\",
1041            // so we loop on starting /'s.
1042            while (!preserveLeadingSlashes && fileName.startsWith("/")) {
1043                fileName = fileName.substring(1);
1044            }
1045            return fileName;
1046        }
1047    
1048        /**
1049         * Evaluate an entry's header format from a header buffer.
1050         *
1051         * @param header The tar entry header buffer to evaluate the format for.
1052         * @return format type
1053         */
1054        private int evaluateType(byte[] header) {
1055            if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
1056                return FORMAT_OLDGNU;
1057            }
1058            if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
1059                return FORMAT_POSIX;
1060            }
1061            return 0;
1062        }
1063    }
1064