001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 package org.apache.commons.compress.archivers.tar; 020 021 import java.io.File; 022 import java.util.Date; 023 import java.util.Locale; 024 025 import org.apache.commons.compress.archivers.ArchiveEntry; 026 027 /** 028 * This class represents an entry in a Tar archive. It consists 029 * of the entry's header, as well as the entry's File. Entries 030 * can be instantiated in one of three ways, depending on how 031 * they are to be used. 032 * <p> 033 * TarEntries that are created from the header bytes read from 034 * an archive are instantiated with the TarEntry( byte[] ) 035 * constructor. These entries will be used when extracting from 036 * or listing the contents of an archive. These entries have their 037 * header filled in using the header bytes. They also set the File 038 * to null, since they reference an archive entry not a file. 039 * <p> 040 * TarEntries that are created from Files that are to be written 041 * into an archive are instantiated with the TarEntry( File ) 042 * constructor. These entries have their header filled in using 043 * the File's information. They also keep a reference to the File 044 * for convenience when writing entries. 045 * <p> 046 * Finally, TarEntries can be constructed from nothing but a name. 047 * This allows the programmer to construct the entry by hand, for 048 * instance when only an InputStream is available for writing to 049 * the archive, and the header information is constructed from 050 * other information. In this case the header fields are set to 051 * defaults and the File is set to null. 052 * 053 * <p> 054 * The C structure for a Tar Entry's header is: 055 * <pre> 056 * struct header { 057 * char name[100]; // TarConstants.NAMELEN - offset 0 058 * char mode[8]; // TarConstants.MODELEN - offset 100 059 * char uid[8]; // TarConstants.UIDLEN - offset 108 060 * char gid[8]; // TarConstants.GIDLEN - offset 116 061 * char size[12]; // TarConstants.SIZELEN - offset 124 062 * char mtime[12]; // TarConstants.MODTIMELEN - offset 136 063 * char chksum[8]; // TarConstants.CHKSUMLEN - offset 148 064 * char linkflag[1]; // - offset 156 065 * char linkname[100]; // TarConstants.NAMELEN - offset 157 066 * The following fields are only present in new-style POSIX tar archives: 067 * char magic[6]; // TarConstants.MAGICLEN - offset 257 068 * char version[2]; // TarConstants.VERSIONLEN - offset 263 069 * char uname[32]; // TarConstants.UNAMELEN - offset 265 070 * char gname[32]; // TarConstants.GNAMELEN - offset 297 071 * char devmajor[8]; // TarConstants.DEVLEN - offset 329 072 * char devminor[8]; // TarConstants.DEVLEN - offset 337 073 * char prefix[155]; // TarConstants.PREFIXLEN - offset 345 074 * // Used if "name" field is not long enough to hold the path 075 * char pad[12]; // NULs - offset 500 076 * } header; 077 * All unused bytes are set to null. 078 * New-style GNU tar files are slightly different from the above. 079 * </pre> 080 * 081 * @NotThreadSafe 082 */ 083 084 public class TarArchiveEntry implements TarConstants, ArchiveEntry { 085 /** The entry's name. */ 086 private String name; 087 088 /** The entry's permission mode. */ 089 private int mode; 090 091 /** The entry's user id. */ 092 private int userId; 093 094 /** The entry's group id. */ 095 private int groupId; 096 097 /** The entry's size. */ 098 private long size; 099 100 /** The entry's modification time. */ 101 private long modTime; 102 103 /** The entry's link flag. */ 104 private byte linkFlag; 105 106 /** The entry's link name. */ 107 private String linkName; 108 109 /** The entry's magic tag. */ 110 private String magic; 111 /** The version of the format */ 112 private String version; 113 114 /** The entry's user name. */ 115 private String userName; 116 117 /** The entry's group name. */ 118 private String groupName; 119 120 /** The entry's major device number. */ 121 private int devMajor; 122 123 /** The entry's minor device number. */ 124 private int devMinor; 125 126 /** The entry's file reference */ 127 private File file; 128 129 /** Maximum length of a user's name in the tar file */ 130 public static final int MAX_NAMELEN = 31; 131 132 /** Default permissions bits for directories */ 133 public static final int DEFAULT_DIR_MODE = 040755; 134 135 /** Default permissions bits for files */ 136 public static final int DEFAULT_FILE_MODE = 0100644; 137 138 /** Convert millis to seconds */ 139 public static final int MILLIS_PER_SECOND = 1000; 140 141 /** 142 * Construct an empty entry and prepares the header values. 143 */ 144 private TarArchiveEntry () { 145 this.magic = MAGIC_POSIX; 146 this.version = VERSION_POSIX; 147 this.name = ""; 148 this.linkName = ""; 149 150 String user = System.getProperty("user.name", ""); 151 152 if (user.length() > MAX_NAMELEN) { 153 user = user.substring(0, MAX_NAMELEN); 154 } 155 156 this.userId = 0; 157 this.groupId = 0; 158 this.userName = user; 159 this.groupName = ""; 160 this.file = null; 161 } 162 163 /** 164 * Construct an entry with only a name. This allows the programmer 165 * to construct the entry's header "by hand". File is set to null. 166 * 167 * @param name the entry name 168 */ 169 public TarArchiveEntry(String name) { 170 this(name, false); 171 } 172 173 /** 174 * Construct an entry with only a name. This allows the programmer 175 * to construct the entry's header "by hand". File is set to null. 176 * 177 * @param name the entry name 178 * @param preserveLeadingSlashes whether to allow leading slashes 179 * in the name. 180 * 181 * @since Apache Commons Compress 1.1 182 */ 183 public TarArchiveEntry(String name, boolean preserveLeadingSlashes) { 184 this(); 185 186 name = normalizeFileName(name, preserveLeadingSlashes); 187 boolean isDir = name.endsWith("/"); 188 189 this.devMajor = 0; 190 this.devMinor = 0; 191 this.name = name; 192 this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE; 193 this.linkFlag = isDir ? LF_DIR : LF_NORMAL; 194 this.userId = 0; 195 this.groupId = 0; 196 this.size = 0; 197 this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND; 198 this.linkName = ""; 199 this.userName = ""; 200 this.groupName = ""; 201 this.devMajor = 0; 202 this.devMinor = 0; 203 204 } 205 206 /** 207 * Construct an entry with a name and a link flag. 208 * 209 * @param name the entry name 210 * @param linkFlag the entry link flag. 211 */ 212 public TarArchiveEntry(String name, byte linkFlag) { 213 this(name); 214 this.linkFlag = linkFlag; 215 if (linkFlag == LF_GNUTYPE_LONGNAME) { 216 magic = MAGIC_GNU; 217 version = VERSION_GNU_SPACE; 218 } 219 } 220 221 /** 222 * Construct an entry for a file. File is set to file, and the 223 * header is constructed from information from the file. 224 * The name is set from the normalized file path. 225 * 226 * @param file The file that the entry represents. 227 */ 228 public TarArchiveEntry(File file) { 229 this(file, normalizeFileName(file.getPath(), false)); 230 } 231 232 /** 233 * Construct an entry for a file. File is set to file, and the 234 * header is constructed from information from the file. 235 * 236 * @param file The file that the entry represents. 237 * @param fileName the name to be used for the entry. 238 */ 239 public TarArchiveEntry(File file, String fileName) { 240 this(); 241 242 this.file = file; 243 244 this.linkName = ""; 245 246 if (file.isDirectory()) { 247 this.mode = DEFAULT_DIR_MODE; 248 this.linkFlag = LF_DIR; 249 250 int nameLength = fileName.length(); 251 if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') { 252 this.name = fileName + "/"; 253 } else { 254 this.name = fileName; 255 } 256 this.size = 0; 257 } else { 258 this.mode = DEFAULT_FILE_MODE; 259 this.linkFlag = LF_NORMAL; 260 this.size = file.length(); 261 this.name = fileName; 262 } 263 264 this.modTime = file.lastModified() / MILLIS_PER_SECOND; 265 this.devMajor = 0; 266 this.devMinor = 0; 267 } 268 269 /** 270 * Construct an entry from an archive's header bytes. File is set 271 * to null. 272 * 273 * @param headerBuf The header bytes from a tar archive entry. 274 */ 275 public TarArchiveEntry(byte[] headerBuf) { 276 this(); 277 parseTarHeader(headerBuf); 278 } 279 280 /** 281 * Determine if the two entries are equal. Equality is determined 282 * by the header names being equal. 283 * 284 * @param it Entry to be checked for equality. 285 * @return True if the entries are equal. 286 */ 287 public boolean equals(TarArchiveEntry it) { 288 return getName().equals(it.getName()); 289 } 290 291 /** 292 * Determine if the two entries are equal. Equality is determined 293 * by the header names being equal. 294 * 295 * @param it Entry to be checked for equality. 296 * @return True if the entries are equal. 297 */ 298 public boolean equals(Object it) { 299 if (it == null || getClass() != it.getClass()) { 300 return false; 301 } 302 return equals((TarArchiveEntry) it); 303 } 304 305 /** 306 * Hashcodes are based on entry names. 307 * 308 * @return the entry hashcode 309 */ 310 public int hashCode() { 311 return getName().hashCode(); 312 } 313 314 /** 315 * Determine if the given entry is a descendant of this entry. 316 * Descendancy is determined by the name of the descendant 317 * starting with this entry's name. 318 * 319 * @param desc Entry to be checked as a descendent of this. 320 * @return True if entry is a descendant of this. 321 */ 322 public boolean isDescendent(TarArchiveEntry desc) { 323 return desc.getName().startsWith(getName()); 324 } 325 326 /** 327 * Get this entry's name. 328 * 329 * @return This entry's name. 330 */ 331 public String getName() { 332 return name.toString(); 333 } 334 335 /** 336 * Set this entry's name. 337 * 338 * @param name This entry's new name. 339 */ 340 public void setName(String name) { 341 this.name = normalizeFileName(name, false); 342 } 343 344 /** 345 * Set the mode for this entry 346 * 347 * @param mode the mode for this entry 348 */ 349 public void setMode(int mode) { 350 this.mode = mode; 351 } 352 353 /** 354 * Get this entry's link name. 355 * 356 * @return This entry's link name. 357 */ 358 public String getLinkName() { 359 return linkName.toString(); 360 } 361 362 /** 363 * Set this entry's link name. 364 * 365 * @param link the link name to use. 366 * 367 * @since Apache Commons Compress 1.1 368 */ 369 public void setLinkName(String link) { 370 this.linkName = link; 371 } 372 373 /** 374 * Get this entry's user id. 375 * 376 * @return This entry's user id. 377 */ 378 public int getUserId() { 379 return userId; 380 } 381 382 /** 383 * Set this entry's user id. 384 * 385 * @param userId This entry's new user id. 386 */ 387 public void setUserId(int userId) { 388 this.userId = userId; 389 } 390 391 /** 392 * Get this entry's group id. 393 * 394 * @return This entry's group id. 395 */ 396 public int getGroupId() { 397 return groupId; 398 } 399 400 /** 401 * Set this entry's group id. 402 * 403 * @param groupId This entry's new group id. 404 */ 405 public void setGroupId(int groupId) { 406 this.groupId = groupId; 407 } 408 409 /** 410 * Get this entry's user name. 411 * 412 * @return This entry's user name. 413 */ 414 public String getUserName() { 415 return userName.toString(); 416 } 417 418 /** 419 * Set this entry's user name. 420 * 421 * @param userName This entry's new user name. 422 */ 423 public void setUserName(String userName) { 424 this.userName = userName; 425 } 426 427 /** 428 * Get this entry's group name. 429 * 430 * @return This entry's group name. 431 */ 432 public String getGroupName() { 433 return groupName.toString(); 434 } 435 436 /** 437 * Set this entry's group name. 438 * 439 * @param groupName This entry's new group name. 440 */ 441 public void setGroupName(String groupName) { 442 this.groupName = groupName; 443 } 444 445 /** 446 * Convenience method to set this entry's group and user ids. 447 * 448 * @param userId This entry's new user id. 449 * @param groupId This entry's new group id. 450 */ 451 public void setIds(int userId, int groupId) { 452 setUserId(userId); 453 setGroupId(groupId); 454 } 455 456 /** 457 * Convenience method to set this entry's group and user names. 458 * 459 * @param userName This entry's new user name. 460 * @param groupName This entry's new group name. 461 */ 462 public void setNames(String userName, String groupName) { 463 setUserName(userName); 464 setGroupName(groupName); 465 } 466 467 /** 468 * Set this entry's modification time. The parameter passed 469 * to this method is in "Java time". 470 * 471 * @param time This entry's new modification time. 472 */ 473 public void setModTime(long time) { 474 modTime = time / MILLIS_PER_SECOND; 475 } 476 477 /** 478 * Set this entry's modification time. 479 * 480 * @param time This entry's new modification time. 481 */ 482 public void setModTime(Date time) { 483 modTime = time.getTime() / MILLIS_PER_SECOND; 484 } 485 486 /** 487 * Set this entry's modification time. 488 * 489 * @return time This entry's new modification time. 490 */ 491 public Date getModTime() { 492 return new Date(modTime * MILLIS_PER_SECOND); 493 } 494 495 /** {@inheritDoc} */ 496 public Date getLastModifiedDate() { 497 return getModTime(); 498 } 499 500 /** 501 * Get this entry's file. 502 * 503 * @return This entry's file. 504 */ 505 public File getFile() { 506 return file; 507 } 508 509 /** 510 * Get this entry's mode. 511 * 512 * @return This entry's mode. 513 */ 514 public int getMode() { 515 return mode; 516 } 517 518 /** 519 * Get this entry's file size. 520 * 521 * @return This entry's file size. 522 */ 523 public long getSize() { 524 return size; 525 } 526 527 /** 528 * Set this entry's file size. 529 * 530 * @param size This entry's new file size. 531 * @throws IllegalArgumentException if the size is < 0 532 * or > {@link TarConstants#MAXSIZE} (077777777777L). 533 */ 534 public void setSize(long size) { 535 if (size > MAXSIZE || size < 0){ 536 throw new IllegalArgumentException("Size is out of range: "+size); 537 } 538 this.size = size; 539 } 540 541 542 /** 543 * Indicate if this entry is a GNU long name block 544 * 545 * @return true if this is a long name extension provided by GNU tar 546 */ 547 public boolean isGNULongNameEntry() { 548 return linkFlag == LF_GNUTYPE_LONGNAME 549 && name.toString().equals(GNU_LONGLINK); 550 } 551 552 /** 553 * Check if this is a Pax header. 554 * 555 * @return <code>true</code> if this is a Pax header. 556 * 557 * @since Apache Commons Compress 1.1 558 */ 559 public boolean isPaxHeader(){ 560 return linkFlag == LF_PAX_EXTENDED_HEADER_LC 561 || linkFlag == LF_PAX_EXTENDED_HEADER_UC; 562 } 563 564 /** 565 * Check if this is a Pax header. 566 * 567 * @return <code>true</code> if this is a Pax header. 568 * 569 * @since Apache Commons Compress 1.1 570 */ 571 public boolean isGlobalPaxHeader(){ 572 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER; 573 } 574 575 /** 576 * Return whether or not this entry represents a directory. 577 * 578 * @return True if this entry is a directory. 579 */ 580 public boolean isDirectory() { 581 if (file != null) { 582 return file.isDirectory(); 583 } 584 585 if (linkFlag == LF_DIR) { 586 return true; 587 } 588 589 if (getName().endsWith("/")) { 590 return true; 591 } 592 593 return false; 594 } 595 596 /** 597 * If this entry represents a file, and the file is a directory, return 598 * an array of TarEntries for this entry's children. 599 * 600 * @return An array of TarEntry's for this entry's children. 601 */ 602 public TarArchiveEntry[] getDirectoryEntries() { 603 if (file == null || !file.isDirectory()) { 604 return new TarArchiveEntry[0]; 605 } 606 607 String[] list = file.list(); 608 TarArchiveEntry[] result = new TarArchiveEntry[list.length]; 609 610 for (int i = 0; i < list.length; ++i) { 611 result[i] = new TarArchiveEntry(new File(file, list[i])); 612 } 613 614 return result; 615 } 616 617 /** 618 * Write an entry's header information to a header buffer. 619 * 620 * @param outbuf The tar entry header buffer to fill in. 621 */ 622 public void writeEntryHeader(byte[] outbuf) { 623 int offset = 0; 624 625 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN); 626 offset = TarUtils.formatOctalBytes(mode, outbuf, offset, MODELEN); 627 offset = TarUtils.formatOctalBytes(userId, outbuf, offset, UIDLEN); 628 offset = TarUtils.formatOctalBytes(groupId, outbuf, offset, GIDLEN); 629 offset = TarUtils.formatLongOctalBytes(size, outbuf, offset, SIZELEN); 630 offset = TarUtils.formatLongOctalBytes(modTime, outbuf, offset, MODTIMELEN); 631 632 int csOffset = offset; 633 634 for (int c = 0; c < CHKSUMLEN; ++c) { 635 outbuf[offset++] = (byte) ' '; 636 } 637 638 outbuf[offset++] = linkFlag; 639 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN); 640 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN); 641 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN); 642 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN); 643 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN); 644 offset = TarUtils.formatOctalBytes(devMajor, outbuf, offset, DEVLEN); 645 offset = TarUtils.formatOctalBytes(devMinor, outbuf, offset, DEVLEN); 646 647 while (offset < outbuf.length) { 648 outbuf[offset++] = 0; 649 } 650 651 long chk = TarUtils.computeCheckSum(outbuf); 652 653 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN); 654 } 655 656 /** 657 * Parse an entry's header information from a header buffer. 658 * 659 * @param header The tar entry header buffer to get information from. 660 */ 661 public void parseTarHeader(byte[] header) { 662 int offset = 0; 663 664 name = TarUtils.parseName(header, offset, NAMELEN); 665 offset += NAMELEN; 666 mode = (int) TarUtils.parseOctal(header, offset, MODELEN); 667 offset += MODELEN; 668 userId = (int) TarUtils.parseOctal(header, offset, UIDLEN); 669 offset += UIDLEN; 670 groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN); 671 offset += GIDLEN; 672 size = TarUtils.parseOctal(header, offset, SIZELEN); 673 offset += SIZELEN; 674 modTime = TarUtils.parseOctal(header, offset, MODTIMELEN); 675 offset += MODTIMELEN; 676 offset += CHKSUMLEN; 677 linkFlag = header[offset++]; 678 linkName = TarUtils.parseName(header, offset, NAMELEN); 679 offset += NAMELEN; 680 magic = TarUtils.parseName(header, offset, MAGICLEN); 681 offset += MAGICLEN; 682 version = TarUtils.parseName(header, offset, VERSIONLEN); 683 offset += VERSIONLEN; 684 userName = TarUtils.parseName(header, offset, UNAMELEN); 685 offset += UNAMELEN; 686 groupName = TarUtils.parseName(header, offset, GNAMELEN); 687 offset += GNAMELEN; 688 devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN); 689 offset += DEVLEN; 690 devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN); 691 offset += DEVLEN; 692 String prefix = TarUtils.parseName(header, offset, PREFIXLEN); 693 // SunOS tar -E does not add / to directory names, so fix up to be consistent 694 if (isDirectory() && !name.endsWith("/")){ 695 name = name + "/"; 696 } 697 if (prefix.length() >0){ 698 name = prefix + "/" + name; 699 } 700 } 701 702 /** 703 * Strips Windows' drive letter as well as any leading slashes, 704 * turns path separators into forward slahes. 705 */ 706 private static String normalizeFileName(String fileName, 707 boolean preserveLeadingSlashes) { 708 String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 709 710 if (osname != null) { 711 712 // Strip off drive letters! 713 // REVIEW Would a better check be "(File.separator == '\')"? 714 715 if (osname.startsWith("windows")) { 716 if (fileName.length() > 2) { 717 char ch1 = fileName.charAt(0); 718 char ch2 = fileName.charAt(1); 719 720 if (ch2 == ':' 721 && ((ch1 >= 'a' && ch1 <= 'z') 722 || (ch1 >= 'A' && ch1 <= 'Z'))) { 723 fileName = fileName.substring(2); 724 } 725 } 726 } else if (osname.indexOf("netware") > -1) { 727 int colon = fileName.indexOf(':'); 728 if (colon != -1) { 729 fileName = fileName.substring(colon + 1); 730 } 731 } 732 } 733 734 fileName = fileName.replace(File.separatorChar, '/'); 735 736 // No absolute pathnames 737 // Windows (and Posix?) paths can start with "\\NetworkDrive\", 738 // so we loop on starting /'s. 739 while (!preserveLeadingSlashes && fileName.startsWith("/")) { 740 fileName = fileName.substring(1); 741 } 742 return fileName; 743 } 744 } 745