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