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.io.UncheckedIOException; 024import java.math.BigDecimal; 025import java.nio.file.DirectoryStream; 026import java.nio.file.Files; 027import java.nio.file.LinkOption; 028import java.nio.file.Path; 029import java.nio.file.attribute.BasicFileAttributes; 030import java.nio.file.attribute.DosFileAttributes; 031import java.nio.file.attribute.FileTime; 032import java.nio.file.attribute.PosixFileAttributes; 033import java.time.Instant; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.Comparator; 037import java.util.Date; 038import java.util.HashMap; 039import java.util.List; 040import java.util.Locale; 041import java.util.Map; 042import java.util.Objects; 043import java.util.Set; 044import java.util.regex.Pattern; 045import java.util.stream.Collectors; 046 047import org.apache.commons.compress.archivers.ArchiveEntry; 048import org.apache.commons.compress.archivers.EntryStreamOffsets; 049import org.apache.commons.compress.archivers.zip.ZipEncoding; 050import org.apache.commons.compress.utils.ArchiveUtils; 051import org.apache.commons.compress.utils.IOUtils; 052import org.apache.commons.compress.utils.TimeUtils; 053 054/** 055 * This class represents an entry in a Tar archive. It consists 056 * of the entry's header, as well as the entry's File. Entries 057 * can be instantiated in one of three ways, depending on how 058 * they are to be used. 059 * <p> 060 * TarEntries that are created from the header bytes read from 061 * an archive are instantiated with the {@link TarArchiveEntry#TarArchiveEntry(byte[])} 062 * constructor. These entries will be used when extracting from 063 * or listing the contents of an archive. These entries have their 064 * header filled in using the header bytes. They also set the File 065 * to null, since they reference an archive entry not a file. 066 * </p> 067 * <p> 068 * TarEntries that are created from Files that are to be written 069 * into an archive are instantiated with the {@link TarArchiveEntry#TarArchiveEntry(File)} 070 * or {@link TarArchiveEntry#TarArchiveEntry(Path)} constructor. 071 * These entries have their header filled in using the File's information. 072 * They also keep a reference to the File for convenience when writing entries. 073 * </p> 074 * <p> 075 * Finally, TarEntries can be constructed from nothing but a name. 076 * This allows the programmer to construct the entry by hand, for 077 * instance when only an InputStream is available for writing to 078 * the archive, and the header information is constructed from 079 * other information. In this case the header fields are set to 080 * defaults and the File is set to null. 081 * </p> 082 * <p> 083 * The C structure for a Tar Entry's header is: 084 * </p> 085 * <pre> 086 * struct header { 087 * char name[100]; // TarConstants.NAMELEN - offset 0 088 * char mode[8]; // TarConstants.MODELEN - offset 100 089 * char uid[8]; // TarConstants.UIDLEN - offset 108 090 * char gid[8]; // TarConstants.GIDLEN - offset 116 091 * char size[12]; // TarConstants.SIZELEN - offset 124 092 * char mtime[12]; // TarConstants.MODTIMELEN - offset 136 093 * char chksum[8]; // TarConstants.CHKSUMLEN - offset 148 094 * char linkflag[1]; // - offset 156 095 * char linkname[100]; // TarConstants.NAMELEN - offset 157 096 * // The following fields are only present in new-style POSIX tar archives: 097 * char magic[6]; // TarConstants.MAGICLEN - offset 257 098 * char version[2]; // TarConstants.VERSIONLEN - offset 263 099 * char uname[32]; // TarConstants.UNAMELEN - offset 265 100 * char gname[32]; // TarConstants.GNAMELEN - offset 297 101 * char devmajor[8]; // TarConstants.DEVLEN - offset 329 102 * char devminor[8]; // TarConstants.DEVLEN - offset 337 103 * char prefix[155]; // TarConstants.PREFIXLEN - offset 345 104 * // Used if "name" field is not long enough to hold the path 105 * char pad[12]; // NULs - offset 500 106 * } header; 107 * </pre> 108 * <p> 109 * All unused bytes are set to null. 110 * New-style GNU tar files are slightly different from the above. 111 * For values of size larger than 077777777777L (11 7s) 112 * or uid and gid larger than 07777777L (7 7s) 113 * the sign bit of the first byte is set, and the rest of the 114 * field is the binary representation of the number. 115 * See {@link TarUtils#parseOctalOrBinary(byte[], int, int)}. 116 * <p> 117 * The C structure for a old GNU Tar Entry's header is: 118 * </p> 119 * <pre> 120 * struct oldgnu_header { 121 * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU - offset 0 122 * char atime[12]; // TarConstants.ATIMELEN_GNU - offset 345 123 * char ctime[12]; // TarConstants.CTIMELEN_GNU - offset 357 124 * char offset[12]; // TarConstants.OFFSETLEN_GNU - offset 369 125 * char longnames[4]; // TarConstants.LONGNAMESLEN_GNU - offset 381 126 * char unused_pad2; // TarConstants.PAD2LEN_GNU - offset 385 127 * struct sparse sp[4]; // TarConstants.SPARSELEN_GNU - offset 386 128 * char isextended; // TarConstants.ISEXTENDEDLEN_GNU - offset 482 129 * char realsize[12]; // TarConstants.REALSIZELEN_GNU - offset 483 130 * char unused_pad[17]; // TarConstants.PAD3LEN_GNU - offset 495 131 * }; 132 * </pre> 133 * <p> 134 * Whereas, "struct sparse" is: 135 * </p> 136 * <pre> 137 * struct sparse { 138 * char offset[12]; // offset 0 139 * char numbytes[12]; // offset 12 140 * }; 141 * </pre> 142 * <p> 143 * The C structure for a xstar (Jörg Schilling star) Tar Entry's header is: 144 * </p> 145 * <pre> 146 * struct star_header { 147 * char name[100]; // offset 0 148 * char mode[8]; // offset 100 149 * char uid[8]; // offset 108 150 * char gid[8]; // offset 116 151 * char size[12]; // offset 124 152 * char mtime[12]; // offset 136 153 * char chksum[8]; // offset 148 154 * char typeflag; // offset 156 155 * char linkname[100]; // offset 157 156 * char magic[6]; // offset 257 157 * char version[2]; // offset 263 158 * char uname[32]; // offset 265 159 * char gname[32]; // offset 297 160 * char devmajor[8]; // offset 329 161 * char devminor[8]; // offset 337 162 * char prefix[131]; // offset 345 163 * char atime[12]; // offset 476 164 * char ctime[12]; // offset 488 165 * char mfill[8]; // offset 500 166 * char xmagic[4]; // offset 508 "tar\0" 167 * }; 168 * </pre> 169 * <p> 170 * which is identical to new-style POSIX up to the first 130 bytes of the prefix. 171 * </p> 172 * <p> 173 * The C structure for the xstar-specific parts of a xstar Tar Entry's header is: 174 * </p> 175 * <pre> 176 * struct xstar_in_header { 177 * char fill[345]; // offset 0 Everything before t_prefix 178 * char prefix[1]; // offset 345 Prefix for t_name 179 * char fill2; // offset 346 180 * char fill3[8]; // offset 347 181 * char isextended; // offset 355 182 * struct sparse sp[SIH]; // offset 356 8 x 12 183 * char realsize[12]; // offset 452 Real size for sparse data 184 * char offset[12]; // offset 464 Offset for multivolume data 185 * char atime[12]; // offset 476 186 * char ctime[12]; // offset 488 187 * char mfill[8]; // offset 500 188 * char xmagic[4]; // offset 508 "tar\0" 189 * }; 190 * </pre> 191 * 192 * @NotThreadSafe 193 */ 194public class TarArchiveEntry implements ArchiveEntry, TarConstants, EntryStreamOffsets { 195 196 private static final TarArchiveEntry[] EMPTY_TAR_ARCHIVE_ENTRY_ARRAY = {}; 197 198 /** 199 * Value used to indicate unknown mode, user/groupids, device numbers and modTime when parsing a file in lenient 200 * mode and the archive contains illegal fields. 201 * @since 1.19 202 */ 203 public static final long UNKNOWN = -1L; 204 205 /** Maximum length of a user's name in the tar file */ 206 public static final int MAX_NAMELEN = 31; 207 208 /** Default permissions bits for directories */ 209 public static final int DEFAULT_DIR_MODE = 040755; 210 211 /** Default permissions bits for files */ 212 public static final int DEFAULT_FILE_MODE = 0100644; 213 214 /** 215 * Convert millis to seconds 216 * @deprecated Unused. 217 */ 218 @Deprecated 219 public static final int MILLIS_PER_SECOND = 1000; 220 221 /** 222 * Regular expression pattern for validating values in pax extended header file time fields. 223 * These fields contain two numeric values (seconds and sub-second values) as per this definition: 224 * https://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html#tag_20_92_13_05 225 * <p> 226 * Since they are parsed into long values, maximum length of each is the same as Long.MAX_VALUE 227 * which is 19 digits. 228 * </p> 229 */ 230 private static final Pattern PAX_EXTENDED_HEADER_FILE_TIMES_PATTERN = Pattern.compile("-?\\d{1,19}(?:\\.\\d{1,19})?"); 231 232 private static FileTime fileTimeFromOptionalSeconds(final long seconds) { 233 if (seconds <= 0) { 234 return null; 235 } 236 return TimeUtils.unixTimeToFileTime(seconds); 237 } 238 239 /** 240 * Strips Windows' drive letter as well as any leading slashes, turns path separators into forward slashes. 241 */ 242 private static String normalizeFileName(String fileName, final boolean preserveAbsolutePath) { 243 if (!preserveAbsolutePath) { 244 final String property = System.getProperty("os.name"); 245 if (property != null) { 246 final String osName = property.toLowerCase(Locale.ROOT); 247 248 // Strip off drive letters! 249 // REVIEW Would a better check be "(File.separator == '\')"? 250 251 if (osName.startsWith("windows")) { 252 if (fileName.length() > 2) { 253 final char ch1 = fileName.charAt(0); 254 final char ch2 = fileName.charAt(1); 255 256 if (ch2 == ':' && (ch1 >= 'a' && ch1 <= 'z' || ch1 >= 'A' && ch1 <= 'Z')) { 257 fileName = fileName.substring(2); 258 } 259 } 260 } else if (osName.contains("netware")) { 261 final int colon = fileName.indexOf(':'); 262 if (colon != -1) { 263 fileName = fileName.substring(colon + 1); 264 } 265 } 266 } 267 } 268 269 fileName = fileName.replace(File.separatorChar, '/'); 270 271 // No absolute pathnames 272 // Windows (and Posix?) paths can start with "\\NetworkDrive\", 273 // so we loop on starting /'s. 274 while (!preserveAbsolutePath && fileName.startsWith("/")) { 275 fileName = fileName.substring(1); 276 } 277 return fileName; 278 } 279 280 private static Instant parseInstantFromDecimalSeconds(final String value) throws IOException { 281 // Validate field values to prevent denial of service attacks with BigDecimal values (see JDK-6560193) 282 if (!TarArchiveEntry.PAX_EXTENDED_HEADER_FILE_TIMES_PATTERN.matcher(value).matches()) { 283 throw new IOException("Corrupted PAX header. Time field value is invalid '" + value + "'"); 284 } 285 286 final BigDecimal epochSeconds = new BigDecimal(value); 287 final long seconds = epochSeconds.longValue(); 288 final long nanos = epochSeconds.remainder(BigDecimal.ONE).movePointRight(9).longValue(); 289 return Instant.ofEpochSecond(seconds, nanos); 290 } 291 292 /** The entry's name. */ 293 private String name = ""; 294 295 /** Whether to allow leading slashes or drive names inside the name */ 296 private final boolean preserveAbsolutePath; 297 298 /** The entry's permission mode. */ 299 private int mode; 300 301 /** The entry's user id. */ 302 private long userId; 303 304 /** The entry's group id. */ 305 private long groupId; 306 307 /** The entry's size. */ 308 private long size; 309 /** 310 * The entry's modification time. 311 * Corresponds to the POSIX {@code mtime} attribute. 312 */ 313 private FileTime mTime; 314 315 /** 316 * The entry's status change time. 317 * Corresponds to the POSIX {@code ctime} attribute. 318 * 319 * @since 1.22 320 */ 321 private FileTime cTime; 322 323 /** 324 * The entry's last access time. 325 * Corresponds to the POSIX {@code atime} attribute. 326 * 327 * @since 1.22 328 */ 329 private FileTime aTime; 330 331 /** 332 * The entry's creation time. 333 * Corresponds to the POSIX {@code birthtime} attribute. 334 * 335 * @since 1.22 336 */ 337 private FileTime birthTime; 338 339 /** If the header checksum is reasonably correct. */ 340 private boolean checkSumOK; 341 342 /** The entry's link flag. */ 343 private byte linkFlag; 344 345 /** The entry's link name. */ 346 private String linkName = ""; 347 348 /** The entry's magic tag. */ 349 private String magic = MAGIC_POSIX; 350 351 /** The version of the format */ 352 private String version = VERSION_POSIX; 353 354 /** The entry's user name. */ 355 private String userName; 356 357 /** The entry's group name. */ 358 private String groupName = ""; 359 360 /** The entry's major device number. */ 361 private int devMajor; 362 363 /** The entry's minor device number. */ 364 private int devMinor; 365 366 /** The sparse headers in tar */ 367 private List<TarArchiveStructSparse> sparseHeaders; 368 369 /** If an extension sparse header follows. */ 370 private boolean isExtended; 371 372 /** The entry's real size in case of a sparse file. */ 373 private long realSize; 374 375 /** is this entry a GNU sparse entry using one of the PAX formats? */ 376 private boolean paxGNUSparse; 377 378 /** is this entry a GNU sparse entry using 1.X PAX formats? 379 * the sparse headers of 1.x PAX Format is stored in file data block */ 380 private boolean paxGNU1XSparse; 381 382 /** is this entry a star sparse entry using the PAX header? */ 383 private boolean starSparse; 384 385 /** The entry's file reference */ 386 private final Path file; 387 388 /** The entry's file linkOptions*/ 389 private final LinkOption[] linkOptions; 390 391 /** Extra, user supplied pax headers */ 392 private final Map<String,String> extraPaxHeaders = new HashMap<>(); 393 394 private long dataOffset = EntryStreamOffsets.OFFSET_UNKNOWN; 395 396 /** 397 * Constructs an empty entry and prepares the header values. 398 */ 399 private TarArchiveEntry(final boolean preserveAbsolutePath) { 400 String user = System.getProperty("user.name", ""); 401 402 if (user.length() > MAX_NAMELEN) { 403 user = user.substring(0, MAX_NAMELEN); 404 } 405 406 this.userName = user; 407 this.file = null; 408 this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS; 409 this.preserveAbsolutePath = preserveAbsolutePath; 410 } 411 412 /** 413 * Constructs an entry from an archive's header bytes. File is set 414 * to null. 415 * 416 * @param headerBuf The header bytes from a tar archive entry. 417 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 418 */ 419 public TarArchiveEntry(final byte[] headerBuf) { 420 this(false); 421 parseTarHeader(headerBuf); 422 } 423 424 /** 425 * Constructs an entry from an archive's header bytes. File is set 426 * to null. 427 * 428 * @param headerBuf The header bytes from a tar archive entry. 429 * @param encoding encoding to use for file names 430 * @since 1.4 431 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 432 * @throws IOException on error 433 */ 434 public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding) 435 throws IOException { 436 this(headerBuf, encoding, false); 437 } 438 439 /** 440 * Constructs an entry from an archive's header bytes. File is set 441 * to null. 442 * 443 * @param headerBuf The header bytes from a tar archive entry. 444 * @param encoding encoding to use for file names 445 * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be 446 * ignored and the fields set to {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead. 447 * @since 1.19 448 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 449 * @throws IOException on error 450 */ 451 public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient) 452 throws IOException { 453 this(Collections.emptyMap(), headerBuf, encoding, lenient); 454 } 455 456 /** 457 * Constructs an entry from an archive's header bytes for random access tar. File is set to null. 458 * @param headerBuf the header bytes from a tar archive entry. 459 * @param encoding encoding to use for file names. 460 * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be 461 * ignored and the fields set to {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead. 462 * @param dataOffset position of the entry data in the random access file. 463 * @since 1.21 464 * @throws IllegalArgumentException if any of the numeric fields have an invalid format. 465 * @throws IOException on error. 466 */ 467 public TarArchiveEntry(final byte[] headerBuf, final ZipEncoding encoding, final boolean lenient, 468 final long dataOffset) throws IOException { 469 this(headerBuf, encoding, lenient); 470 setDataOffset(dataOffset); 471 } 472 473 /** 474 * Constructs an entry for a file. File is set to file, and the 475 * header is constructed from information from the file. 476 * The name is set from the normalized file path. 477 * 478 * <p>The entry's name will be the value of the {@code file}'s 479 * path with all file separators replaced by forward slashes and 480 * leading slashes as well as Windows drive letters stripped. The 481 * name will end in a slash if the {@code file} represents a 482 * directory.</p> 483 * 484 * <p>Note: Since 1.21 this internally uses the same code as the 485 * TarArchiveEntry constructors with a {@link Path} as parameter. 486 * But all thrown exceptions are ignored. If handling those 487 * exceptions is needed consider switching to the path constructors.</p> 488 * 489 * @param file The file that the entry represents. 490 */ 491 public TarArchiveEntry(final File file) { 492 this(file, file.getPath()); 493 } 494 495 /** 496 * Constructs an entry for a file. File is set to file, and the 497 * header is constructed from information from the file. 498 * 499 * <p>The entry's name will be the value of the {@code fileName} 500 * argument with all file separators replaced by forward slashes 501 * and leading slashes as well as Windows drive letters stripped. 502 * The name will end in a slash if the {@code file} represents a 503 * directory.</p> 504 * 505 * <p>Note: Since 1.21 this internally uses the same code as the 506 * TarArchiveEntry constructors with a {@link Path} as parameter. 507 * But all thrown exceptions are ignored. If handling those 508 * exceptions is needed consider switching to the path constructors.</p> 509 * 510 * @param file The file that the entry represents. 511 * @param fileName the name to be used for the entry. 512 */ 513 public TarArchiveEntry(final File file, final String fileName) { 514 final String normalizedName = normalizeFileName(fileName, false); 515 this.file = file.toPath(); 516 this.linkOptions = IOUtils.EMPTY_LINK_OPTIONS; 517 518 try { 519 readFileMode(this.file, normalizedName); 520 } catch (final IOException e) { 521 // Ignore exceptions from NIO for backwards compatibility 522 // Fallback to get size of file if it's no directory to the old file api 523 if (!file.isDirectory()) { 524 this.size = file.length(); 525 } 526 } 527 528 this.userName = ""; 529 try { 530 readOsSpecificProperties(this.file); 531 } catch (final IOException e) { 532 // Ignore exceptions from NIO for backwards compatibility 533 // Fallback to get the last modified date of the file from the old file api 534 this.mTime = FileTime.fromMillis(file.lastModified()); 535 } 536 preserveAbsolutePath = false; 537 } 538 539 /** 540 * Constructs an entry from an archive's header bytes. File is set to null. 541 * 542 * @param globalPaxHeaders the parsed global PAX headers, or null if this is the first one. 543 * @param headerBuf The header bytes from a tar archive entry. 544 * @param encoding encoding to use for file names 545 * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be 546 * ignored and the fields set to {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead. 547 * @since 1.22 548 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 549 * @throws IOException on error 550 */ 551 public TarArchiveEntry(final Map<String, String> globalPaxHeaders, final byte[] headerBuf, 552 final ZipEncoding encoding, final boolean lenient) throws IOException { 553 this(false); 554 parseTarHeader(globalPaxHeaders, headerBuf, encoding, false, lenient); 555 } 556 557 /** 558 * Constructs an entry from an archive's header bytes for random access tar. File is set to null. 559 * @param globalPaxHeaders the parsed global PAX headers, or null if this is the first one. 560 * @param headerBuf the header bytes from a tar archive entry. 561 * @param encoding encoding to use for file names. 562 * @param lenient when set to true illegal values for group/userid, mode, device numbers and timestamp will be 563 * ignored and the fields set to {@link #UNKNOWN}. When set to false such illegal fields cause an exception instead. 564 * @param dataOffset position of the entry data in the random access file. 565 * @since 1.22 566 * @throws IllegalArgumentException if any of the numeric fields have an invalid format. 567 * @throws IOException on error. 568 */ 569 public TarArchiveEntry(final Map<String, String> globalPaxHeaders, final byte[] headerBuf, 570 final ZipEncoding encoding, final boolean lenient, final long dataOffset) throws IOException { 571 this(globalPaxHeaders, headerBuf, encoding, lenient); 572 setDataOffset(dataOffset); 573 } 574 575 /** 576 * Constructs an entry for a file. File is set to file, and the 577 * header is constructed from information from the file. 578 * The name is set from the normalized file path. 579 * 580 * <p>The entry's name will be the value of the {@code file}'s 581 * path with all file separators replaced by forward slashes and 582 * leading slashes as well as Windows drive letters stripped. The 583 * name will end in a slash if the {@code file} represents a 584 * directory.</p> 585 * 586 * @param file The file that the entry represents. 587 * @throws IOException if an I/O error occurs 588 * @since 1.21 589 */ 590 public TarArchiveEntry(final Path file) throws IOException { 591 this(file, file.toString()); 592 } 593 594 /** 595 * Constructs an entry for a file. File is set to file, and the 596 * header is constructed from information from the file. 597 * 598 * <p>The entry's name will be the value of the {@code fileName} 599 * argument with all file separators replaced by forward slashes 600 * and leading slashes as well as Windows drive letters stripped. 601 * The name will end in a slash if the {@code file} represents a 602 * directory.</p> 603 * 604 * @param file The file that the entry represents. 605 * @param fileName the name to be used for the entry. 606 * @param linkOptions options indicating how symbolic links are handled. 607 * @throws IOException if an I/O error occurs 608 * @since 1.21 609 */ 610 public TarArchiveEntry(final Path file, final String fileName, final LinkOption... linkOptions) throws IOException { 611 final String normalizedName = normalizeFileName(fileName, false); 612 this.file = file; 613 this.linkOptions = linkOptions == null ? IOUtils.EMPTY_LINK_OPTIONS : linkOptions; 614 615 readFileMode(file, normalizedName, linkOptions); 616 617 this.userName = ""; 618 readOsSpecificProperties(file); 619 preserveAbsolutePath = false; 620 } 621 622 /** 623 * Constructs an entry with only a name. This allows the programmer 624 * to construct the entry's header "by hand". File is set to null. 625 * 626 * <p>The entry's name will be the value of the {@code name} 627 * argument with all file separators replaced by forward slashes 628 * and leading slashes as well as Windows drive letters stripped.</p> 629 * 630 * @param name the entry name 631 */ 632 public TarArchiveEntry(final String name) { 633 this(name, false); 634 } 635 636 /** 637 * Constructs an entry with only a name. This allows the programmer 638 * to construct the entry's header "by hand". File is set to null. 639 * 640 * <p>The entry's name will be the value of the {@code name} 641 * argument with all file separators replaced by forward slashes. 642 * Leading slashes and Windows drive letters are stripped if 643 * {@code preserveAbsolutePath} is {@code false}.</p> 644 * 645 * @param name the entry name 646 * @param preserveAbsolutePath whether to allow leading slashes 647 * or drive letters in the name. 648 * 649 * @since 1.1 650 */ 651 public TarArchiveEntry(String name, final boolean preserveAbsolutePath) { 652 this(preserveAbsolutePath); 653 654 name = normalizeFileName(name, preserveAbsolutePath); 655 final boolean isDir = name.endsWith("/"); 656 657 this.name = name; 658 this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE; 659 this.linkFlag = isDir ? LF_DIR : LF_NORMAL; 660 this.mTime = FileTime.from(Instant.now()); 661 this.userName = ""; 662 } 663 664 /** 665 * Constructs an entry with a name and a link flag. 666 * 667 * <p>The entry's name will be the value of the {@code name} 668 * argument with all file separators replaced by forward slashes 669 * and leading slashes as well as Windows drive letters 670 * stripped.</p> 671 * 672 * @param name the entry name 673 * @param linkFlag the entry link flag. 674 */ 675 public TarArchiveEntry(final String name, final byte linkFlag) { 676 this(name, linkFlag, false); 677 } 678 679 /** 680 * Constructs an entry with a name and a link flag. 681 * 682 * <p>The entry's name will be the value of the {@code name} 683 * argument with all file separators replaced by forward slashes. 684 * Leading slashes and Windows drive letters are stripped if 685 * {@code preserveAbsolutePath} is {@code false}.</p> 686 * 687 * @param name the entry name 688 * @param linkFlag the entry link flag. 689 * @param preserveAbsolutePath whether to allow leading slashes 690 * or drive letters in the name. 691 * 692 * @since 1.5 693 */ 694 public TarArchiveEntry(final String name, final byte linkFlag, final boolean preserveAbsolutePath) { 695 this(name, preserveAbsolutePath); 696 this.linkFlag = linkFlag; 697 if (linkFlag == LF_GNUTYPE_LONGNAME) { 698 magic = MAGIC_GNU; 699 version = VERSION_GNU_SPACE; 700 } 701 } 702 703 /** 704 * add a PAX header to this entry. If the header corresponds to an existing field in the entry, 705 * that field will be set; otherwise the header will be added to the extraPaxHeaders Map 706 * @param name The full name of the header to set. 707 * @param value value of header. 708 * @since 1.15 709 */ 710 public void addPaxHeader(final String name, final String value) { 711 try { 712 processPaxHeader(name, value); 713 } catch (final IOException ex) { 714 throw new IllegalArgumentException("Invalid input", ex); 715 } 716 } 717 718 /** 719 * clear all extra PAX headers. 720 * @since 1.15 721 */ 722 public void clearExtraPaxHeaders() { 723 extraPaxHeaders.clear(); 724 } 725 726 /** 727 * Determine if the two entries are equal. Equality is determined 728 * by the header names being equal. 729 * 730 * @param it Entry to be checked for equality. 731 * @return True if the entries are equal. 732 */ 733 @Override 734 public boolean equals(final Object it) { 735 if (it == null || getClass() != it.getClass()) { 736 return false; 737 } 738 return equals((TarArchiveEntry) it); 739 } 740 741 /** 742 * Determine if the two entries are equal. Equality is determined 743 * by the header names being equal. 744 * 745 * @param it Entry to be checked for equality. 746 * @return True if the entries are equal. 747 */ 748 public boolean equals(final TarArchiveEntry it) { 749 return it != null && getName().equals(it.getName()); 750 } 751 752 /** 753 * Evaluate an entry's header format from a header buffer. 754 * 755 * @param header The tar entry header buffer to evaluate the format for. 756 * @return format type 757 */ 758 private int evaluateType(final Map<String, String> globalPaxHeaders, final byte[] header) { 759 if (ArchiveUtils.matchAsciiBuffer(MAGIC_GNU, header, MAGIC_OFFSET, MAGICLEN)) { 760 return FORMAT_OLDGNU; 761 } 762 if (ArchiveUtils.matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, MAGICLEN)) { 763 if (isXstar(globalPaxHeaders, header)) { 764 return FORMAT_XSTAR; 765 } 766 return FORMAT_POSIX; 767 } 768 return 0; 769 } 770 771 private int fill(final byte value, final int offset, final byte[] outbuf, final int length) { 772 for (int i = 0; i < length; i++) { 773 outbuf[offset + i] = value; 774 } 775 return offset + length; 776 } 777 778 private int fill(final int value, final int offset, final byte[] outbuf, final int length) { 779 return fill((byte) value, offset, outbuf, length); 780 } 781 782 void fillGNUSparse0xData(final Map<String, String> headers) { 783 paxGNUSparse = true; 784 realSize = Integer.parseInt(headers.get(TarGnuSparseKeys.SIZE)); 785 if (headers.containsKey(TarGnuSparseKeys.NAME)) { 786 // version 0.1 787 name = headers.get(TarGnuSparseKeys.NAME); 788 } 789 } 790 791 void fillGNUSparse1xData(final Map<String, String> headers) throws IOException { 792 paxGNUSparse = true; 793 paxGNU1XSparse = true; 794 if (headers.containsKey(TarGnuSparseKeys.NAME)) { 795 name = headers.get(TarGnuSparseKeys.NAME); 796 } 797 if (headers.containsKey(TarGnuSparseKeys.REALSIZE)) { 798 try { 799 realSize = Integer.parseInt(headers.get(TarGnuSparseKeys.REALSIZE)); 800 } catch (final NumberFormatException ex) { 801 throw new IOException("Corrupted TAR archive. GNU.sparse.realsize header for " 802 + name + " contains non-numeric value"); 803 } 804 } 805 } 806 807 void fillStarSparseData(final Map<String, String> headers) throws IOException { 808 starSparse = true; 809 if (headers.containsKey("SCHILY.realsize")) { 810 try { 811 realSize = Long.parseLong(headers.get("SCHILY.realsize")); 812 } catch (final NumberFormatException ex) { 813 throw new IOException("Corrupted TAR archive. SCHILY.realsize header for " 814 + name + " contains non-numeric value"); 815 } 816 } 817 } 818 819 /** 820 * Gets this entry's creation time. 821 * 822 * @since 1.22 823 * @return This entry's computed creation time. 824 */ 825 public FileTime getCreationTime() { 826 return birthTime; 827 } 828 829 /** 830 * {@inheritDoc} 831 * @since 1.21 832 */ 833 @Override 834 public long getDataOffset() { 835 return dataOffset; 836 } 837 838 /** 839 * Gets this entry's major device number. 840 * 841 * @return This entry's major device number. 842 * @since 1.4 843 */ 844 public int getDevMajor() { 845 return devMajor; 846 } 847 848 /** 849 * Gets this entry's minor device number. 850 * 851 * @return This entry's minor device number. 852 * @since 1.4 853 */ 854 public int getDevMinor() { 855 return devMinor; 856 } 857 858 /** 859 * If this entry represents a file, and the file is a directory, return 860 * an array of TarEntries for this entry's children. 861 * 862 * <p>This method is only useful for entries created from a {@code 863 * File} or {@code Path} but not for entries read from an archive.</p> 864 * 865 * @return An array of TarEntry's for this entry's children. 866 */ 867 public TarArchiveEntry[] getDirectoryEntries() { 868 if (file == null || !isDirectory()) { 869 return EMPTY_TAR_ARCHIVE_ENTRY_ARRAY; 870 } 871 872 final List<TarArchiveEntry> entries = new ArrayList<>(); 873 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(file)) { 874 for (final Path p : dirStream) { 875 entries.add(new TarArchiveEntry(p)); 876 } 877 } catch (final IOException e) { 878 return EMPTY_TAR_ARCHIVE_ENTRY_ARRAY; 879 } 880 return entries.toArray(EMPTY_TAR_ARCHIVE_ENTRY_ARRAY); 881 } 882 883 /** 884 * get named extra PAX header 885 * @param name The full name of an extended PAX header to retrieve 886 * @return The value of the header, if any. 887 * @since 1.15 888 */ 889 public String getExtraPaxHeader(final String name) { 890 return extraPaxHeaders.get(name); 891 } 892 893 /** 894 * get extra PAX Headers 895 * @return read-only map containing any extra PAX Headers 896 * @since 1.15 897 */ 898 public Map<String, String> getExtraPaxHeaders() { 899 return Collections.unmodifiableMap(extraPaxHeaders); 900 } 901 902 /** 903 * Gets this entry's file. 904 * 905 * <p>This method is only useful for entries created from a {@code 906 * File} or {@code Path} but not for entries read from an archive.</p> 907 * 908 * @return this entry's file or null if the entry was not created from a file. 909 */ 910 public File getFile() { 911 if (file == null) { 912 return null; 913 } 914 return file.toFile(); 915 } 916 917 /** 918 * Gets this entry's group id. 919 * 920 * @return This entry's group id. 921 * @deprecated use #getLongGroupId instead as group ids can be 922 * bigger than {@link Integer#MAX_VALUE} 923 */ 924 @Deprecated 925 public int getGroupId() { 926 return (int) (groupId & 0xffffffff); 927 } 928 929 /** 930 * Gets this entry's group name. 931 * 932 * @return This entry's group name. 933 */ 934 public String getGroupName() { 935 return groupName; 936 } 937 938 /** 939 * Gets this entry's last access time. 940 * 941 * @since 1.22 942 * @return This entry's last access time. 943 */ 944 public FileTime getLastAccessTime() { 945 return aTime; 946 } 947 948 /** 949 * Gets this entry's modification time. 950 * This is equivalent to {@link TarArchiveEntry#getLastModifiedTime()}, but precision is truncated to milliseconds. 951 * 952 * @return This entry's modification time. 953 * @see TarArchiveEntry#getLastModifiedTime() 954 */ 955 @Override 956 public Date getLastModifiedDate() { 957 return getModTime(); 958 } 959 960 /** 961 * Gets this entry's modification time. 962 * 963 * @since 1.22 964 * @return This entry's modification time. 965 */ 966 public FileTime getLastModifiedTime() { 967 return mTime; 968 } 969 970 /** 971 * Gets this entry's link flag. 972 * 973 * @return this entry's link flag. 974 * @since 1.23 975 */ 976 public byte getLinkFlag() { 977 return this.linkFlag; 978 } 979 980 /** 981 * Gets this entry's link name. 982 * 983 * @return This entry's link name. 984 */ 985 public String getLinkName() { 986 return linkName; 987 } 988 989 /** 990 * Gets this entry's group id. 991 * 992 * @since 1.10 993 * @return This entry's group id. 994 */ 995 public long getLongGroupId() { 996 return groupId; 997 } 998 999 /** 1000 * Gets this entry's user id. 1001 * 1002 * @return This entry's user id. 1003 * @since 1.10 1004 */ 1005 public long getLongUserId() { 1006 return userId; 1007 } 1008 1009 /** 1010 * Gets this entry's mode. 1011 * 1012 * @return This entry's mode. 1013 */ 1014 public int getMode() { 1015 return mode; 1016 } 1017 1018 /** 1019 * Gets this entry's modification time. 1020 * This is equivalent to {@link TarArchiveEntry#getLastModifiedTime()}, but precision is truncated to milliseconds. 1021 * 1022 * @return This entry's modification time. 1023 * @see TarArchiveEntry#getLastModifiedTime() 1024 */ 1025 public Date getModTime() { 1026 return TimeUtils.toDate(mTime); 1027 } 1028 1029 /** 1030 * Gets this entry's name. 1031 * 1032 * <p>This method returns the raw name as it is stored inside of the archive.</p> 1033 * 1034 * @return This entry's name. 1035 */ 1036 @Override 1037 public String getName() { 1038 return name; 1039 } 1040 1041 /** 1042 * Gets this entry's sparse headers ordered by offset with all empty sparse sections at the start filtered out. 1043 * 1044 * @return immutable list of this entry's sparse headers, never null 1045 * @since 1.21 1046 * @throws IOException if the list of sparse headers contains blocks that overlap 1047 */ 1048 public List<TarArchiveStructSparse> getOrderedSparseHeaders() throws IOException { 1049 if (sparseHeaders == null || sparseHeaders.isEmpty()) { 1050 return Collections.emptyList(); 1051 } 1052 final List<TarArchiveStructSparse> orderedAndFiltered = sparseHeaders.stream() 1053 .filter(s -> s.getOffset() > 0 || s.getNumbytes() > 0) 1054 .sorted(Comparator.comparingLong(TarArchiveStructSparse::getOffset)) 1055 .collect(Collectors.toList()); 1056 1057 final int numberOfHeaders = orderedAndFiltered.size(); 1058 for (int i = 0; i < numberOfHeaders; i++) { 1059 final TarArchiveStructSparse str = orderedAndFiltered.get(i); 1060 if (i + 1 < numberOfHeaders 1061 && str.getOffset() + str.getNumbytes() > orderedAndFiltered.get(i + 1).getOffset()) { 1062 throw new IOException("Corrupted TAR archive. Sparse blocks for " 1063 + getName() + " overlap each other."); 1064 } 1065 if (str.getOffset() + str.getNumbytes() < 0) { 1066 // integer overflow? 1067 throw new IOException("Unreadable TAR archive. Offset and numbytes for sparse block in " 1068 + getName() + " too large."); 1069 } 1070 } 1071 if (!orderedAndFiltered.isEmpty()) { 1072 final TarArchiveStructSparse last = orderedAndFiltered.get(numberOfHeaders - 1); 1073 if (last.getOffset() + last.getNumbytes() > getRealSize()) { 1074 throw new IOException("Corrupted TAR archive. Sparse block extends beyond real size of the entry"); 1075 } 1076 } 1077 1078 return orderedAndFiltered; 1079 } 1080 1081 /** 1082 * Gets this entry's file. 1083 * 1084 * <p>This method is only useful for entries created from a {@code 1085 * File} or {@code Path} but not for entries read from an archive.</p> 1086 * 1087 * @return this entry's file or null if the entry was not created from a file. 1088 * @since 1.21 1089 */ 1090 public Path getPath() { 1091 return file; 1092 } 1093 1094 /** 1095 * Gets this entry's real file size in case of a sparse file. 1096 * 1097 * <p>This is the size a file would take on disk if the entry was expanded.</p> 1098 * 1099 * <p>If the file is not a sparse file, return size instead of realSize.</p> 1100 * 1101 * @return This entry's real file size, if the file is not a sparse file, return size instead of realSize. 1102 */ 1103 public long getRealSize() { 1104 if (!isSparse()) { 1105 return getSize(); 1106 } 1107 return realSize; 1108 } 1109 1110 /** 1111 * Gets this entry's file size. 1112 * 1113 * <p>This is the size the entry's data uses inside the archive. Usually this is the same as {@link 1114 * #getRealSize}, but it doesn't take the "holes" into account when the entry represents a sparse file. 1115 * 1116 * @return This entry's file size. 1117 */ 1118 @Override 1119 public long getSize() { 1120 return size; 1121 } 1122 1123 /** 1124 * Gets this entry's sparse headers 1125 * 1126 * @return This entry's sparse headers 1127 * @since 1.20 1128 */ 1129 public List<TarArchiveStructSparse> getSparseHeaders() { 1130 return sparseHeaders; 1131 } 1132 1133 /** 1134 * Gets this entry's status change time. 1135 * 1136 * @since 1.22 1137 * @return This entry's status change time. 1138 */ 1139 public FileTime getStatusChangeTime() { 1140 return cTime; 1141 } 1142 1143 /** 1144 * Gets this entry's user id. 1145 * 1146 * @return This entry's user id. 1147 * @deprecated use #getLongUserId instead as user ids can be 1148 * bigger than {@link Integer#MAX_VALUE} 1149 */ 1150 @Deprecated 1151 public int getUserId() { 1152 return (int) (userId & 0xffffffff); 1153 } 1154 1155 /** 1156 * Gets this entry's user name. 1157 * 1158 * @return This entry's user name. 1159 */ 1160 public String getUserName() { 1161 return userName; 1162 } 1163 1164 /** 1165 * Hashcodes are based on entry names. 1166 * 1167 * @return the entry hash code 1168 */ 1169 @Override 1170 public int hashCode() { 1171 return getName().hashCode(); 1172 } 1173 1174 /** 1175 * Check if this is a block device entry. 1176 * 1177 * @since 1.2 1178 * @return whether this is a block device 1179 */ 1180 public boolean isBlockDevice() { 1181 return linkFlag == LF_BLK; 1182 } 1183 1184 /** 1185 * Check if this is a character device entry. 1186 * 1187 * @since 1.2 1188 * @return whether this is a character device 1189 */ 1190 public boolean isCharacterDevice() { 1191 return linkFlag == LF_CHR; 1192 } 1193 1194 /** 1195 * Gets this entry's checksum status. 1196 * 1197 * @return if the header checksum is reasonably correct 1198 * @see TarUtils#verifyCheckSum(byte[]) 1199 * @since 1.5 1200 */ 1201 public boolean isCheckSumOK() { 1202 return checkSumOK; 1203 } 1204 1205 /** 1206 * Determine if the given entry is a descendant of this entry. 1207 * Descendancy is determined by the name of the descendant 1208 * starting with this entry's name. 1209 * 1210 * @param desc Entry to be checked as a descendent of this. 1211 * @return True if entry is a descendant of this. 1212 */ 1213 public boolean isDescendent(final TarArchiveEntry desc) { 1214 return desc.getName().startsWith(getName()); 1215 } 1216 1217 /** 1218 * Return whether or not this entry represents a directory. 1219 * 1220 * @return True if this entry is a directory. 1221 */ 1222 @Override 1223 public boolean isDirectory() { 1224 if (file != null) { 1225 return Files.isDirectory(file, linkOptions); 1226 } 1227 1228 if (linkFlag == LF_DIR) { 1229 return true; 1230 } 1231 1232 return !isPaxHeader() && !isGlobalPaxHeader() && getName().endsWith("/"); 1233 } 1234 1235 /** 1236 * Indicates in case of an oldgnu sparse file if an extension 1237 * sparse header follows. 1238 * 1239 * @return true if an extension oldgnu sparse header follows. 1240 */ 1241 public boolean isExtended() { 1242 return isExtended; 1243 } 1244 1245 /** 1246 * Check if this is a FIFO (pipe) entry. 1247 * 1248 * @since 1.2 1249 * @return whether this is a FIFO entry 1250 */ 1251 public boolean isFIFO() { 1252 return linkFlag == LF_FIFO; 1253 } 1254 1255 /** 1256 * Check if this is a "normal file" 1257 * 1258 * @since 1.2 1259 * @return whether this is a "normal file" 1260 */ 1261 public boolean isFile() { 1262 if (file != null) { 1263 return Files.isRegularFile(file, linkOptions); 1264 } 1265 if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) { 1266 return true; 1267 } 1268 return !getName().endsWith("/"); 1269 } 1270 1271 /** 1272 * Check if this is a Pax header. 1273 * 1274 * @return {@code true} if this is a Pax header. 1275 * 1276 * @since 1.1 1277 */ 1278 public boolean isGlobalPaxHeader() { 1279 return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER; 1280 } 1281 1282 /** 1283 * Indicate if this entry is a GNU long linkname block 1284 * 1285 * @return true if this is a long name extension provided by GNU tar 1286 */ 1287 public boolean isGNULongLinkEntry() { 1288 return linkFlag == LF_GNUTYPE_LONGLINK; 1289 } 1290 1291 /** 1292 * Indicate if this entry is a GNU long name block 1293 * 1294 * @return true if this is a long name extension provided by GNU tar 1295 */ 1296 public boolean isGNULongNameEntry() { 1297 return linkFlag == LF_GNUTYPE_LONGNAME; 1298 } 1299 1300 /** 1301 * Indicate if this entry is a GNU sparse block. 1302 * 1303 * @return true if this is a sparse extension provided by GNU tar 1304 */ 1305 public boolean isGNUSparse() { 1306 return isOldGNUSparse() || isPaxGNUSparse(); 1307 } 1308 1309 private boolean isInvalidPrefix(final byte[] header) { 1310 // prefix[130] is guaranteed to be '\0' with XSTAR/XUSTAR 1311 if (header[XSTAR_PREFIX_OFFSET + 130] != 0) { 1312 // except when typeflag is 'M' 1313 if (header[LF_OFFSET] != LF_MULTIVOLUME) { 1314 return true; 1315 } 1316 // We come only here if we try to read in a GNU/xstar/xustar multivolume archive starting past volume #0 1317 // As of 1.22, commons-compress does not support multivolume tar archives. 1318 // If/when it does, this should work as intended. 1319 if ((header[XSTAR_MULTIVOLUME_OFFSET] & 0x80) == 0 1320 && header[XSTAR_MULTIVOLUME_OFFSET + 11] != ' ') { 1321 return true; 1322 } 1323 } 1324 return false; 1325 } 1326 1327 private boolean isInvalidXtarTime(final byte[] buffer, final int offset, final int length) { 1328 // If atime[0]...atime[10] or ctime[0]...ctime[10] is not a POSIX octal number it cannot be 'xstar'. 1329 if ((buffer[offset] & 0x80) == 0) { 1330 final int lastIndex = length - 1; 1331 for (int i = 0; i < lastIndex; i++) { 1332 final byte b = buffer[offset + i]; 1333 if (b < '0' || b > '7') { 1334 return true; 1335 } 1336 } 1337 // Check for both POSIX compliant end of number characters if not using base 256 1338 final byte b = buffer[offset + lastIndex]; 1339 if (b != ' ' && b != 0) { 1340 return true; 1341 } 1342 } 1343 return false; 1344 } 1345 1346 /** 1347 * Check if this is a link entry. 1348 * 1349 * @since 1.2 1350 * @return whether this is a link entry 1351 */ 1352 public boolean isLink() { 1353 return linkFlag == LF_LINK; 1354 } 1355 1356 /** 1357 * Indicate if this entry is a GNU or star sparse block using the 1358 * oldgnu format. 1359 * 1360 * @return true if this is a sparse extension provided by GNU tar or star 1361 * @since 1.11 1362 */ 1363 public boolean isOldGNUSparse() { 1364 return linkFlag == LF_GNUTYPE_SPARSE; 1365 } 1366 1367 /** 1368 * Gets if this entry is a sparse file with 1.X PAX Format or not 1369 * 1370 * @return True if this entry is a sparse file with 1.X PAX Format 1371 * @since 1.20 1372 */ 1373 public boolean isPaxGNU1XSparse() { 1374 return paxGNU1XSparse; 1375 } 1376 1377 /** 1378 * Indicate if this entry is a GNU sparse block using one of the 1379 * PAX formats. 1380 * 1381 * @return true if this is a sparse extension provided by GNU tar 1382 * @since 1.11 1383 */ 1384 public boolean isPaxGNUSparse() { 1385 return paxGNUSparse; 1386 } 1387 1388 /** 1389 * Check if this is a Pax header. 1390 * 1391 * @return {@code true} if this is a Pax header. 1392 * 1393 * @since 1.1 1394 * 1395 */ 1396 public boolean isPaxHeader() { 1397 return linkFlag == LF_PAX_EXTENDED_HEADER_LC 1398 || linkFlag == LF_PAX_EXTENDED_HEADER_UC; 1399 } 1400 1401 /** 1402 * Check whether this is a sparse entry. 1403 * 1404 * @return whether this is a sparse entry 1405 * @since 1.11 1406 */ 1407 public boolean isSparse() { 1408 return isGNUSparse() || isStarSparse(); 1409 } 1410 1411 /** 1412 * Indicate if this entry is a star sparse block using PAX headers. 1413 * 1414 * @return true if this is a sparse extension provided by star 1415 * @since 1.11 1416 */ 1417 public boolean isStarSparse() { 1418 return starSparse; 1419 } 1420 1421 /** 1422 * {@inheritDoc} 1423 * @since 1.21 1424 */ 1425 @Override 1426 public boolean isStreamContiguous() { 1427 return true; 1428 } 1429 1430 /** 1431 * Check if this is a symbolic link entry. 1432 * 1433 * @since 1.2 1434 * @return whether this is a symbolic link 1435 */ 1436 public boolean isSymbolicLink() { 1437 return linkFlag == LF_SYMLINK; 1438 } 1439 1440 /** 1441 * Check for XSTAR / XUSTAR format. 1442 * 1443 * Use the same logic found in star version 1.6 in {@code header.c}, function {@code isxmagic(TCB *ptb)}. 1444 */ 1445 private boolean isXstar(final Map<String, String> globalPaxHeaders, final byte[] header) { 1446 // Check if this is XSTAR 1447 if (ArchiveUtils.matchAsciiBuffer(MAGIC_XSTAR, header, XSTAR_MAGIC_OFFSET, XSTAR_MAGIC_LEN)) { 1448 return true; 1449 } 1450 1451 /* 1452 If SCHILY.archtype is present in the global PAX header, we can use it to identify the type of archive. 1453 1454 Possible values for XSTAR: 1455 - xustar: 'xstar' format without "tar" signature at header offset 508. 1456 - exustar: 'xustar' format variant that always includes x-headers and g-headers. 1457 */ 1458 final String archType = globalPaxHeaders.get("SCHILY.archtype"); 1459 if (archType != null) { 1460 return "xustar".equals(archType) || "exustar".equals(archType); 1461 } 1462 1463 // Check if this is XUSTAR 1464 if (isInvalidPrefix(header)) { 1465 return false; 1466 } 1467 if (isInvalidXtarTime(header, XSTAR_ATIME_OFFSET, ATIMELEN_XSTAR)) { 1468 return false; 1469 } 1470 if (isInvalidXtarTime(header, XSTAR_CTIME_OFFSET, CTIMELEN_XSTAR)) { 1471 return false; 1472 } 1473 1474 return true; 1475 } 1476 1477 private long parseOctalOrBinary(final byte[] header, final int offset, final int length, final boolean lenient) { 1478 if (lenient) { 1479 try { 1480 return TarUtils.parseOctalOrBinary(header, offset, length); 1481 } catch (final IllegalArgumentException ex) { //NOSONAR 1482 return UNKNOWN; 1483 } 1484 } 1485 return TarUtils.parseOctalOrBinary(header, offset, length); 1486 } 1487 1488 /** 1489 * Parse an entry's header information from a header buffer. 1490 * 1491 * @param header The tar entry header buffer to get information from. 1492 * @throws IllegalArgumentException if any of the numeric fields have an invalid format 1493 */ 1494 public void parseTarHeader(final byte[] header) { 1495 try { 1496 parseTarHeader(header, TarUtils.DEFAULT_ENCODING); 1497 } catch (final IOException ex) { // NOSONAR 1498 try { 1499 parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true, false); 1500 } catch (final IOException ex2) { 1501 // not really possible 1502 throw new UncheckedIOException(ex2); //NOSONAR 1503 } 1504 } 1505 } 1506 1507 /** 1508 * Parse an entry's header information from a header buffer. 1509 * 1510 * @param header The tar entry header buffer to get information from. 1511 * @param encoding encoding to use for file names 1512 * @since 1.4 1513 * @throws IllegalArgumentException if any of the numeric fields 1514 * have an invalid format 1515 * @throws IOException on error 1516 */ 1517 public void parseTarHeader(final byte[] header, final ZipEncoding encoding) 1518 throws IOException { 1519 parseTarHeader(header, encoding, false, false); 1520 } 1521 1522 private void parseTarHeader(final byte[] header, final ZipEncoding encoding, 1523 final boolean oldStyle, final boolean lenient) 1524 throws IOException { 1525 parseTarHeader(Collections.emptyMap(), header, encoding, oldStyle, lenient); 1526 } 1527 1528 private void parseTarHeader(final Map<String, String> globalPaxHeaders, final byte[] header, 1529 final ZipEncoding encoding, final boolean oldStyle, final boolean lenient) 1530 throws IOException { 1531 try { 1532 parseTarHeaderUnwrapped(globalPaxHeaders, header, encoding, oldStyle, lenient); 1533 } catch (final IllegalArgumentException ex) { 1534 throw new IOException("Corrupted TAR archive.", ex); 1535 } 1536 } 1537 1538 private void parseTarHeaderUnwrapped(final Map<String, String> globalPaxHeaders, final byte[] header, 1539 final ZipEncoding encoding, final boolean oldStyle, final boolean lenient) 1540 throws IOException { 1541 int offset = 0; 1542 1543 name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 1544 : TarUtils.parseName(header, offset, NAMELEN, encoding); 1545 offset += NAMELEN; 1546 mode = (int) parseOctalOrBinary(header, offset, MODELEN, lenient); 1547 offset += MODELEN; 1548 userId = (int) parseOctalOrBinary(header, offset, UIDLEN, lenient); 1549 offset += UIDLEN; 1550 groupId = (int) parseOctalOrBinary(header, offset, GIDLEN, lenient); 1551 offset += GIDLEN; 1552 size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN); 1553 if (size < 0) { 1554 throw new IOException("broken archive, entry with negative size"); 1555 } 1556 offset += SIZELEN; 1557 mTime = TimeUtils.unixTimeToFileTime(parseOctalOrBinary(header, offset, MODTIMELEN, lenient)); 1558 offset += MODTIMELEN; 1559 checkSumOK = TarUtils.verifyCheckSum(header); 1560 offset += CHKSUMLEN; 1561 linkFlag = header[offset++]; 1562 linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN) 1563 : TarUtils.parseName(header, offset, NAMELEN, encoding); 1564 offset += NAMELEN; 1565 magic = TarUtils.parseName(header, offset, MAGICLEN); 1566 offset += MAGICLEN; 1567 version = TarUtils.parseName(header, offset, VERSIONLEN); 1568 offset += VERSIONLEN; 1569 userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN) 1570 : TarUtils.parseName(header, offset, UNAMELEN, encoding); 1571 offset += UNAMELEN; 1572 groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN) 1573 : TarUtils.parseName(header, offset, GNAMELEN, encoding); 1574 offset += GNAMELEN; 1575 if (linkFlag == LF_CHR || linkFlag == LF_BLK) { 1576 devMajor = (int) parseOctalOrBinary(header, offset, DEVLEN, lenient); 1577 offset += DEVLEN; 1578 devMinor = (int) parseOctalOrBinary(header, offset, DEVLEN, lenient); 1579 offset += DEVLEN; 1580 } else { 1581 offset += 2 * DEVLEN; 1582 } 1583 1584 final int type = evaluateType(globalPaxHeaders, header); 1585 switch (type) { 1586 case FORMAT_OLDGNU: { 1587 aTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, ATIMELEN_GNU, lenient)); 1588 offset += ATIMELEN_GNU; 1589 cTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, CTIMELEN_GNU, lenient)); 1590 offset += CTIMELEN_GNU; 1591 offset += OFFSETLEN_GNU; 1592 offset += LONGNAMESLEN_GNU; 1593 offset += PAD2LEN_GNU; 1594 sparseHeaders = 1595 new ArrayList<>(TarUtils.readSparseStructs(header, offset, SPARSE_HEADERS_IN_OLDGNU_HEADER)); 1596 offset += SPARSELEN_GNU; 1597 isExtended = TarUtils.parseBoolean(header, offset); 1598 offset += ISEXTENDEDLEN_GNU; 1599 realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU); 1600 offset += REALSIZELEN_GNU; // NOSONAR - assignment as documentation 1601 break; 1602 } 1603 case FORMAT_XSTAR: { 1604 final String xstarPrefix = oldStyle 1605 ? TarUtils.parseName(header, offset, PREFIXLEN_XSTAR) 1606 : TarUtils.parseName(header, offset, PREFIXLEN_XSTAR, encoding); 1607 offset += PREFIXLEN_XSTAR; 1608 if (!xstarPrefix.isEmpty()) { 1609 name = xstarPrefix + "/" + name; 1610 } 1611 aTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, ATIMELEN_XSTAR, lenient)); 1612 offset += ATIMELEN_XSTAR; 1613 cTime = fileTimeFromOptionalSeconds(parseOctalOrBinary(header, offset, CTIMELEN_XSTAR, lenient)); 1614 offset += CTIMELEN_XSTAR; // NOSONAR - assignment as documentation 1615 break; 1616 } 1617 case FORMAT_POSIX: 1618 default: { 1619 final String prefix = oldStyle 1620 ? TarUtils.parseName(header, offset, PREFIXLEN) 1621 : TarUtils.parseName(header, offset, PREFIXLEN, encoding); 1622 offset += PREFIXLEN; // NOSONAR - assignment as documentation 1623 // SunOS tar -E does not add / to directory names, so fix 1624 // up to be consistent 1625 if (isDirectory() && !name.endsWith("/")){ 1626 name = name + "/"; 1627 } 1628 if (!prefix.isEmpty()){ 1629 name = prefix + "/" + name; 1630 } 1631 } 1632 } 1633 } 1634 1635 /** 1636 * process one pax header, using the entries extraPaxHeaders map as source for extra headers 1637 * used when handling entries for sparse files. 1638 * @param key 1639 * @param val 1640 * @since 1.15 1641 */ 1642 private void processPaxHeader(final String key, final String val) throws IOException { 1643 processPaxHeader(key, val, extraPaxHeaders); 1644 } 1645 1646 /** 1647 * Process one pax header, using the supplied map as source for extra headers to be used when handling 1648 * entries for sparse files 1649 * 1650 * @param key the header name. 1651 * @param val the header value. 1652 * @param headers map of headers used for dealing with sparse file. 1653 * @throws NumberFormatException if encountered errors when parsing the numbers 1654 * @since 1.15 1655 */ 1656 private void processPaxHeader(final String key, final String val, final Map<String, String> headers) 1657 throws IOException { 1658 /* 1659 * The following headers are defined for Pax. 1660 * charset: cannot use these without changing TarArchiveEntry fields 1661 * mtime 1662 * atime 1663 * ctime 1664 * LIBARCHIVE.creationtime 1665 * comment 1666 * gid, gname 1667 * linkpath 1668 * size 1669 * uid,uname 1670 * SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those 1671 * 1672 * GNU sparse files use additional members, we use 1673 * GNU.sparse.size to detect the 0.0 and 0.1 versions and 1674 * GNU.sparse.realsize for 1.0. 1675 * 1676 * star files use additional members of which we use 1677 * SCHILY.filetype in order to detect star sparse files. 1678 * 1679 * If called from addExtraPaxHeader, these additional headers must be already present . 1680 */ 1681 switch (key) { 1682 case "path": 1683 setName(val); 1684 break; 1685 case "linkpath": 1686 setLinkName(val); 1687 break; 1688 case "gid": 1689 setGroupId(Long.parseLong(val)); 1690 break; 1691 case "gname": 1692 setGroupName(val); 1693 break; 1694 case "uid": 1695 setUserId(Long.parseLong(val)); 1696 break; 1697 case "uname": 1698 setUserName(val); 1699 break; 1700 case "size": 1701 final long size = Long.parseLong(val); 1702 if (size < 0) { 1703 throw new IOException("Corrupted TAR archive. Entry size is negative"); 1704 } 1705 setSize(size); 1706 break; 1707 case "mtime": 1708 setLastModifiedTime(FileTime.from(parseInstantFromDecimalSeconds(val))); 1709 break; 1710 case "atime": 1711 setLastAccessTime(FileTime.from(parseInstantFromDecimalSeconds(val))); 1712 break; 1713 case "ctime": 1714 setStatusChangeTime(FileTime.from(parseInstantFromDecimalSeconds(val))); 1715 break; 1716 case "LIBARCHIVE.creationtime": 1717 setCreationTime(FileTime.from(parseInstantFromDecimalSeconds(val))); 1718 break; 1719 case "SCHILY.devminor": 1720 final int devMinor = Integer.parseInt(val); 1721 if (devMinor < 0) { 1722 throw new IOException("Corrupted TAR archive. Dev-Minor is negative"); 1723 } 1724 setDevMinor(devMinor); 1725 break; 1726 case "SCHILY.devmajor": 1727 final int devMajor = Integer.parseInt(val); 1728 if (devMajor < 0) { 1729 throw new IOException("Corrupted TAR archive. Dev-Major is negative"); 1730 } 1731 setDevMajor(devMajor); 1732 break; 1733 case TarGnuSparseKeys.SIZE: 1734 fillGNUSparse0xData(headers); 1735 break; 1736 case TarGnuSparseKeys.REALSIZE: 1737 fillGNUSparse1xData(headers); 1738 break; 1739 case "SCHILY.filetype": 1740 if ("sparse".equals(val)) { 1741 fillStarSparseData(headers); 1742 } 1743 break; 1744 default: 1745 extraPaxHeaders.put(key, val); 1746 } 1747 } 1748 1749 private void readFileMode(final Path file, final String normalizedName, final LinkOption... options) throws IOException { 1750 if (Files.isDirectory(file, options)) { 1751 this.mode = DEFAULT_DIR_MODE; 1752 this.linkFlag = LF_DIR; 1753 1754 final int nameLength = normalizedName.length(); 1755 if (nameLength == 0 || normalizedName.charAt(nameLength - 1) != '/') { 1756 this.name = normalizedName + "/"; 1757 } else { 1758 this.name = normalizedName; 1759 } 1760 } else { 1761 this.mode = DEFAULT_FILE_MODE; 1762 this.linkFlag = LF_NORMAL; 1763 this.name = normalizedName; 1764 this.size = Files.size(file); 1765 } 1766 } 1767 1768 private void readOsSpecificProperties(final Path file, final LinkOption... options) throws IOException { 1769 final Set<String> availableAttributeViews = file.getFileSystem().supportedFileAttributeViews(); 1770 if (availableAttributeViews.contains("posix")) { 1771 final PosixFileAttributes posixFileAttributes = Files.readAttributes(file, PosixFileAttributes.class, options); 1772 setLastModifiedTime(posixFileAttributes.lastModifiedTime()); 1773 setCreationTime(posixFileAttributes.creationTime()); 1774 setLastAccessTime(posixFileAttributes.lastAccessTime()); 1775 this.userName = posixFileAttributes.owner().getName(); 1776 this.groupName = posixFileAttributes.group().getName(); 1777 if (availableAttributeViews.contains("unix")) { 1778 this.userId = ((Number) Files.getAttribute(file, "unix:uid", options)).longValue(); 1779 this.groupId = ((Number) Files.getAttribute(file, "unix:gid", options)).longValue(); 1780 try { 1781 setStatusChangeTime((FileTime) Files.getAttribute(file, "unix:ctime", options)); 1782 } catch (final IllegalArgumentException ex) { // NOSONAR 1783 // ctime is not supported 1784 } 1785 } 1786 } else { 1787 if (availableAttributeViews.contains("dos")) { 1788 final DosFileAttributes dosFileAttributes = Files.readAttributes(file, DosFileAttributes.class, options); 1789 setLastModifiedTime(dosFileAttributes.lastModifiedTime()); 1790 setCreationTime(dosFileAttributes.creationTime()); 1791 setLastAccessTime(dosFileAttributes.lastAccessTime()); 1792 } else { 1793 final BasicFileAttributes basicFileAttributes = Files.readAttributes(file, BasicFileAttributes.class, options); 1794 setLastModifiedTime(basicFileAttributes.lastModifiedTime()); 1795 setCreationTime(basicFileAttributes.creationTime()); 1796 setLastAccessTime(basicFileAttributes.lastAccessTime()); 1797 } 1798 this.userName = Files.getOwner(file, options).getName(); 1799 } 1800 } 1801 1802 /** 1803 * Sets this entry's creation time. 1804 * 1805 * @param time This entry's new creation time. 1806 * @since 1.22 1807 */ 1808 public void setCreationTime(final FileTime time) { 1809 birthTime = time; 1810 } 1811 1812 /** 1813 * Sets the offset of the data for the tar entry. 1814 * @param dataOffset the position of the data in the tar. 1815 * @since 1.21 1816 */ 1817 public void setDataOffset(final long dataOffset) { 1818 if (dataOffset < 0) { 1819 throw new IllegalArgumentException("The offset can not be smaller than 0"); 1820 } 1821 this.dataOffset = dataOffset; 1822 } 1823 1824 /** 1825 * Sets this entry's major device number. 1826 * 1827 * @param devNo This entry's major device number. 1828 * @throws IllegalArgumentException if the devNo is < 0. 1829 * @since 1.4 1830 */ 1831 public void setDevMajor(final int devNo) { 1832 if (devNo < 0){ 1833 throw new IllegalArgumentException("Major device number is out of " 1834 + "range: " + devNo); 1835 } 1836 this.devMajor = devNo; 1837 } 1838 1839 /** 1840 * Sets this entry's minor device number. 1841 * 1842 * @param devNo This entry's minor device number. 1843 * @throws IllegalArgumentException if the devNo is < 0. 1844 * @since 1.4 1845 */ 1846 public void setDevMinor(final int devNo) { 1847 if (devNo < 0){ 1848 throw new IllegalArgumentException("Minor device number is out of " 1849 + "range: " + devNo); 1850 } 1851 this.devMinor = devNo; 1852 } 1853 1854 /** 1855 * Sets this entry's group id. 1856 * 1857 * @param groupId This entry's new group id. 1858 */ 1859 public void setGroupId(final int groupId) { 1860 setGroupId((long) groupId); 1861 } 1862 1863 /** 1864 * Sets this entry's group id. 1865 * 1866 * @since 1.10 1867 * @param groupId This entry's new group id. 1868 */ 1869 public void setGroupId(final long groupId) { 1870 this.groupId = groupId; 1871 } 1872 1873 /** 1874 * Sets this entry's group name. 1875 * 1876 * @param groupName This entry's new group name. 1877 */ 1878 public void setGroupName(final String groupName) { 1879 this.groupName = groupName; 1880 } 1881 1882 /** 1883 * Convenience method to set this entry's group and user ids. 1884 * 1885 * @param userId This entry's new user id. 1886 * @param groupId This entry's new group id. 1887 */ 1888 public void setIds(final int userId, final int groupId) { 1889 setUserId(userId); 1890 setGroupId(groupId); 1891 } 1892 1893 /** 1894 * Sets this entry's last access time. 1895 * 1896 * @param time This entry's new last access time. 1897 * @since 1.22 1898 */ 1899 public void setLastAccessTime(final FileTime time) { 1900 aTime = time; 1901 } 1902 1903 /** 1904 * Sets this entry's modification time. 1905 * 1906 * @param time This entry's new modification time. 1907 * @since 1.22 1908 */ 1909 public void setLastModifiedTime(final FileTime time) { 1910 mTime = Objects.requireNonNull(time, "Time must not be null"); 1911 } 1912 1913 /** 1914 * Sets this entry's link name. 1915 * 1916 * @param link the link name to use. 1917 * 1918 * @since 1.1 1919 */ 1920 public void setLinkName(final String link) { 1921 this.linkName = link; 1922 } 1923 1924 /** 1925 * Sets the mode for this entry 1926 * 1927 * @param mode the mode for this entry 1928 */ 1929 public void setMode(final int mode) { 1930 this.mode = mode; 1931 } 1932 1933 /** 1934 * Sets this entry's modification time. 1935 * 1936 * @param time This entry's new modification time. 1937 * @see TarArchiveEntry#setLastModifiedTime(FileTime) 1938 */ 1939 public void setModTime(final Date time) { 1940 setLastModifiedTime(TimeUtils.toFileTime(time)); 1941 } 1942 1943 /** 1944 * Sets this entry's modification time. 1945 * 1946 * @param time This entry's new modification time. 1947 * @since 1.21 1948 * @see TarArchiveEntry#setLastModifiedTime(FileTime) 1949 */ 1950 public void setModTime(final FileTime time) { 1951 setLastModifiedTime(time); 1952 } 1953 1954 /** 1955 * Sets this entry's modification time. The parameter passed 1956 * to this method is in "Java time". 1957 * 1958 * @param time This entry's new modification time. 1959 * @see TarArchiveEntry#setLastModifiedTime(FileTime) 1960 */ 1961 public void setModTime(final long time) { 1962 setLastModifiedTime(FileTime.fromMillis(time)); 1963 } 1964 1965 /** 1966 * Sets this entry's name. 1967 * 1968 * @param name This entry's new name. 1969 */ 1970 public void setName(final String name) { 1971 this.name = normalizeFileName(name, this.preserveAbsolutePath); 1972 } 1973 1974 /** 1975 * Convenience method to set this entry's group and user names. 1976 * 1977 * @param userName This entry's new user name. 1978 * @param groupName This entry's new group name. 1979 */ 1980 public void setNames(final String userName, final String groupName) { 1981 setUserName(userName); 1982 setGroupName(groupName); 1983 } 1984 1985 /** 1986 * Sets this entry's file size. 1987 * 1988 * @param size This entry's new file size. 1989 * @throws IllegalArgumentException if the size is < 0. 1990 */ 1991 public void setSize(final long size) { 1992 if (size < 0){ 1993 throw new IllegalArgumentException("Size is out of range: " + size); 1994 } 1995 this.size = size; 1996 } 1997 1998 /** 1999 * Sets this entry's sparse headers 2000 * @param sparseHeaders The new sparse headers 2001 * @since 1.20 2002 */ 2003 public void setSparseHeaders(final List<TarArchiveStructSparse> sparseHeaders) { 2004 this.sparseHeaders = sparseHeaders; 2005 } 2006 2007 /** 2008 * Sets this entry's status change time. 2009 * 2010 * @param time This entry's new status change time. 2011 * @since 1.22 2012 */ 2013 public void setStatusChangeTime(final FileTime time) { 2014 cTime = time; 2015 } 2016 2017 /** 2018 * Sets this entry's user id. 2019 * 2020 * @param userId This entry's new user id. 2021 */ 2022 public void setUserId(final int userId) { 2023 setUserId((long) userId); 2024 } 2025 2026 /** 2027 * Sets this entry's user id. 2028 * 2029 * @param userId This entry's new user id. 2030 * @since 1.10 2031 */ 2032 public void setUserId(final long userId) { 2033 this.userId = userId; 2034 } 2035 2036 /** 2037 * Sets this entry's user name. 2038 * 2039 * @param userName This entry's new user name. 2040 */ 2041 public void setUserName(final String userName) { 2042 this.userName = userName; 2043 } 2044 2045 /** 2046 * Update the entry using a map of pax headers. 2047 * @param headers 2048 * @since 1.15 2049 */ 2050 void updateEntryFromPaxHeaders(final Map<String, String> headers) throws IOException { 2051 for (final Map.Entry<String, String> ent : headers.entrySet()) { 2052 processPaxHeader(ent.getKey(), ent.getValue(), headers); 2053 } 2054 } 2055 2056 /** 2057 * Write an entry's header information to a header buffer. 2058 * 2059 * <p>This method does not use the star/GNU tar/BSD tar extensions.</p> 2060 * 2061 * @param outbuf The tar entry header buffer to fill in. 2062 */ 2063 public void writeEntryHeader(final byte[] outbuf) { 2064 try { 2065 writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false); 2066 } catch (final IOException ex) { // NOSONAR 2067 try { 2068 writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false); 2069 } catch (final IOException ex2) { 2070 // impossible 2071 throw new UncheckedIOException(ex2); //NOSONAR 2072 } 2073 } 2074 } 2075 2076 /** 2077 * Write an entry's header information to a header buffer. 2078 * 2079 * @param outbuf The tar entry header buffer to fill in. 2080 * @param encoding encoding to use when writing the file name. 2081 * @param starMode whether to use the star/GNU tar/BSD tar 2082 * extension for numeric fields if their value doesn't fit in the 2083 * maximum size of standard tar archives 2084 * @since 1.4 2085 * @throws IOException on error 2086 */ 2087 public void writeEntryHeader(final byte[] outbuf, final ZipEncoding encoding, 2088 final boolean starMode) throws IOException { 2089 int offset = 0; 2090 2091 offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN, 2092 encoding); 2093 offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode); 2094 offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN, 2095 starMode); 2096 offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN, 2097 starMode); 2098 offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode); 2099 offset = writeEntryHeaderField(TimeUtils.toUnixTime(mTime), outbuf, offset, 2100 MODTIMELEN, starMode); 2101 2102 final int csOffset = offset; 2103 2104 offset = fill((byte) ' ', offset, outbuf, CHKSUMLEN); 2105 2106 outbuf[offset++] = linkFlag; 2107 offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN, 2108 encoding); 2109 offset = TarUtils.formatNameBytes(magic, outbuf, offset, MAGICLEN); 2110 offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN); 2111 offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN, 2112 encoding); 2113 offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN, 2114 encoding); 2115 offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN, 2116 starMode); 2117 offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN, 2118 starMode); 2119 2120 if (starMode) { 2121 // skip prefix 2122 offset = fill(0, offset, outbuf, PREFIXLEN_XSTAR); 2123 offset = writeEntryHeaderOptionalTimeField(aTime, offset, outbuf, ATIMELEN_XSTAR); 2124 offset = writeEntryHeaderOptionalTimeField(cTime, offset, outbuf, CTIMELEN_XSTAR); 2125 // 8-byte fill 2126 offset = fill(0, offset, outbuf, 8); 2127 // Do not write MAGIC_XSTAR because it causes issues with some TAR tools 2128 // This makes it effectively XUSTAR, which guarantees compatibility with USTAR 2129 offset = fill(0, offset, outbuf, XSTAR_MAGIC_LEN); 2130 } 2131 2132 offset = fill(0, offset, outbuf, outbuf.length - offset); // NOSONAR - assignment as documentation 2133 2134 final long chk = TarUtils.computeCheckSum(outbuf); 2135 2136 TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN); 2137 } 2138 2139 private int writeEntryHeaderField(final long value, final byte[] outbuf, final int offset, 2140 final int length, final boolean starMode) { 2141 if (!starMode && (value < 0 2142 || value >= 1L << 3 * (length - 1))) { 2143 // value doesn't fit into field when written as octal 2144 // number, will be written to PAX header or causes an 2145 // error 2146 return TarUtils.formatLongOctalBytes(0, outbuf, offset, length); 2147 } 2148 return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset, 2149 length); 2150 } 2151 2152 private int writeEntryHeaderOptionalTimeField(final FileTime time, int offset, final byte[] outbuf, final int fieldLength) { 2153 if (time != null) { 2154 offset = writeEntryHeaderField(TimeUtils.toUnixTime(time), outbuf, offset, fieldLength, true); 2155 } else { 2156 offset = fill(0, offset, outbuf, fieldLength); 2157 } 2158 return offset; 2159 } 2160 2161} 2162