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