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.OutputStream; 024import java.io.StringWriter; 025import java.nio.ByteBuffer; 026import java.nio.charset.StandardCharsets; 027import java.nio.file.LinkOption; 028import java.nio.file.Path; 029import java.util.Arrays; 030import java.util.Date; 031import java.util.HashMap; 032import java.util.Map; 033 034import org.apache.commons.compress.archivers.ArchiveEntry; 035import org.apache.commons.compress.archivers.ArchiveOutputStream; 036import org.apache.commons.compress.archivers.zip.ZipEncoding; 037import org.apache.commons.compress.archivers.zip.ZipEncodingHelper; 038import org.apache.commons.compress.utils.CountingOutputStream; 039import org.apache.commons.compress.utils.FixedLengthBlockOutputStream; 040 041/** 042 * The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are provided to put 043 * entries, and then write their contents by writing to this stream using write(). 044 * 045 * <p>tar archives consist of a sequence of records of 512 bytes each 046 * that are grouped into blocks. Prior to Apache Commons Compress 1.14 047 * it has been possible to configure a record size different from 512 048 * bytes and arbitrary block sizes. Starting with Compress 1.15 512 is 049 * the only valid option for the record size and the block size must 050 * be a multiple of 512. Also the default block size changed from 051 * 10240 bytes prior to Compress 1.15 to 512 bytes with Compress 052 * 1.15.</p> 053 * 054 * @NotThreadSafe 055 */ 056public class TarArchiveOutputStream extends ArchiveOutputStream { 057 058 /** 059 * Fail if a long file name is required in the archive. 060 */ 061 public static final int LONGFILE_ERROR = 0; 062 063 /** 064 * Long paths will be truncated in the archive. 065 */ 066 public static final int LONGFILE_TRUNCATE = 1; 067 068 /** 069 * GNU tar extensions are used to store long file names in the archive. 070 */ 071 public static final int LONGFILE_GNU = 2; 072 073 /** 074 * POSIX/PAX extensions are used to store long file names in the archive. 075 */ 076 public static final int LONGFILE_POSIX = 3; 077 078 /** 079 * Fail if a big number (e.g. size > 8GiB) is required in the archive. 080 */ 081 public static final int BIGNUMBER_ERROR = 0; 082 083 /** 084 * star/GNU tar/BSD tar extensions are used to store big number in the archive. 085 */ 086 public static final int BIGNUMBER_STAR = 1; 087 088 /** 089 * POSIX/PAX extensions are used to store big numbers in the archive. 090 */ 091 public static final int BIGNUMBER_POSIX = 2; 092 private static final int RECORD_SIZE = 512; 093 094 private long currSize; 095 private String currName; 096 private long currBytes; 097 private final byte[] recordBuf; 098 private int longFileMode = LONGFILE_ERROR; 099 private int bigNumberMode = BIGNUMBER_ERROR; 100 private int recordsWritten; 101 private final int recordsPerBlock; 102 103 private boolean closed; 104 105 /** 106 * Indicates if putArchiveEntry has been called without closeArchiveEntry 107 */ 108 private boolean haveUnclosedEntry; 109 110 /** 111 * indicates if this archive is finished 112 */ 113 private boolean finished; 114 115 private final FixedLengthBlockOutputStream out; 116 private final CountingOutputStream countingOut; 117 118 private final ZipEncoding zipEncoding; 119 120 // the provided encoding (for unit tests) 121 final String encoding; 122 123 private boolean addPaxHeadersForNonAsciiNames; 124 private static final ZipEncoding ASCII = 125 ZipEncodingHelper.getZipEncoding("ASCII"); 126 127 private static final int BLOCK_SIZE_UNSPECIFIED = -511; 128 129 /** 130 * Constructor for TarArchiveOutputStream. 131 * 132 * <p>Uses a block size of 512 bytes.</p> 133 * 134 * @param os the output stream to use 135 */ 136 public TarArchiveOutputStream(final OutputStream os) { 137 this(os, BLOCK_SIZE_UNSPECIFIED); 138 } 139 140 /** 141 * Constructor for TarArchiveOutputStream. 142 * 143 * <p>Uses a block size of 512 bytes.</p> 144 * 145 * @param os the output stream to use 146 * @param encoding name of the encoding to use for file names 147 * @since 1.4 148 */ 149 public TarArchiveOutputStream(final OutputStream os, final String encoding) { 150 this(os, BLOCK_SIZE_UNSPECIFIED, encoding); 151 } 152 153 /** 154 * Constructor for TarArchiveOutputStream. 155 * 156 * @param os the output stream to use 157 * @param blockSize the block size to use. Must be a multiple of 512 bytes. 158 */ 159 public TarArchiveOutputStream(final OutputStream os, final int blockSize) { 160 this(os, blockSize, null); 161 } 162 163 164 /** 165 * Constructor for TarArchiveOutputStream. 166 * 167 * @param os the output stream to use 168 * @param blockSize the block size to use 169 * @param recordSize the record size to use. Must be 512 bytes. 170 * @deprecated recordSize must always be 512 bytes. An IllegalArgumentException will be thrown 171 * if any other value is used 172 */ 173 @Deprecated 174 public TarArchiveOutputStream(final OutputStream os, final int blockSize, 175 final int recordSize) { 176 this(os, blockSize, recordSize, null); 177 } 178 179 /** 180 * Constructor for TarArchiveOutputStream. 181 * 182 * @param os the output stream to use 183 * @param blockSize the block size to use . Must be a multiple of 512 bytes. 184 * @param recordSize the record size to use. Must be 512 bytes. 185 * @param encoding name of the encoding to use for file names 186 * @since 1.4 187 * @deprecated recordSize must always be 512 bytes. An IllegalArgumentException will be thrown 188 * if any other value is used. 189 */ 190 @Deprecated 191 public TarArchiveOutputStream(final OutputStream os, final int blockSize, 192 final int recordSize, final String encoding) { 193 this(os, blockSize, encoding); 194 if (recordSize != RECORD_SIZE) { 195 throw new IllegalArgumentException( 196 "Tar record size must always be 512 bytes. Attempt to set size of " + recordSize); 197 } 198 199 } 200 201 /** 202 * Constructor for TarArchiveOutputStream. 203 * 204 * @param os the output stream to use 205 * @param blockSize the block size to use. Must be a multiple of 512 bytes. 206 * @param encoding name of the encoding to use for file names 207 * @since 1.4 208 */ 209 public TarArchiveOutputStream(final OutputStream os, final int blockSize, 210 final String encoding) { 211 final int realBlockSize; 212 if (BLOCK_SIZE_UNSPECIFIED == blockSize) { 213 realBlockSize = RECORD_SIZE; 214 } else { 215 realBlockSize = blockSize; 216 } 217 218 if (realBlockSize <=0 || realBlockSize % RECORD_SIZE != 0) { 219 throw new IllegalArgumentException("Block size must be a multiple of 512 bytes. Attempt to use set size of " + blockSize); 220 } 221 out = new FixedLengthBlockOutputStream(countingOut = new CountingOutputStream(os), 222 RECORD_SIZE); 223 this.encoding = encoding; 224 this.zipEncoding = ZipEncodingHelper.getZipEncoding(encoding); 225 226 this.recordBuf = new byte[RECORD_SIZE]; 227 this.recordsPerBlock = realBlockSize / RECORD_SIZE; 228 } 229 230 /** 231 * Set the long file mode. This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or 232 * LONGFILE_GNU(2). This specifies the treatment of long file names (names >= 233 * TarConstants.NAMELEN). Default is LONGFILE_ERROR. 234 * 235 * @param longFileMode the mode to use 236 */ 237 public void setLongFileMode(final int longFileMode) { 238 this.longFileMode = longFileMode; 239 } 240 241 /** 242 * Set the big number mode. This can be BIGNUMBER_ERROR(0), BIGNUMBER_POSIX(1) or 243 * BIGNUMBER_STAR(2). This specifies the treatment of big files (sizes > 244 * TarConstants.MAXSIZE) and other numeric values to big to fit into a traditional tar header. 245 * Default is BIGNUMBER_ERROR. 246 * 247 * @param bigNumberMode the mode to use 248 * @since 1.4 249 */ 250 public void setBigNumberMode(final int bigNumberMode) { 251 this.bigNumberMode = bigNumberMode; 252 } 253 254 /** 255 * Whether to add a PAX extension header for non-ASCII file names. 256 * 257 * @param b whether to add a PAX extension header for non-ASCII file names. 258 * @since 1.4 259 */ 260 public void setAddPaxHeadersForNonAsciiNames(final boolean b) { 261 addPaxHeadersForNonAsciiNames = b; 262 } 263 264 @Deprecated 265 @Override 266 public int getCount() { 267 return (int) getBytesWritten(); 268 } 269 270 @Override 271 public long getBytesWritten() { 272 return countingOut.getBytesWritten(); 273 } 274 275 /** 276 * Ends the TAR archive without closing the underlying OutputStream. 277 * 278 * An archive consists of a series of file entries terminated by an 279 * end-of-archive entry, which consists of two 512 blocks of zero bytes. 280 * POSIX.1 requires two EOF records, like some other implementations. 281 * 282 * @throws IOException on error 283 */ 284 @Override 285 public void finish() throws IOException { 286 if (finished) { 287 throw new IOException("This archive has already been finished"); 288 } 289 290 if (haveUnclosedEntry) { 291 throw new IOException("This archive contains unclosed entries."); 292 } 293 writeEOFRecord(); 294 writeEOFRecord(); 295 padAsNeeded(); 296 out.flush(); 297 finished = true; 298 } 299 300 /** 301 * Closes the underlying OutputStream. 302 * 303 * @throws IOException on error 304 */ 305 @Override 306 public void close() throws IOException { 307 try { 308 if (!finished) { 309 finish(); 310 } 311 } finally { 312 if (!closed) { 313 out.close(); 314 closed = true; 315 } 316 } 317 } 318 319 /** 320 * Get the record size being used by this stream's TarBuffer. 321 * 322 * @return The TarBuffer record size. 323 * @deprecated 324 */ 325 @Deprecated 326 public int getRecordSize() { 327 return RECORD_SIZE; 328 } 329 330 /** 331 * Put an entry on the output stream. This writes the entry's header record and positions the 332 * output stream for writing the contents of the entry. Once this method is called, the stream 333 * is ready for calls to write() to write the entry's contents. Once the contents are written, 334 * closeArchiveEntry() <B>MUST</B> be called to ensure that all buffered data is completely 335 * written to the output stream. 336 * 337 * @param archiveEntry The TarEntry to be written to the archive. 338 * @throws IOException on error 339 * @throws ClassCastException if archiveEntry is not an instance of TarArchiveEntry 340 * @throws IllegalArgumentException if the {@link TarArchiveOutputStream#longFileMode} equals 341 * {@link TarArchiveOutputStream#LONGFILE_ERROR} and the file 342 * name is too long 343 * @throws IllegalArgumentException if the {@link TarArchiveOutputStream#bigNumberMode} equals 344 * {@link TarArchiveOutputStream#BIGNUMBER_ERROR} and one of the numeric values 345 * exceeds the limits of a traditional tar header. 346 */ 347 @Override 348 public void putArchiveEntry(final ArchiveEntry archiveEntry) throws IOException { 349 if (finished) { 350 throw new IOException("Stream has already been finished"); 351 } 352 final TarArchiveEntry entry = (TarArchiveEntry) archiveEntry; 353 if (entry.isGlobalPaxHeader()) { 354 final byte[] data = encodeExtendedPaxHeadersContents(entry.getExtraPaxHeaders()); 355 entry.setSize(data.length); 356 entry.writeEntryHeader(recordBuf, zipEncoding, bigNumberMode == BIGNUMBER_STAR); 357 writeRecord(recordBuf); 358 currSize= entry.getSize(); 359 currBytes = 0; 360 this.haveUnclosedEntry = true; 361 write(data); 362 closeArchiveEntry(); 363 } else { 364 final Map<String, String> paxHeaders = new HashMap<>(); 365 final String entryName = entry.getName(); 366 final boolean paxHeaderContainsPath = handleLongName(entry, entryName, paxHeaders, "path", 367 TarConstants.LF_GNUTYPE_LONGNAME, "file name"); 368 369 final String linkName = entry.getLinkName(); 370 final boolean paxHeaderContainsLinkPath = linkName != null && !linkName.isEmpty() 371 && handleLongName(entry, linkName, paxHeaders, "linkpath", 372 TarConstants.LF_GNUTYPE_LONGLINK, "link name"); 373 374 if (bigNumberMode == BIGNUMBER_POSIX) { 375 addPaxHeadersForBigNumbers(paxHeaders, entry); 376 } else if (bigNumberMode != BIGNUMBER_STAR) { 377 failForBigNumbers(entry); 378 } 379 380 if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsPath 381 && !ASCII.canEncode(entryName)) { 382 paxHeaders.put("path", entryName); 383 } 384 385 if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsLinkPath 386 && (entry.isLink() || entry.isSymbolicLink()) 387 && !ASCII.canEncode(linkName)) { 388 paxHeaders.put("linkpath", linkName); 389 } 390 paxHeaders.putAll(entry.getExtraPaxHeaders()); 391 392 if (!paxHeaders.isEmpty()) { 393 writePaxHeaders(entry, entryName, paxHeaders); 394 } 395 396 entry.writeEntryHeader(recordBuf, zipEncoding, bigNumberMode == BIGNUMBER_STAR); 397 writeRecord(recordBuf); 398 399 currBytes = 0; 400 401 if (entry.isDirectory()) { 402 currSize = 0; 403 } else { 404 currSize = entry.getSize(); 405 } 406 currName = entryName; 407 haveUnclosedEntry = true; 408 } 409 } 410 411 /** 412 * Close an entry. This method MUST be called for all file entries that contain data. The reason 413 * is that we must buffer data written to the stream in order to satisfy the buffer's record 414 * based writes. Thus, there may be data fragments still being assembled that must be written to 415 * the output stream before this entry is closed and the next entry written. 416 * 417 * @throws IOException on error 418 */ 419 @Override 420 public void closeArchiveEntry() throws IOException { 421 if (finished) { 422 throw new IOException("Stream has already been finished"); 423 } 424 if (!haveUnclosedEntry) { 425 throw new IOException("No current entry to close"); 426 } 427 out.flushBlock(); 428 if (currBytes < currSize) { 429 throw new IOException("Entry '" + currName + "' closed at '" 430 + currBytes 431 + "' before the '" + currSize 432 + "' bytes specified in the header were written"); 433 } 434 recordsWritten += (currSize / RECORD_SIZE); 435 if (0 != currSize % RECORD_SIZE) { 436 recordsWritten++; 437 } 438 haveUnclosedEntry = false; 439 } 440 441 /** 442 * Writes bytes to the current tar archive entry. This method is aware of the current entry and 443 * will throw an exception if you attempt to write bytes past the length specified for the 444 * current entry. 445 * 446 * @param wBuf The buffer to write to the archive. 447 * @param wOffset The offset in the buffer from which to get bytes. 448 * @param numToWrite The number of bytes to write. 449 * @throws IOException on error 450 */ 451 @Override 452 public void write(final byte[] wBuf, final int wOffset, final int numToWrite) throws IOException { 453 if (!haveUnclosedEntry) { 454 throw new IllegalStateException("No current tar entry"); 455 } 456 if (currBytes + numToWrite > currSize) { 457 throw new IOException("Request to write '" + numToWrite 458 + "' bytes exceeds size in header of '" 459 + currSize + "' bytes for entry '" 460 + currName + "'"); 461 } 462 out.write(wBuf, wOffset, numToWrite); 463 currBytes += numToWrite; 464 } 465 466 /** 467 * Writes a PAX extended header with the given map as contents. 468 * 469 * @since 1.4 470 */ 471 void writePaxHeaders(final TarArchiveEntry entry, 472 final String entryName, 473 final Map<String, String> headers) throws IOException { 474 String name = "./PaxHeaders.X/" + stripTo7Bits(entryName); 475 if (name.length() >= TarConstants.NAMELEN) { 476 name = name.substring(0, TarConstants.NAMELEN - 1); 477 } 478 final TarArchiveEntry pex = new TarArchiveEntry(name, 479 TarConstants.LF_PAX_EXTENDED_HEADER_LC); 480 transferModTime(entry, pex); 481 482 final byte[] data = encodeExtendedPaxHeadersContents(headers); 483 pex.setSize(data.length); 484 putArchiveEntry(pex); 485 write(data); 486 closeArchiveEntry(); 487 } 488 489 private byte[] encodeExtendedPaxHeadersContents(final Map<String, String> headers) { 490 final StringWriter w = new StringWriter(); 491 for (final Map.Entry<String, String> h : headers.entrySet()) { 492 final String key = h.getKey(); 493 final String value = h.getValue(); 494 int len = key.length() + value.length() 495 + 3 /* blank, equals and newline */ 496 + 2 /* guess 9 < actual length < 100 */; 497 String line = len + " " + key + "=" + value + "\n"; 498 int actualLength = line.getBytes(StandardCharsets.UTF_8).length; 499 while (len != actualLength) { 500 // Adjust for cases where length < 10 or > 100 501 // or where UTF-8 encoding isn't a single octet 502 // per character. 503 // Must be in loop as size may go from 99 to 100 in 504 // first pass so we'd need a second. 505 len = actualLength; 506 line = len + " " + key + "=" + value + "\n"; 507 actualLength = line.getBytes(StandardCharsets.UTF_8).length; 508 } 509 w.write(line); 510 } 511 return w.toString().getBytes(StandardCharsets.UTF_8); 512 } 513 514 private String stripTo7Bits(final String name) { 515 final int length = name.length(); 516 final StringBuilder result = new StringBuilder(length); 517 for (int i = 0; i < length; i++) { 518 final char stripped = (char) (name.charAt(i) & 0x7F); 519 if (shouldBeReplaced(stripped)) { 520 result.append("_"); 521 } else { 522 result.append(stripped); 523 } 524 } 525 return result.toString(); 526 } 527 528 /** 529 * @return true if the character could lead to problems when used inside a TarArchiveEntry name 530 * for a PAX header. 531 */ 532 private boolean shouldBeReplaced(final char c) { 533 return c == 0 // would be read as Trailing null 534 || c == '/' // when used as last character TAE will consider the PAX header a directory 535 || c == '\\'; // same as '/' as slashes get "normalized" on Windows 536 } 537 538 /** 539 * Write an EOF (end of archive) record to the tar archive. An EOF record consists of a record 540 * of all zeros. 541 */ 542 private void writeEOFRecord() throws IOException { 543 Arrays.fill(recordBuf, (byte) 0); 544 writeRecord(recordBuf); 545 } 546 547 @Override 548 public void flush() throws IOException { 549 out.flush(); 550 } 551 552 @Override 553 public ArchiveEntry createArchiveEntry(final File inputFile, final String entryName) 554 throws IOException { 555 if (finished) { 556 throw new IOException("Stream has already been finished"); 557 } 558 return new TarArchiveEntry(inputFile, entryName); 559 } 560 561 @Override 562 public ArchiveEntry createArchiveEntry(final Path inputPath, final String entryName, final LinkOption... options) throws IOException { 563 if (finished) { 564 throw new IOException("Stream has already been finished"); 565 } 566 return new TarArchiveEntry(inputPath, entryName, options); 567 } 568 569 /** 570 * Write an archive record to the archive. 571 * 572 * @param record The record data to write to the archive. 573 * @throws IOException on error 574 */ 575 private void writeRecord(final byte[] record) throws IOException { 576 if (record.length != RECORD_SIZE) { 577 throw new IOException("Record to write has length '" 578 + record.length 579 + "' which is not the record size of '" 580 + RECORD_SIZE + "'"); 581 } 582 583 out.write(record); 584 recordsWritten++; 585 } 586 587 private void padAsNeeded() throws IOException { 588 final int start = recordsWritten % recordsPerBlock; 589 if (start != 0) { 590 for (int i = start; i < recordsPerBlock; i++) { 591 writeEOFRecord(); 592 } 593 } 594 } 595 596 private void addPaxHeadersForBigNumbers(final Map<String, String> paxHeaders, 597 final TarArchiveEntry entry) { 598 addPaxHeaderForBigNumber(paxHeaders, "size", entry.getSize(), 599 TarConstants.MAXSIZE); 600 addPaxHeaderForBigNumber(paxHeaders, "gid", entry.getLongGroupId(), 601 TarConstants.MAXID); 602 addPaxHeaderForBigNumber(paxHeaders, "mtime", 603 entry.getModTime().getTime() / 1000, 604 TarConstants.MAXSIZE); 605 addPaxHeaderForBigNumber(paxHeaders, "uid", entry.getLongUserId(), 606 TarConstants.MAXID); 607 // star extensions by J\u00f6rg Schilling 608 addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devmajor", 609 entry.getDevMajor(), TarConstants.MAXID); 610 addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devminor", 611 entry.getDevMinor(), TarConstants.MAXID); 612 // there is no PAX header for file mode 613 failForBigNumber("mode", entry.getMode(), TarConstants.MAXID); 614 } 615 616 private void addPaxHeaderForBigNumber(final Map<String, String> paxHeaders, 617 final String header, final long value, 618 final long maxValue) { 619 if (value < 0 || value > maxValue) { 620 paxHeaders.put(header, String.valueOf(value)); 621 } 622 } 623 624 private void failForBigNumbers(final TarArchiveEntry entry) { 625 failForBigNumber("entry size", entry.getSize(), TarConstants.MAXSIZE); 626 failForBigNumberWithPosixMessage("group id", entry.getLongGroupId(), TarConstants.MAXID); 627 failForBigNumber("last modification time", 628 entry.getModTime().getTime() / 1000, 629 TarConstants.MAXSIZE); 630 failForBigNumber("user id", entry.getLongUserId(), TarConstants.MAXID); 631 failForBigNumber("mode", entry.getMode(), TarConstants.MAXID); 632 failForBigNumber("major device number", entry.getDevMajor(), 633 TarConstants.MAXID); 634 failForBigNumber("minor device number", entry.getDevMinor(), 635 TarConstants.MAXID); 636 } 637 638 private void failForBigNumber(final String field, final long value, final long maxValue) { 639 failForBigNumber(field, value, maxValue, ""); 640 } 641 642 private void failForBigNumberWithPosixMessage(final String field, final long value, 643 final long maxValue) { 644 failForBigNumber(field, value, maxValue, 645 " Use STAR or POSIX extensions to overcome this limit"); 646 } 647 648 private void failForBigNumber(final String field, final long value, final long maxValue, 649 final String additionalMsg) { 650 if (value < 0 || value > maxValue) { 651 throw new IllegalArgumentException(field + " '" + value //NOSONAR 652 + "' is too big ( > " 653 + maxValue + " )." + additionalMsg); 654 } 655 } 656 657 /** 658 * Handles long file or link names according to the longFileMode setting. 659 * 660 * <p>I.e. if the given name is too long to be written to a plain tar header then <ul> <li>it 661 * creates a pax header who's name is given by the paxHeaderName parameter if longFileMode is 662 * POSIX</li> <li>it creates a GNU longlink entry who's type is given by the linkType parameter 663 * if longFileMode is GNU</li> <li>it throws an exception if longFileMode is ERROR</li> <li>it 664 * truncates the name if longFileMode is TRUNCATE</li> </ul></p> 665 * 666 * @param entry entry the name belongs to 667 * @param name the name to write 668 * @param paxHeaders current map of pax headers 669 * @param paxHeaderName name of the pax header to write 670 * @param linkType type of the GNU entry to write 671 * @param fieldName the name of the field 672 * @throws IllegalArgumentException if the {@link TarArchiveOutputStream#longFileMode} equals 673 * {@link TarArchiveOutputStream#LONGFILE_ERROR} and the file 674 * name is too long 675 * @return whether a pax header has been written. 676 */ 677 private boolean handleLongName(final TarArchiveEntry entry, final String name, 678 final Map<String, String> paxHeaders, 679 final String paxHeaderName, final byte linkType, final String fieldName) 680 throws IOException { 681 final ByteBuffer encodedName = zipEncoding.encode(name); 682 final int len = encodedName.limit() - encodedName.position(); 683 if (len >= TarConstants.NAMELEN) { 684 685 if (longFileMode == LONGFILE_POSIX) { 686 paxHeaders.put(paxHeaderName, name); 687 return true; 688 } 689 if (longFileMode == LONGFILE_GNU) { 690 // create a TarEntry for the LongLink, the contents 691 // of which are the link's name 692 final TarArchiveEntry longLinkEntry = new TarArchiveEntry(TarConstants.GNU_LONGLINK, 693 linkType); 694 695 longLinkEntry.setSize(len + 1L); // +1 for NUL 696 transferModTime(entry, longLinkEntry); 697 putArchiveEntry(longLinkEntry); 698 write(encodedName.array(), encodedName.arrayOffset(), len); 699 write(0); // NUL terminator 700 closeArchiveEntry(); 701 } else if (longFileMode != LONGFILE_TRUNCATE) { 702 throw new IllegalArgumentException(fieldName + " '" + name //NOSONAR 703 + "' is too long ( > " 704 + TarConstants.NAMELEN + " bytes)"); 705 } 706 } 707 return false; 708 } 709 710 private void transferModTime(final TarArchiveEntry from, final TarArchiveEntry to) { 711 Date fromModTime = from.getModTime(); 712 final long fromModTimeSeconds = fromModTime.getTime() / 1000; 713 if (fromModTimeSeconds < 0 || fromModTimeSeconds > TarConstants.MAXSIZE) { 714 fromModTime = new Date(0); 715 } 716 to.setModTime(fromModTime); 717 } 718}