• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied.  See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 package org.apache.commons.compress.archivers.tar;
20 
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.Collections;
24 import java.util.Date;
25 import java.util.HashMap;
26 import java.util.Locale;
27 import java.util.Map;
28 import org.apache.commons.compress.archivers.ArchiveEntry;
29 import org.apache.commons.compress.archivers.zip.ZipEncoding;
30 import org.apache.commons.compress.utils.ArchiveUtils;
31 
32 /**
33  * This class represents an entry in a Tar archive. It consists
34  * of the entry's header, as well as the entry's File. Entries
35  * can be instantiated in one of three ways, depending on how
36  * they are to be used.
37  * <p>
38  * TarEntries that are created from the header bytes read from
39  * an archive are instantiated with the TarEntry( byte[] )
40  * constructor. These entries will be used when extracting from
41  * or listing the contents of an archive. These entries have their
42  * header filled in using the header bytes. They also set the File
43  * to null, since they reference an archive entry not a file.
44  * <p>
45  * TarEntries that are created from Files that are to be written
46  * into an archive are instantiated with the TarEntry( File )
47  * constructor. These entries have their header filled in using
48  * the File's information. They also keep a reference to the File
49  * for convenience when writing entries.
50  * <p>
51  * Finally, TarEntries can be constructed from nothing but a name.
52  * This allows the programmer to construct the entry by hand, for
53  * instance when only an InputStream is available for writing to
54  * the archive, and the header information is constructed from
55  * other information. In this case the header fields are set to
56  * defaults and the File is set to null.
57  *
58  * <p>
59  * The C structure for a Tar Entry's header is:
60  * <pre>
61  * struct header {
62  * char name[100];     // TarConstants.NAMELEN    - offset   0
63  * char mode[8];       // TarConstants.MODELEN    - offset 100
64  * char uid[8];        // TarConstants.UIDLEN     - offset 108
65  * char gid[8];        // TarConstants.GIDLEN     - offset 116
66  * char size[12];      // TarConstants.SIZELEN    - offset 124
67  * char mtime[12];     // TarConstants.MODTIMELEN - offset 136
68  * char chksum[8];     // TarConstants.CHKSUMLEN  - offset 148
69  * char linkflag[1];   //                         - offset 156
70  * char linkname[100]; // TarConstants.NAMELEN    - offset 157
71  * The following fields are only present in new-style POSIX tar archives:
72  * char magic[6];      // TarConstants.MAGICLEN   - offset 257
73  * char version[2];    // TarConstants.VERSIONLEN - offset 263
74  * char uname[32];     // TarConstants.UNAMELEN   - offset 265
75  * char gname[32];     // TarConstants.GNAMELEN   - offset 297
76  * char devmajor[8];   // TarConstants.DEVLEN     - offset 329
77  * char devminor[8];   // TarConstants.DEVLEN     - offset 337
78  * char prefix[155];   // TarConstants.PREFIXLEN  - offset 345
79  * // Used if "name" field is not long enough to hold the path
80  * char pad[12];       // NULs                    - offset 500
81  * } header;
82  * All unused bytes are set to null.
83  * New-style GNU tar files are slightly different from the above.
84  * For values of size larger than 077777777777L (11 7s)
85  * or uid and gid larger than 07777777L (7 7s)
86  * the sign bit of the first byte is set, and the rest of the
87  * field is the binary representation of the number.
88  * See TarUtils.parseOctalOrBinary.
89  * </pre>
90  *
91  * <p>
92  * The C structure for a old GNU Tar Entry's header is:
93  * <pre>
94  * struct oldgnu_header {
95  * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU       - offset 0
96  * char atime[12];        // TarConstants.ATIMELEN_GNU      - offset 345
97  * char ctime[12];        // TarConstants.CTIMELEN_GNU      - offset 357
98  * char offset[12];       // TarConstants.OFFSETLEN_GNU     - offset 369
99  * char longnames[4];     // TarConstants.LONGNAMESLEN_GNU  - offset 381
100  * char unused_pad2;      // TarConstants.PAD2LEN_GNU       - offset 385
101  * struct sparse sp[4];   // TarConstants.SPARSELEN_GNU     - offset 386
102  * char isextended;       // TarConstants.ISEXTENDEDLEN_GNU - offset 482
103  * char realsize[12];     // TarConstants.REALSIZELEN_GNU   - offset 483
104  * char unused_pad[17];   // TarConstants.PAD3LEN_GNU       - offset 495
105  * };
106  * </pre>
107  * Whereas, "struct sparse" is:
108  * <pre>
109  * struct sparse {
110  * char offset[12];   // offset 0
111  * char numbytes[12]; // offset 12
112  * };
113  * </pre>
114  *
115  * <p>
116  * The C structure for a xstar (Jörg Schilling star) Tar Entry's header is:
117  * <pre>
118  * struct star_header {
119  *  char name[100];		// offset   0
120  *  char mode[8];		// offset 100
121  *  char uid[8];		// offset 108
122  *  char gid[8];		// offset 116
123  *  char size[12];		// offset 124
124  *  char mtime[12];		// offset 136
125  *  char chksum[8];		// offset 148
126  *  char typeflag;		// offset 156
127  *  char linkname[100];		// offset 157
128  *  char magic[6];		// offset 257
129  *  char version[2];		// offset 263
130  *  char uname[32];		// offset 265
131  *  char gname[32];		// offset 297
132  *  char devmajor[8];		// offset 329
133  *  char devminor[8];		// offset 337
134  *  char prefix[131];		// offset 345
135  *  char atime[12];             // offset 476
136  *  char ctime[12];             // offset 488
137  *  char mfill[8];              // offset 500
138  *  char xmagic[4];             // offset 508  "tar"
139  * };
140  * </pre>
141  * <p>which is identical to new-style POSIX up to the first 130 bytes of the prefix.</p>
142  *
143  * @NotThreadSafe
144  */
145 
146 public class TarArchiveEntry implements ArchiveEntry, TarConstants {
147     private static final TarArchiveEntry[] EMPTY_TAR_ARCHIVE_ENTRIES = new TarArchiveEntry[0];
148 
149     /** The entry's name. */
150     private String name = "";
151 
152     /** Whether to allow leading slashes or drive names inside the name */
153     private final boolean preserveAbsolutePath;
154 
155     /** The entry's permission mode. */
156     private int mode;
157 
158     /** The entry's user id. */
159     private long userId = 0;
160 
161     /** The entry's group id. */
162     private long groupId = 0;
163 
164     /** The entry's size. */
165     private long size = 0;
166 
167     /** The entry's modification time. */
168     private long modTime;
169 
170     /** If the header checksum is reasonably correct. */
171     private boolean checkSumOK;
172 
173     /** The entry's link flag. */
174     private byte linkFlag;
175 
176     /** The entry's link name. */
177     private String linkName = "";
178 
179     /** The entry's magic tag. */
180     private String magic = MAGIC_POSIX;
181     /** The version of the format */
182     private String version = VERSION_POSIX;
183 
184     /** The entry's user name. */
185     private String userName;
186 
187     /** The entry's group name. */
188     private String groupName = "";
189 
190     /** The entry's major device number. */
191     private int devMajor = 0;
192 
193     /** The entry's minor device number. */
194     private int devMinor = 0;
195 
196     /** If an extension sparse header follows. */
197     private boolean isExtended;
198 
199     /** The entry's real size in case of a sparse file. */
200     private long realSize;
201 
202     /** is this entry a GNU sparse entry using one of the PAX formats? */
203     private boolean paxGNUSparse;
204 
205     /** is this entry a star sparse entry using the PAX header? */
206     private boolean starSparse;
207 
208     /** The entry's file reference */
209     private final File file;
210 
211     /** Extra, user supplied pax headers     */
212     private final Map<String,String> extraPaxHeaders = new HashMap<>();
213 
214     /** Maximum length of a user's name in the tar file */
215     public static final int MAX_NAMELEN = 31;
216 
217     /** Default permissions bits for directories */
218     public static final int DEFAULT_DIR_MODE = 040755;
219 
220     /** Default permissions bits for files */
221     public static final int DEFAULT_FILE_MODE = 0100644;
222 
223     /** Convert millis to seconds */
224     public static final int MILLIS_PER_SECOND = 1000;
225 
226 
227     /**
228      * Construct an empty entry and prepares the header values.
229      */
TarArchiveEntry(boolean preserveAbsolutePath)230     private TarArchiveEntry(boolean preserveAbsolutePath) {
231         String user = System.getProperty("user.name", "");
232 
233         if (user.length() > MAX_NAMELEN) {
234             user = user.substring(0, MAX_NAMELEN);
235         }
236 
237         this.userName = user;
238         this.file = null;
239         this.preserveAbsolutePath = preserveAbsolutePath;
240     }
241 
242     /**
243      * Construct an entry with only a name. This allows the programmer
244      * to construct the entry's header "by hand". File is set to null.
245      *
246      * <p>The entry's name will be the value of the {@code name}
247      * argument with all file separators replaced by forward slashes
248      * and leading slashes as well as Windows drive letters stripped.</p>
249      *
250      * @param name the entry name
251      */
TarArchiveEntry(final String name)252     public TarArchiveEntry(final String name) {
253         this(name, false);
254     }
255 
256     /**
257      * Construct an entry with only a name. This allows the programmer
258      * to construct the entry's header "by hand". File is set to null.
259      *
260      * <p>The entry's name will be the value of the {@code name}
261      * argument with all file separators replaced by forward slashes.
262      * Leading slashes and Windows drive letters are stripped if
263      * {@code preserveAbsolutePath} is {@code false}.</p>
264      *
265      * @param name the entry name
266      * @param preserveAbsolutePath whether to allow leading slashes
267      * or drive letters in the name.
268      *
269      * @since 1.1
270      */
TarArchiveEntry(String name, final boolean preserveAbsolutePath)271     public TarArchiveEntry(String name, final boolean preserveAbsolutePath) {
272         this(preserveAbsolutePath);
273 
274         name = normalizeFileName(name, preserveAbsolutePath);
275         final boolean isDir = name.endsWith("/");
276 
277         this.name = name;
278         this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
279         this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
280         this.modTime = new Date().getTime() / MILLIS_PER_SECOND;
281         this.userName = "";
282     }
283 
284     /**
285      * Construct an entry with a name and a link flag.
286      *
287      * <p>The entry's name will be the value of the {@code name}
288      * argument with all file separators replaced by forward slashes
289      * and leading slashes as well as Windows drive letters
290      * stripped.</p>
291      *
292      * @param name the entry name
293      * @param linkFlag the entry link flag.
294      */
TarArchiveEntry(final String name, final byte linkFlag)295     public TarArchiveEntry(final String name, final byte linkFlag) {
296         this(name, linkFlag, false);
297     }
298 
299     /**
300      * Construct an entry with a name and a link flag.
301      *
302      * <p>The entry's name will be the value of the {@code name}
303      * argument with all file separators replaced by forward slashes.
304      * Leading slashes and Windows drive letters are stripped if
305      * {@code preserveAbsolutePath} is {@code false}.</p>
306      *
307      * @param name the entry name
308      * @param linkFlag the entry link flag.
309      * @param preserveAbsolutePath whether to allow leading slashes
310      * or drive letters in the name.
311      *
312      * @since 1.5
313      */
TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveAbsolutePath)314     public TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveAbsolutePath) {
315         this(name, preserveAbsolutePath);
316         this.linkFlag = linkFlag;
317         if (linkFlag == LF_GNUTYPE_LONGNAME) {
318             magic = MAGIC_GNU;
319             version = VERSION_GNU_SPACE;
320         }
321     }
322 
323     /**
324      * Construct an entry for a file. File is set to file, and the
325      * header is constructed from information from the file.
326      * The name is set from the normalized file path.
327      *
328      * <p>The entry's name will be the value of the {@code file}'s
329      * path with all file separators replaced by forward slashes and
330      * leading slashes as well as Windows drive letters stripped. The
331      * name will end in a slash if the {@code file} represents a
332      * directory.</p>
333      *
334      * @param file The file that the entry represents.
335      */
TarArchiveEntry(final File file)336     public TarArchiveEntry(final File file) {
337         this(file, file.getPath());
338     }
339 
340     /**
341      * Construct an entry for a file. File is set to file, and the
342      * header is constructed from information from the file.
343      *
344      * <p>The entry's name will be the value of the {@code fileName}
345      * argument with all file separators replaced by forward slashes
346      * and leading slashes as well as Windows drive letters stripped.
347      * The name will end in a slash if the {@code file} represents a
348      * directory.</p>
349      *
350      * @param file The file that the entry represents.
351      * @param fileName the name to be used for the entry.
352      */
TarArchiveEntry(final File file, final String fileName)353     public TarArchiveEntry(final File file, final String fileName) {
354         final String normalizedName = normalizeFileName(fileName, false);
355         this.file = file;
356 
357         if (file.isDirectory()) {
358             this.mode = DEFAULT_DIR_MODE;
359             this.linkFlag = LF_DIR;
360 
361             final int nameLength = normalizedName.length();
362             if (nameLength == 0 || normalizedName.charAt(nameLength - 1) != '/') {
363                 this.name = normalizedName + "/";
364             } else {
365                 this.name = normalizedName;
366             }
367         } else {
368             this.mode = DEFAULT_FILE_MODE;
369             this.linkFlag = LF_NORMAL;
370             this.size = file.length();
371             this.name = normalizedName;
372         }
373 
374         this.modTime = file.lastModified() / MILLIS_PER_SECOND;
375         this.userName = "";
376         preserveAbsolutePath = false;
377     }
378 
379     /**
380      * Construct an entry from an archive's header bytes. File is set
381      * to null.
382      *
383      * @param headerBuf The header bytes from a tar archive entry.
384      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
385      */
TarArchiveEntry(final byte[] headerBuf)386     public TarArchiveEntry(final byte[] headerBuf) {
387         this(false);
388         parseTarHeader(headerBuf);
389     }
390 
391     /**
392      * Construct an entry from an archive's header bytes. File is set
393      * to null.
394      *
395      * @param headerBuf The header bytes from a tar archive entry.
396      * @param encoding encoding to use for file names
397      * @since 1.4
398      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
399      * @throws IOException on error
400      */
TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding)401     public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding)
402         throws IOException {
403         this(false);
404         parseTarHeader(headerBuf, encoding);
405     }
406 
407     /**
408      * Determine if the two entries are equal. Equality is determined
409      * by the header names being equal.
410      *
411      * @param it Entry to be checked for equality.
412      * @return True if the entries are equal.
413      */
equals(final TarArchiveEntry it)414     public boolean equals(final TarArchiveEntry it) {
415         return it != null && getName().equals(it.getName());
416     }
417 
418     /**
419      * Determine if the two entries are equal. Equality is determined
420      * by the header names being equal.
421      *
422      * @param it Entry to be checked for equality.
423      * @return True if the entries are equal.
424      */
425     @Override
equals(final Object it)426     public boolean equals(final Object it) {
427         if (it == null || getClass() != it.getClass()) {
428             return false;
429         }
430         return equals((TarArchiveEntry) it);
431     }
432 
433     /**
434      * Hashcodes are based on entry names.
435      *
436      * @return the entry hashcode
437      */
438     @Override
hashCode()439     public int hashCode() {
440         return getName().hashCode();
441     }
442 
443     /**
444      * Determine if the given entry is a descendant of this entry.
445      * Descendancy is determined by the name of the descendant
446      * starting with this entry's name.
447      *
448      * @param desc Entry to be checked as a descendent of this.
449      * @return True if entry is a descendant of this.
450      */
isDescendent(final TarArchiveEntry desc)451     public boolean isDescendent(final TarArchiveEntry desc) {
452         return desc.getName().startsWith(getName());
453     }
454 
455     /**
456      * Get this entry's name.
457      *
458      * <p>This method returns the raw name as it is stored inside of the archive.</p>
459      *
460      * @return This entry's name.
461      */
462     @Override
getName()463     public String getName() {
464         return name;
465     }
466 
467     /**
468      * Set this entry's name.
469      *
470      * @param name This entry's new name.
471      */
setName(final String name)472     public void setName(final String name) {
473         this.name = normalizeFileName(name, this.preserveAbsolutePath);
474     }
475 
476     /**
477      * Set the mode for this entry
478      *
479      * @param mode the mode for this entry
480      */
setMode(final int mode)481     public void setMode(final int mode) {
482         this.mode = mode;
483     }
484 
485     /**
486      * Get this entry's link name.
487      *
488      * @return This entry's link name.
489      */
getLinkName()490     public String getLinkName() {
491         return linkName;
492     }
493 
494     /**
495      * Set this entry's link name.
496      *
497      * @param link the link name to use.
498      *
499      * @since 1.1
500      */
setLinkName(final String link)501     public void setLinkName(final String link) {
502         this.linkName = link;
503     }
504 
505     /**
506      * Get this entry's user id.
507      *
508      * @return This entry's user id.
509      * @deprecated use #getLongUserId instead as user ids can be
510      * bigger than {@link Integer#MAX_VALUE}
511      */
512     @Deprecated
getUserId()513     public int getUserId() {
514         return (int) (userId & 0xffffffff);
515     }
516 
517     /**
518      * Set this entry's user id.
519      *
520      * @param userId This entry's new user id.
521      */
setUserId(final int userId)522     public void setUserId(final int userId) {
523         setUserId((long) userId);
524     }
525 
526     /**
527      * Get this entry's user id.
528      *
529      * @return This entry's user id.
530      * @since 1.10
531      */
getLongUserId()532     public long getLongUserId() {
533         return userId;
534     }
535 
536     /**
537      * Set this entry's user id.
538      *
539      * @param userId This entry's new user id.
540      * @since 1.10
541      */
setUserId(final long userId)542     public void setUserId(final long userId) {
543         this.userId = userId;
544     }
545 
546     /**
547      * Get this entry's group id.
548      *
549      * @return This entry's group id.
550      * @deprecated use #getLongGroupId instead as group ids can be
551      * bigger than {@link Integer#MAX_VALUE}
552      */
553     @Deprecated
getGroupId()554     public int getGroupId() {
555         return (int) (groupId & 0xffffffff);
556     }
557 
558     /**
559      * Set this entry's group id.
560      *
561      * @param groupId This entry's new group id.
562      */
setGroupId(final int groupId)563     public void setGroupId(final int groupId) {
564         setGroupId((long) groupId);
565     }
566 
567     /**
568      * Get this entry's group id.
569      *
570      * @since 1.10
571      * @return This entry's group id.
572      */
getLongGroupId()573     public long getLongGroupId() {
574         return groupId;
575     }
576 
577     /**
578      * Set this entry's group id.
579      *
580      * @since 1.10
581      * @param groupId This entry's new group id.
582      */
setGroupId(final long groupId)583     public void setGroupId(final long groupId) {
584         this.groupId = groupId;
585     }
586 
587     /**
588      * Get this entry's user name.
589      *
590      * @return This entry's user name.
591      */
getUserName()592     public String getUserName() {
593         return userName;
594     }
595 
596     /**
597      * Set this entry's user name.
598      *
599      * @param userName This entry's new user name.
600      */
setUserName(final String userName)601     public void setUserName(final String userName) {
602         this.userName = userName;
603     }
604 
605     /**
606      * Get this entry's group name.
607      *
608      * @return This entry's group name.
609      */
getGroupName()610     public String getGroupName() {
611         return groupName;
612     }
613 
614     /**
615      * Set this entry's group name.
616      *
617      * @param groupName This entry's new group name.
618      */
setGroupName(final String groupName)619     public void setGroupName(final String groupName) {
620         this.groupName = groupName;
621     }
622 
623     /**
624      * Convenience method to set this entry's group and user ids.
625      *
626      * @param userId This entry's new user id.
627      * @param groupId This entry's new group id.
628      */
setIds(final int userId, final int groupId)629     public void setIds(final int userId, final int groupId) {
630         setUserId(userId);
631         setGroupId(groupId);
632     }
633 
634     /**
635      * Convenience method to set this entry's group and user names.
636      *
637      * @param userName This entry's new user name.
638      * @param groupName This entry's new group name.
639      */
setNames(final String userName, final String groupName)640     public void setNames(final String userName, final String groupName) {
641         setUserName(userName);
642         setGroupName(groupName);
643     }
644 
645     /**
646      * Set this entry's modification time. The parameter passed
647      * to this method is in "Java time".
648      *
649      * @param time This entry's new modification time.
650      */
setModTime(final long time)651     public void setModTime(final long time) {
652         modTime = time / MILLIS_PER_SECOND;
653     }
654 
655     /**
656      * Set this entry's modification time.
657      *
658      * @param time This entry's new modification time.
659      */
setModTime(final Date time)660     public void setModTime(final Date time) {
661         modTime = time.getTime() / MILLIS_PER_SECOND;
662     }
663 
664     /**
665      * Set this entry's modification time.
666      *
667      * @return time This entry's new modification time.
668      */
getModTime()669     public Date getModTime() {
670         return new Date(modTime * MILLIS_PER_SECOND);
671     }
672 
673     @Override
getLastModifiedDate()674     public Date getLastModifiedDate() {
675         return getModTime();
676     }
677 
678     /**
679      * Get this entry's checksum status.
680      *
681      * @return if the header checksum is reasonably correct
682      * @see TarUtils#verifyCheckSum(byte[])
683      * @since 1.5
684      */
isCheckSumOK()685     public boolean isCheckSumOK() {
686         return checkSumOK;
687     }
688 
689     /**
690      * Get this entry's file.
691      *
692      * <p>This method is only useful for entries created from a {@code
693      * File} but not for entries read from an archive.</p>
694      *
695      * @return This entry's file.
696      */
getFile()697     public File getFile() {
698         return file;
699     }
700 
701     /**
702      * Get this entry's mode.
703      *
704      * @return This entry's mode.
705      */
getMode()706     public int getMode() {
707         return mode;
708     }
709 
710     /**
711      * Get this entry's file size.
712      *
713      * @return This entry's file size.
714      */
715     @Override
getSize()716     public long getSize() {
717         return size;
718     }
719 
720     /**
721      * Set this entry's file size.
722      *
723      * @param size This entry's new file size.
724      * @throws IllegalArgumentException if the size is &lt; 0.
725      */
setSize(final long size)726     public void setSize(final long size) {
727         if (size < 0){
728             throw new IllegalArgumentException("Size is out of range: "+size);
729         }
730         this.size = size;
731     }
732 
733     /**
734      * Get this entry's major device number.
735      *
736      * @return This entry's major device number.
737      * @since 1.4
738      */
getDevMajor()739     public int getDevMajor() {
740         return devMajor;
741     }
742 
743     /**
744      * Set this entry's major device number.
745      *
746      * @param devNo This entry's major device number.
747      * @throws IllegalArgumentException if the devNo is &lt; 0.
748      * @since 1.4
749      */
setDevMajor(final int devNo)750     public void setDevMajor(final int devNo) {
751         if (devNo < 0){
752             throw new IllegalArgumentException("Major device number is out of "
753                                                + "range: " + devNo);
754         }
755         this.devMajor = devNo;
756     }
757 
758     /**
759      * Get this entry's minor device number.
760      *
761      * @return This entry's minor device number.
762      * @since 1.4
763      */
getDevMinor()764     public int getDevMinor() {
765         return devMinor;
766     }
767 
768     /**
769      * Set this entry's minor device number.
770      *
771      * @param devNo This entry's minor device number.
772      * @throws IllegalArgumentException if the devNo is &lt; 0.
773      * @since 1.4
774      */
setDevMinor(final int devNo)775     public void setDevMinor(final int devNo) {
776         if (devNo < 0){
777             throw new IllegalArgumentException("Minor device number is out of "
778                                                + "range: " + devNo);
779         }
780         this.devMinor = devNo;
781     }
782 
783     /**
784      * Indicates in case of an oldgnu sparse file if an extension
785      * sparse header follows.
786      *
787      * @return true if an extension oldgnu sparse header follows.
788      */
isExtended()789     public boolean isExtended() {
790         return isExtended;
791     }
792 
793     /**
794      * Get this entry's real file size in case of a sparse file.
795      *
796      * @return This entry's real file size.
797      */
getRealSize()798     public long getRealSize() {
799         return realSize;
800     }
801 
802     /**
803      * Indicate if this entry is a GNU sparse block.
804      *
805      * @return true if this is a sparse extension provided by GNU tar
806      */
isGNUSparse()807     public boolean isGNUSparse() {
808         return isOldGNUSparse() || isPaxGNUSparse();
809     }
810 
811     /**
812      * Indicate if this entry is a GNU or star sparse block using the
813      * oldgnu format.
814      *
815      * @return true if this is a sparse extension provided by GNU tar or star
816      * @since 1.11
817      */
isOldGNUSparse()818     public boolean isOldGNUSparse() {
819         return linkFlag == LF_GNUTYPE_SPARSE;
820     }
821 
822     /**
823      * Indicate if this entry is a GNU sparse block using one of the
824      * PAX formats.
825      *
826      * @return true if this is a sparse extension provided by GNU tar
827      * @since 1.11
828      */
isPaxGNUSparse()829     public boolean isPaxGNUSparse() {
830         return paxGNUSparse;
831     }
832 
833     /**
834      * Indicate if this entry is a star sparse block using PAX headers.
835      *
836      * @return true if this is a sparse extension provided by star
837      * @since 1.11
838      */
isStarSparse()839     public boolean isStarSparse() {
840         return starSparse;
841     }
842 
843     /**
844      * Indicate if this entry is a GNU long linkname block
845      *
846      * @return true if this is a long name extension provided by GNU tar
847      */
isGNULongLinkEntry()848     public boolean isGNULongLinkEntry() {
849         return linkFlag == LF_GNUTYPE_LONGLINK;
850     }
851 
852     /**
853      * Indicate if this entry is a GNU long name block
854      *
855      * @return true if this is a long name extension provided by GNU tar
856      */
isGNULongNameEntry()857     public boolean isGNULongNameEntry() {
858         return linkFlag == LF_GNUTYPE_LONGNAME;
859     }
860 
861     /**
862      * Check if this is a Pax header.
863      *
864      * @return {@code true} if this is a Pax header.
865      *
866      * @since 1.1
867      *
868      */
isPaxHeader()869     public boolean isPaxHeader() {
870         return linkFlag == LF_PAX_EXTENDED_HEADER_LC
871             || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
872     }
873 
874     /**
875      * Check if this is a Pax header.
876      *
877      * @return {@code true} if this is a Pax header.
878      *
879      * @since 1.1
880      */
isGlobalPaxHeader()881     public boolean isGlobalPaxHeader() {
882         return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
883     }
884 
885     /**
886      * Return whether or not this entry represents a directory.
887      *
888      * @return True if this entry is a directory.
889      */
890     @Override
isDirectory()891     public boolean isDirectory() {
892         if (file != null) {
893             return file.isDirectory();
894         }
895 
896         if (linkFlag == LF_DIR) {
897             return true;
898         }
899 
900         return !isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/");
901     }
902 
903     /**
904      * Check if this is a "normal file"
905      *
906      * @since 1.2
907      * @return whether this is a "normal file"
908      */
isFile()909     public boolean isFile() {
910         if (file != null) {
911             return file.isFile();
912         }
913         if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
914             return true;
915         }
916         return !getName().endsWith("/");
917     }
918 
919     /**
920      * Check if this is a symbolic link entry.
921      *
922      * @since 1.2
923      * @return whether this is a symbolic link
924      */
isSymbolicLink()925     public boolean isSymbolicLink() {
926         return linkFlag == LF_SYMLINK;
927     }
928 
929     /**
930      * Check if this is a link entry.
931      *
932      * @since 1.2
933      * @return whether this is a link entry
934      */
isLink()935     public boolean isLink() {
936         return linkFlag == LF_LINK;
937     }
938 
939     /**
940      * Check if this is a character device entry.
941      *
942      * @since 1.2
943      * @return whether this is a character device
944      */
isCharacterDevice()945     public boolean isCharacterDevice() {
946         return linkFlag == LF_CHR;
947     }
948 
949     /**
950      * Check if this is a block device entry.
951      *
952      * @since 1.2
953      * @return whether this is a block device
954      */
isBlockDevice()955     public boolean isBlockDevice() {
956         return linkFlag == LF_BLK;
957     }
958 
959     /**
960      * Check if this is a FIFO (pipe) entry.
961      *
962      * @since 1.2
963      * @return whether this is a FIFO entry
964      */
isFIFO()965     public boolean isFIFO() {
966         return linkFlag == LF_FIFO;
967     }
968 
969     /**
970      * Check whether this is a sparse entry.
971      *
972      * @return whether this is a sparse entry
973      * @since 1.11
974      */
isSparse()975     public boolean isSparse() {
976         return isGNUSparse() || isStarSparse();
977     }
978 
979     /**
980      * get extra PAX Headers
981      * @return read-only map containing any extra PAX Headers
982      * @since 1.15
983      */
getExtraPaxHeaders()984     public Map<String, String> getExtraPaxHeaders() {
985         return Collections.unmodifiableMap(extraPaxHeaders);
986     }
987 
988     /**
989      * clear all extra PAX headers.
990      * @since 1.15
991      */
clearExtraPaxHeaders()992     public void clearExtraPaxHeaders() {
993         extraPaxHeaders.clear();
994     }
995 
996     /**
997      * add a PAX header to this entry. If the header corresponds to an existing field in the entry,
998      * that field will be set; otherwise the header will be added to the extraPaxHeaders Map
999      * @param name  The full name of the header to set.
1000      * @param value value of header.
1001      * @since 1.15
1002      */
addPaxHeader(String name,String value)1003     public void addPaxHeader(String name,String value) {
1004          processPaxHeader(name,value);
1005     }
1006 
1007     /**
1008      * get named extra PAX header
1009      * @param name The full name of an extended PAX header to retrieve
1010      * @return The value of the header, if any.
1011      * @since 1.15
1012      */
getExtraPaxHeader(String name)1013     public String getExtraPaxHeader(String name) {
1014         return extraPaxHeaders.get(name);
1015     }
1016 
1017     /**
1018      * Update the entry using a map of pax headers.
1019      * @param headers
1020      * @since 1.15
1021      */
updateEntryFromPaxHeaders(Map<String, String> headers)1022     void updateEntryFromPaxHeaders(Map<String, String> headers) {
1023         for (final Map.Entry<String, String> ent : headers.entrySet()) {
1024             final String key = ent.getKey();
1025             final String val = ent.getValue();
1026             processPaxHeader(key, val, headers);
1027         }
1028     }
1029 
1030     /**
1031      * process one pax header, using the entries extraPaxHeaders map as source for extra headers
1032      * used when handling entries for sparse files.
1033      * @param key
1034      * @param val
1035      * @since 1.15
1036      */
processPaxHeader(String key, String val)1037     private void processPaxHeader(String key, String val) {
1038         processPaxHeader(key,val,extraPaxHeaders);
1039     }
1040 
1041     /**
1042      * Process one pax header, using the supplied map as source for extra headers to be used when handling
1043      * entries for sparse files
1044      *
1045      * @param key  the header name.
1046      * @param val  the header value.
1047      * @param headers  map of headers used for dealing with sparse file.
1048      * @since 1.15
1049      */
processPaxHeader(String key, String val, Map<String, String> headers)1050     private void processPaxHeader(String key, String val, Map<String, String> headers) {
1051     /*
1052      * The following headers are defined for Pax.
1053      * atime, ctime, charset: cannot use these without changing TarArchiveEntry fields
1054      * mtime
1055      * comment
1056      * gid, gname
1057      * linkpath
1058      * size
1059      * uid,uname
1060      * SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
1061      *
1062      * GNU sparse files use additional members, we use
1063      * GNU.sparse.size to detect the 0.0 and 0.1 versions and
1064      * GNU.sparse.realsize for 1.0.
1065      *
1066      * star files use additional members of which we use
1067      * SCHILY.filetype in order to detect star sparse files.
1068      *
1069      * If called from addExtraPaxHeader, these additional headers must be already present .
1070      */
1071         switch (key) {
1072             case "path":
1073                 setName(val);
1074                 break;
1075             case "linkpath":
1076                 setLinkName(val);
1077                 break;
1078             case "gid":
1079                 setGroupId(Long.parseLong(val));
1080                 break;
1081             case "gname":
1082                 setGroupName(val);
1083                 break;
1084             case "uid":
1085                 setUserId(Long.parseLong(val));
1086                 break;
1087             case "uname":
1088                 setUserName(val);
1089                 break;
1090             case "size":
1091                 setSize(Long.parseLong(val));
1092                 break;
1093             case "mtime":
1094                 setModTime((long) (Double.parseDouble(val) * 1000));
1095                 break;
1096             case "SCHILY.devminor":
1097                 setDevMinor(Integer.parseInt(val));
1098                 break;
1099             case "SCHILY.devmajor":
1100                 setDevMajor(Integer.parseInt(val));
1101                 break;
1102             case "GNU.sparse.size":
1103                 fillGNUSparse0xData(headers);
1104                 break;
1105             case "GNU.sparse.realsize":
1106                 fillGNUSparse1xData(headers);
1107                 break;
1108             case "SCHILY.filetype":
1109                 if ("sparse".equals(val)) {
1110                     fillStarSparseData(headers);
1111                 }
1112                 break;
1113             default:
1114                 extraPaxHeaders.put(key,val);
1115         }
1116     }
1117 
1118 
1119 
1120     /**
1121      * If this entry represents a file, and the file is a directory, return
1122      * an array of TarEntries for this entry's children.
1123      *
1124      * <p>This method is only useful for entries created from a {@code
1125      * File} but not for entries read from an archive.</p>
1126      *
1127      * @return An array of TarEntry's for this entry's children.
1128      */
getDirectoryEntries()1129     public TarArchiveEntry[] getDirectoryEntries() {
1130         if (file == null || !file.isDirectory()) {
1131             return EMPTY_TAR_ARCHIVE_ENTRIES;
1132         }
1133 
1134         final String[] list = file.list();
1135         if (list == null) {
1136             return EMPTY_TAR_ARCHIVE_ENTRIES;
1137         }
1138         final TarArchiveEntry[] result = new TarArchiveEntry[list.length];
1139 
1140         for (int i = 0; i < result.length; ++i) {
1141             result[i] = new TarArchiveEntry(new File(file, list[i]));
1142         }
1143 
1144         return result;
1145     }
1146 
1147     /**
1148      * Write an entry's header information to a header buffer.
1149      *
1150      * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
1151      *
1152      * @param outbuf The tar entry header buffer to fill in.
1153      */
writeEntryHeader(final byte[] outbuf)1154     public void writeEntryHeader(final byte[] outbuf) {
1155         try {
1156             writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
1157         } catch (final IOException ex) {
1158             try {
1159                 writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
1160             } catch (final IOException ex2) {
1161                 // impossible
1162                 throw new RuntimeException(ex2); //NOSONAR
1163             }
1164         }
1165     }
1166 
1167     /**
1168      * Write an entry's header information to a header buffer.
1169      *
1170      * @param outbuf The tar entry header buffer to fill in.
1171      * @param encoding encoding to use when writing the file name.
1172      * @param starMode whether to use the star/GNU tar/BSD tar
1173      * extension for numeric fields if their value doesn't fit in the
1174      * maximum size of standard tar archives
1175      * @since 1.4
1176      * @throws IOException on error
1177      */
writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding, final boolean starMode)1178     public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding,
1179                                  final boolean starMode) throws IOException {
1180         int offset = 0;
1181 
1182         offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
1183                                           encoding);
1184         offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
1185         offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
1186                                        starMode);
1187         offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
1188                                        starMode);
1189         offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
1190         offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
1191                                        starMode);
1192 
1193         final int csOffset = offset;
1194 
1195         for (int c = 0; c < CHKSUMLEN; ++c) {
1196             outbuf[offset++] = (byte) ' ';
1197         }
1198 
1199         outbuf[offset++] = linkFlag;
1200         offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
1201                                           encoding);
1202         offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
1203         offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
1204         offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
1205                                           encoding);
1206         offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
1207                                           encoding);
1208         offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
1209                                        starMode);
1210         offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
1211                                        starMode);
1212 
1213         while (offset < outbuf.length) {
1214             outbuf[offset++] = 0;
1215         }
1216 
1217         final long chk = TarUtils.computeCheckSum(outbuf);
1218 
1219         TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
1220     }
1221 
writeEntryHeaderField(final long value, final byte[] outbuf, final int offset, final int length, final boolean starMode)1222     private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset,
1223                                       final int length, final boolean starMode) {
1224         if (!starMode && (value < 0
1225                           || value >= 1L << 3 * (length - 1))) {
1226             // value doesn't fit into field when written as octal
1227             // number, will be written to PAX header or causes an
1228             // error
1229             return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
1230         }
1231         return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
1232                                                      length);
1233     }
1234 
1235     /**
1236      * Parse an entry's header information from a header buffer.
1237      *
1238      * @param header The tar entry header buffer to get information from.
1239      * @throws IllegalArgumentException if any of the numeric fields have an invalid format
1240      */
parseTarHeader(final byte[] header)1241     public void parseTarHeader(final byte[] header) {
1242         try {
1243             parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
1244         } catch (final IOException ex) {
1245             try {
1246                 parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
1247             } catch (final IOException ex2) {
1248                 // not really possible
1249                 throw new RuntimeException(ex2); //NOSONAR
1250             }
1251         }
1252     }
1253 
1254     /**
1255      * Parse an entry's header information from a header buffer.
1256      *
1257      * @param header The tar entry header buffer to get information from.
1258      * @param encoding encoding to use for file names
1259      * @since 1.4
1260      * @throws IllegalArgumentException if any of the numeric fields
1261      * have an invalid format
1262      * @throws IOException on error
1263      */
parseTarHeader(final byte[] header, final ZipEncoding encoding)1264     public void parseTarHeader(final byte[] header, final ZipEncoding encoding)
1265         throws IOException {
1266         parseTarHeader(header, encoding, false);
1267     }
1268 
parseTarHeader(final byte[] header, final ZipEncoding encoding, final boolean oldStyle)1269     private void parseTarHeader(final byte[] header, final ZipEncoding encoding,
1270                                 final boolean oldStyle)
1271         throws IOException {
1272         int offset = 0;
1273 
1274         name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
1275             : TarUtils.parseName(header, offset, NAMELEN, encoding);
1276         offset += NAMELEN;
1277         mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
1278         offset += MODELEN;
1279         userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
1280         offset += UIDLEN;
1281         groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
1282         offset += GIDLEN;
1283         size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
1284         offset += SIZELEN;
1285         modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
1286         offset += MODTIMELEN;
1287         checkSumOK = TarUtils.verifyCheckSum(header);
1288         offset += CHKSUMLEN;
1289         linkFlag = header[offset++];
1290         linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
1291             : TarUtils.parseName(header, offset, NAMELEN, encoding);
1292         offset += NAMELEN;
1293         magic = TarUtils.parseName(header, offset, MAGICLEN);
1294         offset += MAGICLEN;
1295         version = TarUtils.parseName(header, offset, VERSIONLEN);
1296         offset += VERSIONLEN;
1297         userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
1298             : TarUtils.parseName(header, offset, UNAMELEN, encoding);
1299         offset += UNAMELEN;
1300         groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
1301             : TarUtils.parseName(header, offset, GNAMELEN, encoding);
1302         offset += GNAMELEN;
1303         if (linkFlag == LF_CHR || linkFlag == LF_BLK) {
1304             devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
1305             offset += DEVLEN;
1306             devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
1307             offset += DEVLEN;
1308         } else {
1309             offset += 2 * DEVLEN;
1310         }
1311 
1312         final int type = evaluateType(header);
1313         switch (type) {
1314         case FORMAT_OLDGNU: {
1315             offset += ATIMELEN_GNU;
1316             offset += CTIMELEN_GNU;
1317             offset += OFFSETLEN_GNU;
1318             offset += LONGNAMESLEN_GNU;
1319             offset += PAD2LEN_GNU;
1320             offset += SPARSELEN_GNU;
1321             isExtended = TarUtils.parseBoolean(header, offset);
1322             offset += ISEXTENDEDLEN_GNU;
1323             realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
1324             offset += REALSIZELEN_GNU; // NOSONAR - assignment as documentation
1325             break;
1326         }
1327         case FORMAT_XSTAR: {
1328             final String xstarPrefix = oldStyle
1329                 ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR)
1330                 : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding);
1331             if (xstarPrefix.length() > 0) {
1332                 name = xstarPrefix + "/" + name;
1333             }
1334             break;
1335         }
1336         case FORMAT_POSIX:
1337         default: {
1338             final String prefix = oldStyle
1339                 ? TarUtils.parseName(header, offset, PREFIXLEN)
1340                 : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
1341             // SunOS tar -E does not add / to directory names, so fix
1342             // up to be consistent
1343             if (isDirectory() && !name.endsWith("/")){
1344                 name = name + "/";
1345             }
1346             if (prefix.length() > 0){
1347                 name = prefix + "/" + name;
1348             }
1349         }
1350         }
1351     }
1352 
1353     /**
1354      * Strips Windows' drive letter as well as any leading slashes,
1355      * turns path separators into forward slahes.
1356      */
normalizeFileName(String fileName, final boolean preserveAbsolutePath)1357     private static String normalizeFileName(String fileName,
1358                                             final boolean preserveAbsolutePath) {
1359         if (!preserveAbsolutePath) {
1360             final String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
1361 
1362             if (osname != null) {
1363 
1364                 // Strip off drive letters!
1365                 // REVIEW Would a better check be "(File.separator == '\')"?
1366 
1367                 if (osname.startsWith("windows")) {
1368                     if (fileName.length() > 2) {
1369                         final char ch1 = fileName.charAt(0);
1370                         final char ch2 = fileName.charAt(1);
1371 
1372                         if (ch2 == ':'
1373                             && (ch1 >= 'a' && ch1 <= 'z'
1374                                 || ch1 >= 'A' && ch1 <= 'Z')) {
1375                             fileName = fileName.substring(2);
1376                         }
1377                     }
1378                 } else if (osname.contains("netware")) {
1379                     final int colon = fileName.indexOf(':');
1380                     if (colon != -1) {
1381                         fileName = fileName.substring(colon + 1);
1382                     }
1383                 }
1384             }
1385         }
1386 
1387         fileName = fileName.replace(File.separatorChar, '/');
1388 
1389         // No absolute pathnames
1390         // Windows (and Posix?) paths can start with "\\NetworkDrive\",
1391         // so we loop on starting /'s.
1392         while (!preserveAbsolutePath && fileName.startsWith("/")) {
1393             fileName = fileName.substring(1);
1394         }
1395         return fileName;
1396     }
1397 
1398     /**
1399      * Evaluate an entry's header format from a header buffer.
1400      *
1401      * @param header The tar entry header buffer to evaluate the format for.
1402      * @return format type
1403      */
evaluateType(final byte[] header)1404     private int evaluateType(final byte[] header) {
1405         if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) {
1406             return FORMAT_OLDGNU;
1407         }
1408         if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) {
1409             if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET,
1410                                               XSTAR_MAGIC_LEN)) {
1411                 return FORMAT_XSTAR;
1412             }
1413             return FORMAT_POSIX;
1414         }
1415         return 0;
1416     }
1417 
fillGNUSparse0xData(final Map<String, String> headers)1418     void fillGNUSparse0xData(final Map<String, String> headers) {
1419         paxGNUSparse = true;
1420         realSize = Integer.parseInt(headers.get("GNU.sparse.size"));
1421         if (headers.containsKey("GNU.sparse.name")) {
1422             // version 0.1
1423             name = headers.get("GNU.sparse.name");
1424         }
1425     }
1426 
fillGNUSparse1xData(final Map<String, String> headers)1427     void fillGNUSparse1xData(final Map<String, String> headers) {
1428         paxGNUSparse = true;
1429         realSize = Integer.parseInt(headers.get("GNU.sparse.realsize"));
1430         name = headers.get("GNU.sparse.name");
1431     }
1432 
fillStarSparseData(final Map<String, String> headers)1433     void fillStarSparseData(final Map<String, String> headers) {
1434         starSparse = true;
1435         if (headers.containsKey("SCHILY.realsize")) {
1436             realSize = Long.parseLong(headers.get("SCHILY.realsize"));
1437         }
1438     }
1439 }
1440 
1441