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