COMPRESS-289 use a stable last modified time for long name entry,
based on patch by Bob Robertson
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/compress/trunk@1626280 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java
index 2a5d591..077720a 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStream.java
@@ -271,12 +271,12 @@
TarArchiveEntry entry = (TarArchiveEntry) archiveEntry;
Map<String, String> paxHeaders = new HashMap<String, String>();
final String entryName = entry.getName();
- boolean paxHeaderContainsPath = handleLongName(entryName, paxHeaders, "path",
+ boolean paxHeaderContainsPath = handleLongName(entry, entryName, paxHeaders, "path",
TarConstants.LF_GNUTYPE_LONGNAME, "file name");
final String linkName = entry.getLinkName();
boolean paxHeaderContainsLinkPath = linkName != null && linkName.length() > 0
- && handleLongName(linkName, paxHeaders, "linkpath",
+ && handleLongName(entry, linkName, paxHeaders, "linkpath",
TarConstants.LF_GNUTYPE_LONGLINK, "link name");
if (bigNumberMode == BIGNUMBER_POSIX) {
@@ -637,6 +637,7 @@
* <li>it truncates the name if longFileMode is TRUNCATE</li>
* </ul></p>
*
+ * @param entry entry the name belongs to
* @param name the name to write
* @param paxHeaders current map of pax headers
* @param paxHeaderName name of the pax header to write
@@ -644,7 +645,7 @@
* @param fieldName the name of the field
* @return whether a pax header has been written.
*/
- private boolean handleLongName(String name,
+ private boolean handleLongName(TarArchiveEntry entry , String name,
Map<String, String> paxHeaders,
String paxHeaderName, byte linkType, String fieldName)
throws IOException {
@@ -661,6 +662,7 @@
TarArchiveEntry longLinkEntry = new TarArchiveEntry(TarConstants.GNU_LONGLINK, linkType);
longLinkEntry.setSize(len + 1); // +1 for NUL
+ longLinkEntry.setModTime(entry.getModTime());
putArchiveEntry(longLinkEntry);
write(encodedName.array(), encodedName.arrayOffset(), len);
write(0); // NUL terminator
diff --git a/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStreamTest.java b/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStreamTest.java
index 60017a6..f18f6a5 100644
--- a/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/archivers/tar/TarArchiveOutputStreamTest.java
@@ -23,17 +23,23 @@
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;
+
public class TarArchiveOutputStreamTest extends AbstractTestCase {
public void testCount() throws Exception {
@@ -386,7 +392,7 @@
tos.putArchiveEntry(t);
tos.closeArchiveEntry();
tos.close();
-
+
fail("Truncated name didn't throw an exception");
} catch (RuntimeException e) {
// expected
@@ -498,7 +504,7 @@
+ "01234567890123456789012345678901234567890123456789/test";
TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK);
entry.setLinkName(linkname);
-
+
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
@@ -506,7 +512,7 @@
tos.putArchiveEntry(entry);
tos.closeArchiveEntry();
tos.close();
-
+
fail("Truncated link name didn't throw an exception");
} catch (RuntimeException e) {
// expected
@@ -519,14 +525,14 @@
+ "01234567890123456789012345678901234567890123456789/";
TarArchiveEntry entry = new TarArchiveEntry("test" , TarConstants.LF_SYMLINK);
entry.setLinkName(linkname);
-
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(TarArchiveOutputStream.LONGFILE_TRUNCATE);
tos.putArchiveEntry(entry);
tos.closeArchiveEntry();
tos.close();
-
+
byte[] data = bos.toByteArray();
TarArchiveInputStream tin = new TarArchiveInputStream(new ByteArrayInputStream(data));
TarArchiveEntry e = tin.getNextTarEntry();
@@ -557,14 +563,14 @@
+ "01234567890123456789012345678901234567890123456789/test";
TarArchiveEntry entry = new TarArchiveEntry("test", TarConstants.LF_SYMLINK);
entry.setLinkName(linkname);
-
+
ByteArrayOutputStream bos = new ByteArrayOutputStream();
TarArchiveOutputStream tos = new TarArchiveOutputStream(bos, "ASCII");
tos.setLongFileMode(mode);
tos.putArchiveEntry(entry);
tos.closeArchiveEntry();
tos.close();
-
+
byte[] data = bos.toByteArray();
TarArchiveInputStream tin = new TarArchiveInputStream(new ByteArrayInputStream(data));
TarArchiveEntry e = tin.getNextTarEntry();
@@ -591,4 +597,57 @@
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
+ */
+ 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";
+ String fname = longFileName;
+ final Date modificationDate = new Date();
+
+ byte[] archive1 = createTarArchiveContainingOneDirectory(fname, modificationDate);
+ 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
+ byte[] archive2 = createTarArchiveContainingOneDirectory(fname, modificationDate);
+ // and I would expect the MD5 hash to be the same, but for long names it isn't
+ 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);
+ TarArchiveInputStream tarIn = new TarArchiveInputStream(new ByteArrayInputStream(archive2));
+ 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(String fname, Date modificationDate) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ TarArchiveOutputStream tarOut = new TarArchiveOutputStream(baos, 1024);
+ tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
+ 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();
+ }
+
}