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 */ 019package org.apache.commons.compress.archivers.tar; 020 021import java.io.File; 022import java.io.IOException; 023import java.util.Collections; 024import java.util.Date; 025import java.util.HashMap; 026import java.util.Locale; 027import java.util.Map; 028import org.apache.commons.compress.archivers.ArchiveEntry; 029import org.apache.commons.compress.archivers.zip.ZipEncoding; 030import org.apache.commons.compress.utils.ArchiveUtils; 031 032/** 033 * This class represents an entry in a Tar archive. It consists 034 * of the entry's header, as well as the entry's File. Entries 035 * can be instantiated in one of three ways, depending on how 036 * they are to be used. 037 * <p> 038 * TarEntries that are created from the header bytes read from 039 * an archive are instantiated with the TarEntry( byte[] ) 040 * constructor. These entries will be used when extracting from 041 * or listing the contents of an archive. These entries have their 042 * header filled in using the header bytes. They also set the File 043 * to null, since they reference an archive entry not a file. 044 * <p> 045 * TarEntries that are created from Files that are to be written 046 * into an archive are instantiated with the TarEntry( File ) 047 * constructor. These entries have their header filled in using 048 * the File's information. They also keep a reference to the File 049 * for convenience when writing entries. 050 * <p> 051 * Finally, TarEntries can be constructed from nothing but a name. 052 * This allows the programmer to construct the entry by hand, for 053 * instance when only an InputStream is available for writing to 054 * the archive, and the header information is constructed from 055 * other information. In this case the header fields are set to 056 * defaults and the File is set to null. 057 * 058 * <p> 059 * The C structure for a Tar Entry's header is: 060 * <pre> 061 * struct header { 062 * char name[100]; // TarConstants.NAMELEN - offset 0 063 * char mode[8]; // TarConstants.MODELEN - offset 100 064 * char uid[8]; // TarConstants.UIDLEN - offset 108 065 * char gid[8]; // TarConstants.GIDLEN - offset 116 066 * char size[12]; // TarConstants.SIZELEN - offset 124 067 * char mtime[12]; // TarConstants.MODTIMELEN - offset 136 068 * char chksum[8]; // TarConstants.CHKSUMLEN - offset 148 069 * char linkflag[1]; // - offset 156 070 * char linkname[100]; // TarConstants.NAMELEN - offset 157 071 * The following fields are only present in new-style POSIX tar archives: 072 * char magic[6]; // TarConstants.MAGICLEN - offset 257 073 * char version[2]; // TarConstants.VERSIONLEN - offset 263 074 * char uname[32]; // TarConstants.UNAMELEN - offset 265 075 * char gname[32]; // TarConstants.GNAMELEN - offset 297 076 * char devmajor[8]; // TarConstants.DEVLEN - offset 329 077 * char devminor[8]; // TarConstants.DEVLEN - offset 337 078 * char prefix[155]; // TarConstants.PREFIXLEN - offset 345 079 * // Used if "name" field is not long enough to hold the path 080 * char pad[12]; // NULs - offset 500 081 * } header; 082 * All unused bytes are set to null. 083 * New-style GNU tar files are slightly different from the above. 084 * For values of size larger than 077777777777L (11 7s) 085 * or uid and gid larger than 07777777L (7 7s) 086 * the sign bit of the first byte is set, and the rest of the 087 * field is the binary representation of the number. 088 * See TarUtils.parseOctalOrBinary. 089 * </pre> 090 * 091 * <p> 092 * The C structure for a old GNU Tar Entry's header is: 093 * <pre> 094 * struct oldgnu_header { 095 * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU - offset 0 096 * char atime[12]; // TarConstants.ATIMELEN_GNU - offset 345 097 * char ctime[12]; // TarConstants.CTIMELEN_GNU - offset 357 098 * char offset[12]; // TarConstants.OFFSETLEN_GNU - offset 369 099 * 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 146public 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 */ 451 public boolean isDescendent(final TarArchiveEntry desc) { 452 return desc.getName().startsWith(getName()); 453 } 454 455 /** 456 * Get this entry's name. 457 * 458 * @return This entry's name. 459 */ 460 @Override 461 public String getName() { 462 return name; 463 } 464 465 /** 466 * Set this entry's name. 467 * 468 * @param name This entry's new name. 469 */ 470 public void setName(final String name) { 471 this.name = normalizeFileName(name, this.preserveAbsolutePath); 472 } 473 474 /** 475 * Set the mode for this entry 476 * 477 * @param mode the mode for this entry 478 */ 479 public void setMode(final int mode) { 480 this.mode = mode; 481 } 482 483 /** 484 * Get this entry's link name. 485 * 486 * @return This entry's link name. 487 */ 488 public String getLinkName() { 489 return linkName; 490 } 491 492 /** 493 * Set this entry's link name. 494 * 495 * @param link the link name to use. 496 * 497 * @since 1.1 498 */ 499 public void setLinkName(final String link) { 500 this.linkName = link; 501 } 502 503 /** 504 * Get this entry's user id. 505 * 506 * @return This entry's user id. 507 * @deprecated use #getLongUserId instead as user ids can be 508 * bigger than {@link Integer#MAX_VALUE} 509 */ 510 @Deprecated 511 public int getUserId() { 512 return (int) (userId & 0xffffffff); 513 } 514 515 /** 516 * Set this entry's user id. 517 * 518 * @param userId This entry's new user id. 519 */ 520 public void setUserId(final int userId) { 521 setUserId((long) userId); 522 } 523 524 /** 525 * Get this entry's user id. 526 * 527 * @return This entry's user id. 528 * @since 1.10 529 */ 530 public long getLongUserId() { 531 return userId; 532 } 533 534 /** 535 * Set this entry's user id. 536 * 537 * @param userId This entry's new user id. 538 * @since 1.10 539 */ 540 public void setUserId(final long userId) { 541 this.userId = userId; 542 } 543 544 /** 545 * Get this entry's group id. 546 * 547 * @return This entry's group id. 548 * @deprecated use #getLongGroupId instead as group ids can be 549 * bigger than {@link Integer#MAX_VALUE} 550 */ 551 @Deprecated 552 public int getGroupId() { 553 return (int) (groupId & 0xffffffff); 554 } 555 556 /** 557 * Set this entry's group id. 558 * 559 * @param groupId This entry's new group id. 560 */ 561 public void setGroupId(final int groupId) { 562 setGroupId((long) groupId); 563 } 564 565 /** 566 * Get this entry's group id. 567 * 568 * @since 1.10 569 * @return This entry's group id. 570 */ 571 public long getLongGroupId() { 572 return groupId; 573 } 574 575 /** 576 * Set this entry's group id. 577 * 578 * @since 1.10 579 * @param groupId This entry's new group id. 580 */ 581 public void setGroupId(final long groupId) { 582 this.groupId = groupId; 583 } 584 585 /** 586 * Get this entry's user name. 587 * 588 * @return This entry's user name. 589 */ 590 public String getUserName() { 591 return userName; 592 } 593 594 /** 595 * Set this entry's user name. 596 * 597 * @param userName This entry's new user name. 598 */ 599 public void setUserName(final String userName) { 600 this.userName = userName; 601 } 602 603 /** 604 * Get this entry's group name. 605 * 606 * @return This entry's group name. 607 */ 608 public String getGroupName() { 609 return groupName; 610 } 611 612 /** 613 * Set this entry's group name. 614 * 615 * @param groupName This entry's new group name. 616 */ 617 public void setGroupName(final String groupName) { 618 this.groupName = groupName; 619 } 620 621 /** 622 * Convenience method to set this entry's group and user ids. 623 * 624 * @param userId This entry's new user id. 625 * @param groupId This entry's new group id. 626 */ 627 public void setIds(final int userId, final int groupId) { 628 setUserId(userId); 629 setGroupId(groupId); 630 } 631 632 /** 633 * Convenience method to set this entry's group and user names. 634 * 635 * @param userName This entry's new user name. 636 * @param groupName This entry's new group name. 637 */ 638 public void setNames(final String userName, final String groupName) { 639 setUserName(userName); 640 setGroupName(groupName); 641 } 642 643 /** 644 * Set this entry's modification time. The parameter passed 645 * to this method is in "Java time". 646 * 647 * @param time This entry's new modification time. 648 */ 649 public void setModTime(final long time) { 650 modTime = time / MILLIS_PER_SECOND; 651 } 652 653 /** 654 * Set this entry's modification time. 655 * 656 * @param time This entry's new modification time. 657 */ 658 public void setModTime(final Date time) { 659 modTime = time.getTime() / MILLIS_PER_SECOND; 660 } 661 662 /** 663 * Set this entry's modification time. 664 * 665 * @return time This entry's new modification time. 666 */ 667 public Date getModTime() { 668 return new Date(modTime * MILLIS_PER_SECOND); 669 } 670 671 @Override 672 public Date getLastModifiedDate() { 673 return getModTime(); 674 } 675 676 /** 677 * Get this entry's checksum status. 678 * 679 * @return if the header checksum is reasonably correct 680 * @see TarUtils#verifyCheckSum(byte[]) 681 * @since 1.5 682 */ 683 public boolean isCheckSumOK() { 684 return checkSumOK; 685 } 686 687 /** 688 * Get this entry's file. 689 * 690 * <p>This method is only useful for entries created from a {@code 691 * File} but not for entries read from an archive.</p> 692 * 693 * @return This entry's file. 694 */ 695 public File getFile() { 696 return file; 697 } 698 699 /** 700 * Get this entry's mode. 701 * 702 * @return This entry's mode. 703 */ 704 public int getMode() { 705 return mode; 706 } 707 708 /** 709 * Get this entry's file size. 710 * 711 * @return This entry's file size. 712 */ 713 @Override 714 public long getSize() { 715 return size; 716 } 717 718 /** 719 * Set this entry's file size. 720 * 721 * @param size This entry's new file size. 722 * @throws IllegalArgumentException if the size is < 0. 723 */ 724 public void setSize(final long size) { 725 if (size < 0){ 726 throw new IllegalArgumentException("Size is out of range: "+size); 727 } 728 this.size = size; 729 } 730 731 /** 732 * Get this entry's major device number. 733 * 734 * @return This entry's major device number. 735 * @since 1.4 736 */ 737 public int getDevMajor() { 738 return devMajor; 739 } 740 741 /** 742 * Set this entry's major device number. 743 * 744 * @param devNo This entry's major device number. 745 * @throws IllegalArgumentException if the devNo is < 0. 746 * @since 1.4 747 */ 748 public void setDevMajor(final int devNo) { 749 if (devNo < 0){ 750 throw new IllegalArgumentException("Major device number is out of " 751 + "range: " + devNo); 752 } 753 this.devMajor = devNo; 754 } 755 756 /** 757 * Get this entry's minor device number. 758 * 759 * @return This entry's minor device number. 760 * @since 1.4 761 */ 762 public int getDevMinor() { 763 return devMinor; 764 } 765 766 /** 767 * Set this entry's minor device number. 768 * 769 * @param devNo This entry's minor device number. 770 * @throws IllegalArgumentException if the devNo is < 0. 771 * @since 1.4 772 */ 773 public void setDevMinor(final int devNo) { 774 if (devNo < 0){ 775 throw new IllegalArgumentException("Minor device number is out of " 776 + "range: " + devNo); 777 } 778 this.devMinor = devNo; 779 } 780 781 /** 782 * Indicates in case of an oldgnu sparse file if an extension 783 * sparse header follows. 784 * 785 * @return true if an extension oldgnu sparse header follows. 786 */ 787 public boolean isExtended() { 788 return isExtended; 789 } 790 791 /** 792 * Get this entry's real file size in case of a sparse file. 793 * 794 * @return This entry's real file size. 795 */ 796 public long getRealSize() { 797 return realSize; 798 } 799 800 /** 801 * Indicate if this entry is a GNU sparse block. 802 * 803 * @return true if this is a sparse extension provided by GNU tar 804 */ 805 public boolean isGNUSparse() { 806 return isOldGNUSparse() || isPaxGNUSparse(); 807 } 808 809 /** 810 * Indicate if this entry is a GNU or star sparse block using the 811 * oldgnu format. 812 * 813 * @return true if this is a sparse extension provided by GNU tar or star 814 * @since 1.11 815 */ 816 public boolean isOldGNUSparse() { 817 return linkFlag == LF_GNUTYPE_SPARSE; 818 } 819 820 /** 821 * Indicate if this entry is a GNU sparse block using one of the 822 * PAX formats. 823 * 824 * @return true if this is a sparse extension provided by GNU tar 825 * @since 1.11 826 */ 827 public boolean isPaxGNUSparse() { 828 return paxGNUSparse; 829 } 830 831 /** 832 * Indicate if this entry is a star sparse block using PAX headers. 833 * 834 * @return true if this is a sparse extension provided by star 835 * @since 1.11 836 */ 837 public boolean isStarSparse() { 838 return starSparse; 839 } 840 841 /** 842 * Indicate if this entry is a GNU long linkname block 843 * 844 * @return true if this is a long name extension provided by GNU tar 845 */ 846 public boolean isGNULongLinkEntry() { 847 return linkFlag == LF_GNUTYPE_LONGLINK; 848 } 849 850 /** 851 * Indicate if this entry is a GNU long name block 852 * 853 * @return true if this is a long name extension provided by GNU tar 854 */ 855 public boolean isGNULongNameEntry() { 856 return linkFlag == LF_GNUTYPE_LONGNAME; 857 } 858 859 /** 860 * Check if this is a Pax header. 861 * 862 * @return {@code true} if this is a Pax header. 863 * 864 * @since 1.1 865 * 866 */ 867 public boolean isPaxHeader() { 868 return linkFlag == LF_PAX_EXTENDED_HEADER_LC 869 || linkFlag == LF_PAX_EXTENDED_HEADER_UC; 870 } 871 872 /** 873 * Check if this is a Pax header. 874 * 875 * @return {@code true} if this is a Pax header. 876 * 877 * @since 1.1 878 */ 879 public boolean isGlobalPaxHeader() { 880 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER; 881 } 882 883 /** 884 * Return whether or not this entry represents a directory. 885 * 886 * @return True if this entry is a directory. 887 */ 888 @Override 889 public boolean isDirectory() { 890 if (file != null) { 891 return file.isDirectory(); 892 } 893 894 if (linkFlag == LF_DIR) { 895 return true; 896 } 897 898 return !isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/"); 899 } 900 901 /** 902 * Check if this is a "normal file" 903 * 904 * @since 1.2 905 * @return whether this is a "normal file" 906 */ 907 public boolean isFile() { 908 if (file != null) { 909 return file.isFile(); 910 } 911 if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) { 912 return true; 913 } 914 return !getName().endsWith("/"); 915 } 916 917 /** 918 * Check if this is a symbolic link entry. 919 * 920 * @since 1.2 921 * @return whether this is a symbolic link 922 */ 923 public boolean isSymbolicLink() { 924 return linkFlag == LF_SYMLINK; 925 } 926 927 /** 928 * Check if this is a link entry. 929 * 930 * @since 1.2 931 * @return whether this is a link entry 932 */ 933 public boolean isLink() { 934 return linkFlag == LF_LINK; 935 } 936 937 /** 938 * Check if this is a character device entry. 939 * 940 * @since 1.2 941 * @return whether this is a character device 942 */ 943 public boolean isCharacterDevice() { 944 return linkFlag == LF_CHR; 945 } 946 947 /** 948 * Check if this is a block device entry. 949 * 950 * @since 1.2 951 * @return whether this is a block device 952 */ 953 public boolean isBlockDevice() { 954 return linkFlag == LF_BLK; 955 } 956 957 /** 958 * Check if this is a FIFO (pipe) entry. 959 * 960 * @since 1.2 961 * @return whether this is a FIFO entry 962 */ 963 public boolean isFIFO() { 964 return linkFlag == LF_FIFO; 965 } 966 967 /** 968 * Check whether this is a sparse entry. 969 * 970 * @return whether this is a sparse entry 971 * @since 1.11 972 */ 973 public boolean isSparse() { 974 return isGNUSparse() || isStarSparse(); 975 } 976 977 /** 978 * get extra PAX Headers 979 * @return read-only map containing any extra PAX Headers 980 * @since 1.15 981 */ 982 public Map<String, String> getExtraPaxHeaders() { 983 return Collections.unmodifiableMap(extraPaxHeaders); 984 } 985 986 /** 987 * clear all extra PAX headers. 988 * @since 1.15 989 */ 990 public void clearExtraPaxHeaders() { 991 extraPaxHeaders.clear(); 992 } 993 994 /** 995 * add a PAX header to this entry. If the header corresponds to an existing field in the entry, 996 * that field will be set; otherwise the header will be added to the extraPaxHeaders Map 997 * @param name The full name of the header to set. 998 * @param value value of header. 999 * @since 1.15 1000 */ 1001 public void addPaxHeader(String name,String value) { 1002 processPaxHeader(name,value); 1003 } 1004 1005 /** 1006 * get named extra PAX header 1007 * @param name The full name of an extended PAX header to retrieve 1008 * @return The value of the header, if any. 1009 * @since 1.15 1010 */ 1011 public String getExtraPaxHeader(String name) { 1012 return extraPaxHeaders.get(name); 1013 } 1014 1015 /** 1016 * Update the entry using a map of pax headers. 1017 * @param headers 1018 * @since 1.15 1019 */ 1020 void updateEntryFromPaxHeaders(Map<String, String> headers) { 1021 for (final Map.Entry<String, String> ent : headers.entrySet()) { 1022 final String key = ent.getKey(); 1023 final String val = ent.getValue(); 1024 processPaxHeader(key, val, headers); 1025 } 1026 } 1027 1028 /** 1029 * process one pax header, using the entries extraPaxHeaders map as source for extra headers 1030 * used when handling entries for sparse files. 1031 * @param key 1032 * @param val 1033 * @since 1.15 1034 */ 1035 private void processPaxHeader(String key, String val) { 1036 processPaxHeader(key,val,extraPaxHeaders); 1037 } 1038 1039 /** 1040 * Process one pax header, using the supplied map as source for extra headers to be used when handling 1041 * entries for sparse files 1042 * 1043 * @param key the header name. 1044 * @param val the header value. 1045 * @param headers map of headers used for dealing with sparse file. 1046 * @since 1.15 1047 */ 1048 private void processPaxHeader(String key, String val, Map<String, String> headers) { 1049 /* 1050 * The following headers are defined for Pax. 1051 * atime, ctime, charset: cannot use these without changing TarArchiveEntry fields 1052 * mtime 1053 * comment 1054 * gid, gname 1055 * linkpath 1056 * size 1057 * uid,uname 1058 * SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those 1059 * 1060 * GNU sparse files use additional members, we use 1061 * GNU.sparse.size to detect the 0.0 and 0.1 versions and 1062 * GNU.sparse.realsize for 1.0. 1063 * 1064 * star files use additional members of which we use 1065 * SCHILY.filetype in order to detect star sparse files. 1066 * 1067 * If called from addExtraPaxHeader, these additional headers must be already present . 1068 */ 1069 switch (key) { 1070 case "path": 1071 setName(val); 1072 break; 1073 case "linkpath": 1074 setLinkName(val); 1075 break; 1076 case "gid": 1077 setGroupId(Long.parseLong(val)); 1078 break; 1079 case "gname": 1080 setGroupName(val); 1081 break; 1082 case "uid": 1083 setUserId(Long.parseLong(val)); 1084 break; 1085 case "uname": 1086 setUserName(val); 1087 break; 1088 case "size": 1089 setSize(Long.parseLong(val)); 1090 break; 1091 case "mtime": 1092 setModTime((long) (Double.parseDouble(val) * 1000)); 1093 break; 1094 case "SCHILY.devminor": 1095 setDevMinor(Integer.parseInt(val)); 1096 break; 1097 case "SCHILY.devmajor": 1098 setDevMajor(Integer.parseInt(val)); 1099 break; 1100 case "GNU.sparse.size": 1101 fillGNUSparse0xData(headers); 1102 break; 1103 case "GNU.sparse.realsize": 1104 fillGNUSparse1xData(headers); 1105 break; 1106 case "SCHILY.filetype": 1107 if ("sparse".equals(val)) { 1108 fillStarSparseData(headers); 1109 } 1110 break; 1111 default: 1112 extraPaxHeaders.put(key,val); 1113 } 1114 } 1115 1116 1117 1118 /** 1119 * If this entry represents a file, and the file is a directory, return 1120 * an array of TarEntries for this entry's children. 1121 * 1122 * <p>This method is only useful for entries created from a {@code 1123 * File} but not for entries read from an archive.</p> 1124 * 1125 * @return An array of TarEntry's for this entry's children. 1126 */ 1127 public TarArchiveEntry[] getDirectoryEntries() { 1128 if (file == null || !file.isDirectory()) { 1129 return EMPTY_TAR_ARCHIVE_ENTRIES; 1130 } 1131 1132 final String[] list = file.list(); 1133 if (list == null) { 1134 return EMPTY_TAR_ARCHIVE_ENTRIES; 1135 } 1136 final TarArchiveEntry[] result = new TarArchiveEntry[list.length]; 1137 1138 for (int i = 0; i < result.length; ++i) { 1139 result[i] = new TarArchiveEntry(new File(file, list[i])); 1140 } 1141 1142 return result; 1143 } 1144 1145 /** 1146 * Write an entry's header information to a header buffer. 1147 * 1148 * <p>This method does not use the star/GNU tar/BSD tar extensions.</p> 1149 * 1150 * @param outbuf The tar entry header buffer to fill in. 1151 */ 1152 public void writeEntryHeader(final byte[] outbuf) { 1153 try { 1154 writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false); 1155 } catch (final IOException ex) { 1156 try { 1157 writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false); 1158 } catch (final IOException ex2) { 1159 // impossible 1160 throw new RuntimeException(ex2); //NOSONAR 1161 } 1162 } 1163 } 1164 1165 /** 1166 * Write an entry's header information to a header buffer. 1167 * 1168 * @param outbuf The tar entry header buffer to fill in. 1169 * @param encoding encoding to use when writing the file name. 1170 * @param starMode whether to use the star/GNU tar/BSD tar 1171 * extension for numeric fields if their value doesn't fit in the 1172 * maximum size of standard tar archives 1173 * @since 1.4 1174 * @throws IOException on error 1175 */ 1176 public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding, 1177 final boolean starMode) throws IOException { 1178 int offset = 0; 1179 1180 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN, 1181 encoding); 1182 offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode); 1183 offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN, 1184 starMode); 1185 offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN, 1186 starMode); 1187 offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode); 1188 offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN, 1189 starMode); 1190 1191 final int csOffset = offset; 1192 1193 for (int c = 0; c < CHKSUMLEN; ++c) { 1194 outbuf[offset++] = (byte) ' '; 1195 } 1196 1197 outbuf[offset++] = linkFlag; 1198 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN, 1199 encoding); 1200 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN); 1201 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN); 1202 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN, 1203 encoding); 1204 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN, 1205 encoding); 1206 offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN, 1207 starMode); 1208 offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN, 1209 starMode); 1210 1211 while (offset < outbuf.length) { 1212 outbuf[offset++] = 0; 1213 } 1214 1215 final long chk = TarUtils.computeCheckSum(outbuf); 1216 1217 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN); 1218 } 1219 1220 private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset, 1221 final int length, final boolean starMode) { 1222 if (!starMode && (value < 0 1223 || value >= 1L << 3 * (length - 1))) { 1224 // value doesn't fit into field when written as octal 1225 // number, will be written to PAX header or causes an 1226 // error 1227 return TarUtils.formatLongOctalBytes(0, outbuf, offset, length); 1228 } 1229 return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset, 1230 length); 1231 } 1232 1233 /** 1234 * Parse an entry's header information from a header buffer. 1235 * 1236 * @param header The tar entry header buffer to get information from. 1237 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 1238 */ 1239 public void parseTarHeader(final byte[] header) { 1240 try { 1241 parseTarHeader(header, TarUtils.DEFAULT_ENCODING); 1242 } catch (final IOException ex) { 1243 try { 1244 parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true); 1245 } catch (final IOException ex2) { 1246 // not really possible 1247 throw new RuntimeException(ex2); //NOSONAR 1248 } 1249 } 1250 } 1251 1252 /** 1253 * Parse an entry's header information from a header buffer. 1254 * 1255 * @param header The tar entry header buffer to get information from. 1256 * @param encoding encoding to use for file names 1257 * @since 1.4 1258 * @throws IllegalArgumentException if any of the numeric fields 1259 * have an invalid format 1260 * @throws IOException on error 1261 */ 1262 public void parseTarHeader(final byte[] header, final ZipEncoding encoding) 1263 throws IOException { 1264 parseTarHeader(header, encoding, false); 1265 } 1266 1267 private void parseTarHeader(final byte[] header, final ZipEncoding encoding, 1268 final boolean oldStyle) 1269 throws IOException { 1270 int offset = 0; 1271 1272 name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 1273 : TarUtils.parseName(header, offset, NAMELEN, encoding); 1274 offset += NAMELEN; 1275 mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN); 1276 offset += MODELEN; 1277 userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN); 1278 offset += UIDLEN; 1279 groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN); 1280 offset += GIDLEN; 1281 size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN); 1282 offset += SIZELEN; 1283 modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN); 1284 offset += MODTIMELEN; 1285 checkSumOK = TarUtils.verifyCheckSum(header); 1286 offset += CHKSUMLEN; 1287 linkFlag = header[offset++]; 1288 linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 1289 : TarUtils.parseName(header, offset, NAMELEN, encoding); 1290 offset += NAMELEN; 1291 magic = TarUtils.parseName(header, offset, MAGICLEN); 1292 offset += MAGICLEN; 1293 version = TarUtils.parseName(header, offset, VERSIONLEN); 1294 offset += VERSIONLEN; 1295 userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN) 1296 : TarUtils.parseName(header, offset, UNAMELEN, encoding); 1297 offset += UNAMELEN; 1298 groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN) 1299 : TarUtils.parseName(header, offset, GNAMELEN, encoding); 1300 offset += GNAMELEN; 1301 if (linkFlag == LF_CHR || linkFlag == LF_BLK) { 1302 devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN); 1303 offset += DEVLEN; 1304 devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN); 1305 offset += DEVLEN; 1306 } else { 1307 offset += 2 * DEVLEN; 1308 } 1309 1310 final int type = evaluateType(header); 1311 switch (type) { 1312 case FORMAT_OLDGNU: { 1313 offset += ATIMELEN_GNU; 1314 offset += CTIMELEN_GNU; 1315 offset += OFFSETLEN_GNU; 1316 offset += LONGNAMESLEN_GNU; 1317 offset += PAD2LEN_GNU; 1318 offset += SPARSELEN_GNU; 1319 isExtended = TarUtils.parseBoolean(header, offset); 1320 offset += ISEXTENDEDLEN_GNU; 1321 realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU); 1322 offset += REALSIZELEN_GNU; // NOSONAR - assignment as documentation 1323 break; 1324 } 1325 case FORMAT_XSTAR: { 1326 final String xstarPrefix = oldStyle 1327 ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR) 1328 : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding); 1329 if (xstarPrefix.length() > 0) { 1330 name = xstarPrefix + "/" + name; 1331 } 1332 break; 1333 } 1334 case FORMAT_POSIX: 1335 default: { 1336 final String prefix = oldStyle 1337 ? TarUtils.parseName(header, offset, PREFIXLEN) 1338 : TarUtils.parseName(header, offset, PREFIXLEN, encoding); 1339 // SunOS tar -E does not add / to directory names, so fix 1340 // up to be consistent 1341 if (isDirectory() && !name.endsWith("/")){ 1342 name = name + "/"; 1343 } 1344 if (prefix.length() > 0){ 1345 name = prefix + "/" + name; 1346 } 1347 } 1348 } 1349 } 1350 1351 /** 1352 * Strips Windows' drive letter as well as any leading slashes, 1353 * turns path separators into forward slahes. 1354 */ 1355 private static String normalizeFileName(String fileName, 1356 final boolean preserveAbsolutePath) { 1357 if (!preserveAbsolutePath) { 1358 final String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 1359 1360 if (osname != null) { 1361 1362 // Strip off drive letters! 1363 // REVIEW Would a better check be "(File.separator == '\')"? 1364 1365 if (osname.startsWith("windows")) { 1366 if (fileName.length() > 2) { 1367 final char ch1 = fileName.charAt(0); 1368 final char ch2 = fileName.charAt(1); 1369 1370 if (ch2 == ':' 1371 && (ch1 >= 'a' && ch1 <= 'z' 1372 || ch1 >= 'A' && ch1 <= 'Z')) { 1373 fileName = fileName.substring(2); 1374 } 1375 } 1376 } else if (osname.contains("netware")) { 1377 final int colon = fileName.indexOf(':'); 1378 if (colon != -1) { 1379 fileName = fileName.substring(colon + 1); 1380 } 1381 } 1382 } 1383 } 1384 1385 fileName = fileName.replace(File.separatorChar, '/'); 1386 1387 // No absolute pathnames 1388 // Windows (and Posix?) paths can start with "\\NetworkDrive\", 1389 // so we loop on starting /'s. 1390 while (!preserveAbsolutePath && fileName.startsWith("/")) { 1391 fileName = fileName.substring(1); 1392 } 1393 return fileName; 1394 } 1395 1396 /** 1397 * Evaluate an entry's header format from a header buffer. 1398 * 1399 * @param header The tar entry header buffer to evaluate the format for. 1400 * @return format type 1401 */ 1402 private int evaluateType(final byte[] header) { 1403 if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) { 1404 return FORMAT_OLDGNU; 1405 } 1406 if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) { 1407 if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET, 1408 XSTAR_MAGIC_LEN)) { 1409 return FORMAT_XSTAR; 1410 } 1411 return FORMAT_POSIX; 1412 } 1413 return 0; 1414 } 1415 1416 void fillGNUSparse0xData(final Map<String, String> headers) { 1417 paxGNUSparse = true; 1418 realSize = Integer.parseInt(headers.get("GNU.sparse.size")); 1419 if (headers.containsKey("GNU.sparse.name")) { 1420 // version 0.1 1421 name = headers.get("GNU.sparse.name"); 1422 } 1423 } 1424 1425 void fillGNUSparse1xData(final Map<String, String> headers) { 1426 paxGNUSparse = true; 1427 realSize = Integer.parseInt(headers.get("GNU.sparse.realsize")); 1428 name = headers.get("GNU.sparse.name"); 1429 } 1430 1431 void fillStarSparseData(final Map<String, String> headers) { 1432 starSparse = true; 1433 if (headers.containsKey("SCHILY.realsize")) { 1434 realSize = Long.parseLong(headers.get("SCHILY.realsize")); 1435 } 1436 } 1437} 1438