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(); 171 172 name = normalizeFileName(name); 173 boolean isDir = name.endsWith("/"); 174 175 this.devMajor = 0; 176 this.devMinor = 0; 177 this.name = name; 178 this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE; 179 this.linkFlag = isDir ? LF_DIR : LF_NORMAL; 180 this.userId = 0; 181 this.groupId = 0; 182 this.size = 0; 183 this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND; 184 this.linkName = ""; 185 this.userName = ""; 186 this.groupName = ""; 187 this.devMajor = 0; 188 this.devMinor = 0; 189 190 } 191 192 /** 193 * Construct an entry with a name and a link flag. 194 * 195 * @param name the entry name 196 * @param linkFlag the entry link flag. 197 */ 198 public TarArchiveEntry(String name, byte linkFlag) { 199 this(name); 200 this.linkFlag = linkFlag; 201 } 202 203 /** 204 * Construct an entry for a file. File is set to file, and the 205 * header is constructed from information from the file. 206 * The name is set from the normalized file path. 207 * 208 * @param file The file that the entry represents. 209 */ 210 public TarArchiveEntry(File file) { 211 this(file, normalizeFileName(file.getPath())); 212 } 213 214 /** 215 * Construct an entry for a file. File is set to file, and the 216 * header is constructed from information from the file. 217 * 218 * @param file The file that the entry represents. 219 * @param fileName the name to be used for the entry. 220 */ 221 public TarArchiveEntry(File file, String fileName) { 222 this(); 223 224 this.file = file; 225 226 this.linkName = ""; 227 228 if (file.isDirectory()) { 229 this.mode = DEFAULT_DIR_MODE; 230 this.linkFlag = LF_DIR; 231 232 int nameLength = fileName.length(); 233 if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') { 234 this.name = fileName + "/"; 235 } else { 236 this.name = fileName; 237 } 238 this.size = 0; 239 } else { 240 this.mode = DEFAULT_FILE_MODE; 241 this.linkFlag = LF_NORMAL; 242 this.size = file.length(); 243 this.name = fileName; 244 } 245 246 this.modTime = file.lastModified() / MILLIS_PER_SECOND; 247 this.devMajor = 0; 248 this.devMinor = 0; 249 } 250 251 /** 252 * Construct an entry from an archive's header bytes. File is set 253 * to null. 254 * 255 * @param headerBuf The header bytes from a tar archive entry. 256 */ 257 public TarArchiveEntry(byte[] headerBuf) { 258 this(); 259 parseTarHeader(headerBuf); 260 } 261 262 /** 263 * Determine if the two entries are equal. Equality is determined 264 * by the header names being equal. 265 * 266 * @param it Entry to be checked for equality. 267 * @return True if the entries are equal. 268 */ 269 public boolean equals(TarArchiveEntry it) { 270 return getName().equals(it.getName()); 271 } 272 273 /** 274 * Determine if the two entries are equal. Equality is determined 275 * by the header names being equal. 276 * 277 * @param it Entry to be checked for equality. 278 * @return True if the entries are equal. 279 */ 280 public boolean equals(Object it) { 281 if (it == null || getClass() != it.getClass()) { 282 return false; 283 } 284 return equals((TarArchiveEntry) it); 285 } 286 287 /** 288 * Hashcodes are based on entry names. 289 * 290 * @return the entry hashcode 291 */ 292 public int hashCode() { 293 return getName().hashCode(); 294 } 295 296 /** 297 * Determine if the given entry is a descendant of this entry. 298 * Descendancy is determined by the name of the descendant 299 * starting with this entry's name. 300 * 301 * @param desc Entry to be checked as a descendent of this. 302 * @return True if entry is a descendant of this. 303 */ 304 public boolean isDescendent(TarArchiveEntry desc) { 305 return desc.getName().startsWith(getName()); 306 } 307 308 /** 309 * Get this entry's name. 310 * 311 * @return This entry's name. 312 */ 313 public String getName() { 314 return name.toString(); 315 } 316 317 /** 318 * Set this entry's name. 319 * 320 * @param name This entry's new name. 321 */ 322 public void setName(String name) { 323 this.name = normalizeFileName(name); 324 } 325 326 /** 327 * Set the mode for this entry 328 * 329 * @param mode the mode for this entry 330 */ 331 public void setMode(int mode) { 332 this.mode = mode; 333 } 334 335 /** 336 * Get this entry's link name. 337 * 338 * @return This entry's link name. 339 */ 340 public String getLinkName() { 341 return linkName.toString(); 342 } 343 344 /** 345 * Get this entry's user id. 346 * 347 * @return This entry's user id. 348 */ 349 public int getUserId() { 350 return userId; 351 } 352 353 /** 354 * Set this entry's user id. 355 * 356 * @param userId This entry's new user id. 357 */ 358 public void setUserId(int userId) { 359 this.userId = userId; 360 } 361 362 /** 363 * Get this entry's group id. 364 * 365 * @return This entry's group id. 366 */ 367 public int getGroupId() { 368 return groupId; 369 } 370 371 /** 372 * Set this entry's group id. 373 * 374 * @param groupId This entry's new group id. 375 */ 376 public void setGroupId(int groupId) { 377 this.groupId = groupId; 378 } 379 380 /** 381 * Get this entry's user name. 382 * 383 * @return This entry's user name. 384 */ 385 public String getUserName() { 386 return userName.toString(); 387 } 388 389 /** 390 * Set this entry's user name. 391 * 392 * @param userName This entry's new user name. 393 */ 394 public void setUserName(String userName) { 395 this.userName = userName; 396 } 397 398 /** 399 * Get this entry's group name. 400 * 401 * @return This entry's group name. 402 */ 403 public String getGroupName() { 404 return groupName.toString(); 405 } 406 407 /** 408 * Set this entry's group name. 409 * 410 * @param groupName This entry's new group name. 411 */ 412 public void setGroupName(String groupName) { 413 this.groupName = groupName; 414 } 415 416 /** 417 * Convenience method to set this entry's group and user ids. 418 * 419 * @param userId This entry's new user id. 420 * @param groupId This entry's new group id. 421 */ 422 public void setIds(int userId, int groupId) { 423 setUserId(userId); 424 setGroupId(groupId); 425 } 426 427 /** 428 * Convenience method to set this entry's group and user names. 429 * 430 * @param userName This entry's new user name. 431 * @param groupName This entry's new group name. 432 */ 433 public void setNames(String userName, String groupName) { 434 setUserName(userName); 435 setGroupName(groupName); 436 } 437 438 /** 439 * Set this entry's modification time. The parameter passed 440 * to this method is in "Java time". 441 * 442 * @param time This entry's new modification time. 443 */ 444 public void setModTime(long time) { 445 modTime = time / MILLIS_PER_SECOND; 446 } 447 448 /** 449 * Set this entry's modification time. 450 * 451 * @param time This entry's new modification time. 452 */ 453 public void setModTime(Date time) { 454 modTime = time.getTime() / MILLIS_PER_SECOND; 455 } 456 457 /** 458 * Set this entry's modification time. 459 * 460 * @return time This entry's new modification time. 461 */ 462 public Date getModTime() { 463 return new Date(modTime * MILLIS_PER_SECOND); 464 } 465 466 /** 467 * Get this entry's file. 468 * 469 * @return This entry's file. 470 */ 471 public File getFile() { 472 return file; 473 } 474 475 /** 476 * Get this entry's mode. 477 * 478 * @return This entry's mode. 479 */ 480 public int getMode() { 481 return mode; 482 } 483 484 /** 485 * Get this entry's file size. 486 * 487 * @return This entry's file size. 488 */ 489 public long getSize() { 490 return size; 491 } 492 493 /** 494 * Set this entry's file size. 495 * 496 * @param size This entry's new file size. 497 * @throws IllegalArgumentException if the size is < 0 498 * or > {@link TarConstants#MAXSIZE} (077777777777L). 499 */ 500 public void setSize(long size) { 501 if (size > MAXSIZE || size < 0){ 502 throw new IllegalArgumentException("Size is out of range: "+size); 503 } 504 this.size = size; 505 } 506 507 508 /** 509 * Indicate if this entry is a GNU long name block 510 * 511 * @return true if this is a long name extension provided by GNU tar 512 */ 513 public boolean isGNULongNameEntry() { 514 return linkFlag == LF_GNUTYPE_LONGNAME 515 && name.toString().equals(GNU_LONGLINK); 516 } 517 518 /** 519 * Return whether or not this entry represents a directory. 520 * 521 * @return True if this entry is a directory. 522 */ 523 public boolean isDirectory() { 524 if (file != null) { 525 return file.isDirectory(); 526 } 527 528 if (linkFlag == LF_DIR) { 529 return true; 530 } 531 532 if (getName().endsWith("/")) { 533 return true; 534 } 535 536 return false; 537 } 538 539 /** 540 * If this entry represents a file, and the file is a directory, return 541 * an array of TarEntries for this entry's children. 542 * 543 * @return An array of TarEntry's for this entry's children. 544 */ 545 public TarArchiveEntry[] getDirectoryEntries() { 546 if (file == null || !file.isDirectory()) { 547 return new TarArchiveEntry[0]; 548 } 549 550 String[] list = file.list(); 551 TarArchiveEntry[] result = new TarArchiveEntry[list.length]; 552 553 for (int i = 0; i < list.length; ++i) { 554 result[i] = new TarArchiveEntry(new File(file, list[i])); 555 } 556 557 return result; 558 } 559 560 /** 561 * Write an entry's header information to a header buffer. 562 * 563 * @param outbuf The tar entry header buffer to fill in. 564 */ 565 public void writeEntryHeader(byte[] outbuf) { 566 int offset = 0; 567 568 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN); 569 offset = TarUtils.formatOctalBytes(mode, outbuf, offset, MODELEN); 570 offset = TarUtils.formatOctalBytes(userId, outbuf, offset, UIDLEN); 571 offset = TarUtils.formatOctalBytes(groupId, outbuf, offset, GIDLEN); 572 offset = TarUtils.formatLongOctalBytes(size, outbuf, offset, SIZELEN); 573 offset = TarUtils.formatLongOctalBytes(modTime, outbuf, offset, MODTIMELEN); 574 575 int csOffset = offset; 576 577 for (int c = 0; c < CHKSUMLEN; ++c) { 578 outbuf[offset++] = (byte) ' '; 579 } 580 581 outbuf[offset++] = linkFlag; 582 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN); 583 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN); 584 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN); 585 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN); 586 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN); 587 offset = TarUtils.formatOctalBytes(devMajor, outbuf, offset, DEVLEN); 588 offset = TarUtils.formatOctalBytes(devMinor, outbuf, offset, DEVLEN); 589 590 while (offset < outbuf.length) { 591 outbuf[offset++] = 0; 592 } 593 594 long chk = TarUtils.computeCheckSum(outbuf); 595 596 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN); 597 } 598 599 /** 600 * Parse an entry's header information from a header buffer. 601 * 602 * @param header The tar entry header buffer to get information from. 603 */ 604 public void parseTarHeader(byte[] header) { 605 int offset = 0; 606 607 name = TarUtils.parseName(header, offset, NAMELEN); 608 offset += NAMELEN; 609 mode = (int) TarUtils.parseOctal(header, offset, MODELEN); 610 offset += MODELEN; 611 userId = (int) TarUtils.parseOctal(header, offset, UIDLEN); 612 offset += UIDLEN; 613 groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN); 614 offset += GIDLEN; 615 size = TarUtils.parseOctal(header, offset, SIZELEN); 616 offset += SIZELEN; 617 modTime = TarUtils.parseOctal(header, offset, MODTIMELEN); 618 offset += MODTIMELEN; 619 offset += CHKSUMLEN; 620 linkFlag = header[offset++]; 621 linkName = TarUtils.parseName(header, offset, NAMELEN); 622 offset += NAMELEN; 623 magic = TarUtils.parseName(header, offset, MAGICLEN); 624 offset += MAGICLEN; 625 version = TarUtils.parseName(header, offset, VERSIONLEN); 626 offset += VERSIONLEN; 627 userName = TarUtils.parseName(header, offset, UNAMELEN); 628 offset += UNAMELEN; 629 groupName = TarUtils.parseName(header, offset, GNAMELEN); 630 offset += GNAMELEN; 631 devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN); 632 offset += DEVLEN; 633 devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN); 634 } 635 636 /** 637 * Strips Windows' drive letter as well as any leading slashes, 638 * turns path separators into forward slahes. 639 */ 640 private static String normalizeFileName(String fileName) { 641 String osname = System.getProperty("os.name").toLowerCase(Locale.US); 642 643 if (osname != null) { 644 645 // Strip off drive letters! 646 // REVIEW Would a better check be "(File.separator == '\')"? 647 648 if (osname.startsWith("windows")) { 649 if (fileName.length() > 2) { 650 char ch1 = fileName.charAt(0); 651 char ch2 = fileName.charAt(1); 652 653 if (ch2 == ':' 654 && ((ch1 >= 'a' && ch1 <= 'z') 655 || (ch1 >= 'A' && ch1 <= 'Z'))) { 656 fileName = fileName.substring(2); 657 } 658 } 659 } else if (osname.indexOf("netware") > -1) { 660 int colon = fileName.indexOf(':'); 661 if (colon != -1) { 662 fileName = fileName.substring(colon + 1); 663 } 664 } 665 } 666 667 fileName = fileName.replace(File.separatorChar, '/'); 668 669 // No absolute pathnames 670 // Windows (and Posix?) paths can start with "\\NetworkDrive\", 671 // so we loop on starting /'s. 672 while (fileName.startsWith("/")) { 673 fileName = fileName.substring(1); 674 } 675 return fileName; 676 } 677 } 678