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.util.Date;
023    import java.util.Locale;
024    
025    import org.apache.commons.compress.archivers.ArchiveEntry;
026    
027    /**
028     * This class represents an entry in a Tar archive. It consists
029     * of the entry's header, as well as the entry's File. Entries
030     * can be instantiated in one of three ways, depending on how
031     * they are to be used.
032     * <p>
033     * TarEntries that are created from the header bytes read from
034     * an archive are instantiated with the TarEntry( byte[] )
035     * constructor. These entries will be used when extracting from
036     * or listing the contents of an archive. These entries have their
037     * header filled in using the header bytes. They also set the File
038     * to null, since they reference an archive entry not a file.
039     * <p>
040     * TarEntries that are created from Files that are to be written
041     * into an archive are instantiated with the TarEntry( File )
042     * constructor. These entries have their header filled in using
043     * the File's information. They also keep a reference to the File
044     * for convenience when writing entries.
045     * <p>
046     * Finally, TarEntries can be constructed from nothing but a name.
047     * This allows the programmer to construct the entry by hand, for
048     * instance when only an InputStream is available for writing to
049     * the archive, and the header information is constructed from
050     * other information. In this case the header fields are set to
051     * defaults and the File is set to null.
052     *
053     * <p>
054     * The C structure for a Tar Entry's header is:
055     * <pre>
056     * struct header {
057     * char name[100];     // TarConstants.NAMELEN    - offset   0
058     * char mode[8];       // TarConstants.MODELEN    - offset 100
059     * char uid[8];        // TarConstants.UIDLEN     - offset 108
060     * char gid[8];        // TarConstants.GIDLEN     - offset 116
061     * char size[12];      // TarConstants.SIZELEN    - offset 124
062     * char mtime[12];     // TarConstants.MODTIMELEN - offset 136
063     * char chksum[8];     // TarConstants.CHKSUMLEN  - offset 148
064     * char linkflag[1];   //                         - offset 156
065     * char linkname[100]; // TarConstants.NAMELEN    - offset 157
066     * The following fields are only present in new-style POSIX tar archives:
067     * char magic[6];      // TarConstants.MAGICLEN   - offset 257
068     * char version[2];    // TarConstants.VERSIONLEN - offset 263
069     * char uname[32];     // TarConstants.UNAMELEN   - offset 265
070     * char gname[32];     // TarConstants.GNAMELEN   - offset 297
071     * char devmajor[8];   // TarConstants.DEVLEN     - offset 329
072     * char devminor[8];   // TarConstants.DEVLEN     - offset 337
073     * char prefix[155];   // TarConstants.PREFIXLEN  - offset 345
074     * // Used if "name" field is not long enough to hold the path
075     * char pad[12];       // NULs                    - offset 500
076     * } header;
077     * All unused bytes are set to null.
078     * New-style GNU tar files are slightly different from the above.
079     * </pre>
080     * 
081     * @NotThreadSafe
082     */
083    
084    public class TarArchiveEntry implements TarConstants, ArchiveEntry {
085        /** The entry's name. */
086        private String name;
087    
088        /** The entry's permission mode. */
089        private int mode;
090    
091        /** The entry's user id. */
092        private int userId;
093    
094        /** The entry's group id. */
095        private int groupId;
096    
097        /** The entry's size. */
098        private long size;
099    
100        /** The entry's modification time. */
101        private long modTime;
102    
103        /** The entry's link flag. */
104        private byte linkFlag;
105    
106        /** The entry's link name. */
107        private String linkName;
108    
109        /** The entry's magic tag. */
110        private String magic;
111        /** The version of the format */
112        private String version;
113    
114        /** The entry's user name. */
115        private String userName;
116    
117        /** The entry's group name. */
118        private String groupName;
119    
120        /** The entry's major device number. */
121        private int devMajor;
122    
123        /** The entry's minor device number. */
124        private int devMinor;
125    
126        /** The entry's file reference */
127        private File file;
128    
129        /** Maximum length of a user's name in the tar file */
130        public static final int MAX_NAMELEN = 31;
131    
132        /** Default permissions bits for directories */
133        public static final int DEFAULT_DIR_MODE = 040755;
134    
135        /** Default permissions bits for files */
136        public static final int DEFAULT_FILE_MODE = 0100644;
137    
138        /** Convert millis to seconds */
139        public static final int MILLIS_PER_SECOND = 1000;
140    
141        /**
142         * Construct an empty entry and prepares the header values.
143         */
144        private TarArchiveEntry () {
145            this.magic = MAGIC_POSIX;
146            this.version = VERSION_POSIX;
147            this.name = "";
148            this.linkName = "";
149    
150            String user = System.getProperty("user.name", "");
151    
152            if (user.length() > MAX_NAMELEN) {
153                user = user.substring(0, MAX_NAMELEN);
154            }
155    
156            this.userId = 0;
157            this.groupId = 0;
158            this.userName = user;
159            this.groupName = "";
160            this.file = null;
161        }
162    
163        /**
164         * Construct an entry with only a name. This allows the programmer
165         * to construct the entry's header "by hand". File is set to null.
166         *
167         * @param name the entry name
168         */
169        public TarArchiveEntry(String name) {
170            this(name, false);
171        }
172    
173        /**
174         * Construct an entry with only a name. This allows the programmer
175         * to construct the entry's header "by hand". File is set to null.
176         *
177         * @param name the entry name
178         * @param preserveLeadingSlashes whether to allow leading slashes
179         * in the name.
180         */
181        public TarArchiveEntry(String name, boolean preserveLeadingSlashes) {
182            this();
183    
184            name = normalizeFileName(name, preserveLeadingSlashes);
185            boolean isDir = name.endsWith("/");
186    
187            this.devMajor = 0;
188            this.devMinor = 0;
189            this.name = name;
190            this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
191            this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
192            this.userId = 0;
193            this.groupId = 0;
194            this.size = 0;
195            this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
196            this.linkName = "";
197            this.userName = "";
198            this.groupName = "";
199            this.devMajor = 0;
200            this.devMinor = 0;
201    
202        }
203    
204        /**
205         * Construct an entry with a name and a link flag.
206         *
207         * @param name the entry name
208         * @param linkFlag the entry link flag.
209         */
210        public TarArchiveEntry(String name, byte linkFlag) {
211            this(name);
212            this.linkFlag = linkFlag;
213            if (linkFlag == LF_GNUTYPE_LONGNAME) {
214                magic = MAGIC_GNU;
215                version = VERSION_GNU_SPACE;
216            }
217        }
218    
219        /**
220         * Construct an entry for a file. File is set to file, and the
221         * header is constructed from information from the file.
222         * The name is set from the normalized file path.
223         *
224         * @param file The file that the entry represents.
225         */
226        public TarArchiveEntry(File file) {
227            this(file, normalizeFileName(file.getPath(), false));
228        }
229        
230        /**
231         * Construct an entry for a file. File is set to file, and the
232         * header is constructed from information from the file.
233         *
234         * @param file The file that the entry represents.
235         * @param fileName the name to be used for the entry.
236         */
237        public TarArchiveEntry(File file, String fileName) {
238            this();
239    
240            this.file = file;
241    
242            this.linkName = "";
243    
244            if (file.isDirectory()) {
245                this.mode = DEFAULT_DIR_MODE;
246                this.linkFlag = LF_DIR;
247    
248                int nameLength = fileName.length();
249                if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') {
250                    this.name = fileName + "/";
251                } else {
252                    this.name = fileName;                
253                }
254                this.size = 0;
255            } else {
256                this.mode = DEFAULT_FILE_MODE;
257                this.linkFlag = LF_NORMAL;
258                this.size = file.length();
259                this.name = fileName;
260            }
261    
262            this.modTime = file.lastModified() / MILLIS_PER_SECOND;
263            this.devMajor = 0;
264            this.devMinor = 0;
265        }
266    
267        /**
268         * Construct an entry from an archive's header bytes. File is set
269         * to null.
270         *
271         * @param headerBuf The header bytes from a tar archive entry.
272         */
273        public TarArchiveEntry(byte[] headerBuf) {
274            this();
275            parseTarHeader(headerBuf);
276        }
277    
278        /**
279         * Determine if the two entries are equal. Equality is determined
280         * by the header names being equal.
281         *
282         * @param it Entry to be checked for equality.
283         * @return True if the entries are equal.
284         */
285        public boolean equals(TarArchiveEntry it) {
286            return getName().equals(it.getName());
287        }
288    
289        /**
290         * Determine if the two entries are equal. Equality is determined
291         * by the header names being equal.
292         *
293         * @param it Entry to be checked for equality.
294         * @return True if the entries are equal.
295         */
296        public boolean equals(Object it) {
297            if (it == null || getClass() != it.getClass()) {
298                return false;
299            }
300            return equals((TarArchiveEntry) it);
301        }
302    
303        /**
304         * Hashcodes are based on entry names.
305         *
306         * @return the entry hashcode
307         */
308        public int hashCode() {
309            return getName().hashCode();
310        }
311    
312        /**
313         * Determine if the given entry is a descendant of this entry.
314         * Descendancy is determined by the name of the descendant
315         * starting with this entry's name.
316         *
317         * @param desc Entry to be checked as a descendent of this.
318         * @return True if entry is a descendant of this.
319         */
320        public boolean isDescendent(TarArchiveEntry desc) {
321            return desc.getName().startsWith(getName());
322        }
323    
324        /**
325         * Get this entry's name.
326         *
327         * @return This entry's name.
328         */
329        public String getName() {
330            return name.toString();
331        }
332    
333        /**
334         * Set this entry's name.
335         *
336         * @param name This entry's new name.
337         */
338        public void setName(String name) {
339            this.name = normalizeFileName(name, false);
340        }
341    
342        /**
343         * Set the mode for this entry
344         *
345         * @param mode the mode for this entry
346         */
347        public void setMode(int mode) {
348            this.mode = mode;
349        }
350    
351        /**
352         * Get this entry's link name.
353         *
354         * @return This entry's link name.
355         */
356        public String getLinkName() {
357            return linkName.toString();
358        }
359    
360        /**
361         * Get this entry's user id.
362         *
363         * @return This entry's user id.
364         */
365        public int getUserId() {
366            return userId;
367        }
368    
369        /**
370         * Set this entry's user id.
371         *
372         * @param userId This entry's new user id.
373         */
374        public void setUserId(int userId) {
375            this.userId = userId;
376        }
377    
378        /**
379         * Get this entry's group id.
380         *
381         * @return This entry's group id.
382         */
383        public int getGroupId() {
384            return groupId;
385        }
386    
387        /**
388         * Set this entry's group id.
389         *
390         * @param groupId This entry's new group id.
391         */
392        public void setGroupId(int groupId) {
393            this.groupId = groupId;
394        }
395    
396        /**
397         * Get this entry's user name.
398         *
399         * @return This entry's user name.
400         */
401        public String getUserName() {
402            return userName.toString();
403        }
404    
405        /**
406         * Set this entry's user name.
407         *
408         * @param userName This entry's new user name.
409         */
410        public void setUserName(String userName) {
411            this.userName = userName;
412        }
413    
414        /**
415         * Get this entry's group name.
416         *
417         * @return This entry's group name.
418         */
419        public String getGroupName() {
420            return groupName.toString();
421        }
422    
423        /**
424         * Set this entry's group name.
425         *
426         * @param groupName This entry's new group name.
427         */
428        public void setGroupName(String groupName) {
429            this.groupName = groupName;
430        }
431    
432        /**
433         * Convenience method to set this entry's group and user ids.
434         *
435         * @param userId This entry's new user id.
436         * @param groupId This entry's new group id.
437         */
438        public void setIds(int userId, int groupId) {
439            setUserId(userId);
440            setGroupId(groupId);
441        }
442    
443        /**
444         * Convenience method to set this entry's group and user names.
445         *
446         * @param userName This entry's new user name.
447         * @param groupName This entry's new group name.
448         */
449        public void setNames(String userName, String groupName) {
450            setUserName(userName);
451            setGroupName(groupName);
452        }
453    
454        /**
455         * Set this entry's modification time. The parameter passed
456         * to this method is in "Java time".
457         *
458         * @param time This entry's new modification time.
459         */
460        public void setModTime(long time) {
461            modTime = time / MILLIS_PER_SECOND;
462        }
463    
464        /**
465         * Set this entry's modification time.
466         *
467         * @param time This entry's new modification time.
468         */
469        public void setModTime(Date time) {
470            modTime = time.getTime() / MILLIS_PER_SECOND;
471        }
472    
473        /**
474         * Set this entry's modification time.
475         *
476         * @return time This entry's new modification time.
477         */
478        public Date getModTime() {
479            return new Date(modTime * MILLIS_PER_SECOND);
480        }
481    
482        /** {@inheritDocs} */
483        public Date getLastModifiedDate() {
484            return getModTime();
485        }
486    
487        /**
488         * Get this entry's file.
489         *
490         * @return This entry's file.
491         */
492        public File getFile() {
493            return file;
494        }
495    
496        /**
497         * Get this entry's mode.
498         *
499         * @return This entry's mode.
500         */
501        public int getMode() {
502            return mode;
503        }
504    
505        /**
506         * Get this entry's file size.
507         *
508         * @return This entry's file size.
509         */
510        public long getSize() {
511            return size;
512        }
513    
514        /**
515         * Set this entry's file size.
516         *
517         * @param size This entry's new file size.
518         * @throws IllegalArgumentException if the size is < 0
519         * or > {@link TarConstants#MAXSIZE} (077777777777L).
520         */
521        public void setSize(long size) {
522            if (size > MAXSIZE || size < 0){
523                throw new IllegalArgumentException("Size is out of range: "+size);
524            }
525            this.size = size;
526        }
527    
528    
529        /**
530         * Indicate if this entry is a GNU long name block
531         *
532         * @return true if this is a long name extension provided by GNU tar
533         */
534        public boolean isGNULongNameEntry() {
535            return linkFlag == LF_GNUTYPE_LONGNAME
536                && name.toString().equals(GNU_LONGLINK);
537        }
538    
539        /**
540         * Return whether or not this entry represents a directory.
541         *
542         * @return True if this entry is a directory.
543         */
544        public boolean isDirectory() {
545            if (file != null) {
546                return file.isDirectory();
547            }
548    
549            if (linkFlag == LF_DIR) {
550                return true;
551            }
552    
553            if (getName().endsWith("/")) {
554                return true;
555            }
556    
557            return false;
558        }
559    
560        /**
561         * If this entry represents a file, and the file is a directory, return
562         * an array of TarEntries for this entry's children.
563         *
564         * @return An array of TarEntry's for this entry's children.
565         */
566        public TarArchiveEntry[] getDirectoryEntries() {
567            if (file == null || !file.isDirectory()) {
568                return new TarArchiveEntry[0];
569            }
570    
571            String[]   list = file.list();
572            TarArchiveEntry[] result = new TarArchiveEntry[list.length];
573    
574            for (int i = 0; i < list.length; ++i) {
575                result[i] = new TarArchiveEntry(new File(file, list[i]));
576            }
577    
578            return result;
579        }
580    
581        /**
582         * Write an entry's header information to a header buffer.
583         *
584         * @param outbuf The tar entry header buffer to fill in.
585         */
586        public void writeEntryHeader(byte[] outbuf) {
587            int offset = 0;
588    
589            offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN);
590            offset = TarUtils.formatOctalBytes(mode, outbuf, offset, MODELEN);
591            offset = TarUtils.formatOctalBytes(userId, outbuf, offset, UIDLEN);
592            offset = TarUtils.formatOctalBytes(groupId, outbuf, offset, GIDLEN);
593            offset = TarUtils.formatLongOctalBytes(size, outbuf, offset, SIZELEN);
594            offset = TarUtils.formatLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
595    
596            int csOffset = offset;
597    
598            for (int c = 0; c < CHKSUMLEN; ++c) {
599                outbuf[offset++] = (byte) ' ';
600            }
601    
602            outbuf[offset++] = linkFlag;
603            offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN);
604            offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
605            offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
606            offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN);
607            offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN);
608            offset = TarUtils.formatOctalBytes(devMajor, outbuf, offset, DEVLEN);
609            offset = TarUtils.formatOctalBytes(devMinor, outbuf, offset, DEVLEN);
610    
611            while (offset < outbuf.length) {
612                outbuf[offset++] = 0;
613            }
614    
615            long chk = TarUtils.computeCheckSum(outbuf);
616    
617            TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
618        }
619    
620        /**
621         * Parse an entry's header information from a header buffer.
622         *
623         * @param header The tar entry header buffer to get information from.
624         */
625        public void parseTarHeader(byte[] header) {
626            int offset = 0;
627    
628            name = TarUtils.parseName(header, offset, NAMELEN);
629            offset += NAMELEN;
630            mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
631            offset += MODELEN;
632            userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
633            offset += UIDLEN;
634            groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
635            offset += GIDLEN;
636            size = TarUtils.parseOctal(header, offset, SIZELEN);
637            offset += SIZELEN;
638            modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
639            offset += MODTIMELEN;
640            offset += CHKSUMLEN;
641            linkFlag = header[offset++];
642            linkName = TarUtils.parseName(header, offset, NAMELEN);
643            offset += NAMELEN;
644            magic = TarUtils.parseName(header, offset, MAGICLEN);
645            offset += MAGICLEN;
646            version = TarUtils.parseName(header, offset, VERSIONLEN);
647            offset += VERSIONLEN;
648            userName = TarUtils.parseName(header, offset, UNAMELEN);
649            offset += UNAMELEN;
650            groupName = TarUtils.parseName(header, offset, GNAMELEN);
651            offset += GNAMELEN;
652            devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
653            offset += DEVLEN;
654            devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
655        }
656    
657        /**
658         * Strips Windows' drive letter as well as any leading slashes,
659         * turns path separators into forward slahes.
660         */
661        private static String normalizeFileName(String fileName,
662                                                boolean preserveLeadingSlashes) {
663            String osname = System.getProperty("os.name").toLowerCase(Locale.US);
664    
665            if (osname != null) {
666    
667                // Strip off drive letters!
668                // REVIEW Would a better check be "(File.separator == '\')"?
669    
670                if (osname.startsWith("windows")) {
671                    if (fileName.length() > 2) {
672                        char ch1 = fileName.charAt(0);
673                        char ch2 = fileName.charAt(1);
674    
675                        if (ch2 == ':'
676                            && ((ch1 >= 'a' && ch1 <= 'z')
677                                || (ch1 >= 'A' && ch1 <= 'Z'))) {
678                            fileName = fileName.substring(2);
679                        }
680                    }
681                } else if (osname.indexOf("netware") > -1) {
682                    int colon = fileName.indexOf(':');
683                    if (colon != -1) {
684                        fileName = fileName.substring(colon + 1);
685                    }
686                }
687            }
688    
689            fileName = fileName.replace(File.separatorChar, '/');
690    
691            // No absolute pathnames
692            // Windows (and Posix?) paths can start with "\\NetworkDrive\",
693            // so we loop on starting /'s.
694            while (!preserveLeadingSlashes && fileName.startsWith("/")) {
695                fileName = fileName.substring(1);
696            }
697            return fileName;
698        }
699    }
700