You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@ant.apache.org by bo...@apache.org on 2012/06/16 06:12:38 UTC

svn commit: r1350857 [1/2] - in /ant/core/trunk: ./ src/main/org/apache/tools/tar/ src/main/org/apache/tools/zip/

Author: bodewig
Date: Sat Jun 16 04:12:37 2012
New Revision: 1350857

URL: http://svn.apache.org/viewvc?rev=1350857&view=rev
Log:
merge tar package from Compress, bringing some POSIX tar support

Added:
    ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java
      - copied, changed from r1348527, commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseEntry.java
Modified:
    ant/core/trunk/WHATSNEW
    ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java
    ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java
    ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java
    ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java
    ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java
    ant/core/trunk/src/main/org/apache/tools/tar/TarUtils.java
    ant/core/trunk/src/main/org/apache/tools/zip/ZipEncoding.java
    ant/core/trunk/src/main/org/apache/tools/zip/ZipEncodingHelper.java

Modified: ant/core/trunk/WHATSNEW
URL: http://svn.apache.org/viewvc/ant/core/trunk/WHATSNEW?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/WHATSNEW (original)
+++ ant/core/trunk/WHATSNEW Sat Jun 16 04:12:37 2012
@@ -49,6 +49,9 @@ Other changes:
    Java VMs.
    Bugzilla Report 52706.
 
+ * merged the TAR package from Commons Compress, it can now read
+   archives using POSIX extension headers and STAR extensions.
+
 Changes from Ant 1.8.3 TO Ant 1.8.4
 ===================================
 

