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 < 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 < 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 < 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