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.Date; 024import java.util.Locale; 025 026import org.apache.commons.compress.archivers.ArchiveEntry; 027import org.apache.commons.compress.archivers.zip.ZipEncoding; 028import org.apache.commons.compress.utils.ArchiveUtils; 029 030/** 031 * This class represents an entry in a Tar archive. It consists 032 * of the entry's header, as well as the entry's File. Entries 033 * can be instantiated in one of three ways, depending on how 034 * they are to be used. 035 * <p> 036 * TarEntries that are created from the header bytes read from 037 * an archive are instantiated with the TarEntry( byte[] ) 038 * constructor. These entries will be used when extracting from 039 * or listing the contents of an archive. These entries have their 040 * header filled in using the header bytes. They also set the File 041 * to null, since they reference an archive entry not a file. 042 * <p> 043 * TarEntries that are created from Files that are to be written 044 * into an archive are instantiated with the TarEntry( File ) 045 * constructor. These entries have their header filled in using 046 * the File's information. They also keep a reference to the File 047 * for convenience when writing entries. 048 * <p> 049 * Finally, TarEntries can be constructed from nothing but a name. 050 * This allows the programmer to construct the entry by hand, for 051 * instance when only an InputStream is available for writing to 052 * the archive, and the header information is constructed from 053 * other information. In this case the header fields are set to 054 * defaults and the File is set to null. 055 * 056 * <p> 057 * The C structure for a Tar Entry's header is: 058 * <pre> 059 * struct header { 060 * char name[100]; // TarConstants.NAMELEN - offset 0 061 * char mode[8]; // TarConstants.MODELEN - offset 100 062 * char uid[8]; // TarConstants.UIDLEN - offset 108 063 * char gid[8]; // TarConstants.GIDLEN - offset 116 064 * char size[12]; // TarConstants.SIZELEN - offset 124 065 * char mtime[12]; // TarConstants.MODTIMELEN - offset 136 066 * char chksum[8]; // TarConstants.CHKSUMLEN - offset 148 067 * char linkflag[1]; // - offset 156 068 * char linkname[100]; // TarConstants.NAMELEN - offset 157 069 * The following fields are only present in new-style POSIX tar archives: 070 * char magic[6]; // TarConstants.MAGICLEN - offset 257 071 * char version[2]; // TarConstants.VERSIONLEN - offset 263 072 * char uname[32]; // TarConstants.UNAMELEN - offset 265 073 * char gname[32]; // TarConstants.GNAMELEN - offset 297 074 * char devmajor[8]; // TarConstants.DEVLEN - offset 329 075 * char devminor[8]; // TarConstants.DEVLEN - offset 337 076 * char prefix[155]; // TarConstants.PREFIXLEN - offset 345 077 * // Used if "name" field is not long enough to hold the path 078 * char pad[12]; // NULs - offset 500 079 * } header; 080 * All unused bytes are set to null. 081 * New-style GNU tar files are slightly different from the above. 082 * For values of size larger than 077777777777L (11 7s) 083 * or uid and gid larger than 07777777L (7 7s) 084 * the sign bit of the first byte is set, and the rest of the 085 * field is the binary representation of the number. 086 * See TarUtils.parseOctalOrBinary. 087 * </pre> 088 * 089 * <p> 090 * The C structure for a old GNU Tar Entry's header is: 091 * <pre> 092 * struct oldgnu_header { 093 * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU - offset 0 094 * char atime[12]; // TarConstants.ATIMELEN_GNU - offset 345 095 * char ctime[12]; // TarConstants.CTIMELEN_GNU - offset 357 096 * char offset[12]; // TarConstants.OFFSETLEN_GNU - offset 369 097 * char longnames[4]; // TarConstants.LONGNAMESLEN_GNU - offset 381 098 * char unused_pad2; // TarConstants.PAD2LEN_GNU - offset 385 099 * struct sparse sp[4]; // TarConstants.SPARSELEN_GNU - offset 386 100 * char isextended; // TarConstants.ISEXTENDEDLEN_GNU - offset 482 101 * char realsize[12]; // TarConstants.REALSIZELEN_GNU - offset 483 102 * char unused_pad[17]; // TarConstants.PAD3LEN_GNU - offset 495 103 * }; 104 * </pre> 105 * Whereas, "struct sparse" is: 106 * <pre> 107 * struct sparse { 108 * char offset[12]; // offset 0 109 * char numbytes[12]; // offset 12 110 * }; 111 * </pre> 112 * 113 * @NotThreadSafe 114 */ 115 116public class TarArchiveEntry implements TarConstants, ArchiveEntry { 117 /** The entry's name. */ 118 private String name = ""; 119 120 /** The entry's permission mode. */ 121 private int mode; 122 123 /** The entry's user id. */ 124 private int userId = 0; 125 126 /** The entry's group id. */ 127 private int groupId = 0; 128 129 /** The entry's size. */ 130 private long size = 0; 131 132 /** The entry's modification time. */ 133 private long modTime; 134 135 /** If the header checksum is reasonably correct. */ 136 private boolean checkSumOK; 137 138 /** The entry's link flag. */ 139 private byte linkFlag; 140 141 /** The entry's link name. */ 142 private String linkName = ""; 143 144 /** The entry's magic tag. */ 145 private String magic = MAGIC_POSIX; 146 /** The version of the format */ 147 private String version = VERSION_POSIX; 148 149 /** The entry's user name. */ 150 private String userName; 151 152 /** The entry's group name. */ 153 private String groupName = ""; 154 155 /** The entry's major device number. */ 156 private int devMajor = 0; 157 158 /** The entry's minor device number. */ 159 private int devMinor = 0; 160 161 /** If an extension sparse header follows. */ 162 private boolean isExtended; 163 164 /** The entry's real size in case of a sparse file. */ 165 private long realSize; 166 167 /** The entry's file reference */ 168 private final File file; 169 170 /** Maximum length of a user's name in the tar file */ 171 public static final int MAX_NAMELEN = 31; 172 173 /** Default permissions bits for directories */ 174 public static final int DEFAULT_DIR_MODE = 040755; 175 176 /** Default permissions bits for files */ 177 public static final int DEFAULT_FILE_MODE = 0100644; 178 179 /** Convert millis to seconds */ 180 public static final int MILLIS_PER_SECOND = 1000; 181 182 /** 183 * Construct an empty entry and prepares the header values. 184 */ 185 private TarArchiveEntry() { 186 String user = System.getProperty("user.name", ""); 187 188 if (user.length() > MAX_NAMELEN) { 189 user = user.substring(0, MAX_NAMELEN); 190 } 191 192 this.userName = user; 193 this.file = null; 194 } 195 196 /** 197 * Construct an entry with only a name. This allows the programmer 198 * to construct the entry's header "by hand". File is set to null. 199 * 200 * @param name the entry name 201 */ 202 public TarArchiveEntry(String name) { 203 this(name, false); 204 } 205 206 /** 207 * Construct an entry with only a name. This allows the programmer 208 * to construct the entry's header "by hand". File is set to null. 209 * 210 * @param name the entry name 211 * @param preserveLeadingSlashes whether to allow leading slashes 212 * in the name. 213 * 214 * @since 1.1 215 */ 216 public TarArchiveEntry(String name, boolean preserveLeadingSlashes) { 217 this(); 218 219 name = normalizeFileName(name, preserveLeadingSlashes); 220 boolean isDir = name.endsWith("/"); 221 222 this.name = name; 223 this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE; 224 this.linkFlag = isDir ? LF_DIR : LF_NORMAL; 225 this.modTime = new Date().getTime() / MILLIS_PER_SECOND; 226 this.userName = ""; 227 } 228 229 /** 230 * Construct an entry with a name and a link flag. 231 * 232 * @param name the entry name 233 * @param linkFlag the entry link flag. 234 */ 235 public TarArchiveEntry(String name, byte linkFlag) { 236 this(name, linkFlag, false); 237 } 238 239 /** 240 * Construct an entry with a name and a link flag. 241 * 242 * @param name the entry name 243 * @param linkFlag the entry link flag. 244 * @param preserveLeadingSlashes whether to allow leading slashes 245 * in the name. 246 * 247 * @since 1.5 248 */ 249 public TarArchiveEntry(String name, byte linkFlag, boolean preserveLeadingSlashes) { 250 this(name, preserveLeadingSlashes); 251 this.linkFlag = linkFlag; 252 if (linkFlag == LF_GNUTYPE_LONGNAME) { 253 magic = MAGIC_GNU; 254 version = VERSION_GNU_SPACE; 255 } 256 } 257 258 /** 259 * Construct an entry for a file. File is set to file, and the 260 * header is constructed from information from the file. 261 * The name is set from the normalized file path. 262 * 263 * @param file The file that the entry represents. 264 */ 265 public TarArchiveEntry(File file) { 266 this(file, normalizeFileName(file.getPath(), false)); 267 } 268 269 /** 270 * Construct an entry for a file. File is set to file, and the 271 * header is constructed from information from the file. 272 * 273 * @param file The file that the entry represents. 274 * @param fileName the name to be used for the entry. 275 */ 276 public TarArchiveEntry(File file, String fileName) { 277 this.file = file; 278 279 if (file.isDirectory()) { 280 this.mode = DEFAULT_DIR_MODE; 281 this.linkFlag = LF_DIR; 282 283 int nameLength = fileName.length(); 284 if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') { 285 this.name = fileName + "/"; 286 } else { 287 this.name = fileName; 288 } 289 } else { 290 this.mode = DEFAULT_FILE_MODE; 291 this.linkFlag = LF_NORMAL; 292 this.size = file.length(); 293 this.name = fileName; 294 } 295 296 this.modTime = file.lastModified() / MILLIS_PER_SECOND; 297 this.userName = ""; 298 } 299 300 /** 301 * Construct an entry from an archive's header bytes. File is set 302 * to null. 303 * 304 * @param headerBuf The header bytes from a tar archive entry. 305 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 306 */ 307 public TarArchiveEntry(byte[] headerBuf) { 308 this(); 309 parseTarHeader(headerBuf); 310 } 311 312 /** 313 * Construct an entry from an archive's header bytes. File is set 314 * to null. 315 * 316 * @param headerBuf The header bytes from a tar archive entry. 317 * @param encoding encoding to use for file names 318 * @since 1.4 319 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 320 */ 321 public TarArchiveEntry(byte[] headerBuf, ZipEncoding encoding) 322 throws IOException { 323 this(); 324 parseTarHeader(headerBuf, encoding); 325 } 326 327 /** 328 * Determine if the two entries are equal. Equality is determined 329 * by the header names being equal. 330 * 331 * @param it Entry to be checked for equality. 332 * @return True if the entries are equal. 333 */ 334 public boolean equals(TarArchiveEntry it) { 335 return getName().equals(it.getName()); 336 } 337 338 /** 339 * Determine if the two entries are equal. Equality is determined 340 * by the header names being equal. 341 * 342 * @param it Entry to be checked for equality. 343 * @return True if the entries are equal. 344 */ 345 @Override 346 public boolean equals(Object it) { 347 if (it == null || getClass() != it.getClass()) { 348 return false; 349 } 350 return equals((TarArchiveEntry) it); 351 } 352 353 /** 354 * Hashcodes are based on entry names. 355 * 356 * @return the entry hashcode 357 */ 358 @Override 359 public int hashCode() { 360 return getName().hashCode(); 361 } 362 363 /** 364 * Determine if the given entry is a descendant of this entry. 365 * Descendancy is determined by the name of the descendant 366 * starting with this entry's name. 367 * 368 * @param desc Entry to be checked as a descendent of this. 369 * @return True if entry is a descendant of this. 370 */ 371 public boolean isDescendent(TarArchiveEntry desc) { 372 return desc.getName().startsWith(getName()); 373 } 374 375 /** 376 * Get this entry's name. 377 * 378 * @return This entry's name. 379 */ 380 public String getName() { 381 return name.toString(); 382 } 383 384 /** 385 * Set this entry's name. 386 * 387 * @param name This entry's new name. 388 */ 389 public void setName(String name) { 390 this.name = normalizeFileName(name, false); 391 } 392 393 /** 394 * Set the mode for this entry 395 * 396 * @param mode the mode for this entry 397 */ 398 public void setMode(int mode) { 399 this.mode = mode; 400 } 401 402 /** 403 * Get this entry's link name. 404 * 405 * @return This entry's link name. 406 */ 407 public String getLinkName() { 408 return linkName.toString(); 409 } 410 411 /** 412 * Set this entry's link name. 413 * 414 * @param link the link name to use. 415 * 416 * @since 1.1 417 */ 418 public void setLinkName(String link) { 419 this.linkName = link; 420 } 421 422 /** 423 * Get this entry's user id. 424 * 425 * @return This entry's user id. 426 */ 427 public int getUserId() { 428 return userId; 429 } 430 431 /** 432 * Set this entry's user id. 433 * 434 * @param userId This entry's new user id. 435 */ 436 public void setUserId(int userId) { 437 this.userId = userId; 438 } 439 440 /** 441 * Get this entry's group id. 442 * 443 * @return This entry's group id. 444 */ 445 public int getGroupId() { 446 return groupId; 447 } 448 449 /** 450 * Set this entry's group id. 451 * 452 * @param groupId This entry's new group id. 453 */ 454 public void setGroupId(int groupId) { 455 this.groupId = groupId; 456 } 457 458 /** 459 * Get this entry's user name. 460 * 461 * @return This entry's user name. 462 */ 463 public String getUserName() { 464 return userName.toString(); 465 } 466 467 /** 468 * Set this entry's user name. 469 * 470 * @param userName This entry's new user name. 471 */ 472 public void setUserName(String userName) { 473 this.userName = userName; 474 } 475 476 /** 477 * Get this entry's group name. 478 * 479 * @return This entry's group name. 480 */ 481 public String getGroupName() { 482 return groupName.toString(); 483 } 484 485 /** 486 * Set this entry's group name. 487 * 488 * @param groupName This entry's new group name. 489 */ 490 public void setGroupName(String groupName) { 491 this.groupName = groupName; 492 } 493 494 /** 495 * Convenience method to set this entry's group and user ids. 496 * 497 * @param userId This entry's new user id. 498 * @param groupId This entry's new group id. 499 */ 500 public void setIds(int userId, int groupId) { 501 setUserId(userId); 502 setGroupId(groupId); 503 } 504 505 /** 506 * Convenience method to set this entry's group and user names. 507 * 508 * @param userName This entry's new user name. 509 * @param groupName This entry's new group name. 510 */ 511 public void setNames(String userName, String groupName) { 512 setUserName(userName); 513 setGroupName(groupName); 514 } 515 516 /** 517 * Set this entry's modification time. The parameter passed 518 * to this method is in "Java time". 519 * 520 * @param time This entry's new modification time. 521 */ 522 public void setModTime(long time) { 523 modTime = time / MILLIS_PER_SECOND; 524 } 525 526 /** 527 * Set this entry's modification time. 528 * 529 * @param time This entry's new modification time. 530 */ 531 public void setModTime(Date time) { 532 modTime = time.getTime() / MILLIS_PER_SECOND; 533 } 534 535 /** 536 * Set this entry's modification time. 537 * 538 * @return time This entry's new modification time. 539 */ 540 public Date getModTime() { 541 return new Date(modTime * MILLIS_PER_SECOND); 542 } 543 544 public Date getLastModifiedDate() { 545 return getModTime(); 546 } 547 548 /** 549 * Get this entry's checksum status. 550 * 551 * @return if the header checksum is reasonably correct 552 * @see TarUtils#verifyCheckSum(byte[]) 553 * @since 1.5 554 */ 555 public boolean isCheckSumOK() { 556 return checkSumOK; 557 } 558 559 /** 560 * Get this entry's file. 561 * 562 * @return This entry's file. 563 */ 564 public File getFile() { 565 return file; 566 } 567 568 /** 569 * Get this entry's mode. 570 * 571 * @return This entry's mode. 572 */ 573 public int getMode() { 574 return mode; 575 } 576 577 /** 578 * Get this entry's file size. 579 * 580 * @return This entry's file size. 581 */ 582 public long getSize() { 583 return size; 584 } 585 586 /** 587 * Set this entry's file size. 588 * 589 * @param size This entry's new file size. 590 * @throws IllegalArgumentException if the size is < 0. 591 */ 592 public void setSize(long size) { 593 if (size < 0){ 594 throw new IllegalArgumentException("Size is out of range: "+size); 595 } 596 this.size = size; 597 } 598 599 /** 600 * Get this entry's major device number. 601 * 602 * @return This entry's major device number. 603 * @since 1.4 604 */ 605 public int getDevMajor() { 606 return devMajor; 607 } 608 609 /** 610 * Set this entry's major device number. 611 * 612 * @param devNo This entry's major device number. 613 * @throws IllegalArgumentException if the devNo is < 0. 614 * @since 1.4 615 */ 616 public void setDevMajor(int devNo) { 617 if (devNo < 0){ 618 throw new IllegalArgumentException("Major device number is out of " 619 + "range: " + devNo); 620 } 621 this.devMajor = devNo; 622 } 623 624 /** 625 * Get this entry's minor device number. 626 * 627 * @return This entry's minor device number. 628 * @since 1.4 629 */ 630 public int getDevMinor() { 631 return devMinor; 632 } 633 634 /** 635 * Set this entry's minor device number. 636 * 637 * @param devNo This entry's minor device number. 638 * @throws IllegalArgumentException if the devNo is < 0. 639 * @since 1.4 640 */ 641 public void setDevMinor(int devNo) { 642 if (devNo < 0){ 643 throw new IllegalArgumentException("Minor device number is out of " 644 + "range: " + devNo); 645 } 646 this.devMinor = devNo; 647 } 648 649 /** 650 * Indicates in case of a sparse file if an extension sparse header 651 * follows. 652 * 653 * @return true if an extension sparse header follows. 654 */ 655 public boolean isExtended() { 656 return isExtended; 657 } 658 659 /** 660 * Get this entry's real file size in case of a sparse file. 661 * 662 * @return This entry's real file size. 663 */ 664 public long getRealSize() { 665 return realSize; 666 } 667 668 /** 669 * Indicate if this entry is a GNU sparse block 670 * 671 * @return true if this is a sparse extension provided by GNU tar 672 */ 673 public boolean isGNUSparse() { 674 return linkFlag == LF_GNUTYPE_SPARSE; 675 } 676 677 /** 678 * Indicate if this entry is a GNU long linkname block 679 * 680 * @return true if this is a long name extension provided by GNU tar 681 */ 682 public boolean isGNULongLinkEntry() { 683 return linkFlag == LF_GNUTYPE_LONGLINK 684 && name.equals(GNU_LONGLINK); 685 } 686 687 /** 688 * Indicate if this entry is a GNU long name block 689 * 690 * @return true if this is a long name extension provided by GNU tar 691 */ 692 public boolean isGNULongNameEntry() { 693 return linkFlag == LF_GNUTYPE_LONGNAME 694 && name.equals(GNU_LONGLINK); 695 } 696 697 /** 698 * Check if this is a Pax header. 699 * 700 * @return {@code true} if this is a Pax header. 701 * 702 * @since 1.1 703 * 704 */ 705 public boolean isPaxHeader(){ 706 return linkFlag == LF_PAX_EXTENDED_HEADER_LC 707 || linkFlag == LF_PAX_EXTENDED_HEADER_UC; 708 } 709 710 /** 711 * Check if this is a Pax header. 712 * 713 * @return {@code true} if this is a Pax header. 714 * 715 * @since 1.1 716 */ 717 public boolean isGlobalPaxHeader(){ 718 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER; 719 } 720 721 /** 722 * Return whether or not this entry represents a directory. 723 * 724 * @return True if this entry is a directory. 725 */ 726 public boolean isDirectory() { 727 if (file != null) { 728 return file.isDirectory(); 729 } 730 731 if (linkFlag == LF_DIR) { 732 return true; 733 } 734 735 if (getName().endsWith("/")) { 736 return true; 737 } 738 739 return false; 740 } 741 742 /** 743 * Check if this is a "normal file" 744 * 745 * @since 1.2 746 */ 747 public boolean isFile() { 748 if (file != null) { 749 return file.isFile(); 750 } 751 if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) { 752 return true; 753 } 754 return !getName().endsWith("/"); 755 } 756 757 /** 758 * Check if this is a symbolic link entry. 759 * 760 * @since 1.2 761 */ 762 public boolean isSymbolicLink() { 763 return linkFlag == LF_SYMLINK; 764 } 765 766 /** 767 * Check if this is a link entry. 768 * 769 * @since 1.2 770 */ 771 public boolean isLink() { 772 return linkFlag == LF_LINK; 773 } 774 775 /** 776 * Check if this is a character device entry. 777 * 778 * @since 1.2 779 */ 780 public boolean isCharacterDevice() { 781 return linkFlag == LF_CHR; 782 } 783 784 /** 785 * Check if this is a block device entry. 786 * 787 * @since 1.2 788 */ 789 public boolean isBlockDevice() { 790 return linkFlag == LF_BLK; 791 } 792 793 /** 794 * Check if this is a FIFO (pipe) entry. 795 * 796 * @since 1.2 797 */ 798 public boolean isFIFO() { 799 return linkFlag == LF_FIFO; 800 } 801 802 /** 803 * If this entry represents a file, and the file is a directory, return 804 * an array of TarEntries for this entry's children. 805 * 806 * @return An array of TarEntry's for this entry's children. 807 */ 808 public TarArchiveEntry[] getDirectoryEntries() { 809 if (file == null || !file.isDirectory()) { 810 return new TarArchiveEntry[0]; 811 } 812 813 String[] list = file.list(); 814 TarArchiveEntry[] result = new TarArchiveEntry[list.length]; 815 816 for (int i = 0; i < list.length; ++i) { 817 result[i] = new TarArchiveEntry(new File(file, list[i])); 818 } 819 820 return result; 821 } 822 823 /** 824 * Write an entry's header information to a header buffer. 825 * 826 * <p>This method does not use the star/GNU tar/BSD tar extensions.</p> 827 * 828 * @param outbuf The tar entry header buffer to fill in. 829 */ 830 public void writeEntryHeader(byte[] outbuf) { 831 try { 832 writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false); 833 } catch (IOException ex) { 834 try { 835 writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false); 836 } catch (IOException ex2) { 837 // impossible 838 throw new RuntimeException(ex2); 839 } 840 } 841 } 842 843 /** 844 * Write an entry's header information to a header buffer. 845 * 846 * @param outbuf The tar entry header buffer to fill in. 847 * @param encoding encoding to use when writing the file name. 848 * @param starMode whether to use the star/GNU tar/BSD tar 849 * extension for numeric fields if their value doesn't fit in the 850 * maximum size of standard tar archives 851 * @since 1.4 852 */ 853 public void writeEntryHeader(byte[] outbuf, ZipEncoding encoding, 854 boolean starMode) throws IOException { 855 int offset = 0; 856 857 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN, 858 encoding); 859 offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode); 860 offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN, 861 starMode); 862 offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN, 863 starMode); 864 offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode); 865 offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN, 866 starMode); 867 868 int csOffset = offset; 869 870 for (int c = 0; c < CHKSUMLEN; ++c) { 871 outbuf[offset++] = (byte) ' '; 872 } 873 874 outbuf[offset++] = linkFlag; 875 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN, 876 encoding); 877 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN); 878 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN); 879 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN, 880 encoding); 881 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN, 882 encoding); 883 offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN, 884 starMode); 885 offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN, 886 starMode); 887 888 while (offset < outbuf.length) { 889 outbuf[offset++] = 0; 890 } 891 892 long chk = TarUtils.computeCheckSum(outbuf); 893 894 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN); 895 } 896 897 private int writeEntryHeaderField(long value, byte[] outbuf, int offset, 898 int length, boolean starMode) { 899 if (!starMode && (value < 0 900 || value >= 1l << 3 * (length - 1))) { 901 // value doesn't fit into field when written as octal 902 // number, will be written to PAX header or causes an 903 // error 904 return TarUtils.formatLongOctalBytes(0, outbuf, offset, length); 905 } 906 return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset, 907 length); 908 } 909 910 /** 911 * Parse an entry's header information from a header buffer. 912 * 913 * @param header The tar entry header buffer to get information from. 914 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 915 */ 916 public void parseTarHeader(byte[] header) { 917 try { 918 parseTarHeader(header, TarUtils.DEFAULT_ENCODING); 919 } catch (IOException ex) { 920 try { 921 parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true); 922 } catch (IOException ex2) { 923 // not really possible 924 throw new RuntimeException(ex2); 925 } 926 } 927 } 928 929 /** 930 * Parse an entry's header information from a header buffer. 931 * 932 * @param header The tar entry header buffer to get information from. 933 * @param encoding encoding to use for file names 934 * @since 1.4 935 * @throws IllegalArgumentException if any of the numeric fields 936 * have an invalid format 937 */ 938 public void parseTarHeader(byte[] header, ZipEncoding encoding) 939 throws IOException { 940 parseTarHeader(header, encoding, false); 941 } 942 943 private void parseTarHeader(byte[] header, ZipEncoding encoding, 944 final boolean oldStyle) 945 throws IOException { 946 int offset = 0; 947 948 name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 949 : TarUtils.parseName(header, offset, NAMELEN, encoding); 950 offset += NAMELEN; 951 mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN); 952 offset += MODELEN; 953 userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN); 954 offset += UIDLEN; 955 groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN); 956 offset += GIDLEN; 957 size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN); 958 offset += SIZELEN; 959 modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN); 960 offset += MODTIMELEN; 961 checkSumOK = TarUtils.verifyCheckSum(header); 962 offset += CHKSUMLEN; 963 linkFlag = header[offset++]; 964 linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 965 : TarUtils.parseName(header, offset, NAMELEN, encoding); 966 offset += NAMELEN; 967 magic = TarUtils.parseName(header, offset, MAGICLEN); 968 offset += MAGICLEN; 969 version = TarUtils.parseName(header, offset, VERSIONLEN); 970 offset += VERSIONLEN; 971 userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN) 972 : TarUtils.parseName(header, offset, UNAMELEN, encoding); 973 offset += UNAMELEN; 974 groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN) 975 : TarUtils.parseName(header, offset, GNAMELEN, encoding); 976 offset += GNAMELEN; 977 devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN); 978 offset += DEVLEN; 979 devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN); 980 offset += DEVLEN; 981 982 int type = evaluateType(header); 983 switch (type) { 984 case FORMAT_OLDGNU: { 985 offset += ATIMELEN_GNU; 986 offset += CTIMELEN_GNU; 987 offset += OFFSETLEN_GNU; 988 offset += LONGNAMESLEN_GNU; 989 offset += PAD2LEN_GNU; 990 offset += SPARSELEN_GNU; 991 isExtended = TarUtils.parseBoolean(header, offset); 992 offset += ISEXTENDEDLEN_GNU; 993 realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU); 994 offset += REALSIZELEN_GNU; 995 break; 996 } 997 case FORMAT_POSIX: 998 default: { 999 String prefix = oldStyle 1000 ? TarUtils.parseName(header, offset, PREFIXLEN) 1001 : TarUtils.parseName(header, offset, PREFIXLEN, encoding); 1002 // SunOS tar -E does not add / to directory names, so fix 1003 // up to be consistent 1004 if (isDirectory() && !name.endsWith("/")){ 1005 name = name + "/"; 1006 } 1007 if (prefix.length() > 0){ 1008 name = prefix + "/" + name; 1009 } 1010 } 1011 } 1012 } 1013 1014 /** 1015 * Strips Windows' drive letter as well as any leading slashes, 1016 * turns path separators into forward slahes. 1017 */ 1018 private static String normalizeFileName(String fileName, 1019 boolean preserveLeadingSlashes) { 1020 String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 1021 1022 if (osname != null) { 1023 1024 // Strip off drive letters! 1025 // REVIEW Would a better check be "(File.separator == '\')"? 1026 1027 if (osname.startsWith("windows")) { 1028 if (fileName.length() > 2) { 1029 char ch1 = fileName.charAt(0); 1030 char ch2 = fileName.charAt(1); 1031 1032 if (ch2 == ':' 1033 && (ch1 >= 'a' && ch1 <= 'z' 1034 || ch1 >= 'A' && ch1 <= 'Z')) { 1035 fileName = fileName.substring(2); 1036 } 1037 } 1038 } else if (osname.indexOf("netware") > -1) { 1039 int colon = fileName.indexOf(':'); 1040 if (colon != -1) { 1041 fileName = fileName.substring(colon + 1); 1042 } 1043 } 1044 } 1045 1046 fileName = fileName.replace(File.separatorChar, '/'); 1047 1048 // No absolute pathnames 1049 // Windows (and Posix?) paths can start with "\\NetworkDrive\", 1050 // so we loop on starting /'s. 1051 while (!preserveLeadingSlashes && fileName.startsWith("/")) { 1052 fileName = fileName.substring(1); 1053 } 1054 return fileName; 1055 } 1056 1057 /** 1058 * Evaluate an entry's header format from a header buffer. 1059 * 1060 * @param header The tar entry header buffer to evaluate the format for. 1061 * @return format type 1062 */ 1063 private int evaluateType(byte[] header) { 1064 if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) { 1065 return FORMAT_OLDGNU; 1066 } 1067 if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) { 1068 return FORMAT_POSIX; 1069 } 1070 return 0; 1071 } 1072} 1073