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 * @since Apache Commons Compress 1.1
182 */
183 public TarArchiveEntry(String name, boolean preserveLeadingSlashes) {
184 this();
185
186 name = normalizeFileName(name, preserveLeadingSlashes);
187 boolean isDir = name.endsWith("/");
188
189 this.devMajor = 0;
190 this.devMinor = 0;
191 this.name = name;
192 this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
193 this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
194 this.userId = 0;
195 this.groupId = 0;
196 this.size = 0;
197 this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
198 this.linkName = "";
199 this.userName = "";
200 this.groupName = "";
201 this.devMajor = 0;
202 this.devMinor = 0;
203
204 }
205
206 /**
207 * Construct an entry with a name and a link flag.
208 *
209 * @param name the entry name
210 * @param linkFlag the entry link flag.
211 */
212 public TarArchiveEntry(String name, byte linkFlag) {
213 this(name);
214 this.linkFlag = linkFlag;
215 if (linkFlag == LF_GNUTYPE_LONGNAME) {
216 magic = MAGIC_GNU;
217 version = VERSION_GNU_SPACE;
218 }
219 }
220
221 /**
222 * Construct an entry for a file. File is set to file, and the
223 * header is constructed from information from the file.
224 * The name is set from the normalized file path.
225 *
226 * @param file The file that the entry represents.
227 */
228 public TarArchiveEntry(File file) {
229 this(file, normalizeFileName(file.getPath(), false));
230 }
231
232 /**
233 * Construct an entry for a file. File is set to file, and the
234 * header is constructed from information from the file.
235 *
236 * @param file The file that the entry represents.
237 * @param fileName the name to be used for the entry.
238 */
239 public TarArchiveEntry(File file, String fileName) {
240 this();
241
242 this.file = file;
243
244 this.linkName = "";
245
246 if (file.isDirectory()) {
247 this.mode = DEFAULT_DIR_MODE;
248 this.linkFlag = LF_DIR;
249
250 int nameLength = fileName.length();
251 if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') {
252 this.name = fileName + "/";
253 } else {
254 this.name = fileName;
255 }
256 this.size = 0;
257 } else {
258 this.mode = DEFAULT_FILE_MODE;
259 this.linkFlag = LF_NORMAL;
260 this.size = file.length();
261 this.name = fileName;
262 }
263
264 this.modTime = file.lastModified() / MILLIS_PER_SECOND;
265 this.devMajor = 0;
266 this.devMinor = 0;
267 }
268
269 /**
270 * Construct an entry from an archive's header bytes. File is set
271 * to null.
272 *
273 * @param headerBuf The header bytes from a tar archive entry.
274 */
275 public TarArchiveEntry(byte[] headerBuf) {
276 this();
277 parseTarHeader(headerBuf);
278 }
279
280 /**
281 * Determine if the two entries are equal. Equality is determined
282 * by the header names being equal.
283 *
284 * @param it Entry to be checked for equality.
285 * @return True if the entries are equal.
286 */
287 public boolean equals(TarArchiveEntry it) {
288 return getName().equals(it.getName());
289 }
290
291 /**
292 * Determine if the two entries are equal. Equality is determined
293 * by the header names being equal.
294 *
295 * @param it Entry to be checked for equality.
296 * @return True if the entries are equal.
297 */
298 public boolean equals(Object it) {
299 if (it == null || getClass() != it.getClass()) {
300 return false;
301 }
302 return equals((TarArchiveEntry) it);
303 }
304
305 /**
306 * Hashcodes are based on entry names.
307 *
308 * @return the entry hashcode
309 */
310 public int hashCode() {
311 return getName().hashCode();
312 }
313
314 /**
315 * Determine if the given entry is a descendant of this entry.
316 * Descendancy is determined by the name of the descendant
317 * starting with this entry's name.
318 *
319 * @param desc Entry to be checked as a descendent of this.
320 * @return True if entry is a descendant of this.
321 */
322 public boolean isDescendent(TarArchiveEntry desc) {
323 return desc.getName().startsWith(getName());
324 }
325
326 /**
327 * Get this entry's name.
328 *
329 * @return This entry's name.
330 */
331 public String getName() {
332 return name.toString();
333 }
334
335 /**
336 * Set this entry's name.
337 *
338 * @param name This entry's new name.
339 */
340 public void setName(String name) {
341 this.name = normalizeFileName(name, false);
342 }
343
344 /**
345 * Set the mode for this entry
346 *
347 * @param mode the mode for this entry
348 */
349 public void setMode(int mode) {
350 this.mode = mode;
351 }
352
353 /**
354 * Get this entry's link name.
355 *
356 * @return This entry's link name.
357 */
358 public String getLinkName() {
359 return linkName.toString();
360 }
361
362 /**
363 * Set this entry's link name.
364 *
365 * @param link the link name to use.
366 *
367 * @since Apache Commons Compress 1.1
368 */
369 public void setLinkName(String link) {
370 this.linkName = link;
371 }
372
373 /**
374 * Get this entry's user id.
375 *
376 * @return This entry's user id.
377 */
378 public int getUserId() {
379 return userId;
380 }
381
382 /**
383 * Set this entry's user id.
384 *
385 * @param userId This entry's new user id.
386 */
387 public void setUserId(int userId) {
388 this.userId = userId;
389 }
390
391 /**
392 * Get this entry's group id.
393 *
394 * @return This entry's group id.
395 */
396 public int getGroupId() {
397 return groupId;
398 }
399
400 /**
401 * Set this entry's group id.
402 *
403 * @param groupId This entry's new group id.
404 */
405 public void setGroupId(int groupId) {
406 this.groupId = groupId;
407 }
408
409 /**
410 * Get this entry's user name.
411 *
412 * @return This entry's user name.
413 */
414 public String getUserName() {
415 return userName.toString();
416 }
417
418 /**
419 * Set this entry's user name.
420 *
421 * @param userName This entry's new user name.
422 */
423 public void setUserName(String userName) {
424 this.userName = userName;
425 }
426
427 /**
428 * Get this entry's group name.
429 *
430 * @return This entry's group name.
431 */
432 public String getGroupName() {
433 return groupName.toString();
434 }
435
436 /**
437 * Set this entry's group name.
438 *
439 * @param groupName This entry's new group name.
440 */
441 public void setGroupName(String groupName) {
442 this.groupName = groupName;
443 }
444
445 /**
446 * Convenience method to set this entry's group and user ids.
447 *
448 * @param userId This entry's new user id.
449 * @param groupId This entry's new group id.
450 */
451 public void setIds(int userId, int groupId) {
452 setUserId(userId);
453 setGroupId(groupId);
454 }
455
456 /**
457 * Convenience method to set this entry's group and user names.
458 *
459 * @param userName This entry's new user name.
460 * @param groupName This entry's new group name.
461 */
462 public void setNames(String userName, String groupName) {
463 setUserName(userName);
464 setGroupName(groupName);
465 }
466
467 /**
468 * Set this entry's modification time. The parameter passed
469 * to this method is in "Java time".
470 *
471 * @param time This entry's new modification time.
472 */
473 public void setModTime(long time) {
474 modTime = time / MILLIS_PER_SECOND;
475 }
476
477 /**
478 * Set this entry's modification time.
479 *
480 * @param time This entry's new modification time.
481 */
482 public void setModTime(Date time) {
483 modTime = time.getTime() / MILLIS_PER_SECOND;
484 }
485
486 /**
487 * Set this entry's modification time.
488 *
489 * @return time This entry's new modification time.
490 */
491 public Date getModTime() {
492 return new Date(modTime * MILLIS_PER_SECOND);
493 }
494
495 /** {@inheritDoc} */
496 public Date getLastModifiedDate() {
497 return getModTime();
498 }
499
500 /**
501 * Get this entry's file.
502 *
503 * @return This entry's file.
504 */
505 public File getFile() {
506 return file;
507 }
508
509 /**
510 * Get this entry's mode.
511 *
512 * @return This entry's mode.
513 */
514 public int getMode() {
515 return mode;
516 }
517
518 /**
519 * Get this entry's file size.
520 *
521 * @return This entry's file size.
522 */
523 public long getSize() {
524 return size;
525 }
526
527 /**
528 * Set this entry's file size.
529 *
530 * @param size This entry's new file size.
531 * @throws IllegalArgumentException if the size is < 0
532 * or > {@link TarConstants#MAXSIZE} (077777777777L).
533 */
534 public void setSize(long size) {
535 if (size > MAXSIZE || size < 0){
536 throw new IllegalArgumentException("Size is out of range: "+size);
537 }
538 this.size = size;
539 }
540
541
542 /**
543 * Indicate if this entry is a GNU long name block
544 *
545 * @return true if this is a long name extension provided by GNU tar
546 */
547 public boolean isGNULongNameEntry() {
548 return linkFlag == LF_GNUTYPE_LONGNAME
549 && name.toString().equals(GNU_LONGLINK);
550 }
551
552 /**
553 * Check if this is a Pax header.
554 *
555 * @return <code>true</code> if this is a Pax header.
556 *
557 * @since Apache Commons Compress 1.1
558 */
559 public boolean isPaxHeader(){
560 return linkFlag == LF_PAX_EXTENDED_HEADER_LC
561 || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
562 }
563
564 /**
565 * Check if this is a Pax header.
566 *
567 * @return <code>true</code> if this is a Pax header.
568 *
569 * @since Apache Commons Compress 1.1
570 */
571 public boolean isGlobalPaxHeader(){
572 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
573 }
574
575 /**
576 * Return whether or not this entry represents a directory.
577 *
578 * @return True if this entry is a directory.
579 */
580 public boolean isDirectory() {
581 if (file != null) {
582 return file.isDirectory();
583 }
584
585 if (linkFlag == LF_DIR) {
586 return true;
587 }
588
589 if (getName().endsWith("/")) {
590 return true;
591 }
592
593 return false;
594 }
595
596 /**
597 * If this entry represents a file, and the file is a directory, return
598 * an array of TarEntries for this entry's children.
599 *
600 * @return An array of TarEntry's for this entry's children.
601 */
602 public TarArchiveEntry[] getDirectoryEntries() {
603 if (file == null || !file.isDirectory()) {
604 return new TarArchiveEntry[0];
605 }
606
607 String[] list = file.list();
608 TarArchiveEntry[] result = new TarArchiveEntry[list.length];
609
610 for (int i = 0; i < list.length; ++i) {
611 result[i] = new TarArchiveEntry(new File(file, list[i]));
612 }
613
614 return result;
615 }
616
617 /**
618 * Write an entry's header information to a header buffer.
619 *
620 * @param outbuf The tar entry header buffer to fill in.
621 */
622 public void writeEntryHeader(byte[] outbuf) {
623 int offset = 0;
624
625 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN);
626 offset = TarUtils.formatOctalBytes(mode, outbuf, offset, MODELEN);
627 offset = TarUtils.formatOctalBytes(userId, outbuf, offset, UIDLEN);
628 offset = TarUtils.formatOctalBytes(groupId, outbuf, offset, GIDLEN);
629 offset = TarUtils.formatLongOctalBytes(size, outbuf, offset, SIZELEN);
630 offset = TarUtils.formatLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
631
632 int csOffset = offset;
633
634 for (int c = 0; c < CHKSUMLEN; ++c) {
635 outbuf[offset++] = (byte) ' ';
636 }
637
638 outbuf[offset++] = linkFlag;
639 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN);
640 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN);
641 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
642 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN);
643 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN);
644 offset = TarUtils.formatOctalBytes(devMajor, outbuf, offset, DEVLEN);
645 offset = TarUtils.formatOctalBytes(devMinor, outbuf, offset, DEVLEN);
646
647 while (offset < outbuf.length) {
648 outbuf[offset++] = 0;
649 }
650
651 long chk = TarUtils.computeCheckSum(outbuf);
652
653 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
654 }
655
656 /**
657 * Parse an entry's header information from a header buffer.
658 *
659 * @param header The tar entry header buffer to get information from.
660 */
661 public void parseTarHeader(byte[] header) {
662 int offset = 0;
663
664 name = TarUtils.parseName(header, offset, NAMELEN);
665 offset += NAMELEN;
666 mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
667 offset += MODELEN;
668 userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
669 offset += UIDLEN;
670 groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
671 offset += GIDLEN;
672 size = TarUtils.parseOctal(header, offset, SIZELEN);
673 offset += SIZELEN;
674 modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
675 offset += MODTIMELEN;
676 offset += CHKSUMLEN;
677 linkFlag = header[offset++];
678 linkName = TarUtils.parseName(header, offset, NAMELEN);
679 offset += NAMELEN;
680 magic = TarUtils.parseName(header, offset, MAGICLEN);
681 offset += MAGICLEN;
682 version = TarUtils.parseName(header, offset, VERSIONLEN);
683 offset += VERSIONLEN;
684 userName = TarUtils.parseName(header, offset, UNAMELEN);
685 offset += UNAMELEN;
686 groupName = TarUtils.parseName(header, offset, GNAMELEN);
687 offset += GNAMELEN;
688 devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
689 offset += DEVLEN;
690 devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
691 offset += DEVLEN;
692 String prefix = TarUtils.parseName(header, offset, PREFIXLEN);
693 // SunOS tar -E does not add / to directory names, so fix up to be consistent
694 if (isDirectory() && !name.endsWith("/")){
695 name = name + "/";
696 }
697 if (prefix.length() >0){
698 name = prefix + "/" + name;
699 }
700 }
701
702 /**
703 * Strips Windows' drive letter as well as any leading slashes,
704 * turns path separators into forward slahes.
705 */
706 private static String normalizeFileName(String fileName,
707 boolean preserveLeadingSlashes) {
708 String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH);
709
710 if (osname != null) {
711
712 // Strip off drive letters!
713 // REVIEW Would a better check be "(File.separator == '\')"?
714
715 if (osname.startsWith("windows")) {
716 if (fileName.length() > 2) {
717 char ch1 = fileName.charAt(0);
718 char ch2 = fileName.charAt(1);
719
720 if (ch2 == ':'
721 && ((ch1 >= 'a' && ch1 <= 'z')
722 || (ch1 >= 'A' && ch1 <= 'Z'))) {
723 fileName = fileName.substring(2);
724 }
725 }
726 } else if (osname.indexOf("netware") > -1) {
727 int colon = fileName.indexOf(':');
728 if (colon != -1) {
729 fileName = fileName.substring(colon + 1);
730 }
731 }
732 }
733
734 fileName = fileName.replace(File.separatorChar, '/');
735
736 // No absolute pathnames
737 // Windows (and Posix?) paths can start with "\\NetworkDrive\",
738 // so we loop on starting /'s.
739 while (!preserveLeadingSlashes && fileName.startsWith("/")) {
740 fileName = fileName.substring(1);
741 }
742 return fileName;
743 }
744 }
745