001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 * 017 */ 018 019/* 020 * This package is based on the work done by Timothy Gerard Endres 021 * (time@ice.com) to whom the Ant project is very grateful for his great code. 022 */ 023 024package org.apache.activemq.console.command.store.tar; 025 026import java.io.File; 027import java.util.Date; 028import java.util.Locale; 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[NAMSIZ]; 061 * char mode[8]; 062 * char uid[8]; 063 * char gid[8]; 064 * char size[12]; 065 * char mtime[12]; 066 * char chksum[8]; 067 * char linkflag; 068 * char linkname[NAMSIZ]; 069 * char magic[8]; 070 * char uname[TUNMLEN]; 071 * char gname[TGNMLEN]; 072 * char devmajor[8]; 073 * char devminor[8]; 074 * } header; 075 * </pre> 076 * 077 */ 078 079public class TarEntry implements TarConstants { 080 /** The entry's name. */ 081 private StringBuffer name; 082 083 /** The entry's permission mode. */ 084 private int mode; 085 086 /** The entry's user id. */ 087 private int userId; 088 089 /** The entry's group id. */ 090 private int groupId; 091 092 /** The entry's size. */ 093 private long size; 094 095 /** The entry's modification time. */ 096 private long modTime; 097 098 /** The entry's link flag. */ 099 private byte linkFlag; 100 101 /** The entry's link name. */ 102 private StringBuffer linkName; 103 104 /** The entry's magic tag. */ 105 private StringBuffer magic; 106 107 /** The entry's user name. */ 108 private StringBuffer userName; 109 110 /** The entry's group name. */ 111 private StringBuffer groupName; 112 113 /** The entry's major device number. */ 114 private int devMajor; 115 116 /** The entry's minor device number. */ 117 private int devMinor; 118 119 /** The entry's file reference */ 120 private File file; 121 122 /** Maximum length of a user's name in the tar file */ 123 public static final int MAX_NAMELEN = 31; 124 125 /** Default permissions bits for directories */ 126 public static final int DEFAULT_DIR_MODE = 040755; 127 128 /** Default permissions bits for files */ 129 public static final int DEFAULT_FILE_MODE = 0100644; 130 131 /** Convert millis to seconds */ 132 public static final int MILLIS_PER_SECOND = 1000; 133 134 /** 135 * Construct an empty entry and prepares the header values. 136 */ 137 private TarEntry () { 138 this.magic = new StringBuffer(TMAGIC); 139 this.name = new StringBuffer(); 140 this.linkName = new StringBuffer(); 141 142 String user = System.getProperty("user.name", ""); 143 144 if (user.length() > MAX_NAMELEN) { 145 user = user.substring(0, MAX_NAMELEN); 146 } 147 148 this.userId = 0; 149 this.groupId = 0; 150 this.userName = new StringBuffer(user); 151 this.groupName = new StringBuffer(""); 152 this.file = null; 153 } 154 155 /** 156 * Construct an entry with only a name. This allows the programmer 157 * to construct the entry's header "by hand". File is set to null. 158 * 159 * @param name the entry name 160 */ 161 public TarEntry(String name) { 162 this(name, false); 163 } 164 165 /** 166 * Construct an entry with only a name. This allows the programmer 167 * to construct the entry's header "by hand". File is set to null. 168 * 169 * @param name the entry name 170 * @param preserveLeadingSlashes whether to allow leading slashes 171 * in the name. 172 */ 173 public TarEntry(String name, boolean preserveLeadingSlashes) { 174 this(); 175 176 name = normalizeFileName(name, preserveLeadingSlashes); 177 boolean isDir = name.endsWith("/"); 178 179 this.devMajor = 0; 180 this.devMinor = 0; 181 this.name = new StringBuffer(name); 182 this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE; 183 this.linkFlag = isDir ? LF_DIR : LF_NORMAL; 184 this.userId = 0; 185 this.groupId = 0; 186 this.size = 0; 187 this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND; 188 this.linkName = new StringBuffer(""); 189 this.userName = new StringBuffer(""); 190 this.groupName = new StringBuffer(""); 191 this.devMajor = 0; 192 this.devMinor = 0; 193 194 } 195 196 /** 197 * Construct an entry with a name and a link flag. 198 * 199 * @param name the entry name 200 * @param linkFlag the entry link flag. 201 */ 202 public TarEntry(String name, byte linkFlag) { 203 this(name); 204 this.linkFlag = linkFlag; 205 if (linkFlag == LF_GNUTYPE_LONGNAME) { 206 magic = new StringBuffer(GNU_TMAGIC); 207 } 208 } 209 210 /** 211 * Construct an entry for a file. File is set to file, and the 212 * header is constructed from information from the file. 213 * 214 * @param file The file that the entry represents. 215 */ 216 public TarEntry(File file) { 217 this(); 218 219 this.file = file; 220 221 String fileName = normalizeFileName(file.getPath(), false); 222 this.linkName = new StringBuffer(""); 223 this.name = new StringBuffer(fileName); 224 225 if (file.isDirectory()) { 226 this.mode = DEFAULT_DIR_MODE; 227 this.linkFlag = LF_DIR; 228 229 int nameLength = name.length(); 230 if (nameLength == 0 || name.charAt(nameLength - 1) != '/') { 231 this.name.append("/"); 232 } 233 this.size = 0; 234 } else { 235 this.mode = DEFAULT_FILE_MODE; 236 this.linkFlag = LF_NORMAL; 237 this.size = file.length(); 238 } 239 240 this.modTime = file.lastModified() / MILLIS_PER_SECOND; 241 this.devMajor = 0; 242 this.devMinor = 0; 243 } 244 245 /** 246 * Construct an entry from an archive's header bytes. File is set 247 * to null. 248 * 249 * @param headerBuf The header bytes from a tar archive entry. 250 */ 251 public TarEntry(byte[] headerBuf) { 252 this(); 253 parseTarHeader(headerBuf); 254 } 255 256 /** 257 * Determine if the two entries are equal. Equality is determined 258 * by the header names being equal. 259 * 260 * @param it Entry to be checked for equality. 261 * @return True if the entries are equal. 262 */ 263 public boolean equals(TarEntry it) { 264 return getName().equals(it.getName()); 265 } 266 267 /** 268 * Determine if the two entries are equal. Equality is determined 269 * by the header names being equal. 270 * 271 * @param it Entry to be checked for equality. 272 * @return True if the entries are equal. 273 */ 274 public boolean equals(Object it) { 275 if (it == null || getClass() != it.getClass()) { 276 return false; 277 } 278 return equals((TarEntry) it); 279 } 280 281 /** 282 * Hashcodes are based on entry names. 283 * 284 * @return the entry hashcode 285 */ 286 public int hashCode() { 287 return getName().hashCode(); 288 } 289 290 /** 291 * Determine if the given entry is a descendant of this entry. 292 * Descendancy is determined by the name of the descendant 293 * starting with this entry's name. 294 * 295 * @param desc Entry to be checked as a descendent of this. 296 * @return True if entry is a descendant of this. 297 */ 298 public boolean isDescendent(TarEntry desc) { 299 return desc.getName().startsWith(getName()); 300 } 301 302 /** 303 * Get this entry's name. 304 * 305 * @return This entry's name. 306 */ 307 public String getName() { 308 return name.toString(); 309 } 310 311 /** 312 * Set this entry's name. 313 * 314 * @param name This entry's new name. 315 */ 316 public void setName(String name) { 317 this.name = new StringBuffer(normalizeFileName(name, false)); 318 } 319 320 /** 321 * Set the mode for this entry 322 * 323 * @param mode the mode for this entry 324 */ 325 public void setMode(int mode) { 326 this.mode = mode; 327 } 328 329 /** 330 * Get this entry's link name. 331 * 332 * @return This entry's link name. 333 */ 334 public String getLinkName() { 335 return linkName.toString(); 336 } 337 338 /** 339 * Get this entry's user id. 340 * 341 * @return This entry's user id. 342 */ 343 public int getUserId() { 344 return userId; 345 } 346 347 /** 348 * Set this entry's user id. 349 * 350 * @param userId This entry's new user id. 351 */ 352 public void setUserId(int userId) { 353 this.userId = userId; 354 } 355 356 /** 357 * Get this entry's group id. 358 * 359 * @return This entry's group id. 360 */ 361 public int getGroupId() { 362 return groupId; 363 } 364 365 /** 366 * Set this entry's group id. 367 * 368 * @param groupId This entry's new group id. 369 */ 370 public void setGroupId(int groupId) { 371 this.groupId = groupId; 372 } 373 374 /** 375 * Get this entry's user name. 376 * 377 * @return This entry's user name. 378 */ 379 public String getUserName() { 380 return userName.toString(); 381 } 382 383 /** 384 * Set this entry's user name. 385 * 386 * @param userName This entry's new user name. 387 */ 388 public void setUserName(String userName) { 389 this.userName = new StringBuffer(userName); 390 } 391 392 /** 393 * Get this entry's group name. 394 * 395 * @return This entry's group name. 396 */ 397 public String getGroupName() { 398 return groupName.toString(); 399 } 400 401 /** 402 * Set this entry's group name. 403 * 404 * @param groupName This entry's new group name. 405 */ 406 public void setGroupName(String groupName) { 407 this.groupName = new StringBuffer(groupName); 408 } 409 410 /** 411 * Convenience method to set this entry's group and user ids. 412 * 413 * @param userId This entry's new user id. 414 * @param groupId This entry's new group id. 415 */ 416 public void setIds(int userId, int groupId) { 417 setUserId(userId); 418 setGroupId(groupId); 419 } 420 421 /** 422 * Convenience method to set this entry's group and user names. 423 * 424 * @param userName This entry's new user name. 425 * @param groupName This entry's new group name. 426 */ 427 public void setNames(String userName, String groupName) { 428 setUserName(userName); 429 setGroupName(groupName); 430 } 431 432 /** 433 * Set this entry's modification time. The parameter passed 434 * to this method is in "Java time". 435 * 436 * @param time This entry's new modification time. 437 */ 438 public void setModTime(long time) { 439 modTime = time / MILLIS_PER_SECOND; 440 } 441 442 /** 443 * Set this entry's modification time. 444 * 445 * @param time This entry's new modification time. 446 */ 447 public void setModTime(Date time) { 448 modTime = time.getTime() / MILLIS_PER_SECOND; 449 } 450 451 /** 452 * Set this entry's modification time. 453 * 454 * @return time This entry's new modification time. 455 */ 456 public Date getModTime() { 457 return new Date(modTime * MILLIS_PER_SECOND); 458 } 459 460 /** 461 * Get this entry's file. 462 * 463 * @return This entry's file. 464 */ 465 public File getFile() { 466 return file; 467 } 468 469 /** 470 * Get this entry's mode. 471 * 472 * @return This entry's mode. 473 */ 474 public int getMode() { 475 return mode; 476 } 477 478 /** 479 * Get this entry's file size. 480 * 481 * @return This entry's file size. 482 */ 483 public long getSize() { 484 return size; 485 } 486 487 /** 488 * Set this entry's file size. 489 * 490 * @param size This entry's new file size. 491 */ 492 public void setSize(long size) { 493 this.size = size; 494 } 495 496 497 /** 498 * Indicate if this entry is a GNU long name block 499 * 500 * @return true if this is a long name extension provided by GNU tar 501 */ 502 public boolean isGNULongNameEntry() { 503 return linkFlag == LF_GNUTYPE_LONGNAME 504 && name.toString().equals(GNU_LONGLINK); 505 } 506 507 /** 508 * Return whether or not this entry represents a directory. 509 * 510 * @return True if this entry is a directory. 511 */ 512 public boolean isDirectory() { 513 if (file != null) { 514 return file.isDirectory(); 515 } 516 517 if (linkFlag == LF_DIR) { 518 return true; 519 } 520 521 if (getName().endsWith("/")) { 522 return true; 523 } 524 525 return false; 526 } 527 528 /** 529 * If this entry represents a file, and the file is a directory, return 530 * an array of TarEntries for this entry's children. 531 * 532 * @return An array of TarEntry's for this entry's children. 533 */ 534 public TarEntry[] getDirectoryEntries() { 535 if (file == null || !file.isDirectory()) { 536 return new TarEntry[0]; 537 } 538 539 String[] list = file.list(); 540 TarEntry[] result = new TarEntry[list.length]; 541 542 for (int i = 0; i < list.length; ++i) { 543 result[i] = new TarEntry(new File(file, list[i])); 544 } 545 546 return result; 547 } 548 549 /** 550 * Write an entry's header information to a header buffer. 551 * 552 * @param outbuf The tar entry header buffer to fill in. 553 */ 554 public void writeEntryHeader(byte[] outbuf) { 555 int offset = 0; 556 557 offset = TarUtils.getNameBytes(name, outbuf, offset, NAMELEN); 558 offset = TarUtils.getOctalBytes(mode, outbuf, offset, MODELEN); 559 offset = TarUtils.getOctalBytes(userId, outbuf, offset, UIDLEN); 560 offset = TarUtils.getOctalBytes(groupId, outbuf, offset, GIDLEN); 561 offset = TarUtils.getLongOctalBytes(size, outbuf, offset, SIZELEN); 562 offset = TarUtils.getLongOctalBytes(modTime, outbuf, offset, MODTIMELEN); 563 564 int csOffset = offset; 565 566 for (int c = 0; c < CHKSUMLEN; ++c) { 567 outbuf[offset++] = (byte) ' '; 568 } 569 570 outbuf[offset++] = linkFlag; 571 offset = TarUtils.getNameBytes(linkName, outbuf, offset, NAMELEN); 572 offset = TarUtils.getNameBytes(magic, outbuf, offset, MAGICLEN); 573 offset = TarUtils.getNameBytes(userName, outbuf, offset, UNAMELEN); 574 offset = TarUtils.getNameBytes(groupName, outbuf, offset, GNAMELEN); 575 offset = TarUtils.getOctalBytes(devMajor, outbuf, offset, DEVLEN); 576 offset = TarUtils.getOctalBytes(devMinor, outbuf, offset, DEVLEN); 577 578 while (offset < outbuf.length) { 579 outbuf[offset++] = 0; 580 } 581 582 long chk = TarUtils.computeCheckSum(outbuf); 583 584 TarUtils.getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN); 585 } 586 587 /** 588 * Parse an entry's header information from a header buffer. 589 * 590 * @param header The tar entry header buffer to get information from. 591 */ 592 public void parseTarHeader(byte[] header) { 593 int offset = 0; 594 595 name = TarUtils.parseName(header, offset, NAMELEN); 596 offset += NAMELEN; 597 mode = (int) TarUtils.parseOctal(header, offset, MODELEN); 598 offset += MODELEN; 599 userId = (int) TarUtils.parseOctal(header, offset, UIDLEN); 600 offset += UIDLEN; 601 groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN); 602 offset += GIDLEN; 603 size = TarUtils.parseOctal(header, offset, SIZELEN); 604 offset += SIZELEN; 605 modTime = TarUtils.parseOctal(header, offset, MODTIMELEN); 606 offset += MODTIMELEN; 607 offset += CHKSUMLEN; 608 linkFlag = header[offset++]; 609 linkName = TarUtils.parseName(header, offset, NAMELEN); 610 offset += NAMELEN; 611 magic = TarUtils.parseName(header, offset, MAGICLEN); 612 offset += MAGICLEN; 613 userName = TarUtils.parseName(header, offset, UNAMELEN); 614 offset += UNAMELEN; 615 groupName = TarUtils.parseName(header, offset, GNAMELEN); 616 offset += GNAMELEN; 617 devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN); 618 offset += DEVLEN; 619 devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN); 620 } 621 622 /** 623 * Strips Windows' drive letter as well as any leading slashes, 624 * turns path separators into forward slahes. 625 */ 626 private static String normalizeFileName(String fileName, 627 boolean preserveLeadingSlashes) { 628 String osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 629 630 if (osname != null) { 631 632 // Strip off drive letters! 633 // REVIEW Would a better check be "(File.separator == '\')"? 634 635 if (osname.startsWith("windows")) { 636 if (fileName.length() > 2) { 637 char ch1 = fileName.charAt(0); 638 char ch2 = fileName.charAt(1); 639 640 if (ch2 == ':' 641 && ((ch1 >= 'a' && ch1 <= 'z') 642 || (ch1 >= 'A' && ch1 <= 'Z'))) { 643 fileName = fileName.substring(2); 644 } 645 } 646 } else if (osname.indexOf("netware") > -1) { 647 int colon = fileName.indexOf(':'); 648 if (colon != -1) { 649 fileName = fileName.substring(colon + 1); 650 } 651 } 652 } 653 654 fileName = fileName.replace(File.separatorChar, '/'); 655 656 // No absolute pathnames 657 // Windows (and Posix?) paths can start with "\\NetworkDrive\", 658 // so we loop on starting /'s. 659 while (!preserveLeadingSlashes && fileName.startsWith("/")) { 660 fileName = fileName.substring(1); 661 } 662 return fileName; 663 } 664}