1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package org.apache.commons.compress.archivers.tar; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assert.fail; 27 28 import java.io.ByteArrayInputStream; 29 import java.io.ByteArrayOutputStream; 30 import java.io.File; 31 import java.io.FileInputStream; 32 import java.io.FileOutputStream; 33 import java.io.IOException; 34 import java.io.InputStream; 35 import java.io.InputStreamReader; 36 import java.io.Reader; 37 import java.security.MessageDigest; 38 import java.util.Calendar; 39 import java.util.Date; 40 import java.util.HashMap; 41 import java.util.Map; 42 import java.util.TimeZone; 43 import org.apache.commons.compress.AbstractTestCase; 44 import org.apache.commons.compress.archivers.ArchiveEntry; 45 import org.apache.commons.compress.archivers.ArchiveOutputStream; 46 import org.apache.commons.compress.archivers.ArchiveStreamFactory; 47 import org.apache.commons.compress.utils.CharsetNames; 48 import org.apache.commons.compress.utils.IOUtils; 49 import org.junit.Assert; 50 import org.junit.Test; 51 52 public class TarArchiveOutputStreamTest extends AbstractTestCase { 53 54 @Test 55 public void testCount() throws Exception { 56 final File f = File.createTempFile("commons-compress-tarcount", ".tar"); 57 f.deleteOnExit(); 58 final FileOutputStream fos = new FileOutputStream(f); 59 60 final ArchiveOutputStream tarOut = new ArchiveStreamFactory() 61 .createArchiveOutputStream(ArchiveStreamFactory.TAR, fos); 62 63 final File file1 = getFile("test1.xml"); 64 final TarArchiveEntry sEntry = new TarArchiveEntry(file1, file1.getName()); 65 tarOut.putArchiveEntry(sEntry); 66 67 final FileInputStream in = new FileInputStream(file1); 68 final byte[] buf = new byte[8192]; 69 70 int read = 0; 71 while ((read = in.read(buf)) > 0) { 72 tarOut.write(buf, 0, read); 73 } 74 75 in.close(); 76 tarOut.closeArchiveEntry(); 77 tarOut.close(); 78 79 assertEquals(f.length(), tarOut.getBytesWritten()); 80 } 81 82 @Test 83 public void testMaxFileSizeError() throws Exception { 84 final TarArchiveEntry t = new TarArchiveEntry("foo"); 85 t.setSize(077777777777L); 86 TarArchiveOutputStream tos = 87 new TarArchiveOutputStream(new ByteArrayOutputStream()); 88 tos.putArchiveEntry(t); 89 t.setSize(0100000000000L); 90 tos = new TarArchiveOutputStream(new ByteArrayOutputStream()); 91 try { 92 tos.putArchiveEntry(t); 93 fail("Should have generated RuntimeException"); 94 } catch (final RuntimeException expected) { 95 } 96 } 97 98 @Test 99 public void testBigNumberStarMode() throws Exception { 100 final TarArchiveEntry t = new TarArchiveEntry("foo"); 101 t.setSize(0100000000000L); 102 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 103 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 104 tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR); 105 tos.putArchiveEntry(t); 106 // make sure header is written to byte array 107 tos.write(new byte[10 * 1024]); 108 final byte[] data = bos.toByteArray(); 109 assertEquals(0x80, 110 data[TarConstants.NAMELEN 111 + TarConstants.MODELEN 112 + TarConstants.UIDLEN 113 + TarConstants.GIDLEN] & 0x80); 114 final TarArchiveInputStream tin = 115 new TarArchiveInputStream(new ByteArrayInputStream(data)); 116 final TarArchiveEntry e = tin.getNextTarEntry(); 117 assertEquals(0100000000000L, e.getSize()); 118 tin.close(); 119 // generates IOE because of unclosed entries. 120 // However we don't really want to create such large entries. 121 closeQuietly(tos); 122 } 123 124 @Test 125 public void testBigNumberPosixMode() throws Exception { 126 final TarArchiveEntry t = new TarArchiveEntry("foo"); 127 t.setSize(0100000000000L); 128 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 129 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 130 tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX); 131 tos.putArchiveEntry(t); 132 // make sure header is written to byte array 133 tos.write(new byte[10 * 1024]); 134 final byte[] data = bos.toByteArray(); 135 assertEquals("00000000000 ", 136 new String(data, 137 1024 + TarConstants.NAMELEN 138 + TarConstants.MODELEN 139 + TarConstants.UIDLEN 140 + TarConstants.GIDLEN, 12, 141 CharsetNames.UTF_8)); 142 final TarArchiveInputStream tin = 143 new TarArchiveInputStream(new ByteArrayInputStream(data)); 144 final TarArchiveEntry e = tin.getNextTarEntry(); 145 assertEquals(0100000000000L, e.getSize()); 146 tin.close(); 147 // generates IOE because of unclosed entries. 148 // However we don't really want to create such large entries. 149 closeQuietly(tos); 150 } 151 152 @Test 153 public void testWriteSimplePaxHeaders() throws Exception { 154 final Map<String, String> m = new HashMap<>(); 155 m.put("a", "b"); 156 final byte[] data = writePaxHeader(m); 157 assertEquals("00000000006 ", 158 new String(data, TarConstants.NAMELEN 159 + TarConstants.MODELEN 160 + TarConstants.UIDLEN 161 + TarConstants.GIDLEN, 12, 162 CharsetNames.UTF_8)); 163 assertEquals("6 a=b\n", new String(data, 512, 6, CharsetNames.UTF_8)); 164 } 165 166 @Test 167 public void testPaxHeadersWithLength99() throws Exception { 168 final Map<String, String> m = new HashMap<>(); 169 m.put("a", 170 "0123456789012345678901234567890123456789" 171 + "01234567890123456789012345678901234567890123456789" 172 + "012"); 173 final byte[] data = writePaxHeader(m); 174 assertEquals("00000000143 ", 175 new String(data, TarConstants.NAMELEN 176 + TarConstants.MODELEN 177 + TarConstants.UIDLEN 178 + TarConstants.GIDLEN, 12, 179 CharsetNames.UTF_8)); 180 assertEquals("99 a=0123456789012345678901234567890123456789" 181 + "01234567890123456789012345678901234567890123456789" 182 + "012\n", new String(data, 512, 99, CharsetNames.UTF_8)); 183 } 184 185 @Test 186 public void testPaxHeadersWithLength101() throws Exception { 187 final Map<String, String> m = new HashMap<>(); 188 m.put("a", 189 "0123456789012345678901234567890123456789" 190 + "01234567890123456789012345678901234567890123456789" 191 + "0123"); 192 final byte[] data = writePaxHeader(m); 193 assertEquals("00000000145 ", 194 new String(data, TarConstants.NAMELEN 195 + TarConstants.MODELEN 196 + TarConstants.UIDLEN 197 + TarConstants.GIDLEN, 12, 198 CharsetNames.UTF_8)); 199 assertEquals("101 a=0123456789012345678901234567890123456789" 200 + "01234567890123456789012345678901234567890123456789" 201 + "0123\n", new String(data, 512, 101, CharsetNames.UTF_8)); 202 } 203 204 private byte[] writePaxHeader(final Map<String, String> m) throws Exception { 205 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 206 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 207 tos.writePaxHeaders(new TarArchiveEntry("x"), "foo", m); 208 209 // add a dummy entry so data gets written 210 final TarArchiveEntry t = new TarArchiveEntry("foo"); 211 t.setSize(10 * 1024); 212 tos.putArchiveEntry(t); 213 tos.write(new byte[10 * 1024]); 214 tos.closeArchiveEntry(); 215 tos.close(); 216 217 return bos.toByteArray(); 218 } 219 220 @Test 221 public void testWriteLongFileNamePosixMode() throws Exception { 222 final String n = "01234567890123456789012345678901234567890123456789" 223 + "01234567890123456789012345678901234567890123456789" 224 + "01234567890123456789012345678901234567890123456789"; 225 final TarArchiveEntry t = 226 new TarArchiveEntry(n); 227 t.setSize(10 * 1024); 228 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 229 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 230 tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); 231 tos.putArchiveEntry(t); 232 tos.write(new byte[10 * 1024]); 233 tos.closeArchiveEntry(); 234 final byte[] data = bos.toByteArray(); 235 assertEquals("160 path=" + n + "\n", 236 new String(data, 512, 160, CharsetNames.UTF_8)); 237 final TarArchiveInputStream tin = 238 new TarArchiveInputStream(new ByteArrayInputStream(data)); 239 final TarArchiveEntry e = tin.getNextTarEntry(); 240 assertEquals(n, e.getName()); 241 tin.close(); 242 tos.close(); 243 } 244 245 @Test 246 public void testOldEntryStarMode() throws Exception { 247 final TarArchiveEntry t = new TarArchiveEntry("foo"); 248 t.setSize(Integer.MAX_VALUE); 249 t.setModTime(-1000); 250 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 251 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 252 tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR); 253 tos.putArchiveEntry(t); 254 // make sure header is written to byte array 255 tos.write(new byte[10 * 1024]); 256 final byte[] data = bos.toByteArray(); 257 assertEquals((byte) 0xff, 258 data[TarConstants.NAMELEN 259 + TarConstants.MODELEN 260 + TarConstants.UIDLEN 261 + TarConstants.GIDLEN 262 + TarConstants.SIZELEN]); 263 final TarArchiveInputStream tin = 264 new TarArchiveInputStream(new ByteArrayInputStream(data)); 265 final TarArchiveEntry e = tin.getNextTarEntry(); 266 final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 267 cal.set(1969, 11, 31, 23, 59, 59); 268 cal.set(Calendar.MILLISECOND, 0); 269 assertEquals(cal.getTime(), e.getLastModifiedDate()); 270 tin.close(); 271 // generates IOE because of unclosed entries. 272 // However we don't really want to create such large entries. 273 closeQuietly(tos); 274 } 275 276 @Test 277 public void testOldEntryPosixMode() throws Exception { 278 final TarArchiveEntry t = new TarArchiveEntry("foo"); 279 t.setSize(Integer.MAX_VALUE); 280 t.setModTime(-1000); 281 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 282 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 283 tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX); 284 tos.putArchiveEntry(t); 285 // make sure header is written to byte array 286 tos.write(new byte[10 * 1024]); 287 final byte[] data = bos.toByteArray(); 288 assertEquals("00000000000 ", 289 new String(data, 290 1024 + TarConstants.NAMELEN 291 + TarConstants.MODELEN 292 + TarConstants.UIDLEN 293 + TarConstants.GIDLEN 294 + TarConstants.SIZELEN, 12, 295 CharsetNames.UTF_8)); 296 final TarArchiveInputStream tin = 297 new TarArchiveInputStream(new ByteArrayInputStream(data)); 298 final TarArchiveEntry e = tin.getNextTarEntry(); 299 final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); 300 cal.set(1969, 11, 31, 23, 59, 59); 301 cal.set(Calendar.MILLISECOND, 0); 302 assertEquals(cal.getTime(), e.getLastModifiedDate()); 303 tin.close(); 304 // generates IOE because of unclosed entries. 305 // However we don't really want to create such large entries. 306 closeQuietly(tos); 307 } 308 309 @Test 310 public void testOldEntryError() throws Exception { 311 final TarArchiveEntry t = new TarArchiveEntry("foo"); 312 t.setSize(Integer.MAX_VALUE); 313 t.setModTime(-1000); 314 final TarArchiveOutputStream tos = 315 new TarArchiveOutputStream(new ByteArrayOutputStream()); 316 try { 317 tos.putArchiveEntry(t); 318 fail("Should have generated RuntimeException"); 319 } catch (final RuntimeException expected) { 320 } 321 tos.close(); 322 } 323 324 @Test 325 public void testWriteNonAsciiPathNamePaxHeader() throws Exception { 326 final String n = "\u00e4"; 327 final TarArchiveEntry t = new TarArchiveEntry(n); 328 t.setSize(10 * 1024); 329 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 330 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 331 tos.setAddPaxHeadersForNonAsciiNames(true); 332 tos.putArchiveEntry(t); 333 tos.write(new byte[10 * 1024]); 334 tos.closeArchiveEntry(); 335 tos.close(); 336 final byte[] data = bos.toByteArray(); 337 assertEquals("11 path=" + n + "\n", 338 new String(data, 512, 11, CharsetNames.UTF_8)); 339 final TarArchiveInputStream tin = 340 new TarArchiveInputStream(new ByteArrayInputStream(data)); 341 final TarArchiveEntry e = tin.getNextTarEntry(); 342 assertEquals(n, e.getName()); 343 tin.close(); 344 } 345 346 @Test 347 public void testWriteNonAsciiLinkPathNamePaxHeader() throws Exception { 348 final String n = "\u00e4"; 349 final TarArchiveEntry t = new TarArchiveEntry("a", TarConstants.LF_LINK); 350 t.setSize(10 * 1024); 351 t.setLinkName(n); 352 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 353 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 354 tos.setAddPaxHeadersForNonAsciiNames(true); 355 tos.putArchiveEntry(t); 356 tos.write(new byte[10 * 1024]); 357 tos.closeArchiveEntry(); 358 tos.close(); 359 final byte[] data = bos.toByteArray(); 360 assertEquals("15 linkpath=" + n + "\n", 361 new String(data, 512, 15, CharsetNames.UTF_8)); 362 final TarArchiveInputStream tin = 363 new TarArchiveInputStream(new ByteArrayInputStream(data)); 364 final TarArchiveEntry e = tin.getNextTarEntry(); 365 assertEquals(n, e.getLinkName()); 366 tin.close(); 367 } 368 369 /** 370 * @see "https://issues.apache.org/jira/browse/COMPRESS-200" 371 */ 372 @Test 373 public void testRoundtripWith67CharFileNameGnu() throws Exception { 374 testRoundtripWith67CharFileName(TarArchiveOutputStream.LONGFILE_GNU); 375 } 376 377 /** 378 * @see "https://issues.apache.org/jira/browse/COMPRESS-200" 379 */ 380 @Test 381 public void testRoundtripWith67CharFileNamePosix() throws Exception { 382 testRoundtripWith67CharFileName(TarArchiveOutputStream.LONGFILE_POSIX); 383 } 384 385 private void testRoundtripWith67CharFileName(final int mode) throws Exception { 386 final String n = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" 387 + "AAAAAAA"; 388 assertEquals(67, n.length()); 389 final TarArchiveEntry t = new TarArchiveEntry(n); 390 t.setSize(10 * 1024); 391 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 392 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 393 tos.setLongFileMode(mode); 394 tos.putArchiveEntry(t); 395 tos.write(new byte[10 * 1024]); 396 tos.closeArchiveEntry(); 397 tos.close(); 398 final byte[] data = bos.toByteArray(); 399 final TarArchiveInputStream tin = 400 new TarArchiveInputStream(new ByteArrayInputStream(data)); 401 final TarArchiveEntry e = tin.getNextTarEntry(); 402 assertEquals(n, e.getName()); 403 tin.close(); 404 } 405 406 @Test 407 public void testWriteLongDirectoryNameErrorMode() throws Exception { 408 final String n = "01234567890123456789012345678901234567890123456789" 409 + "01234567890123456789012345678901234567890123456789" 410 + "01234567890123456789012345678901234567890123456789/"; 411 412 try { 413 final TarArchiveEntry t = new TarArchiveEntry(n); 414 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 415 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 416 tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_ERROR); 417 tos.putArchiveEntry(t); 418 tos.closeArchiveEntry(); 419 tos.close(); 420 421 fail("Truncated name didn't throw an exception"); 422 } catch (final RuntimeException e) { 423 // expected 424 } 425 } 426 427 @Test 428 public void testWriteLongDirectoryNameTruncateMode() throws Exception { 429 final String n = "01234567890123456789012345678901234567890123456789" 430 + "01234567890123456789012345678901234567890123456789" 431 + "01234567890123456789012345678901234567890123456789/"; 432 final TarArchiveEntry t = new TarArchiveEntry(n); 433 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 434 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 435 tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_TRUNCATE); 436 tos.putArchiveEntry(t); 437 tos.closeArchiveEntry(); 438 tos.close(); 439 final byte[] data = bos.toByteArray(); 440 final TarArchiveInputStream tin = 441 new TarArchiveInputStream(new ByteArrayInputStream(data)); 442 final TarArchiveEntry e = tin.getNextTarEntry(); 443 assertEquals("Entry name", n.substring(0, TarConstants.NAMELEN) + "/", e.getName()); 444 assertTrue("The entry is not a directory", e.isDirectory()); 445 tin.close(); 446 } 447 448 /** 449 * @see "https://issues.apache.org/jira/browse/COMPRESS-203" 450 */ 451 @Test 452 public void testWriteLongDirectoryNameGnuMode() throws Exception { 453 testWriteLongDirectoryName(TarArchiveOutputStream.LONGFILE_GNU); 454 } 455 456 /** 457 * @see "https://issues.apache.org/jira/browse/COMPRESS-203" 458 */ 459 @Test 460 public void testWriteLongDirectoryNamePosixMode() throws Exception { 461 testWriteLongDirectoryName(TarArchiveOutputStream.LONGFILE_POSIX); 462 } 463 464 private void testWriteLongDirectoryName(final int mode) throws Exception { 465 final String n = "01234567890123456789012345678901234567890123456789" 466 + "01234567890123456789012345678901234567890123456789" 467 + "01234567890123456789012345678901234567890123456789/"; 468 final TarArchiveEntry t = new TarArchiveEntry(n); 469 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 470 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 471 tos.setLongFileMode(mode); 472 tos.putArchiveEntry(t); 473 tos.closeArchiveEntry(); 474 tos.close(); 475 final byte[] data = bos.toByteArray(); 476 final TarArchiveInputStream tin = 477 new TarArchiveInputStream(new ByteArrayInputStream(data)); 478 final TarArchiveEntry e = tin.getNextTarEntry(); 479 assertEquals(n, e.getName()); 480 assertTrue(e.isDirectory()); 481 tin.close(); 482 } 483 484 /** 485 * @see "https://issues.apache.org/jira/browse/COMPRESS-203" 486 */ 487 @Test 488 public void testWriteNonAsciiDirectoryNamePosixMode() throws Exception { 489 final String n = "f\u00f6\u00f6/"; 490 final TarArchiveEntry t = new TarArchiveEntry(n); 491 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 492 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 493 tos.setAddPaxHeadersForNonAsciiNames(true); 494 tos.putArchiveEntry(t); 495 tos.closeArchiveEntry(); 496 tos.close(); 497 final byte[] data = bos.toByteArray(); 498 final TarArchiveInputStream tin = 499 new TarArchiveInputStream(new ByteArrayInputStream(data)); 500 final TarArchiveEntry e = tin.getNextTarEntry(); 501 assertEquals(n, e.getName()); 502 assertTrue(e.isDirectory()); 503 tin.close(); 504 } 505 506 /** 507 * @see "https://issues.apache.org/jira/browse/COMPRESS-265" 508 */ 509 @Test 510 public void testWriteNonAsciiNameWithUnfortunateNamePosixMode() throws Exception { 511 final String n = "f\u00f6\u00f6\u00dc"; 512 final TarArchiveEntry t = new TarArchiveEntry(n); 513 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 514 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 515 tos.setAddPaxHeadersForNonAsciiNames(true); 516 tos.putArchiveEntry(t); 517 tos.closeArchiveEntry(); 518 tos.close(); 519 final byte[] data = bos.toByteArray(); 520 final TarArchiveInputStream tin = 521 new TarArchiveInputStream(new ByteArrayInputStream(data)); 522 final TarArchiveEntry e = tin.getNextTarEntry(); 523 assertEquals(n, e.getName()); 524 assertFalse(e.isDirectory()); 525 tin.close(); 526 } 527 528 /** 529 * @see "https://issues.apache.org/jira/browse/COMPRESS-237" 530 */ 531 @Test 532 public void testWriteLongLinkNameErrorMode() throws Exception { 533 final String linkname = "01234567890123456789012345678901234567890123456789" 534 + "01234567890123456789012345678901234567890123456789" 535 + "01234567890123456789012345678901234567890123456789/test"; 536 final TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK); 537 entry.setLinkName(linkname); 538 539 try { 540 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 541 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 542 tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_ERROR); 543 tos.putArchiveEntry(entry); 544 tos.closeArchiveEntry(); 545 tos.close(); 546 547 fail("Truncated link name didn't throw an exception"); 548 } catch (final RuntimeException e) { 549 // expected 550 } 551 } 552 553 @Test 554 public void testWriteLongLinkNameTruncateMode() throws Exception { 555 final String linkname = "01234567890123456789012345678901234567890123456789" 556 + "01234567890123456789012345678901234567890123456789" 557 + "01234567890123456789012345678901234567890123456789/"; 558 final TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK); 559 entry.setLinkName(linkname); 560 561 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 562 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 563 tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_TRUNCATE); 564 tos.putArchiveEntry(entry); 565 tos.closeArchiveEntry(); 566 tos.close(); 567 568 final byte[] data = bos.toByteArray(); 569 final TarArchiveInputStream tin = new TarArchiveInputStream(new ByteArrayInputStream(data)); 570 final TarArchiveEntry e = tin.getNextTarEntry(); 571 assertEquals("Link name", linkname.substring(0, TarConstants.NAMELEN), e.getLinkName()); 572 tin.close(); 573 } 574 575 /** 576 * @see "https://issues.apache.org/jira/browse/COMPRESS-237" 577 */ 578 @Test 579 public void testWriteLongLinkNameGnuMode() throws Exception { 580 testWriteLongLinkName(TarArchiveOutputStream.LONGFILE_GNU); 581 } 582 583 /** 584 * @see "https://issues.apache.org/jira/browse/COMPRESS-237" 585 */ 586 @Test 587 public void testWriteLongLinkNamePosixMode() throws Exception { 588 testWriteLongLinkName(TarArchiveOutputStream.LONGFILE_POSIX); 589 } 590 591 /** 592 * @see "https://issues.apache.org/jira/browse/COMPRESS-237" 593 */ 594 private void testWriteLongLinkName(final int mode) throws Exception { 595 final String linkname = "01234567890123456789012345678901234567890123456789" 596 + "01234567890123456789012345678901234567890123456789" 597 + "01234567890123456789012345678901234567890123456789/test"; 598 final TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK); 599 entry.setLinkName(linkname); 600 601 final ByteArrayOutputStream bos = new ByteArrayOutputStream(); 602 final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII"); 603 tos.setLongFileMode(mode); 604 tos.putArchiveEntry(entry); 605 tos.closeArchiveEntry(); 606 tos.close(); 607 608 final byte[] data = bos.toByteArray(); 609 final TarArchiveInputStream tin = new TarArchiveInputStream(new ByteArrayInputStream(data)); 610 final TarArchiveEntry e = tin.getNextTarEntry(); 611 assertEquals("Entry name", "test", e.getName()); 612 assertEquals("Link name", linkname, e.getLinkName()); 613 assertTrue("The entry is not a symbolic link", e.isSymbolicLink()); 614 tin.close(); 615 } 616 617 @SuppressWarnings("deprecation") 618 @Test public void testRecordSize() throws IOException { 619 try { 620 TarArchiveOutputStream tos = 621 new TarArchiveOutputStream(new ByteArrayOutputStream(),512,511); 622 fail("should have rejected recordSize of 511"); 623 } catch(IllegalArgumentException e) { 624 // expected; 625 } 626 try { 627 TarArchiveOutputStream tos = 628 new TarArchiveOutputStream(new ByteArrayOutputStream(),512,511,null); 629 fail("should have rejected recordSize of 511"); 630 } catch(IllegalArgumentException e) { 631 // expected; 632 } 633 try (TarArchiveOutputStream tos = new TarArchiveOutputStream(new ByteArrayOutputStream(), 634 512, 512)) { 635 assertEquals("recordSize",512,tos.getRecordSize()); 636 } 637 try (TarArchiveOutputStream tos = new TarArchiveOutputStream(new ByteArrayOutputStream(), 638 512, 512, null)) { 639 assertEquals("recordSize",512,tos.getRecordSize()); 640 } 641 } 642 @Test 643 public void testBlockSizes() throws Exception { 644 String fileName = "/test1.xml"; 645 byte[] contents = getResourceContents(fileName); 646 testPadding(TarConstants.DEFAULT_BLKSIZE, fileName, contents); // USTAR / pre-pax 647 testPadding(5120, fileName, contents); // PAX default 648 testPadding(1<<15, fileName, contents); //PAX max 649 testPadding(-2, fileName, contents); // don't specify a block size -> use minimum length 650 try { 651 testPadding(511, fileName, contents); // don't specify a block size -> use minimum length 652 fail("should have thrown an illegal argument exception"); 653 } catch (IllegalArgumentException e) { 654 //expected 655 } 656 try { 657 testPadding(0, fileName, contents); // don't specify a block size -> use minimum length 658 fail("should have thrown an illegal argument exception"); 659 } catch (IllegalArgumentException e) { 660 //expected 661 } 662 // test with "content" that is an exact multiple of record length 663 contents = new byte[2048]; 664 java.util.Arrays.fill(contents, (byte) 42); 665 testPadding(TarConstants.DEFAULT_BLKSIZE, fileName, contents); 666 } 667 668 private void testPadding(int blockSize, String fileName, byte[] contents) throws IOException { 669 final File f = File.createTempFile("commons-compress-padding", ".tar"); 670 f.deleteOnExit(); 671 final FileOutputStream fos = new FileOutputStream(f); 672 final TarArchiveOutputStream tos; 673 if (blockSize != -2) { 674 tos = new TarArchiveOutputStream(fos, blockSize); 675 } else { 676 blockSize = 512; 677 tos = new TarArchiveOutputStream(fos); 678 } 679 TarArchiveEntry sEntry; 680 sEntry = new TarArchiveEntry(fileName); 681 sEntry.setSize(contents.length); 682 tos.putArchiveEntry(sEntry); 683 tos.write(contents); 684 tos.closeArchiveEntry(); 685 tos.close(); 686 int fileRecordsSize = (int) Math.ceil((double) contents.length / 512) * 512; 687 final int headerSize = 512; 688 final int endOfArchiveSize = 1024; 689 int unpaddedSize = headerSize + fileRecordsSize + endOfArchiveSize; 690 int paddedSize = (int) Math.ceil((double)unpaddedSize/blockSize)*blockSize; 691 assertEquals(paddedSize, f.length()); 692 } 693 694 private byte[] getResourceContents(String name) throws IOException { 695 ByteArrayOutputStream bos; 696 try (InputStream resourceAsStream = getClass().getResourceAsStream(name)) { 697 bos = new ByteArrayOutputStream(); 698 IOUtils.copy(resourceAsStream, bos); 699 } 700 return bos.toByteArray(); 701 } 702 @Test public void testPutGlobalPaxHeaderEntry() throws IOException { 703 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 704 TarArchiveOutputStream tos = new TarArchiveOutputStream(bos); 705 int pid = 73; 706 int globCount = 1; 707 byte lfPaxGlobalExtendedHeader = TarConstants.LF_PAX_GLOBAL_EXTENDED_HEADER; 708 TarArchiveEntry globalHeader = new TarArchiveEntry("/tmp/GlobalHead." + pid + "." + globCount, 709 lfPaxGlobalExtendedHeader); 710 globalHeader.addPaxHeader("SCHILLY.xattr.user.org.apache.weasels","global-weasels"); 711 tos.putArchiveEntry(globalHeader); 712 TarArchiveEntry entry = new TarArchiveEntry("message"); 713 String x = "If at first you don't succeed, give up"; 714 entry.setSize(x.length()); 715 tos.putArchiveEntry(entry); 716 tos.write(x.getBytes()); 717 tos.closeArchiveEntry(); 718 entry = new TarArchiveEntry("counter-message"); 719 String y = "Nothing succeeds like excess"; 720 entry.setSize(y.length()); 721 entry.addPaxHeader("SCHILLY.xattr.user.org.apache.weasels.species","unknown"); 722 tos.putArchiveEntry(entry); 723 tos.write(y.getBytes()); 724 tos.closeArchiveEntry(); 725 tos.close(); 726 TarArchiveInputStream in = new TarArchiveInputStream(new ByteArrayInputStream(bos.toByteArray())); 727 TarArchiveEntry entryIn = in.getNextTarEntry(); 728 assertNotNull(entryIn); 729 assertEquals("message",entryIn.getName()); 730 assertEquals("global-weasels",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels")); 731 Reader reader = new InputStreamReader(in); 732 for(int i=0;i<x.length();i++) { 733 assertEquals(x.charAt(i),reader.read()); 734 } 735 assertEquals(-1,reader.read()); 736 entryIn = in.getNextTarEntry(); 737 assertEquals("counter-message",entryIn.getName()); 738 assertEquals("global-weasels",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels")); 739 assertEquals("unknown",entryIn.getExtraPaxHeader("SCHILLY.xattr.user.org.apache.weasels.species")); 740 assertNull(in.getNextTarEntry()); 741 } 742 743 /** 744 * When using long file names the longLinkEntry included the current timestamp as the Entry 745 * modification date. This was never exposed to the client but it caused identical archives to 746 * have different MD5 hashes. 747 */ 748 @Test 749 public void testLongNameMd5Hash() throws Exception { 750 final String longFileName = "a/considerably/longer/file/name/which/forces/use/of/the/long/link/header/which/appears/to/always/use/the/current/time/as/modification/date"; 751 final String fname = longFileName; 752 final Date modificationDate = new Date(); 753 754 final byte[] archive1 = createTarArchiveContainingOneDirectory(fname, modificationDate); 755 final byte[] digest1 = MessageDigest.getInstance("MD5").digest(archive1); 756 757 // let a second elapse otherwise the modification dates will be equal 758 Thread.sleep(1000L); 759 760 // now recreate exactly the same tar file 761 final byte[] archive2 = createTarArchiveContainingOneDirectory(fname, modificationDate); 762 // and I would expect the MD5 hash to be the same, but for long names it isn't 763 final byte[] digest2 = MessageDigest.getInstance("MD5").digest(archive2); 764 765 Assert.assertArrayEquals(digest1, digest2); 766 767 // do I still have the correct modification date? 768 // let a second elapse so we don't get the current time 769 Thread.sleep(1000); 770 final TarArchiveInputStream tarIn = new TarArchiveInputStream( 771 new ByteArrayInputStream(archive2)); 772 final ArchiveEntry nextEntry = tarIn.getNextEntry(); 773 assertEquals(longFileName, nextEntry.getName()); 774 // tar archive stores modification time to second granularity only (floored) 775 assertEquals(modificationDate.getTime() / 1000, 776 nextEntry.getLastModifiedDate().getTime() / 1000); 777 tarIn.close(); 778 } 779 780 private static byte[] createTarArchiveContainingOneDirectory(final String fname, 781 final Date modificationDate) throws IOException { 782 final ByteArrayOutputStream baos = new ByteArrayOutputStream(); 783 final TarArchiveOutputStream tarOut = new TarArchiveOutputStream(baos, 1024); 784 tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU); 785 final TarArchiveEntry tarEntry = new TarArchiveEntry("d"); 786 tarEntry.setModTime(modificationDate); 787 tarEntry.setMode(TarArchiveEntry.DEFAULT_DIR_MODE); 788 tarEntry.setModTime(modificationDate.getTime()); 789 tarEntry.setName(fname); 790 tarOut.putArchiveEntry(tarEntry); 791 tarOut.closeArchiveEntry(); 792 tarOut.close(); 793 794 return baos.toByteArray(); 795 } 796 797 } 798