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