Copied: ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java (from r1348527, commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseEntry.java)
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java?p2=ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java&p1=commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseEntry.java&r1=1348527&r2=1350857&rev=1350857&view=diff
==============================================================================
--- commons/proper/compress/trunk/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveSparseEntry.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarArchiveSparseEntry.java Sat Jun 16 04:12:37 2012
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.commons.compress.archivers.tar;
+package org.apache.tools.tar;
 
 import java.io.IOException;
 

Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarBuffer.java Sat Jun 16 04:12:37 2012
@@ -51,12 +51,13 @@ public class TarBuffer {
 
     private InputStream     inStream;
     private OutputStream    outStream;
-    private byte[]          blockBuffer;
+    private final int       blockSize;
+    private final int       recordSize;
+    private final int       recsPerBlock;
+    private final byte[]    blockBuffer;
+
     private int             currBlkIdx;
     private int             currRecIdx;
-    private int             blockSize;
-    private int             recordSize;
-    private int             recsPerBlock;
     private boolean         debug;
 
     /**
@@ -83,10 +84,7 @@ public class TarBuffer {
      * @param recordSize the record size to use
      */
     public TarBuffer(InputStream inStream, int blockSize, int recordSize) {
-        this.inStream = inStream;
-        this.outStream = null;
-
-        this.initialize(blockSize, recordSize);
+        this(inStream, null, blockSize, recordSize);
     }
 
     /**
@@ -113,16 +111,15 @@ public class TarBuffer {
      * @param recordSize the record size to use
      */
     public TarBuffer(OutputStream outStream, int blockSize, int recordSize) {
-        this.inStream = null;
-        this.outStream = outStream;
-
-        this.initialize(blockSize, recordSize);
+        this(null, outStream, blockSize, recordSize);
     }
 
     /**
-     * Initialization common to all constructors.
+     * Private constructor to perform common setup.
      */
-    private void initialize(int blockSize, int recordSize) {
+    private TarBuffer(InputStream inStream, OutputStream outStream, int blockSize, int recordSize) {
+        this.inStream = inStream;
+        this.outStream = outStream;
         this.debug = false;
         this.blockSize = blockSize;
         this.recordSize = recordSize;
@@ -194,10 +191,8 @@ public class TarBuffer {
             throw new IOException("reading (via skip) from an output buffer");
         }
 
-        if (currRecIdx >= recsPerBlock) {
-            if (!readBlock()) {
-                return;    // UNDONE
-            }
+        if (currRecIdx >= recsPerBlock && !readBlock()) {
+            return;    // UNDONE
         }
 
         currRecIdx++;
@@ -216,13 +211,14 @@ public class TarBuffer {
         }
 
         if (inStream == null) {
+            if (outStream == null) {
+                throw new IOException("input buffer is closed");
+            }
             throw new IOException("reading from an output buffer");
         }
 
-        if (currRecIdx >= recsPerBlock) {
-            if (!readBlock()) {
-                return null;
-            }
+        if (currRecIdx >= recsPerBlock && !readBlock()) {
+            return null;
         }
 
         byte[] result = new byte[recordSize];
@@ -337,6 +333,9 @@ public class TarBuffer {
         }
 
         if (outStream == null) {
+            if (inStream == null){
+                throw new IOException("Output buffer is closed");
+            }
             throw new IOException("writing to an input buffer");
         }
 
@@ -374,6 +373,9 @@ public class TarBuffer {
         }
 
         if (outStream == null) {
+            if (inStream == null){
+                throw new IOException("Output buffer is closed");
+            }
             throw new IOException("writing to an input buffer");
         }
 
@@ -454,9 +456,8 @@ public class TarBuffer {
         } else if (inStream != null) {
             if (inStream != System.in) {
                 inStream.close();
-
-                inStream = null;
             }
+            inStream = null;
         }
     }
 }

Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarConstants.java Sat Jun 16 04:12:37 2012
@@ -26,11 +26,23 @@ package org.apache.tools.tar;
 /**
  * This interface contains all the definitions used in the package.
  *
+ * For tar formats (FORMAT_OLDGNU, FORMAT_POSIX, etc.) see GNU tar
+ * <I>tar.h</I> type <I>enum archive_format</I>
  */
 // CheckStyle:InterfaceIsTypeCheck OFF (bc)
 public interface TarConstants {
 
     /**
+     * GNU format as per before tar 1.12.
+     */
+    int    FORMAT_OLDGNU = 2;
+
+    /**
+     * Pure Posix format.
+     */
+    int    FORMAT_POSIX = 3;
+
+    /**
      * The length of the name field in a header buffer.
      */
     int    NAMELEN = 100;
@@ -51,26 +63,49 @@ public interface TarConstants {
     int    GIDLEN = 8;
 
     /**
+     * The maximum value of gid/uid in a tar archive which can
+     * be expressed in octal char notation (that's 7 sevens, octal).
+     */
+    long    MAXID = 07777777L;
+ 
+    /**
      * The length of the checksum field in a header buffer.
      */
     int    CHKSUMLEN = 8;
 
     /**
      * The length of the size field in a header buffer.
+     * Includes the trailing space or NUL.
      */
     int    SIZELEN = 12;
 
     /**
-     * The maximum size of a file in a tar archive (That's 11 sevens, octal).
+     * The maximum size of a file in a tar archive 
+     * which can be expressed in octal char notation (that's 11 sevens, octal).
      */
     long   MAXSIZE = 077777777777L;
 
+    /** Offset of start of magic field within header record */
+    int    MAGIC_OFFSET = 257;
     /**
-     * The length of the magic field in a header buffer.
+     * The length of the magic field in a header buffer including the version.
      */
     int    MAGICLEN = 8;
 
     /**
+     * The length of the magic field in a header buffer.
+     */
+    int    PURE_MAGICLEN = 6;
+
+    /** Offset of start of magic field within header record */
+    int    VERSION_OFFSET = 263;
+    /**
+     * Previously this was regarded as part of "magic" field, but it
+     * is separate.
+     */
+    int    VERSIONLEN = 2;
+
+    /**
      * The length of the modification time field in a header buffer.
      */
     int    MODTIMELEN = 12;
@@ -86,11 +121,77 @@ public interface TarConstants {
     int    GNAMELEN = 32;
 
     /**
-     * The length of the devices field in a header buffer.
+     * The length of each of the device fields (major and minor) in a header buffer.
      */
     int    DEVLEN = 8;
 
     /**
+     * Length of the prefix field.
+     * 
+     */
+    int    PREFIXLEN = 155;
+
+    /**
+     * The length of the access time field in an old GNU header buffer.
+     * 
+     */
+    int    ATIMELEN_GNU = 12;
+
+    /**
+     * The length of the created time field in an old GNU header buffer.
+     * 
+     */
+    int    CTIMELEN_GNU = 12;
+
+    /**
+     * The length of the multivolume start offset field in an old GNU header buffer. 
+     * 
+     */
+    int    OFFSETLEN_GNU = 12;
+
+    /**
+     * The length of the long names field in an old GNU header buffer. 
+     * 
+     */
+    int    LONGNAMESLEN_GNU = 4;
+
+    /**
+     * The length of the padding field in an old GNU header buffer. 
+     * 
+     */
+    int    PAD2LEN_GNU = 1;
+
+    /**
+     * The sum of the length of all sparse headers in an old GNU header buffer. 
+     * 
+     */
+    int    SPARSELEN_GNU = 96;
+
+    /**
+     * The length of the is extension field in an old GNU header buffer. 
+     * 
+     */
+    int    ISEXTENDEDLEN_GNU = 1;
+
+    /**
+     * The length of the real size field in an old GNU header buffer. 
+     * 
+     */
+    int    REALSIZELEN_GNU = 12;
+
+    /**
+     * The sum of the length of all sparse headers in a sparse header buffer. 
+     * 
+     */
+    int    SPARSELEN_GNU_SPARSE = 504;
+
+    /**
+     * The length of the is extension field in a sparse header buffer. 
+     * 
+     */
+    int    ISEXTENDEDLEN_GNU_SPARSE = 1;
+
+    /**
      * LF_ constants represent the "link flag" of an entry, or more commonly,
      * the "entry type". This is the "old way" of indicating a normal file.
      */
@@ -137,22 +238,51 @@ public interface TarConstants {
     byte   LF_CONTIG = (byte) '7';
 
     /**
-     * The magic tag representing a POSIX tar archive.
+     * Identifies the *next* file on the tape as having a long name.
      */
+    byte LF_GNUTYPE_LONGNAME = (byte) 'L';
+
+    /**
+     * Sparse file type.
+     */
+    byte LF_GNUTYPE_SPARSE = (byte) 'S';
+
+    // See "http://www.opengroup.org/onlinepubs/009695399/utilities/pax.html#tag_04_100_13_02"
+
+    /**
+     * Identifies the entry as a Pax extended header.
+     */
+    byte LF_PAX_EXTENDED_HEADER_LC = (byte) 'x';
+
+    /**
+     * Identifies the entry as a Pax extended header (SunOS tar -E).
+     */
+    byte LF_PAX_EXTENDED_HEADER_UC = (byte) 'X';
+
+    /**
+     * Identifies the entry as a Pax global extended header.
+     */
+    byte LF_PAX_GLOBAL_EXTENDED_HEADER = (byte) 'g';
+
     String TMAGIC = "ustar";
 
     /**
+     * The magic tag representing a POSIX tar archive.
+     */
+    String MAGIC_POSIX = "ustar\0";
+    String VERSION_POSIX = "00";
+
+    /**
      * The magic tag representing a GNU tar archive.
      */
     String GNU_TMAGIC = "ustar  ";
+    // Appear to be two possible GNU versions
+    String VERSION_GNU_SPACE = " \0";
+    String VERSION_GNU_ZERO  = "0\0";
 
     /**
-     * The namr of the GNU tar entry which contains a long name.
+     * The name of the GNU tar entry which contains a long name.
      */
     String GNU_LONGLINK = "././@LongLink";
 
-    /**
-     * Identifies the *next* file on the tape as having a long name.
-     */
-    byte LF_GNUTYPE_LONGNAME = (byte) 'L';
 }

Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarEntry.java Sat Jun 16 04:12:37 2012
@@ -24,9 +24,13 @@
 package org.apache.tools.tar;
 
 import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.util.Date;
 import java.util.Locale;
 
+import org.apache.tools.zip.ZipEncoding;
+
 /**
  * This class represents an entry in a Tar archive. It consists
  * of the entry's header, as well as the entry's File. Entries
@@ -72,13 +76,44 @@ import java.util.Locale;
  * char devmajor[8];
  * char devminor[8];
  * } header;
+ * All unused bytes are set to null.
+ * New-style GNU tar files are slightly different from the above.
+ * For values of size larger than 077777777777L (11 7s)
+ * or uid and gid larger than 07777777L (7 7s)
+ * the sign bit of the first byte is set, and the rest of the
+ * field is the binary representation of the number.
+ * See TarUtils.parseOctalOrBinary.
+ * </pre>
+ * 
+ * <p>
+ * The C structure for a old GNU Tar Entry's header is:
+ * <pre>
+ * struct oldgnu_header {
+ * char unused_pad1[345]; // TarConstants.PAD1LEN_GNU       - offset 0
+ * char atime[12];        // TarConstants.ATIMELEN_GNU      - offset 345
+ * char ctime[12];        // TarConstants.CTIMELEN_GNU      - offset 357
+ * char offset[12];       // TarConstants.OFFSETLEN_GNU     - offset 369
+ * char longnames[4];     // TarConstants.LONGNAMESLEN_GNU  - offset 381
+ * char unused_pad2;      // TarConstants.PAD2LEN_GNU       - offset 385
+ * struct sparse sp[4];   // TarConstants.SPARSELEN_GNU     - offset 386
+ * char isextended;       // TarConstants.ISEXTENDEDLEN_GNU - offset 482
+ * char realsize[12];     // TarConstants.REALSIZELEN_GNU   - offset 483
+ * char unused_pad[17];   // TarConstants.PAD3LEN_GNU       - offset 495
+ * };
+ * </pre>
+ * Whereas, "struct sparse" is:
+ * <pre>
+ * struct sparse {
+ * char offset[12];   // offset 0
+ * char numbytes[12]; // offset 12
+ * };
  * </pre>
  *
  */
 
 public class TarEntry implements TarConstants {
     /** The entry's name. */
-    private StringBuffer name;
+    private String name;
 
     /** The entry's permission mode. */
     private int mode;
@@ -99,16 +134,18 @@ public class TarEntry implements TarCons
     private byte linkFlag;
 
     /** The entry's link name. */
-    private StringBuffer linkName;
+    private String linkName;
 
     /** The entry's magic tag. */
-    private StringBuffer magic;
+    private String magic;
+    /** The version of the format */
+    private String version;
 
     /** The entry's user name. */
-    private StringBuffer userName;
+    private String userName;
 
     /** The entry's group name. */
-    private StringBuffer groupName;
+    private String groupName;
 
     /** The entry's major device number. */
     private int devMajor;
@@ -116,6 +153,12 @@ public class TarEntry implements TarCons
     /** The entry's minor device number. */
     private int devMinor;
 
+    /** If an extension sparse header follows. */
+    private boolean isExtended;
+
+    /** The entry's real size in case of a sparse file. */
+    private long realSize;
+
     /** The entry's file reference */
     private File file;
 
@@ -134,10 +177,11 @@ public class TarEntry implements TarCons
     /**
      * Construct an empty entry and prepares the header values.
      */
-    private TarEntry () {
-        this.magic = new StringBuffer(TMAGIC);
-        this.name = new StringBuffer();
-        this.linkName = new StringBuffer();
+    private TarEntry() {
+        this.magic = MAGIC_POSIX;
+        this.version = VERSION_POSIX;
+        this.name = "";
+        this.linkName = "";
 
         String user = System.getProperty("user.name", "");
 
@@ -147,8 +191,8 @@ public class TarEntry implements TarCons
 
         this.userId = 0;
         this.groupId = 0;
-        this.userName = new StringBuffer(user);
-        this.groupName = new StringBuffer("");
+        this.userName = user;
+        this.groupName = "";
         this.file = null;
     }
 
@@ -178,19 +222,16 @@ public class TarEntry implements TarCons
 
         this.devMajor = 0;
         this.devMinor = 0;
-        this.name = new StringBuffer(name);
+        this.name = name;
         this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
         this.linkFlag = isDir ? LF_DIR : LF_NORMAL;
         this.userId = 0;
         this.groupId = 0;
         this.size = 0;
         this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
-        this.linkName = new StringBuffer("");
-        this.userName = new StringBuffer("");
-        this.groupName = new StringBuffer("");
-        this.devMajor = 0;
-        this.devMinor = 0;
-
+        this.linkName = "";
+        this.userName = "";
+        this.groupName = "";
     }
 
     /**
@@ -203,38 +244,52 @@ public class TarEntry implements TarCons
         this(name);
         this.linkFlag = linkFlag;
         if (linkFlag == LF_GNUTYPE_LONGNAME) {
-            magic = new StringBuffer(GNU_TMAGIC);
+            magic = GNU_TMAGIC;
+            version = VERSION_GNU_SPACE;
         }
     }
 
     /**
      * Construct an entry for a file. File is set to file, and the
      * header is constructed from information from the file.
+     * The name is set from the normalized file path.
      *
      * @param file The file that the entry represents.
      */
     public TarEntry(File file) {
+        this(file, normalizeFileName(file.getPath(), false));
+    }
+
+    /**
+     * Construct an entry for a file. File is set to file, and the
+     * header is constructed from information from the file.
+     *
+     * @param file The file that the entry represents.
+     * @param fileName the name to be used for the entry.
+     */
+    public TarEntry(File file, String fileName) {
         this();
 
         this.file = file;
 
-        String fileName = normalizeFileName(file.getPath(), false);
-        this.linkName = new StringBuffer("");
-        this.name = new StringBuffer(fileName);
+        this.linkName = "";
 
         if (file.isDirectory()) {
             this.mode = DEFAULT_DIR_MODE;
             this.linkFlag = LF_DIR;
 
-            int nameLength = name.length();
-            if (nameLength == 0 || name.charAt(nameLength - 1) != '/') {
-                this.name.append("/");
+            int nameLength = fileName.length();
+            if (nameLength == 0 || fileName.charAt(nameLength - 1) != '/') {
+                this.name = fileName + "/";
+            } else {
+                this.name = fileName;
             }
             this.size = 0;
         } else {
             this.mode = DEFAULT_FILE_MODE;
             this.linkFlag = LF_NORMAL;
             this.size = file.length();
+            this.name = fileName;
         }
 
         this.modTime = file.lastModified() / MILLIS_PER_SECOND;
@@ -247,6 +302,7 @@ public class TarEntry implements TarCons
      * to null.
      *
      * @param headerBuf The header bytes from a tar archive entry.
+     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
      */
     public TarEntry(byte[] headerBuf) {
         this();
@@ -254,6 +310,20 @@ public class TarEntry implements TarCons
     }
 
     /**
+     * Construct an entry from an archive's header bytes. File is set
+     * to null.
+     *
+     * @param headerBuf The header bytes from a tar archive entry.
+     * @param encoding encoding to use for file names
+     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
+     */
+    public TarEntry(byte[] headerBuf, ZipEncoding encoding)
+        throws IOException {
+        this();
+        parseTarHeader(headerBuf, encoding);
+    }
+
+    /**
      * Determine if the two entries are equal. Equality is determined
      * by the header names being equal.
      *
@@ -271,6 +341,7 @@ public class TarEntry implements TarCons
      * @param it Entry to be checked for equality.
      * @return True if the entries are equal.
      */
+    @Override
     public boolean equals(Object it) {
         if (it == null || getClass() != it.getClass()) {
             return false;
@@ -283,6 +354,7 @@ public class TarEntry implements TarCons
      *
      * @return the entry hashcode
      */
+    @Override
     public int hashCode() {
         return getName().hashCode();
     }
@@ -314,7 +386,7 @@ public class TarEntry implements TarCons
      * @param name This entry's new name.
      */
     public void setName(String name) {
-        this.name = new StringBuffer(normalizeFileName(name, false));
+        this.name = normalizeFileName(name, false);
     }
 
     /**
@@ -336,6 +408,15 @@ public class TarEntry implements TarCons
     }
 
     /**
+     * Set this entry's link name.
+     * 
+     * @param link the link name to use.
+     */
+    public void setLinkName(String link) {
+        this.linkName = link;
+    }
+
+    /**
      * Get this entry's user id.
      *
      * @return This entry's user id.
@@ -386,7 +467,7 @@ public class TarEntry implements TarCons
      * @param userName This entry's new user name.
      */
     public void setUserName(String userName) {
-        this.userName = new StringBuffer(userName);
+        this.userName = userName;
     }
 
     /**
@@ -404,7 +485,7 @@ public class TarEntry implements TarCons
      * @param groupName This entry's new group name.
      */
     public void setGroupName(String groupName) {
-        this.groupName = new StringBuffer(groupName);
+        this.groupName = groupName;
     }
 
     /**
@@ -488,11 +569,88 @@ public class TarEntry implements TarCons
      * Set this entry's file size.
      *
      * @param size This entry's new file size.
+     * @throws IllegalArgumentException if the size is &lt; 0.
      */
     public void setSize(long size) {
+        if (size < 0){
+            throw new IllegalArgumentException("Size is out of range: "+size);
+        }
         this.size = size;
     }
 
+    /**
+     * Get this entry's major device number.
+     *
+     * @return This entry's major device number.
+     */
+    public int getDevMajor() {
+        return devMajor;
+    }
+
+    /**
+     * Set this entry's major device number.
+     *
+     * @param devNo This entry's major device number.
+     * @throws IllegalArgumentException if the devNo is &lt; 0.
+     */
+    public void setDevMajor(int devNo) {
+        if (devNo < 0){
+            throw new IllegalArgumentException("Major device number is out of "
+                                               + "range: " + devNo);
+        }
+        this.devMajor = devNo;
+    }
+
+    /**
+     * Get this entry's minor device number.
+     *
+     * @return This entry's minor device number.
+     */
+    public int getDevMinor() {
+        return devMinor;
+    }
+
+    /**
+     * Set this entry's minor device number.
+     *
+     * @param devNo This entry's minor device number.
+     * @throws IllegalArgumentException if the devNo is &lt; 0.
+     */
+    public void setDevMinor(int devNo) {
+        if (devNo < 0){
+            throw new IllegalArgumentException("Minor device number is out of "
+                                               + "range: " + devNo);
+        }
+        this.devMinor = devNo;
+    }
+
+    /**
+     * Indicates in case of a sparse file if an extension sparse header
+     * follows.
+     *
+     * @return true if an extension sparse header follows.
+     */
+    public boolean isExtended() {
+        return isExtended;
+    }
+
+    /**
+     * Get this entry's real file size in case of a sparse file.
+     *
+     * @return This entry's real file size.
+     */
+    public long getRealSize() {
+        return realSize;
+    }
+
+    /**
+     * Indicate if this entry is a GNU sparse block 
+     *
+     * @return true if this is a sparse extension provided by GNU tar
+     */
+    public boolean isGNUSparse() {
+        return linkFlag == LF_GNUTYPE_SPARSE;
+    }
 
     /**
      * Indicate if this entry is a GNU long name block
@@ -501,7 +659,26 @@ public class TarEntry implements TarCons
      */
     public boolean isGNULongNameEntry() {
         return linkFlag == LF_GNUTYPE_LONGNAME
-                           && name.toString().equals(GNU_LONGLINK);
+                           && name.equals(GNU_LONGLINK);
+    }
+
+    /**
+     * Check if this is a Pax header.
+     * 
+     * @return {@code true} if this is a Pax header.
+     */
+    public boolean isPaxHeader(){
+        return linkFlag == LF_PAX_EXTENDED_HEADER_LC
+            || linkFlag == LF_PAX_EXTENDED_HEADER_UC;
+    }
+
+    /**
+     * Check if this is a Pax header.
+     * 
+     * @return {@code true} if this is a Pax header.
+     */
+    public boolean isGlobalPaxHeader(){
+        return linkFlag == LF_PAX_GLOBAL_EXTENDED_HEADER;
     }
 
     /**
@@ -526,6 +703,54 @@ public class TarEntry implements TarCons
     }
 
     /**
+     * Check if this is a "normal file"
+     */
+    public boolean isFile() {
+        if (file != null) {
+            return file.isFile();
+        }
+        if (linkFlag == LF_OLDNORM || linkFlag == LF_NORMAL) {
+            return true;
+        }
+        return !getName().endsWith("/");
+    }
+
+    /**
+     * Check if this is a symbolic link entry.
+     */
+    public boolean isSymbolicLink() {
+        return linkFlag == LF_SYMLINK;
+    }
+
+    /**
+     * Check if this is a link entry.
+     */
+    public boolean isLink() {
+        return linkFlag == LF_LINK;
+    }
+
+    /**
+     * Check if this is a character device entry.
+     */
+    public boolean isCharacterDevice() {
+        return linkFlag == LF_CHR;
+    }
+
+    /**
+     * Check if this is a block device entry.
+     */
+    public boolean isBlockDevice() {
+        return linkFlag == LF_BLK;
+    }
+
+    /**
+     * Check if this is a FIFO (pipe) entry.
+     */
+    public boolean isFIFO() {
+        return linkFlag == LF_FIFO;
+    }
+
+    /**
      * If this entry represents a file, and the file is a directory, return
      * an array of TarEntries for this entry's children.
      *
@@ -549,17 +774,46 @@ public class TarEntry implements TarCons
     /**
      * Write an entry's header information to a header buffer.
      *
+     * <p>This method does not use the star/GNU tar/BSD tar extensions.</p>
+     *
      * @param outbuf The tar entry header buffer to fill in.
      */
     public void writeEntryHeader(byte[] outbuf) {
+        try {
+            writeEntryHeader(outbuf, TarUtils.DEFAULT_ENCODING, false);
+        } catch (IOException ex) {
+            try {
+                writeEntryHeader(outbuf, TarUtils.FALLBACK_ENCODING, false);
+            } catch (IOException ex2) {
+                // impossible
+                throw new RuntimeException(ex2);
+            }
+        }
+    }
+
+    /**
+     * Write an entry's header information to a header buffer.
+     *
+     * @param outbuf The tar entry header buffer to fill in.
+     * @param encoding encoding to use when writing the file name.
+     * @param starMode whether to use the star/GNU tar/BSD tar
+     * extension for numeric fields if their value doesn't fit in the
+     * maximum size of standard tar archives
+     */
+    public void writeEntryHeader(byte[] outbuf, ZipEncoding encoding,
+                                 boolean starMode) throws IOException {
         int offset = 0;
 
-        offset = TarUtils.getNameBytes(name, outbuf, offset, NAMELEN);
-        offset = TarUtils.getOctalBytes(mode, outbuf, offset, MODELEN);
-        offset = TarUtils.getOctalBytes(userId, outbuf, offset, UIDLEN);
-        offset = TarUtils.getOctalBytes(groupId, outbuf, offset, GIDLEN);
-        offset = TarUtils.getLongOctalBytes(size, outbuf, offset, SIZELEN);
-        offset = TarUtils.getLongOctalBytes(modTime, outbuf, offset, MODTIMELEN);
+        offset = TarUtils.formatNameBytes(name, outbuf, offset, NAMELEN,
+                                          encoding);
+        offset = writeEntryHeaderField(mode, outbuf, offset, MODELEN, starMode);
+        offset = writeEntryHeaderField(userId, outbuf, offset, UIDLEN,
+                                       starMode);
+        offset = writeEntryHeaderField(groupId, outbuf, offset, GIDLEN,
+                                       starMode);
+        offset = writeEntryHeaderField(size, outbuf, offset, SIZELEN, starMode);
+        offset = writeEntryHeaderField(modTime, outbuf, offset, MODTIMELEN,
+                                       starMode);
 
         int csOffset = offset;
 
@@ -568,12 +822,18 @@ public class TarEntry implements TarCons
         }
 
         outbuf[offset++] = linkFlag;
-        offset = TarUtils.getNameBytes(linkName, outbuf, offset, NAMELEN);
-        offset = TarUtils.getNameBytes(magic, outbuf, offset, MAGICLEN);
-        offset = TarUtils.getNameBytes(userName, outbuf, offset, UNAMELEN);
-        offset = TarUtils.getNameBytes(groupName, outbuf, offset, GNAMELEN);
-        offset = TarUtils.getOctalBytes(devMajor, outbuf, offset, DEVLEN);
-        offset = TarUtils.getOctalBytes(devMinor, outbuf, offset, DEVLEN);
+        offset = TarUtils.formatNameBytes(linkName, outbuf, offset, NAMELEN,
+                                          encoding);
+        offset = TarUtils.formatNameBytes(magic, outbuf, offset, PURE_MAGICLEN);
+        offset = TarUtils.formatNameBytes(version, outbuf, offset, VERSIONLEN);
+        offset = TarUtils.formatNameBytes(userName, outbuf, offset, UNAMELEN,
+                                          encoding);
+        offset = TarUtils.formatNameBytes(groupName, outbuf, offset, GNAMELEN,
+                                          encoding);
+        offset = writeEntryHeaderField(devMajor, outbuf, offset, DEVLEN,
+                                       starMode);
+        offset = writeEntryHeaderField(devMinor, outbuf, offset, DEVLEN,
+                                       starMode);
 
         while (offset < outbuf.length) {
             outbuf[offset++] = 0;
@@ -581,42 +841,122 @@ public class TarEntry implements TarCons
 
         long chk = TarUtils.computeCheckSum(outbuf);
 
-        TarUtils.getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
+        TarUtils.formatCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
+    }
+
+    private int writeEntryHeaderField(long value, byte[] outbuf, int offset,
+                                      int length, boolean starMode) {
+        if (!starMode && (value < 0
+                          || value >= (1l << (3 * (length - 1))))) {
+            // value doesn't fit into field when written as octal
+            // number, will be written to PAX header or causes an
+            // error
+            return TarUtils.formatLongOctalBytes(0, outbuf, offset, length);
+        }
+        return TarUtils.formatLongOctalOrBinaryBytes(value, outbuf, offset,
+                                                     length);
     }
 
     /**
      * Parse an entry's header information from a header buffer.
      *
      * @param header The tar entry header buffer to get information from.
+     * @throws IllegalArgumentException if any of the numeric fields have an invalid format
      */
     public void parseTarHeader(byte[] header) {
+        try {
+            parseTarHeader(header, TarUtils.DEFAULT_ENCODING);
+        } catch (IOException ex) {
+            try {
+                parseTarHeader(header, TarUtils.DEFAULT_ENCODING, true);
+            } catch (IOException ex2) {
+                // not really possible
+                throw new RuntimeException(ex2);
+            }
+        }
+    }
+
+    /**
+     * Parse an entry's header information from a header buffer.
+     *
+     * @param header The tar entry header buffer to get information from.
+     * @param encoding encoding to use for file names
+     * @throws IllegalArgumentException if any of the numeric fields
+     * have an invalid format
+     */
+    public void parseTarHeader(byte[] header, ZipEncoding encoding)
+        throws IOException {
+        parseTarHeader(header, encoding, false);
+    }
+
+    private void parseTarHeader(byte[] header, ZipEncoding encoding,
+                                final boolean oldStyle)
+        throws IOException {
         int offset = 0;
 
-        name = TarUtils.parseName(header, offset, NAMELEN);
+        name = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
+            : TarUtils.parseName(header, offset, NAMELEN, encoding);
         offset += NAMELEN;
-        mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
+        mode = (int) TarUtils.parseOctalOrBinary(header, offset, MODELEN);
         offset += MODELEN;
-        userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
+        userId = (int) TarUtils.parseOctalOrBinary(header, offset, UIDLEN);
         offset += UIDLEN;
-        groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
+        groupId = (int) TarUtils.parseOctalOrBinary(header, offset, GIDLEN);
         offset += GIDLEN;
-        size = TarUtils.parseOctal(header, offset, SIZELEN);
+        size = TarUtils.parseOctalOrBinary(header, offset, SIZELEN);
         offset += SIZELEN;
-        modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
+        modTime = TarUtils.parseOctalOrBinary(header, offset, MODTIMELEN);
         offset += MODTIMELEN;
         offset += CHKSUMLEN;
         linkFlag = header[offset++];
-        linkName = TarUtils.parseName(header, offset, NAMELEN);
+        linkName = oldStyle ? TarUtils.parseName(header, offset, NAMELEN)
+            : TarUtils.parseName(header, offset, NAMELEN, encoding);
         offset += NAMELEN;
-        magic = TarUtils.parseName(header, offset, MAGICLEN);
-        offset += MAGICLEN;
-        userName = TarUtils.parseName(header, offset, UNAMELEN);
+        magic = TarUtils.parseName(header, offset, PURE_MAGICLEN);
+        offset += PURE_MAGICLEN;
+        version = TarUtils.parseName(header, offset, VERSIONLEN);
+        offset += VERSIONLEN;
+        userName = oldStyle ? TarUtils.parseName(header, offset, UNAMELEN)
+            : TarUtils.parseName(header, offset, UNAMELEN, encoding);
         offset += UNAMELEN;
-        groupName = TarUtils.parseName(header, offset, GNAMELEN);
+        groupName = oldStyle ? TarUtils.parseName(header, offset, GNAMELEN)
+            : TarUtils.parseName(header, offset, GNAMELEN, encoding);
         offset += GNAMELEN;
-        devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
+        devMajor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
+        offset += DEVLEN;
+        devMinor = (int) TarUtils.parseOctalOrBinary(header, offset, DEVLEN);
         offset += DEVLEN;
-        devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
+
+        int type = evaluateType(header);
+        switch (type) {
+        case FORMAT_OLDGNU: {
+            offset += ATIMELEN_GNU;
+            offset += CTIMELEN_GNU;
+            offset += OFFSETLEN_GNU;
+            offset += LONGNAMESLEN_GNU;
+            offset += PAD2LEN_GNU;
+            offset += SPARSELEN_GNU;
+            isExtended = TarUtils.parseBoolean(header, offset);
+            offset += ISEXTENDEDLEN_GNU;
+            realSize = TarUtils.parseOctal(header, offset, REALSIZELEN_GNU);
+            offset += REALSIZELEN_GNU;
+            break;
+        }
+        case FORMAT_POSIX:
+        default: {
+            String prefix = oldStyle
+                ? TarUtils.parseName(header, offset, PREFIXLEN)
+                : TarUtils.parseName(header, offset, PREFIXLEN, encoding);
+            // SunOS tar -E does not add / to directory names, so fix
+            // up to be consistent
+            if (isDirectory() && !name.endsWith("/")){
+                name = name + "/";
+            }
+            if (prefix.length() > 0){
+                name = prefix + "/" + name;
+            }
+        }
+        }
     }
 
     /**
@@ -661,4 +1001,85 @@ public class TarEntry implements TarCons
         }
         return fileName;
     }
+
+    /**
+     * Evaluate an entry's header format from a header buffer.
+     *
+     * @param header The tar entry header buffer to evaluate the format for.
+     * @return format type
+     */
+    private int evaluateType(byte[] header) {
+        if (matchAsciiBuffer(GNU_TMAGIC, header, MAGIC_OFFSET, PURE_MAGICLEN)) {
+            return FORMAT_OLDGNU;
+        }
+        if (matchAsciiBuffer(MAGIC_POSIX, header, MAGIC_OFFSET, PURE_MAGICLEN)) {
+            return FORMAT_POSIX;
+        }
+        return 0;
+    }
+
+    /**
+     * Check if buffer contents matches Ascii String.
+     * 
+     * @param expected
+     * @param buffer
+     * @param offset
+     * @param length
+     * @return {@code true} if buffer is the same as the expected string
+     */
+    private static boolean matchAsciiBuffer(String expected, byte[] buffer,
+                                            int offset, int length){
+        byte[] buffer1;
+        try {
+            buffer1 = expected.getBytes("ASCII");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e); // Should not happen
+        }
+        return isEqual(buffer1, 0, buffer1.length, buffer, offset, length,
+                       false);
+    }
+
+    /**
+     * Compare byte buffers, optionally ignoring trailing nulls
+     * 
+     * @param buffer1
+     * @param offset1
+     * @param length1
+     * @param buffer2
+     * @param offset2
+     * @param length2
+     * @param ignoreTrailingNulls
+     * @return {@code true} if buffer1 and buffer2 have same contents, having regard to trailing nulls
+     */
+    private static boolean isEqual(
+            final byte[] buffer1, final int offset1, final int length1,
+            final byte[] buffer2, final int offset2, final int length2,
+            boolean ignoreTrailingNulls){
+        int minLen=length1 < length2 ? length1 : length2;
+        for (int i=0; i < minLen; i++){
+            if (buffer1[offset1+i] != buffer2[offset2+i]){
+                return false;
+            }
+        }
+        if (length1 == length2){
+            return true;
+        }
+        if (ignoreTrailingNulls){
+            if (length1 > length2){
+                for(int i = length2; i < length1; i++){
+                    if (buffer1[offset1+i] != 0){
+                        return false;
+                    }
+                }
+            } else {
+                for(int i = length1; i < length2; i++){
+                    if (buffer2[offset2+i] != 0){
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+        return false;
+    }
 }

Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarInputStream.java Sat Jun 16 04:12:37 2012
@@ -23,10 +23,17 @@
 
 package org.apache.tools.tar;
 
+import java.io.ByteArrayOutputStream;
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.tools.zip.ZipEncoding;
+import org.apache.tools.zip.ZipEncodingHelper;
 
 /**
  * The TarInputStream reads a UNIX tar archive as an InputStream.
@@ -59,6 +66,8 @@ public class TarInputStream extends Filt
 
     // CheckStyle:VisibilityModifier ON
 
+    private final ZipEncoding encoding;
+
     /**
      * Constructor for TarInputStream.
      * @param is the input stream to use
@@ -70,6 +79,15 @@ public class TarInputStream extends Filt
     /**
      * Constructor for TarInputStream.
      * @param is the input stream to use
+     * @param encoding name of the encoding to use for file names
+     */
+    public TarInputStream(InputStream is, String encoding) {
+        this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, encoding);
+    }
+
+    /**
+     * Constructor for TarInputStream.
+     * @param is the input stream to use
      * @param blockSize the block size to use
      */
     public TarInputStream(InputStream is, int blockSize) {
@@ -80,16 +98,38 @@ public class TarInputStream extends Filt
      * Constructor for TarInputStream.
      * @param is the input stream to use
      * @param blockSize the block size to use
+     * @param encoding name of the encoding to use for file names
+     */
+    public TarInputStream(InputStream is, int blockSize, String encoding) {
+        this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE, encoding);
+    }
+
+    /**
+     * Constructor for TarInputStream.
+     * @param is the input stream to use
+     * @param blockSize the block size to use
      * @param recordSize the record size to use
      */
     public TarInputStream(InputStream is, int blockSize, int recordSize) {
-        super(is);
+        this(is, blockSize, recordSize, null);
+    }
 
+    /**
+     * Constructor for TarInputStream.
+     * @param is the input stream to use
+     * @param blockSize the block size to use
+     * @param recordSize the record size to use
+     * @param encoding name of the encoding to use for file names
+     */
+    public TarInputStream(InputStream is, int blockSize, int recordSize,
+                          String encoding) {
+        super(is);
         this.buffer = new TarBuffer(is, blockSize, recordSize);
         this.readBuf = null;
         this.oneBuf = new byte[1];
         this.debug = false;
         this.hasHitEOF = false;
+        this.encoding = ZipEncodingHelper.getZipEncoding(encoding);
     }
 
     /**
@@ -106,6 +146,7 @@ public class TarInputStream extends Filt
      * Closes this stream. Calls the TarBuffer's close() method.
      * @throws IOException on error
      */
+    @Override
     public void close() throws IOException {
         buffer.close();
     }
@@ -131,6 +172,7 @@ public class TarInputStream extends Filt
      * @return The number of available bytes for the current entry.
      * @throws IOException for signature
      */
+    @Override
     public int available() throws IOException {
         if (entrySize - entryOffset > Integer.MAX_VALUE) {
             return Integer.MAX_VALUE;
@@ -148,6 +190,7 @@ public class TarInputStream extends Filt
      * @return the number actually skipped
      * @throws IOException on error
      */
+    @Override
     public long skip(long numToSkip) throws IOException {
         // REVIEW
         // This is horribly inefficient, but it ensures that we
@@ -171,6 +214,7 @@ public class TarInputStream extends Filt
      *
      * @return False.
      */
+    @Override
     public boolean markSupported() {
         return false;
     }
@@ -180,12 +224,14 @@ public class TarInputStream extends Filt
      *
      * @param markLimit The limit to mark.
      */
+    @Override
     public void mark(int markLimit) {
     }
 
     /**
      * Since we do not support marking just yet, we do nothing.
      */
+    @Override
     public void reset() {
     }
 
@@ -230,44 +276,37 @@ public class TarInputStream extends Filt
             readBuf = null;
         }
 
-        byte[] headerBuf = buffer.readRecord();
-
-        if (headerBuf == null) {
-            if (debug) {
-                System.err.println("READ NULL RECORD");
-            }
-            hasHitEOF = true;
-        } else if (buffer.isEOFRecord(headerBuf)) {
-            if (debug) {
-                System.err.println("READ EOF RECORD");
-            }
-            hasHitEOF = true;
-        }
+        byte[] headerBuf = getRecord();
 
         if (hasHitEOF) {
             currEntry = null;
-        } else {
-            currEntry = new TarEntry(headerBuf);
-
-            if (debug) {
-                System.err.println("TarInputStream: SET CURRENTRY '"
-                        + currEntry.getName()
-                        + "' size = "
-                        + currEntry.getSize());
-            }
-
-            entryOffset = 0;
+            return null;
+        }
 
-            entrySize = currEntry.getSize();
+        try {
+            currEntry = new TarEntry(headerBuf, encoding);
+        } catch (IllegalArgumentException e) {
+            IOException ioe = new IOException("Error detected parsing the header");
+            ioe.initCause(e);
+            throw ioe;
+        }
+        if (debug) {
+            System.err.println("TarInputStream: SET CURRENTRY '"
+                               + currEntry.getName()
+                               + "' size = "
+                               + currEntry.getSize());
         }
 
-        if (currEntry != null && currEntry.isGNULongNameEntry()) {
+        entryOffset = 0;
+        entrySize = currEntry.getSize();
+
+        if (currEntry.isGNULongNameEntry()) {
             // read in the name
             StringBuffer longName = new StringBuffer();
             byte[] buf = new byte[SMALL_BUFFER_SIZE];
             int length = 0;
             while ((length = read(buf)) >= 0) {
-                longName.append(new String(buf, 0, length));
+                longName.append(new String(buf, 0, length)); // TODO default charset?
             }
             getNextEntry();
             if (currEntry == null) {
@@ -283,10 +322,177 @@ public class TarInputStream extends Filt
             currEntry.setName(longName.toString());
         }
 
+        if (currEntry.isPaxHeader()){ // Process Pax headers
+            paxHeaders();
+        }
+
+        if (currEntry.isGNUSparse()){ // Process sparse files
+            readGNUSparse();
+        }
+
+        // If the size of the next element in the archive has changed
+        // due to a new size being reported in the posix header
+        // information, we update entrySize here so that it contains
+        // the correct value.
+        entrySize = currEntry.getSize();
         return currEntry;
     }
 
     /**
+     * Get the next record in this tar archive. This will skip
+     * over any remaining data in the current entry, if there
+     * is one, and place the input stream at the header of the
+     * next entry.
+     * If there are no more entries in the archive, null will
+     * be returned to indicate that the end of the archive has
+     * been reached.
+     *
+     * @return The next header in the archive, or null.
+     * @throws IOException on error
+     */
+    private byte[] getRecord() throws IOException {
+        if (hasHitEOF) {
+            return null;
+        }
+
+        byte[] headerBuf = buffer.readRecord();
+
+        if (headerBuf == null) {
+            if (debug) {
+                System.err.println("READ NULL RECORD");
+            }
+            hasHitEOF = true;
+        } else if (buffer.isEOFRecord(headerBuf)) {
+            if (debug) {
+                System.err.println("READ EOF RECORD");
+            }
+            hasHitEOF = true;
+        }
+
+        return hasHitEOF ? null : headerBuf;
+    }
+
+    private void paxHeaders() throws IOException{
+        Map<String, String> headers = parsePaxHeaders(this);
+        getNextEntry(); // Get the actual file entry
+        applyPaxHeadersToCurrentEntry(headers);
+    }
+
+    Map<String, String> parsePaxHeaders(InputStream i) throws IOException {
+        Map<String, String> headers = new HashMap<String, String>();
+        // Format is "length keyword=value\n";
+        while(true){ // get length
+            int ch;
+            int len = 0;
+            int read = 0;
+            while((ch = i.read()) != -1) {
+                read++;
+                if (ch == ' '){ // End of length string
+                    // Get keyword
+                    ByteArrayOutputStream coll = new ByteArrayOutputStream();
+                    while((ch = i.read()) != -1) {
+                        read++;
+                        if (ch == '='){ // end of keyword
+                            String keyword = coll.toString("UTF-8");
+                            // Get rest of entry
+                            byte[] rest = new byte[len - read];
+                            int got = i.read(rest);
+                            if (got != len - read){
+                                throw new IOException("Failed to read "
+                                                      + "Paxheader. Expected "
+                                                      + (len - read)
+                                                      + " bytes, read "
+                                                      + got);
+                            }
+                            // Drop trailing NL
+                            String value = new String(rest, 0,
+                                                      len - read - 1, "UTF-8");
+                            headers.put(keyword, value);
+                            break;
+                        }
+                        coll.write((byte) ch);
+                    }
+                    break; // Processed single header
+                }
+                len *= 10;
+                len += ch - '0';
+            }
+            if (ch == -1){ // EOF
+                break;
+            }
+        }
+        return headers;
+    }
+
+    private void applyPaxHeadersToCurrentEntry(Map<String, String> headers) {
+        /*
+         * The following headers are defined for Pax.
+         * atime, ctime, charset: cannot use these without changing TarEntry fields
+         * mtime
+         * comment
+         * gid, gname
+         * linkpath
+         * size
+         * uid,uname
+         * SCHILY.devminor, SCHILY.devmajor: don't have setters/getters for those
+         */
+        for (Entry<String, String> ent : headers.entrySet()){
+            String key = ent.getKey();
+            String val = ent.getValue();
+            if ("path".equals(key)){
+                currEntry.setName(val);
+            } else if ("linkpath".equals(key)){
+                currEntry.setLinkName(val);
+            } else if ("gid".equals(key)){
+                currEntry.setGroupId(Integer.parseInt(val));
+            } else if ("gname".equals(key)){
+                currEntry.setGroupName(val);
+            } else if ("uid".equals(key)){
+                currEntry.setUserId(Integer.parseInt(val));
+            } else if ("uname".equals(key)){
+                currEntry.setUserName(val);
+            } else if ("size".equals(key)){
+                currEntry.setSize(Long.parseLong(val));
+            } else if ("mtime".equals(key)){
+                currEntry.setModTime((long) (Double.parseDouble(val) * 1000));
+            } else if ("SCHILY.devminor".equals(key)){
+                currEntry.setDevMinor(Integer.parseInt(val));
+            } else if ("SCHILY.devmajor".equals(key)){
+                currEntry.setDevMajor(Integer.parseInt(val));
+            }
+        }
+    }
+
+    /**
+     * Adds the sparse chunks from the current entry to the sparse chunks,
+     * including any additional sparse entries following the current entry.
+     * 
+     * @throws IOException on error 
+     * 
+     * @todo Sparse files get not yet really processed. 
+     */
+    private void readGNUSparse() throws IOException {
+        /* we do not really process sparse files yet
+        sparses = new ArrayList();
+        sparses.addAll(currEntry.getSparses());
+        */
+        if (currEntry.isExtended()) {
+            TarArchiveSparseEntry entry;
+            do {
+                byte[] headerBuf = getRecord();
+                if (hasHitEOF) {
+                    currEntry = null;
+                    break;
+                }
+                entry = new TarArchiveSparseEntry(headerBuf);
+                /* we do not really process sparse files yet
+                sparses.addAll(entry.getSparses());
+                */
+            } while (entry.isExtended());
+        }
+    }
+
+    /**
      * Reads a byte from the current tar archive entry.
      *
      * This method simply calls read( byte[], int, int ).
@@ -294,6 +500,7 @@ public class TarInputStream extends Filt
      * @return The byte read, or -1 at EOF.
      * @throws IOException on error
      */
+    @Override
     public int read() throws IOException {
         int num = read(oneBuf, 0, 1);
         return num == -1 ? -1 : ((int) oneBuf[0]) & BYTE_MASK;
@@ -312,6 +519,7 @@ public class TarInputStream extends Filt
      * @return The number of bytes read, or -1 at EOF.
      * @throws IOException on error
      */
+    @Override
     public int read(byte[] buf, int offset, int numToRead) throws IOException {
         int totalRead = 0;
 
@@ -399,4 +607,14 @@ public class TarInputStream extends Filt
             out.write(buf, 0, numRead);
         }
     }
+
+    /**
+     * Whether this class is able to read the given entry.
+     *
+     * <p>May return false if the current entry is a sparse file.</p>
+     */
+    public boolean canReadEntryData(TarEntry te) {
+        return !te.isGNUSparse();
+    }
 }
+

Modified: ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java
URL: http://svn.apache.org/viewvc/ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java?rev=1350857&r1=1350856&r2=1350857&view=diff
==============================================================================
--- ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java (original)
+++ ant/core/trunk/src/main/org/apache/tools/tar/TarOutputStream.java Sat Jun 16 04:12:37 2012
@@ -23,9 +23,15 @@
 
 package org.apache.tools.tar;
 
+import java.io.File;
 import java.io.FilterOutputStream;
-import java.io.OutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.io.StringWriter;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.tools.zip.ZipEncoding;
+import org.apache.tools.zip.ZipEncodingHelper;
 
 /**
  * The TarOutputStream writes a UNIX tar archive as an OutputStream.
@@ -43,6 +49,18 @@ public class TarOutputStream extends Fil
     /** GNU tar extensions are used to store long file names in the archive. */
     public static final int LONGFILE_GNU = 2;
 
+    /** POSIX/PAX extensions are used to store long file names in the archive. */
+    public static final int LONGFILE_POSIX = 3;
+
+    /** Fail if a big number (e.g. size &gt; 8GiB) is required in the archive. */
+    public static final int BIGNUMBER_ERROR = 0;
+
+    /** star/GNU tar/BSD tar extensions are used to store big number in the archive. */
+    public static final int BIGNUMBER_STAR = 1;
+
+    /** POSIX/PAX extensions are used to store big numbers in the archive. */
+    public static final int BIGNUMBER_POSIX = 2;
+
     // CheckStyle:VisibilityModifier OFF - bc
     protected boolean   debug;
     protected long      currSize;
@@ -56,8 +74,22 @@ public class TarOutputStream extends Fil
     protected int       longFileMode = LONGFILE_ERROR;
     // CheckStyle:VisibilityModifier ON
 
+    private int       bigNumberMode = BIGNUMBER_ERROR;
+
     private boolean closed = false;
 
+    /** Indicates if putNextEntry has been called without closeEntry */
+    private boolean haveUnclosedEntry = false;
+
+    /** indicates if this archive is finished */
+    private boolean finished = false;
+
+    private final ZipEncoding encoding;
+
+    private boolean addPaxHeadersForNonAsciiNames = false;
+    private static final ZipEncoding ASCII =
+        ZipEncodingHelper.getZipEncoding("ASCII");
+
     /**
      * Constructor for TarInputStream.
      * @param os the output stream to use
@@ -69,6 +101,15 @@ public class TarOutputStream extends Fil
     /**
      * Constructor for TarInputStream.
      * @param os the output stream to use
+     * @param encoding name of the encoding to use for file names
+     */
+    public TarOutputStream(OutputStream os, String encoding) {
+        this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE, encoding);
+    }
+
+    /**
+     * Constructor for TarInputStream.
+     * @param os the output stream to use
      * @param blockSize the block size to use
      */
     public TarOutputStream(OutputStream os, int blockSize) {
@@ -79,10 +120,33 @@ public class TarOutputStream extends Fil
      * Constructor for TarInputStream.
      * @param os the output stream to use
      * @param blockSize the block size to use
+     * @param encoding name of the encoding to use for file names
+     */
+    public TarOutputStream(OutputStream os, int blockSize, String encoding) {
+        this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE, encoding);
+    }
+
+    /**
+     * Constructor for TarInputStream.
+     * @param os the output stream to use
+     * @param blockSize the block size to use
      * @param recordSize the record size to use
      */
     public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
+        this(os, blockSize, recordSize, null);
+    }
+
+    /**
+     * Constructor for TarInputStream.
+     * @param os the output stream to use
+     * @param blockSize the block size to use
+     * @param recordSize the record size to use
+     * @param encoding name of the encoding to use for file names
+     */
+    public TarOutputStream(OutputStream os, int blockSize, int recordSize,
+                           String encoding) {
         super(os);
+        this.encoding = ZipEncodingHelper.getZipEncoding(encoding);
 
         this.buffer = new TarBuffer(os, blockSize, recordSize);
         this.debug = false;
@@ -103,6 +167,23 @@ public class TarOutputStream extends Fil
         this.longFileMode = longFileMode;
     }
 
+    /**
+     * Set the big number mode.
+     * This can be BIGNUMBER_ERROR(0), BIGNUMBER_POSIX(1) or BIGNUMBER_STAR(2).
+     * This specifies the treatment of big files (sizes &gt; TarConstants.MAXSIZE) and other numeric values to big to fit into a traditional tar header.
+     * Default is BIGNUMBER_ERROR.
+     * @param bigNumberMode the mode to use
+     */
+    public void setBigNumberMode(int bigNumberMode) {
+        this.bigNumberMode = bigNumberMode;
+    }
+
+    /**
+     * Whether to add a PAX extension header for non-ASCII file names.
+     */
+    public void setAddPaxHeadersForNonAsciiNames(boolean b) {
+        addPaxHeadersForNonAsciiNames = b;
+    }
 
     /**
      * Sets the debugging flag.
@@ -124,15 +205,25 @@ public class TarOutputStream extends Fil
 
     /**
      * Ends the TAR archive without closing the underlying OutputStream.
-     * The result is that the two EOF records of nulls are written.
+     * 
+     * An archive consists of a series of file entries terminated by an
+     * end-of-archive entry, which consists of two 512 blocks of zero bytes. 
+     * POSIX.1 requires two EOF records, like some other implementations.
+     * 
      * @throws IOException on error
      */
     public void finish() throws IOException {
-        // See Bugzilla 28776 for a discussion on this
-        // http://issues.apache.org/bugzilla/show_bug.cgi?id=28776
+        if (finished) {
+            throw new IOException("This archive has already been finished");
+        }
+
+        if (haveUnclosedEntry) {
+            throw new IOException("This archives contains unclosed entries.");
+        }
         writeEOFRecord();
         writeEOFRecord();
         buffer.flushBlock();
+        finished = true;
     }
 
     /**
@@ -141,9 +232,13 @@ public class TarOutputStream extends Fil
      * TarBuffer's close().
      * @throws IOException on error
      */
+    @Override
     public void close() throws IOException {
-        if (!closed) {
+        if(!finished) {
             finish();
+        }
+
+        if (!closed) {
             buffer.close();
             out.close();
             closed = true;
@@ -172,27 +267,59 @@ public class TarOutputStream extends Fil
      * @throws IOException on error
      */
     public void putNextEntry(TarEntry entry) throws IOException {
-        if (entry.getName().length() >= TarConstants.NAMELEN) {
-
-            if (longFileMode == LONGFILE_GNU) {
+        if(finished) {
+            throw new IOException("Stream has already been finished");
+        }
+        Map<String, String> paxHeaders = new HashMap<String, String>();
+        final String entryName = entry.getName();
+        final byte[] nameBytes = encoding.encode(entryName).array();
+        boolean paxHeaderContainsPath = false;
+        if (nameBytes.length >= TarConstants.NAMELEN) {
+
+            if (longFileMode == LONGFILE_POSIX) {
+                paxHeaders.put("path", entryName);
+                paxHeaderContainsPath = true;
+            } else if (longFileMode == LONGFILE_GNU) {
                 // create a TarEntry for the LongLink, the contents
                 // of which are the entry's name
                 TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK,
                                                       TarConstants.LF_GNUTYPE_LONGNAME);
 
-                longLinkEntry.setSize(entry.getName().length() + 1);
+                longLinkEntry.setSize(nameBytes.length + 1); // +1 for NUL
                 putNextEntry(longLinkEntry);
-                write(entry.getName().getBytes());
-                write(0);
+                write(nameBytes);
+                write(0); // NUL terminator
                 closeEntry();
             } else if (longFileMode != LONGFILE_TRUNCATE) {
-                throw new RuntimeException("file name '" + entry.getName()
-                                             + "' is too long ( > "
-                                             + TarConstants.NAMELEN + " bytes)");
+                throw new RuntimeException("file name '" + entryName
+                                           + "' is too long ( > "
+                                           + TarConstants.NAMELEN + " bytes)");
             }
         }
 
-        entry.writeEntryHeader(recordBuf);
+        if (bigNumberMode == BIGNUMBER_POSIX) {
+            addPaxHeadersForBigNumbers(paxHeaders, entry);
+        } else if (bigNumberMode != BIGNUMBER_STAR) {
+            failForBigNumbers(entry);
+        }
+
+        if (addPaxHeadersForNonAsciiNames && !paxHeaderContainsPath
+            && !ASCII.canEncode(entryName)) {
+            paxHeaders.put("path", entryName);
+        }
+
+        if (addPaxHeadersForNonAsciiNames
+            && (entry.isLink() || entry.isSymbolicLink())
+            && !ASCII.canEncode(entry.getLinkName())) {
+            paxHeaders.put("linkpath", entry.getLinkName());
+        }
+
+        if (paxHeaders.size() > 0) {
+            writePaxHeaders(entryName, paxHeaders);
+        }
+
+        entry.writeEntryHeader(recordBuf, encoding,
+                               bigNumberMode == BIGNUMBER_STAR);
         buffer.writeRecord(recordBuf);
 
         currBytes = 0;
@@ -202,7 +329,8 @@ public class TarOutputStream extends Fil
         } else {
             currSize = entry.getSize();
         }
-        currName = entry.getName();
+        currName = entryName;
+        haveUnclosedEntry = true;
     }
 
     /**
@@ -216,6 +344,12 @@ public class TarOutputStream extends Fil
      * @throws IOException on error
      */
     public void closeEntry() throws IOException {
+        if (finished) {
+            throw new IOException("Stream has already been finished");
+        }
+        if (!haveUnclosedEntry){
+            throw new IOException("No current entry to close");
+        }
         if (assemLen > 0) {
             for (int i = assemLen; i < assemBuf.length; ++i) {
                 assemBuf[i] = 0;
@@ -233,6 +367,7 @@ public class TarOutputStream extends Fil
                                   + "' before the '" + currSize
                                   + "' bytes specified in the header were written");
         }
+        haveUnclosedEntry = false;
     }
 
     /**
@@ -243,6 +378,7 @@ public class TarOutputStream extends Fil
      * @param b The byte written.
      * @throws IOException on error
      */
+    @Override
     public void write(int b) throws IOException {
         oneBuf[0] = (byte) b;
 
@@ -275,6 +411,7 @@ public class TarOutputStream extends Fil
      * @param numToWrite The number of bytes to write.
      * @throws IOException on error
      */
+    @Override
     public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
         if ((currBytes + numToWrite) > currSize) {
             throw new IOException("request to write '" + numToWrite
@@ -341,6 +478,58 @@ public class TarOutputStream extends Fil
     }
 
     /**
+     * Writes a PAX extended header with the given map as contents.
+     */
+    void writePaxHeaders(String entryName,
+                         Map<String, String> headers) throws IOException {
+        String name = "./PaxHeaders.X/" + stripTo7Bits(entryName);
+        if (name.length() >= TarConstants.NAMELEN) {
+            name = name.substring(0, TarConstants.NAMELEN - 1);
+        }
+        TarEntry pex = new TarEntry(name,
+                                    TarConstants.LF_PAX_EXTENDED_HEADER_LC);
+
+        StringWriter w = new StringWriter();
+        for (Map.Entry<String, String> h : headers.entrySet()) {
+            String key = h.getKey();
+            String value = h.getValue();
+            int len = key.length() + value.length()
+                + 3 /* blank, equals and newline */
+                + 2 /* guess 9 < actual length < 100 */;
+            String line = len + " " + key + "=" + value + "\n";
+            int actualLength = line.getBytes("UTF-8").length;
+            while (len != actualLength) {
+                // Adjust for cases where length < 10 or > 100
+                // or where UTF-8 encoding isn't a single octet
+                // per character.
+                // Must be in loop as size may go from 99 to 100 in
+                // first pass so we'd need a second.
+                len = actualLength;
+                line = len + " " + key + "=" + value + "\n";
+                actualLength = line.getBytes("UTF-8").length;
+            }
+            w.write(line);
+        }
+        byte[] data = w.toString().getBytes("UTF-8");
+        pex.setSize(data.length);
+        putNextEntry(pex);
+        write(data);
+        closeEntry();
+    }
+
+    private String stripTo7Bits(String name) {
+        final int length = name.length();
+        StringBuffer result = new StringBuffer(length);
+        for (int i = 0; i < length; i++) {
+            char stripped = (char) (name.charAt(i) & 0x7F);
+            if (stripped != 0) { // would be read as Trailing null
+                result.append(stripped);
+            }
+        }
+        return result.toString();
+    }
+
+    /**
      * Write an EOF (end of archive) record to the tar archive.
      * An EOF record consists of a record of all zeros.
      */
@@ -351,6 +540,53 @@ public class TarOutputStream extends Fil
 
         buffer.writeRecord(recordBuf);
     }
-}
 
+    private void addPaxHeadersForBigNumbers(Map<String, String> paxHeaders,
+                                            TarEntry entry) {
+        addPaxHeaderForBigNumber(paxHeaders, "size", entry.getSize(),
+                                 TarConstants.MAXSIZE);
+        addPaxHeaderForBigNumber(paxHeaders, "gid", entry.getGroupId(),
+                                 TarConstants.MAXID);
+        addPaxHeaderForBigNumber(paxHeaders, "mtime",                                 entry.getModTime().getTime() / 1000,
+                                 TarConstants.MAXSIZE);
+        addPaxHeaderForBigNumber(paxHeaders, "uid", entry.getUserId(),
+                                 TarConstants.MAXID);
+        // star extensions by J\u00f6rg Schilling
+        addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devmajor",
+                                 entry.getDevMajor(), TarConstants.MAXID);
+        addPaxHeaderForBigNumber(paxHeaders, "SCHILY.devminor",
+                                 entry.getDevMinor(), TarConstants.MAXID);
+        // there is no PAX header for file mode
+        failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
+    }
+
+    private void addPaxHeaderForBigNumber(Map<String, String> paxHeaders,
+                                          String header, long value,
+                                          long maxValue) {
+        if (value < 0 || value > maxValue) {
+            paxHeaders.put(header, String.valueOf(value));
+        }
+    }
 
+    private void failForBigNumbers(TarEntry entry) {
+        failForBigNumber("entry size", entry.getSize(), TarConstants.MAXSIZE);
+        failForBigNumber("group id", entry.getGroupId(), TarConstants.MAXID);
+        failForBigNumber("last modification time",
+                         entry.getModTime().getTime() / 1000,
+                         TarConstants.MAXSIZE);
+        failForBigNumber("user id", entry.getUserId(), TarConstants.MAXID);
+        failForBigNumber("mode", entry.getMode(), TarConstants.MAXID);
+        failForBigNumber("major device number", entry.getDevMajor(),
+                         TarConstants.MAXID);
+        failForBigNumber("minor device number", entry.getDevMinor(),
+                         TarConstants.MAXID);
+    }
+
+    private void failForBigNumber(String field, long value, long maxValue) {
+        if (value < 0 || value > maxValue) {
+            throw new RuntimeException(field + " '" + value
+                                       + "' is too big ( > "
+                                       + maxValue + " )");
+        }
+    }
+}