Use PAX headers for big or negative numeric values in tar headers.  COMPRESS-182

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/compress/trunk@1297339 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 9c05fe7..ca95de3 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
@@ -237,14 +237,10 @@
             }
         }
 
-        if (entry.getSize() > TarConstants.MAXSIZE) {
-            if (bigFileMode == BIGFILE_POSIX) {
-                paxHeaders.put("size", String.valueOf(entry.getSize()));
-            } else if (bigFileMode != BIGFILE_STAR) {
-                throw new RuntimeException("file size '" + entry.getSize()
-                                           + "' is too big ( > "
-                                           + TarConstants.MAXSIZE + " bytes)");
-            }
+        if (bigFileMode == BIGFILE_POSIX) {
+            addPaxHeadersForBigNumbers(paxHeaders, entry);
+        } else if (bigFileMode != BIGFILE_STAR) {
+            failForBigNumbers(entry);
         }
 
         if (paxHeaders.size() > 0) {
@@ -450,4 +446,58 @@
         }
         return new TarArchiveEntry(inputFile, entryName);
     }
+
+    private void addPaxHeadersForBigNumbers(Map<String, String> paxHeaders,
+                                            TarArchiveEntry entry) {
+        if (entry.getSize() > TarConstants.MAXSIZE) {
+            paxHeaders.put("size", String.valueOf(entry.getSize()));
+        }
+        if (entry.getGroupId() > TarConstants.MAXID) {
+            paxHeaders.put("gid", String.valueOf(entry.getGroupId()));
+        }
+        final long mtime =  entry.getModTime().getTime() / 1000;
+        if (mtime < 0 || mtime > TarConstants.MAXSIZE) {
+            paxHeaders.put("mtime", String.valueOf(mtime));
+        }
+        if (entry.getUserId() > TarConstants.MAXID) {
+            paxHeaders.put("uid", String.valueOf(entry.getUserId()));
+        }
+        if (entry.getMode() > TarConstants.MAXID) {
+            throw new RuntimeException("mode '" + entry.getMode()
+                                       + "' is too big ( > "
+                                       + TarConstants.MAXID + " bytes)");
+        }
+        // TODO add devMajor and devMinor
+    }
+
+    private void failForBigNumbers(TarArchiveEntry entry) {
+        if (entry.getSize() > TarConstants.MAXSIZE) {
+            throw new RuntimeException("file size '" + entry.getSize()
+                                       + "' is too big ( > "
+                                       + TarConstants.MAXSIZE + " bytes)");
+        }
+        if (entry.getGroupId() > TarConstants.MAXID) {
+            throw new RuntimeException("group id '" + entry.getGroupId()
+                                       + "' is too big ( > "
+                                       + TarConstants.MAXID + " bytes)");
+        }
+        final long mtime =  entry.getModTime().getTime() / 1000;
+        if (mtime < 0 || mtime > TarConstants.MAXSIZE) {
+            throw new RuntimeException("last modification time '"
+                                       + entry.getModTime()
+                                       + "' is too big ( > "
+                                       + TarConstants.MAXSIZE + " bytes)");
+        }
+        if (entry.getUserId() > TarConstants.MAXID) {
+            throw new RuntimeException("user id '" + entry.getUserId()
+                                       + "' is too big ( > "
+                                       + TarConstants.MAXID + " bytes)");
+        }
+        if (entry.getMode() > TarConstants.MAXID) {
+            throw new RuntimeException("mode '" + entry.getMode()
+                                       + "' is too big ( > "
+                                       + TarConstants.MAXID + " bytes)");
+        }
+        // TODO add devMajor and devMinor
+    }
 }
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 0e8a52b..1b1f656 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
@@ -233,4 +233,45 @@
         assertEquals(cal.getTime(), e.getLastModifiedDate());
     }
 
+    public void testOldEntryPosixMode() throws Exception {
+        TarArchiveEntry t = new TarArchiveEntry("foo");
+        t.setSize(Integer.MAX_VALUE);
+        t.setModTime(-1000);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        TarArchiveOutputStream tos = new TarArchiveOutputStream(bos);
+        tos.setBigFileMode(TarArchiveOutputStream.BIGFILE_POSIX);
+        tos.putArchiveEntry(t);
+        // make sure header is written to byte array
+        tos.write(new byte[10 * 1024]);
+        byte[] data = bos.toByteArray();
+        assertEquals("00000000000 ",
+                     new String(data,
+                                1024 + TarConstants.NAMELEN
+                                + TarConstants.MODELEN
+                                + TarConstants.UIDLEN
+                                + TarConstants.GIDLEN
+                                + TarConstants.SIZELEN, 12,
+                                "UTF-8"));
+        TarArchiveInputStream tin =
+            new TarArchiveInputStream(new ByteArrayInputStream(data));
+        TarArchiveEntry e = tin.getNextTarEntry();
+        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());
+    }
+
+    public void testOldEntryError() throws Exception {
+        TarArchiveEntry t = new TarArchiveEntry("foo");
+        t.setSize(Integer.MAX_VALUE);
+        t.setModTime(-1000);
+        TarArchiveOutputStream tos =
+            new TarArchiveOutputStream(new ByteArrayOutputStream());
+        try {
+            tos.putArchiveEntry(t);
+            fail("Should have generated RuntimeException");
+        } catch (RuntimeException expected) {
+        }
+    }
+
 }
\ No newline at end of file