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