blob: 85cdb66a30fbf956a537628095cf909f600f9ce5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.commons.compress.archivers.tar;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import org.apache.commons.compress.AbstractTestCase;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.utils.CharsetNames;
import org.apache.commons.compress.utils.IOUtils;
import org.junit.Assert;
import org.junit.Test;
public class TarArchiveOutputStreamTest extends AbstractTestCase {
@Test
public void testCount() throws Exception {
final File f = File.createTempFile("commons-compress-tarcount", ".tar");
f.deleteOnExit();
final FileOutputStream fos = new FileOutputStream(f);
final ArchiveOutputStream tarOut = new ArchiveStreamFactory()
.createArchiveOutputStream(ArchiveStreamFactory.TAR, fos);
final File file1 = getFile("test1.xml");
final TarArchiveEntry sEntry = new TarArchiveEntry(file1, file1.getName());
tarOut.putArchiveEntry(sEntry);
final FileInputStream in = new FileInputStream(file1);
final byte[] buf = new byte[8192];
int read = 0;
while ((read = in.read(buf)) > 0) {
tarOut.write(buf, 0, read);
}
in.close();
tarOut.closeArchiveEntry();
tarOut.close();
assertEquals(f.length(), tarOut.getBytesWritten());
}
@Test
public void testMaxFileSizeError() throws Exception {
final TarArchiveEntry t = new TarArchiveEntry("foo");
t.setSize(077777777777L);
TarArchiveOutputStream tos =
new TarArchiveOutputStream(new ByteArrayOutputStream());
tos.putArchiveEntry(t);
t.setSize(0100000000000L);
tos = new TarArchiveOutputStream(new ByteArrayOutputStream());
try {
tos.putArchiveEntry(t);
fail("Should have generated RuntimeException");
} catch (final RuntimeException expected) {
}
}
@Test
public void testBigNumberStarMode() throws Exception {
final TarArchiveEntry t = new TarArchiveEntry("foo");
t.setSize(0100000000000L);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
tos.putArchiveEntry(t);
// make sure header is written to byte array
tos.write(new byte[10 * 1024]);
final byte[] data = bos.toByteArray();
assertEquals(0x80,
data[TarConstants.NAMELEN
+ TarConstants.MODELEN
+ TarConstants.UIDLEN
+ TarConstants.GIDLEN] & 0x80);
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(0100000000000L, e.getSize());
tin.close();
// generates IOE because of unclosed entries.
// However we don't really want to create such large entries.
closeQuietly(tos);
}
@Test
public void testBigNumberPosixMode() throws Exception {
final TarArchiveEntry t = new TarArchiveEntry("foo");
t.setSize(0100000000000L);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
tos.putArchiveEntry(t);
// make sure header is written to byte array
tos.write(new byte[10 * 1024]);
final byte[] data = bos.toByteArray();
assertEquals("00000000000 ",
new String(data,
1024 + TarConstants.NAMELEN
+ TarConstants.MODELEN
+ TarConstants.UIDLEN
+ TarConstants.GIDLEN, 12,
CharsetNames.UTF_8));
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(0100000000000L, e.getSize());
tin.close();
// generates IOE because of unclosed entries.
// However we don't really want to create such large entries.
closeQuietly(tos);
}
@Test
public void testWriteSimplePaxHeaders() throws Exception {
final Map<String, String> m = new HashMap<>();
m.put("a", "b");
final byte[] data = writePaxHeader(m);
assertEquals("00000000006 ",
new String(data, TarConstants.NAMELEN
+ TarConstants.MODELEN
+ TarConstants.UIDLEN
+ TarConstants.GIDLEN, 12,
CharsetNames.UTF_8));
assertEquals("6 a=b\n", new String(data, 512, 6, CharsetNames.UTF_8));
}
@Test
public void testPaxHeadersWithLength99() throws Exception {
final Map<String, String> m = new HashMap<>();
m.put("a",
"0123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "012");
final byte[] data = writePaxHeader(m);
assertEquals("00000000143 ",
new String(data, TarConstants.NAMELEN
+ TarConstants.MODELEN
+ TarConstants.UIDLEN
+ TarConstants.GIDLEN, 12,
CharsetNames.UTF_8));
assertEquals("99 a=0123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "012\n", new String(data, 512, 99, CharsetNames.UTF_8));
}
@Test
public void testPaxHeadersWithLength101() throws Exception {
final Map<String, String> m = new HashMap<>();
m.put("a",
"0123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "0123");
final byte[] data = writePaxHeader(m);
assertEquals("00000000145 ",
new String(data, TarConstants.NAMELEN
+ TarConstants.MODELEN
+ TarConstants.UIDLEN
+ TarConstants.GIDLEN, 12,
CharsetNames.UTF_8));
assertEquals("101 a=0123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "0123\n", new String(data, 512, 101, CharsetNames.UTF_8));
}
private byte[] writePaxHeader(final Map<String, String> m) throws Exception {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.writePaxHeaders(new TarArchiveEntry("x"), "foo", m);
// add a dummy entry so data gets written
final TarArchiveEntry t = new TarArchiveEntry("foo");
t.setSize(10 * 1024);
tos.putArchiveEntry(t);
tos.write(new byte[10 * 1024]);
tos.closeArchiveEntry();
tos.close();
return bos.toByteArray();
}
@Test
public void testWriteLongFileNamePosixMode() throws Exception {
final String n = "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789";
final TarArchiveEntry t =
new TarArchiveEntry(n);
t.setSize(10 * 1024);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
tos.putArchiveEntry(t);
tos.write(new byte[10 * 1024]);
tos.closeArchiveEntry();
final byte[] data = bos.toByteArray();
assertEquals("160 path=" + n + "\n",
new String(data, 512, 160, CharsetNames.UTF_8));
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(n, e.getName());
tin.close();
tos.close();
}
@Test
public void testOldEntryStarMode() throws Exception {
final TarArchiveEntry t = new TarArchiveEntry("foo");
t.setSize(Integer.MAX_VALUE);
t.setModTime(-1000);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR);
tos.putArchiveEntry(t);
// make sure header is written to byte array
tos.write(new byte[10 * 1024]);
final byte[] data = bos.toByteArray();
assertEquals((byte) 0xff,
data[TarConstants.NAMELEN
+ TarConstants.MODELEN
+ TarConstants.UIDLEN
+ TarConstants.GIDLEN
+ TarConstants.SIZELEN]);
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.set(1969, 11, 31, 23, 59, 59);
cal.set(Calendar.MILLISECOND, 0);
assertEquals(cal.getTime(), e.getLastModifiedDate());
tin.close();
// generates IOE because of unclosed entries.
// However we don't really want to create such large entries.
closeQuietly(tos);
}
@Test
public void testOldEntryPosixMode() throws Exception {
final TarArchiveEntry t = new TarArchiveEntry("foo");
t.setSize(Integer.MAX_VALUE);
t.setModTime(-1000);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
tos.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_POSIX);
tos.putArchiveEntry(t);
// make sure header is written to byte array
tos.write(new byte[10 * 1024]);
final byte[] data = bos.toByteArray();
assertEquals("00000000000 ",
new String(data,
1024 + TarConstants.NAMELEN
+ TarConstants.MODELEN
+ TarConstants.UIDLEN
+ TarConstants.GIDLEN
+ TarConstants.SIZELEN, 12,
CharsetNames.UTF_8));
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
final Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.set(1969, 11, 31, 23, 59, 59);
cal.set(Calendar.MILLISECOND, 0);
assertEquals(cal.getTime(), e.getLastModifiedDate());
tin.close();
// generates IOE because of unclosed entries.
// However we don't really want to create such large entries.
closeQuietly(tos);
}
@Test
public void testOldEntryError() throws Exception {
final TarArchiveEntry t = new TarArchiveEntry("foo");
t.setSize(Integer.MAX_VALUE);
t.setModTime(-1000);
final TarArchiveOutputStream tos =
new TarArchiveOutputStream(new ByteArrayOutputStream());
try {
tos.putArchiveEntry(t);
fail("Should have generated RuntimeException");
} catch (final RuntimeException expected) {
}
tos.close();
}
@Test
public void testWriteNonAsciiPathNamePaxHeader() throws Exception {
final String n = "\u00e4";
final TarArchiveEntry t = new TarArchiveEntry(n);
t.setSize(10 * 1024);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
tos.setAddPaxHeadersForNonAsciiNames(true);
tos.putArchiveEntry(t);
tos.write(new byte[10 * 1024]);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
assertEquals("11 path=" + n + "\n",
new String(data, 512, 11, CharsetNames.UTF_8));
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(n, e.getName());
tin.close();
}
@Test
public void testWriteNonAsciiLinkPathNamePaxHeader() throws Exception {
final String n = "\u00e4";
final TarArchiveEntry t = new TarArchiveEntry("a", TarConstants.LF_LINK);
t.setSize(10 * 1024);
t.setLinkName(n);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
tos.setAddPaxHeadersForNonAsciiNames(true);
tos.putArchiveEntry(t);
tos.write(new byte[10 * 1024]);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
assertEquals("15 linkpath=" + n + "\n",
new String(data, 512, 15, CharsetNames.UTF_8));
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(n, e.getLinkName());
tin.close();
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-200"
*/
@Test
public void testRoundtripWith67CharFileNameGnu() throws Exception {
testRoundtripWith67CharFileName(TarArchiveOutputStream.LONGFILE_GNU);
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-200"
*/
@Test
public void testRoundtripWith67CharFileNamePosix() throws Exception {
testRoundtripWith67CharFileName(TarArchiveOutputStream.LONGFILE_POSIX);
}
private void testRoundtripWith67CharFileName(final int mode) throws Exception {
final String n = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAA";
assertEquals(67, n.length());
final TarArchiveEntry t = new TarArchiveEntry(n);
t.setSize(10 * 1024);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(mode);
tos.putArchiveEntry(t);
tos.write(new byte[10 * 1024]);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(n, e.getName());
tin.close();
}
@Test
public void testWriteLongDirectoryNameErrorMode() throws Exception {
final String n = "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789/";
try {
final TarArchiveEntry t = new TarArchiveEntry(n);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_ERROR);
tos.putArchiveEntry(t);
tos.closeArchiveEntry();
tos.close();
fail("Truncated name didn't throw an exception");
} catch (final RuntimeException e) {
// expected
}
}
@Test
public void testWriteLongDirectoryNameTruncateMode() throws Exception {
final String n = "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789/";
final TarArchiveEntry t = new TarArchiveEntry(n);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_TRUNCATE);
tos.putArchiveEntry(t);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals("Entry name", n.substring(0, TarConstants.NAMELEN) + "/", e.getName());
assertTrue("The entry is not a directory", e.isDirectory());
tin.close();
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-203"
*/
@Test
public void testWriteLongDirectoryNameGnuMode() throws Exception {
testWriteLongDirectoryName(TarArchiveOutputStream.LONGFILE_GNU);
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-203"
*/
@Test
public void testWriteLongDirectoryNamePosixMode() throws Exception {
testWriteLongDirectoryName(TarArchiveOutputStream.LONGFILE_POSIX);
}
private void testWriteLongDirectoryName(final int mode) throws Exception {
final String n = "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789/";
final TarArchiveEntry t = new TarArchiveEntry(n);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(mode);
tos.putArchiveEntry(t);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(n, e.getName());
assertTrue(e.isDirectory());
tin.close();
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-203"
*/
@Test
public void testWriteNonAsciiDirectoryNamePosixMode() throws Exception {
final String n = "f\u00f6\u00f6/";
final TarArchiveEntry t = new TarArchiveEntry(n);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
tos.setAddPaxHeadersForNonAsciiNames(true);
tos.putArchiveEntry(t);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(n, e.getName());
assertTrue(e.isDirectory());
tin.close();
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-265"
*/
@Test
public void testWriteNonAsciiNameWithUnfortunateNamePosixMode() throws Exception {
final String n = "f\u00f6\u00f6\u00dc";
final TarArchiveEntry t = new TarArchiveEntry(n);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
tos.setAddPaxHeadersForNonAsciiNames(true);
tos.putArchiveEntry(t);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
final TarArchiveInputStream tin =
new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals(n, e.getName());
assertFalse(e.isDirectory());
tin.close();
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-237"
*/
@Test
public void testWriteLongLinkNameErrorMode() throws Exception {
final String linkname = "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789/test";
final TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK);
entry.setLinkName(linkname);
try {
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_ERROR);
tos.putArchiveEntry(entry);
tos.closeArchiveEntry();
tos.close();
fail("Truncated link name didn't throw an exception");
} catch (final RuntimeException e) {
// expected
}
}
@Test
public void testWriteLongLinkNameTruncateMode() throws Exception {
final String linkname = "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789/";
final TarArchiveEntry entry = new TarArchiveEntry("test" , TarConstants.LF_SYMLINK);
entry.setLinkName(linkname);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_TRUNCATE);
tos.putArchiveEntry(entry);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
final TarArchiveInputStream tin = new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals("Link name", linkname.substring(0, TarConstants.NAMELEN), e.getLinkName());
tin.close();
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-237"
*/
@Test
public void testWriteLongLinkNameGnuMode() throws Exception {
testWriteLongLinkName(TarArchiveOutputStream.LONGFILE_GNU);
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-237"
*/
@Test
public void testWriteLongLinkNamePosixMode() throws Exception {
testWriteLongLinkName(TarArchiveOutputStream.LONGFILE_POSIX);
}
/**
* @see "https://issues.apache.org/jira/browse/COMPRESS-237"
*/
private void testWriteLongLinkName(final int mode) throws Exception {
final String linkname = "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789"
+ "01234567890123456789012345678901234567890123456789/test";
final TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK);
entry.setLinkName(linkname);
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
final TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(mode);
tos.putArchiveEntry(entry);
tos.closeArchiveEntry();
tos.close();
final byte[] data = bos.toByteArray();
final TarArchiveInputStream tin = new TarArchiveInputStream(new ByteArrayInputStream(data));
final TarArchiveEntry e = tin.getNextTarEntry();
assertEquals("Entry name", "test", e.getName());
assertEquals("Link name", linkname, e.getLinkName());
assertTrue("The entry is not a symbolic link", e.isSymbolicLink());
tin.close();
}
@Test
public void testPadsOutputToFullBlockLength() throws Exception {
final File f = File.createTempFile("commons-compress-padding", ".tar");
f.deleteOnExit();
final FileOutputStream fos = new FileOutputStream(f);
final TarArchiveOutputStream tos = new TarArchiveOutputStream(fos);
final File file1 = getFile("test1.xml");
final TarArchiveEntry sEntry = new TarArchiveEntry(file1, file1.getName());
tos.putArchiveEntry(sEntry);
final FileInputStream in = new FileInputStream(file1);
IOUtils.copy(in, tos);
in.close();
tos.closeArchiveEntry();
tos.close();
// test1.xml is small enough to fit into the default block size
assertEquals(TarConstants.DEFAULT_BLKSIZE, f.length());
}
/**
* When using long file names the longLinkEntry included the
* current timestamp as the Entry modification date. This was
* never exposed to the client but it caused identical archives to
* have different MD5 hashes.
*
* @throws Exception
*/
@Test
public void testLongNameMd5Hash() throws Exception {
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";
final String fname = longFileName;
final Date modificationDate = new Date();
final byte[] archive1 = createTarArchiveContainingOneDirectory(fname, modificationDate);
final byte[] digest1 = MessageDigest.getInstance("MD5").digest(archive1);
// let a second elapse otherwise the modification dates will be equal
Thread.sleep(1000L);
// now recreate exactly the same tar file
final byte[] archive2 = createTarArchiveContainingOneDirectory(fname, modificationDate);
// and I would expect the MD5 hash to be the same, but for long names it isn't
final byte[] digest2 = MessageDigest.getInstance("MD5").digest(archive2);
Assert.assertArrayEquals(digest1, digest2);
// do I still have the correct modification date?
// let a second elapse so we don't get the current time
Thread.sleep(1000);
final TarArchiveInputStream tarIn = new TarArchiveInputStream(new ByteArrayInputStream(archive2));
final ArchiveEntry nextEntry = tarIn.getNextEntry();
assertEquals(longFileName, nextEntry.getName());
// tar archive stores modification time to second granularity only (floored)
assertEquals(modificationDate.getTime() / 1000, nextEntry.getLastModifiedDate().getTime() / 1000);
tarIn.close();
}
private static byte[] createTarArchiveContainingOneDirectory(final String fname, final Date modificationDate) throws IOException {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final TarArchiveOutputStream tarOut = new TarArchiveOutputStream(baos, 1024);
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
final TarArchiveEntry tarEntry = new TarArchiveEntry("d");
tarEntry.setModTime(modificationDate);
tarEntry.setMode(TarArchiveEntry.DEFAULT_DIR_MODE);
tarEntry.setModTime(modificationDate.getTime());
tarEntry.setName(fname);
tarOut.putArchiveEntry(tarEntry);
tarOut.closeArchiveEntry();
tarOut.close();
return baos.toByteArray();
}
}