applied patch from Christian Grobmeier
updated tar implementation from ant
https://issues.apache.org/jira/browse/SANDBOX-273
git-svn-id: https://svn.apache.org/repos/asf/commons/sandbox/compress/trunk@732682 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
index f580067..4a6491b 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarArchiveEntry.java
@@ -25,28 +25,34 @@
import org.apache.commons.compress.archivers.ArchiveEntry;
/**
- * This class represents an entry in a Tar archive. It consists of the entry's
- * header, as well as the entry's File. Entries can be instantiated in one of
- * three ways, depending on how they are to be used. <p>
+ * This class represents an entry in a Tar archive. It consists
+ * of the entry's header, as well as the entry's File. Entries
+ * can be instantiated in one of three ways, depending on how
+ * they are to be used.
+ * <p>
+ * TarEntries that are created from the header bytes read from
+ * an archive are instantiated with the TarEntry( byte[] )
+ * constructor. These entries will be used when extracting from
+ * or listing the contents of an archive. These entries have their
+ * header filled in using the header bytes. They also set the File
+ * to null, since they reference an archive entry not a file.
+ * <p>
+ * TarEntries that are created from Files that are to be written
+ * into an archive are instantiated with the TarEntry( File )
+ * constructor. These entries have their header filled in using
+ * the File's information. They also keep a reference to the File
+ * for convenience when writing entries.
+ * <p>
+ * Finally, TarEntries can be constructed from nothing but a name.
+ * This allows the programmer to construct the entry by hand, for
+ * instance when only an InputStream is available for writing to
+ * the archive, and the header information is constructed from
+ * other information. In this case the header fields are set to
+ * defaults and the File is set to null.
*
- * TarEntries that are created from the header bytes read from an archive are
- * instantiated with the TarEntry( byte[] ) constructor. These entries will be
- * used when extracting from or listing the contents of an archive. These
- * entries have their header filled in using the header bytes. They also set the
- * File to null, since they reference an archive entry not a file. <p>
- *
- * TarEntries that are created from Files that are to be written into an archive
- * are instantiated with the TarEntry( File ) constructor. These entries have
- * their header filled in using the File's information. They also keep a
- * reference to the File for convenience when writing entries. <p>
- *
- * Finally, TarEntries can be constructed from nothing but a name. This allows
- * the programmer to construct the entry by hand, for instance when only an
- * InputStream is available for writing to the archive, and the header
- * information is constructed from other information. In this case the header
- * fields are set to defaults and the File is set to null. <p>
- *
- * The C structure for a Tar Entry's header is: <pre>
+ * <p>
+ * The C structure for a Tar Entry's header is:
+ * <pre>
* struct header {
* char name[NAMSIZ];
* char mode[8];
@@ -64,424 +70,247 @@
* char devminor[8];
* } header;
* </pre>
+ *
*/
-public class TarArchiveEntry implements ArchiveEntry {
- /**
- * The length of the name field in a header buffer.
- */
- public static final int NAMELEN = 99;
- /**
- * The entry's modification time.
- */
- private int m_checkSum;
+public class TarArchiveEntry implements TarConstants, ArchiveEntry {
+ /** The entry's name. */
+ private StringBuffer name;
- /**
- * The entry's group name.
- */
- private int m_devMajor;
+ /** The entry's permission mode. */
+ private int mode;
- /**
- * The entry's major device number.
- */
- private int m_devMinor;
+ /** The entry's user id. */
+ private int userId;
- /**
- * The entry's minor device number.
- */
- private File m_file;
+ /** The entry's group id. */
+ private int groupId;
- /**
- * The entry's user id.
- */
- private int m_groupID;
+ /** The entry's size. */
+ private long size;
- /**
- * The entry's user name.
- */
- private StringBuffer m_groupName;
+ /** The entry's modification time. */
+ private long modTime;
- /**
- * The entry's checksum.
- */
- private byte m_linkFlag;
+ /** The entry's link flag. */
+ private byte linkFlag;
- /**
- * The entry's link flag.
- */
- private StringBuffer m_linkName;
+ /** The entry's link name. */
+ private StringBuffer linkName;
- /**
- * The entry's link name.
- */
- private StringBuffer m_magic;
+ /** The entry's magic tag. */
+ private StringBuffer magic;
- /**
- * The entry's size.
- */
- private long m_modTime;
+ /** The entry's user name. */
+ private StringBuffer userName;
- /**
- * The entry's name.
- */
- private int m_mode;
+ /** The entry's group name. */
+ private StringBuffer groupName;
- private StringBuffer m_name;
+ /** The entry's major device number. */
+ private int devMajor;
- /**
- * The entry's group id.
- */
- private long m_size;
+ /** The entry's minor device number. */
+ private int devMinor;
- /**
- * The entry's permission mode.
- */
- private int m_userID;
+ /** The entry's file reference */
+ private File file;
- /**
- * The entry's magic tag.
- */
- private StringBuffer m_userName;
+ /** Maximum length of a user's name in the tar file */
+ public static final int MAX_NAMELEN = 31;
- /**
- * Construct an entry with only a name. This allows the programmer to
- * construct the entry's header "by hand". File is set to null.
- *
- * @param name the name of the entry
- */
- public TarArchiveEntry( final String name )
- {
- this();
+ /** Default permissions bits for directories */
+ public static final int DEFAULT_DIR_MODE = 040755;
- final boolean isDir = name.endsWith( "/" );
+ /** Default permissions bits for files */
+ public static final int DEFAULT_FILE_MODE = 0100644;
- m_name = new StringBuffer( name );
- m_mode = isDir ? 040755 : 0100644;
- m_linkFlag = isDir ? TarConstants.LF_DIR : TarConstants.LF_NORMAL;
- m_modTime = ( new Date() ).getTime() / 1000;
- m_linkName = new StringBuffer( "" );
- m_userName = new StringBuffer( "" );
- m_groupName = new StringBuffer( "" );
- }
-
- /**
- * Construct an entry with a name an a link flag.
- *
- * @param name Description of Parameter
- * @param linkFlag Description of Parameter
- */
- public TarArchiveEntry( final String name, final byte linkFlag )
- {
- this( name );
- m_linkFlag = linkFlag;
- }
-
- /**
- * 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.
- */
- public TarArchiveEntry( final File file )
- {
- this();
-
- m_file = file;
-
- String name = file.getPath();
-
- // Strip off drive letters!
- final String osName =
- System.getProperty( "os.name" ).toLowerCase( Locale.US );
- if( -1 != osName.indexOf( "netware" ) )
- {
- if( name.length() > 2 )
- {
- final char ch1 = name.charAt( 0 );
- final char ch2 = name.charAt( 1 );
-
- if( ch2 == ':' &&
- ( ( ch1 >= 'a' && ch1 <= 'z' ) ||
- ( ch1 >= 'A' && ch1 <= 'Z' ) ) )
- {
- name = name.substring( 2 );
- }
- }
- }
- else if( -1 != osName.indexOf( "netware" ) )
- {
- final int colon = name.indexOf( ':' );
- if( colon != -1 )
- {
- name = name.substring( colon + 1 );
- }
- }
-
- name = name.replace( File.separatorChar, '/' );
-
- // No absolute pathnames
- // Windows (and Posix?) paths can start with "\\NetworkDrive\",
- // so we loop on starting /'s.
- while( name.startsWith( "/" ) )
- {
- name = name.substring( 1 );
- }
-
- m_linkName = new StringBuffer( "" );
- m_name = new StringBuffer( name );
-
- if( file.isDirectory() )
- {
- m_mode = 040755;
- m_linkFlag = TarConstants.LF_DIR;
-
- if( m_name.charAt( m_name.length() - 1 ) != '/' )
- {
- m_name.append( "/" );
- }
- }
- else
- {
- m_mode = 0100644;
- m_linkFlag = TarConstants.LF_NORMAL;
- }
-
- m_size = file.length();
- m_modTime = file.lastModified() / 1000;
- m_checkSum = 0;
- m_devMajor = 0;
- m_devMinor = 0;
- }
-
- /**
- * Construct an entry from an archive's header bytes. File is set to null.
- *
- * @param header The header bytes from a tar archive entry.
- */
- public TarArchiveEntry( final byte[] header )
- {
- this();
- parseTarHeader( header );
- }
+ /** Convert millis to seconds */
+ public static final int MILLIS_PER_SECOND = 1000;
/**
* Construct an empty entry and prepares the header values.
*/
- private TarArchiveEntry()
- {
- m_magic = new StringBuffer( TarConstants.TMAGIC );
- m_name = new StringBuffer();
- m_linkName = new StringBuffer();
+ private TarArchiveEntry () {
+ this.magic = new StringBuffer(TMAGIC);
+ this.name = new StringBuffer();
+ this.linkName = new StringBuffer();
- String user = System.getProperty( "user.name", "" );
- if( user.length() > 31 )
- {
- user = user.substring( 0, 31 );
+ String user = System.getProperty("user.name", "");
+
+ if (user.length() > MAX_NAMELEN) {
+ user = user.substring(0, MAX_NAMELEN);
}
- m_userName = new StringBuffer( user );
- m_groupName = new StringBuffer( "" );
+ this.userId = 0;
+ this.groupId = 0;
+ this.userName = new StringBuffer(user);
+ this.groupName = new StringBuffer("");
+ this.file = null;
}
/**
- * Set this entry's group id.
+ * Construct an entry with only a name. This allows the programmer
+ * to construct the entry's header "by hand". File is set to null.
*
- * @param groupId This entry's new group id.
+ * @param name the entry name
*/
- public void setGroupID( final int groupId )
- {
- m_groupID = groupId;
+ public TarArchiveEntry(String name) {
+ this();
+
+ boolean isDir = name.endsWith("/");
+
+ this.devMajor = 0;
+ this.devMinor = 0;
+ this.name = new StringBuffer(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;
+
}
/**
- * Set this entry's group id.
+ * Construct an entry with a name and a link flag.
*
- * @param groupId This entry's new group id.
- * @deprecated Use setGroupID() instead
- * @see #setGroupID(int)
+ * @param name the entry name
+ * @param linkFlag the entry link flag.
*/
- public void setGroupId( final int groupId )
- {
- m_groupID = groupId;
+ public TarArchiveEntry(String name, byte linkFlag) {
+ this(name);
+ this.linkFlag = linkFlag;
}
/**
- * Set this entry's group name.
+ * Construct an entry for a file. File is set to file, and the
+ * header is constructed from information from the file.
*
- * @param groupName This entry's new group name.
+ * @param file The file that the entry represents.
*/
- public void setGroupName( final String groupName )
- {
- m_groupName = new StringBuffer( groupName );
- }
+ public TarArchiveEntry(File file) {
+ this();
- /**
- * Set this entry's modification time. The parameter passed to this method
- * is in "Java time".
- *
- * @param time This entry's new modification time.
- */
- public void setModTime( final long time )
- {
- m_modTime = time / 1000;
- }
+ this.file = file;
- /**
- * Set this entry's modification time.
- *
- * @param time This entry's new modification time.
- */
- public void setModTime( final Date time )
- {
- m_modTime = time.getTime() / 1000;
- }
+ String fileName = file.getPath();
+ String osname = System.getProperty("os.name").toLowerCase(Locale.US);
- /**
- * Set the mode for this entry
- *
- * @param mode The new Mode value
- */
- public void setMode( final int mode )
- {
- m_mode = mode;
- }
+ if (osname != null) {
- /**
- * Set this entry's name.
- *
- * @param name This entry's new name.
- */
- public void setName( final String name )
- {
- m_name = new StringBuffer( name );
- }
+ // Strip off drive letters!
+ // REVIEW Would a better check be "(File.separator == '\')"?
- /**
- * Set this entry's file size.
- *
- * @param size This entry's new file size.
- */
- public void setSize( final long size )
- {
- m_size = size;
- }
+ if (osname.startsWith("windows")) {
+ if (fileName.length() > 2) {
+ char ch1 = fileName.charAt(0);
+ char ch2 = fileName.charAt(1);
- /**
- * Set this entry's user id.
- *
- * @param userId This entry's new user id.
- */
- public void setUserID( final int userId )
- {
- m_userID = userId;
- }
-
- /**
- * Set this entry's user id.
- *
- * @param userId This entry's new user id.
- * @deprecated Use setUserID() instead
- * @see #setUserID(int)
- */
- public void setUserId( final int userId )
- {
- m_userID = userId;
- }
-
- /**
- * Set this entry's user name.
- *
- * @param userName This entry's new user name.
- */
- public void setUserName( final String userName )
- {
- m_userName = new StringBuffer( userName );
- }
-
- /**
- * If this entry represents a file, and the file is a directory, return an
- * array of TarEntries for this entry's children.
- *
- * @return An array of TarEntry's for this entry's children.
- */
- public TarArchiveEntry[] getDirectoryEntries()
- {
- if( null == m_file || !m_file.isDirectory() )
- {
- return new TarArchiveEntry[ 0 ];
+ if (ch2 == ':'
+ && ((ch1 >= 'a' && ch1 <= 'z')
+ || (ch1 >= 'A' && ch1 <= 'Z'))) {
+ fileName = fileName.substring(2);
+ }
+ }
+ } else if (osname.indexOf("netware") > -1) {
+ int colon = fileName.indexOf(':');
+ if (colon != -1) {
+ fileName = fileName.substring(colon + 1);
+ }
+ }
}
- final String[] list = m_file.list();
- final TarArchiveEntry[] result = new TarArchiveEntry[ list.length ];
+ fileName = fileName.replace(File.separatorChar, '/');
- for( int i = 0; i < list.length; ++i )
- {
- result[ i ] = new TarArchiveEntry( new File( m_file, list[ i ] ) );
+ // No absolute pathnames
+ // Windows (and Posix?) paths can start with "\\NetworkDrive\",
+ // so we loop on starting /'s.
+ while (fileName.startsWith("/")) {
+ fileName = fileName.substring(1);
}
- return result;
+ this.linkName = new StringBuffer("");
+ this.name = new StringBuffer(fileName);
+
+ if (file.isDirectory()) {
+ this.mode = DEFAULT_DIR_MODE;
+ this.linkFlag = LF_DIR;
+
+ if (this.name.charAt(this.name.length() - 1) != '/') {
+ this.name.append("/");
+ }
+ } else {
+ this.mode = DEFAULT_FILE_MODE;
+ this.linkFlag = LF_NORMAL;
+ }
+
+ this.size = file.length();
+ this.modTime = file.lastModified() / MILLIS_PER_SECOND;
+ this.devMajor = 0;
+ this.devMinor = 0;
}
/**
- * Get this entry's file.
+ * Construct an entry from an archive's header bytes. File is set
+ * to null.
*
- * @return This entry's file.
+ * @param headerBuf The header bytes from a tar archive entry.
*/
- public File getFile()
- {
- return m_file;
+ public TarArchiveEntry(byte[] headerBuf) {
+ this();
+ parseTarHeader(headerBuf);
}
/**
- * Get this entry's group id.
+ * Determine if the two entries are equal. Equality is determined
+ * by the header names being equal.
*
- * @return This entry's group id.
- * @deprecated Use getGroupID() instead
- * @see #getGroupID()
+ * @param it Entry to be checked for equality.
+ * @return True if the entries are equal.
*/
- public int getGroupId()
- {
- return m_groupID;
+ public boolean equals(TarArchiveEntry it) {
+ return getName().equals(it.getName());
}
/**
- * Get this entry's group id.
+ * Determine if the two entries are equal. Equality is determined
+ * by the header names being equal.
*
- * @return This entry's group id.
+ * @param it Entry to be checked for equality.
+ * @return True if the entries are equal.
*/
- public int getGroupID()
- {
- return m_groupID;
+ public boolean equals(Object it) {
+ if (it == null || getClass() != it.getClass()) {
+ return false;
+ }
+ return equals((TarArchiveEntry) it);
}
/**
- * Get this entry's group name.
+ * Hashcodes are based on entry names.
*
- * @return This entry's group name.
+ * @return the entry hashcode
*/
- public String getGroupName()
- {
- return m_groupName.toString();
+ public int hashCode() {
+ return getName().hashCode();
}
/**
- * Set this entry's modification time.
+ * Determine if the given entry is a descendant of this entry.
+ * Descendancy is determined by the name of the descendant
+ * starting with this entry's name.
*
- * @return The ModTime value
+ * @param desc Entry to be checked as a descendent of this.
+ * @return True if entry is a descendant of this.
*/
- public Date getModTime()
- {
- return new Date( m_modTime * 1000 );
- }
-
- /**
- * Get this entry's mode.
- *
- * @return This entry's mode.
- */
- public int getMode()
- {
- return m_mode;
+ public boolean isDescendent(TarArchiveEntry desc) {
+ return desc.getName().startsWith(getName());
}
/**
@@ -489,41 +318,35 @@
*
* @return This entry's name.
*/
- public String getName()
- {
- return m_name.toString();
+ public String getName() {
+ return name.toString();
}
/**
- * Get this entry's file size.
+ * Set this entry's name.
*
- * @return This entry's file size.
+ * @param name This entry's new name.
*/
- public long getSize()
- {
- return m_size;
+ public void setName(String name) {
+ this.name = new StringBuffer(name);
}
/**
- * Get this entry's checksum.
+ * Set the mode for this entry
*
- * @return This entry's checksum.
+ * @param mode the mode for this entry
*/
- public int getCheckSum()
- {
- return m_checkSum;
+ public void setMode(int mode) {
+ this.mode = mode;
}
/**
- * Get this entry's user id.
+ * Get this entry's link name.
*
- * @return This entry's user id.
- * @deprecated Use getUserID() instead
- * @see #getUserID()
+ * @return This entry's link name.
*/
- public int getUserId()
- {
- return m_userID;
+ public String getLinkName() {
+ return linkName.toString();
}
/**
@@ -531,9 +354,35 @@
*
* @return This entry's user id.
*/
- public int getUserID()
- {
- return m_userID;
+ public int getUserId() {
+ return userId;
+ }
+
+ /**
+ * Set this entry's user id.
+ *
+ * @param userId This entry's new user id.
+ */
+ public void setUserId(int userId) {
+ this.userId = userId;
+ }
+
+ /**
+ * Get this entry's group id.
+ *
+ * @return This entry's group id.
+ */
+ public int getGroupId() {
+ return groupId;
+ }
+
+ /**
+ * Set this entry's group id.
+ *
+ * @param groupId This entry's new group id.
+ */
+ public void setGroupId(int groupId) {
+ this.groupId = groupId;
}
/**
@@ -541,22 +390,132 @@
*
* @return This entry's user name.
*/
- public String getUserName()
- {
- return m_userName.toString();
+ public String getUserName() {
+ return userName.toString();
}
/**
- * Determine if the given entry is a descendant of this entry. Descendancy
- * is determined by the name of the descendant starting with this entry's
- * name.
+ * Set this entry's user name.
*
- * @param desc Entry to be checked as a descendent of
- * @return True if entry is a descendant of
+ * @param userName This entry's new user name.
*/
- public boolean isDescendent( final TarArchiveEntry desc )
- {
- return desc.getName().startsWith( getName() );
+ public void setUserName(String userName) {
+ this.userName = new StringBuffer(userName);
+ }
+
+ /**
+ * Get this entry's group name.
+ *
+ * @return This entry's group name.
+ */
+ public String getGroupName() {
+ return groupName.toString();
+ }
+
+ /**
+ * Set this entry's group name.
+ *
+ * @param groupName This entry's new group name.
+ */
+ public void setGroupName(String groupName) {
+ this.groupName = new StringBuffer(groupName);
+ }
+
+ /**
+ * Convenience method to set this entry's group and user ids.
+ *
+ * @param userId This entry's new user id.
+ * @param groupId This entry's new group id.
+ */
+ public void setIds(int userId, int groupId) {
+ setUserId(userId);
+ setGroupId(groupId);
+ }
+
+ /**
+ * Convenience method to set this entry's group and user names.
+ *
+ * @param userName This entry's new user name.
+ * @param groupName This entry's new group name.
+ */
+ public void setNames(String userName, String groupName) {
+ setUserName(userName);
+ setGroupName(groupName);
+ }
+
+ /**
+ * Set this entry's modification time. The parameter passed
+ * to this method is in "Java time".
+ *
+ * @param time This entry's new modification time.
+ */
+ public void setModTime(long time) {
+ modTime = time / MILLIS_PER_SECOND;
+ }
+
+ /**
+ * Set this entry's modification time.
+ *
+ * @param time This entry's new modification time.
+ */
+ public void setModTime(Date time) {
+ modTime = time.getTime() / MILLIS_PER_SECOND;
+ }
+
+ /**
+ * Set this entry's modification time.
+ *
+ * @return time This entry's new modification time.
+ */
+ public Date getModTime() {
+ return new Date(modTime * MILLIS_PER_SECOND);
+ }
+
+ /**
+ * Get this entry's file.
+ *
+ * @return This entry's file.
+ */
+ public File getFile() {
+ return file;
+ }
+
+ /**
+ * Get this entry's mode.
+ *
+ * @return This entry's mode.
+ */
+ public int getMode() {
+ return mode;
+ }
+
+ /**
+ * Get this entry's file size.
+ *
+ * @return This entry's file size.
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * Set this entry's file size.
+ *
+ * @param size This entry's new file size.
+ */
+ public void setSize(long size) {
+ this.size = size;
+ }
+
+
+ /**
+ * Indicate if this entry is a GNU long name block
+ *
+ * @return true if this is a long name extension provided by GNU tar
+ */
+ public boolean isGNULongNameEntry() {
+ return linkFlag == LF_GNUTYPE_LONGNAME
+ && name.toString().equals(GNU_LONGLINK);
}
/**
@@ -564,20 +523,16 @@
*
* @return True if this entry is a directory.
*/
- public boolean isDirectory()
- {
- if( m_file != null )
- {
- return m_file.isDirectory();
+ public boolean isDirectory() {
+ if (file != null) {
+ return file.isDirectory();
}
- if( m_linkFlag == TarConstants.LF_DIR )
- {
+ if (linkFlag == LF_DIR) {
return true;
}
- if( getName().endsWith( "/" ) )
- {
+ if (getName().endsWith("/")) {
return true;
}
@@ -585,26 +540,62 @@
}
/**
- * Indicate if this entry is a GNU long name block
+ * If this entry represents a file, and the file is a directory, return
+ * an array of TarEntries for this entry's children.
*
- * @return true if this is a long name extension provided by GNU tar
+ * @return An array of TarEntry's for this entry's children.
*/
- public boolean isGNULongNameEntry()
- {
- return m_linkFlag == TarConstants.LF_GNUTYPE_LONGNAME &&
- m_name.toString().equals( TarConstants.GNU_LONGLINK );
+ public TarArchiveEntry[] getDirectoryEntries() {
+ if (file == null || !file.isDirectory()) {
+ return new TarArchiveEntry[0];
+ }
+
+ String[] list = file.list();
+ TarArchiveEntry[] result = new TarArchiveEntry[list.length];
+
+ for (int i = 0; i < list.length; ++i) {
+ result[i] = new TarArchiveEntry(new File(file, list[i]));
+ }
+
+ return result;
}
/**
- * Determine if the two entries are equal. Equality is determined by the
- * header names being equal.
+ * Write an entry's header information to a header buffer.
*
- * @param other Entry to be checked for equality.
- * @return True if the entries are equal.
+ * @param outbuf The tar entry header buffer to fill in.
*/
- public boolean equals( final TarArchiveEntry other )
- {
- return getName().equals( other.getName() );
+ public void writeEntryHeader(byte[] outbuf) {
+ 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);
+
+ int csOffset = offset;
+
+ for (int c = 0; c < CHKSUMLEN; ++c) {
+ outbuf[offset++] = (byte) ' ';
+ }
+
+ 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);
+
+ while (offset < outbuf.length) {
+ outbuf[offset++] = 0;
+ }
+
+ long chk = TarUtils.computeCheckSum(outbuf);
+
+ TarUtils.getCheckSumOctalBytes(chk, outbuf, csOffset, CHKSUMLEN);
}
/**
@@ -612,74 +603,34 @@
*
* @param header The tar entry header buffer to get information from.
*/
- private void parseTarHeader( final byte[] header )
- {
+ public void parseTarHeader(byte[] header) {
int offset = 0;
- m_name = TarUtils.parseName( header, offset, NAMELEN );
+ name = TarUtils.parseName(header, offset, NAMELEN);
offset += NAMELEN;
- m_mode = (int)TarUtils.parseOctal( header, offset, TarConstants.MODELEN );
- offset += TarConstants.MODELEN;
- m_userID = (int)TarUtils.parseOctal( header, offset, TarConstants.UIDLEN );
- offset += TarConstants.UIDLEN;
- m_groupID = (int)TarUtils.parseOctal( header, offset, TarConstants.GIDLEN );
- offset += TarConstants.GIDLEN;
- m_size = TarUtils.parseOctal( header, offset, TarConstants.SIZELEN );
- offset += TarConstants.SIZELEN;
- m_modTime = TarUtils.parseOctal( header, offset, TarConstants.MODTIMELEN );
- offset += TarConstants.MODTIMELEN;
- m_checkSum = (int)TarUtils.parseOctal( header, offset, TarConstants.CHKSUMLEN );
- offset += TarConstants.CHKSUMLEN;
- m_linkFlag = header[ offset++ ];
- m_linkName = TarUtils.parseName( header, offset, NAMELEN );
+ mode = (int) TarUtils.parseOctal(header, offset, MODELEN);
+ offset += MODELEN;
+ userId = (int) TarUtils.parseOctal(header, offset, UIDLEN);
+ offset += UIDLEN;
+ groupId = (int) TarUtils.parseOctal(header, offset, GIDLEN);
+ offset += GIDLEN;
+ size = TarUtils.parseOctal(header, offset, SIZELEN);
+ offset += SIZELEN;
+ modTime = TarUtils.parseOctal(header, offset, MODTIMELEN);
+ offset += MODTIMELEN;
+ offset += CHKSUMLEN;
+ linkFlag = header[offset++];
+ linkName = TarUtils.parseName(header, offset, NAMELEN);
offset += NAMELEN;
- m_magic = TarUtils.parseName( header, offset, TarConstants.MAGICLEN );
- offset += TarConstants.MAGICLEN;
- m_userName = TarUtils.parseName( header, offset, TarConstants.UNAMELEN );
- offset += TarConstants.UNAMELEN;
- m_groupName = TarUtils.parseName( header, offset, TarConstants.GNAMELEN );
- offset += TarConstants.GNAMELEN;
- m_devMajor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
- offset += TarConstants.DEVLEN;
- m_devMinor = (int)TarUtils.parseOctal( header, offset, TarConstants.DEVLEN );
- }
-
- /**
- * Write an entry's header information to a header buffer.
- *
- * @param buffer The tar entry header buffer to fill in.
- */
- public void writeEntryHeader( final byte[] buffer )
- {
- int offset = 0;
-
- offset = TarUtils.getNameBytes( m_name, buffer, offset, NAMELEN );
- offset = TarUtils.getOctalBytes( m_mode, buffer, offset, TarConstants.MODELEN );
- offset = TarUtils.getOctalBytes( m_userID, buffer, offset, TarConstants.UIDLEN );
- offset = TarUtils.getOctalBytes( m_groupID, buffer, offset, TarConstants.GIDLEN );
- offset = TarUtils.getLongOctalBytes( m_size, buffer, offset, TarConstants.SIZELEN );
- offset = TarUtils.getLongOctalBytes( m_modTime, buffer, offset, TarConstants.MODTIMELEN );
-
- final int checkSumOffset = offset;
- for( int i = 0; i < TarConstants.CHKSUMLEN; ++i )
- {
- buffer[ offset++ ] = (byte)' ';
- }
-
- buffer[ offset++ ] = m_linkFlag;
- offset = TarUtils.getNameBytes( m_linkName, buffer, offset, NAMELEN );
- offset = TarUtils.getNameBytes( m_magic, buffer, offset, TarConstants.MAGICLEN );
- offset = TarUtils.getNameBytes( m_userName, buffer, offset, TarConstants.UNAMELEN );
- offset = TarUtils.getNameBytes( m_groupName, buffer, offset, TarConstants.GNAMELEN );
- offset = TarUtils.getOctalBytes( m_devMajor, buffer, offset, TarConstants.DEVLEN );
- offset = TarUtils.getOctalBytes( m_devMinor, buffer, offset, TarConstants.DEVLEN );
-
- while( offset < buffer.length )
- {
- buffer[ offset++ ] = 0;
- }
-
- final long checkSum = TarUtils.computeCheckSum( buffer );
- TarUtils.getCheckSumOctalBytes( checkSum, buffer, checkSumOffset, TarConstants.CHKSUMLEN );
+ magic = TarUtils.parseName(header, offset, MAGICLEN);
+ offset += MAGICLEN;
+ userName = TarUtils.parseName(header, offset, UNAMELEN);
+ offset += UNAMELEN;
+ groupName = TarUtils.parseName(header, offset, GNAMELEN);
+ offset += GNAMELEN;
+ devMajor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
+ offset += DEVLEN;
+ devMinor = (int) TarUtils.parseOctal(header, offset, DEVLEN);
}
}
+
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarBuffer.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarBuffer.java
index a72e806..fd16aed 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarBuffer.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarBuffer.java
@@ -24,65 +24,129 @@
import java.util.Arrays;
/**
- * The TarBuffer class implements the tar archive concept of a buffered input
- * stream. This concept goes back to the days of blocked tape drives and special
- * io devices. In the Java universe, the only real function that this class
- * performs is to ensure that files have the correct "block" size, or other tars
- * will complain. <p>
+ * The TarBuffer class implements the tar archive concept
+ * of a buffered input stream. This concept goes back to the
+ * days of blocked tape drives and special io devices. In the
+ * Java universe, the only real function that this class
+ * performs is to ensure that files have the correct "block"
+ * size, or other tars will complain.
+ * <p>
+ * You should never have a need to access this class directly.
+ * TarBuffers are created by Tar IO Streams.
*
- * You should never have a need to access this class directly. TarBuffers are
- * created by Tar IO Streams.
*/
-class TarBuffer
-{
- public static final int DEFAULT_RECORDSIZE = ( 512 );
- public static final int DEFAULT_BLOCKSIZE = ( DEFAULT_RECORDSIZE * 20 );
- private byte[] m_blockBuffer;
- private int m_blockSize;
- private int m_currBlkIdx;
- private int m_currRecIdx;
- private boolean m_debug;
+public class TarBuffer {
- private InputStream m_input;
- private OutputStream m_output;
- private int m_recordSize;
- private int m_recsPerBlock;
+ /** Default record size */
+ public static final int DEFAULT_RCDSIZE = (512);
- public TarBuffer( final InputStream input )
- {
- this( input, TarBuffer.DEFAULT_BLOCKSIZE );
+ /** Default block size */
+ public static final int DEFAULT_BLKSIZE = (DEFAULT_RCDSIZE * 20);
+
+ private InputStream inStream;
+ private OutputStream outStream;
+ private byte[] blockBuffer;
+ private int currBlkIdx;
+ private int currRecIdx;
+ private int blockSize;
+ private int recordSize;
+ private int recsPerBlock;
+ private boolean debug;
+
+ /**
+ * Constructor for a TarBuffer on an input stream.
+ * @param inStream the input stream to use
+ */
+ public TarBuffer(InputStream inStream) {
+ this(inStream, TarBuffer.DEFAULT_BLKSIZE);
}
- public TarBuffer( final InputStream input, final int blockSize )
- {
- this( input, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
+ /**
+ * Constructor for a TarBuffer on an input stream.
+ * @param inStream the input stream to use
+ * @param blockSize the block size to use
+ */
+ public TarBuffer(InputStream inStream, int blockSize) {
+ this(inStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
}
- public TarBuffer( final InputStream input,
- final int blockSize,
- final int recordSize )
- {
- m_input = input;
- initialize( blockSize, recordSize );
+ /**
+ * Constructor for a TarBuffer on an input stream.
+ * @param inStream the input stream to use
+ * @param blockSize the block size to use
+ * @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);
}
- public TarBuffer( final OutputStream output )
- {
- this( output, TarBuffer.DEFAULT_BLOCKSIZE );
+ /**
+ * Constructor for a TarBuffer on an output stream.
+ * @param outStream the output stream to use
+ */
+ public TarBuffer(OutputStream outStream) {
+ this(outStream, TarBuffer.DEFAULT_BLKSIZE);
}
- public TarBuffer( final OutputStream output, final int blockSize )
- {
- this( output, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
+ /**
+ * Constructor for a TarBuffer on an output stream.
+ * @param outStream the output stream to use
+ * @param blockSize the block size to use
+ */
+ public TarBuffer(OutputStream outStream, int blockSize) {
+ this(outStream, blockSize, TarBuffer.DEFAULT_RCDSIZE);
}
- public TarBuffer( final OutputStream output,
- final int blockSize,
- final int recordSize )
- {
- m_output = output;
- initialize( blockSize, recordSize );
+ /**
+ * Constructor for a TarBuffer on an output stream.
+ * @param outStream the output stream to use
+ * @param blockSize the block size to use
+ * @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);
+ }
+
+ /**
+ * Initialization common to all constructors.
+ */
+ private void initialize(int blockSize, int recordSize) {
+ this.debug = false;
+ this.blockSize = blockSize;
+ this.recordSize = recordSize;
+ this.recsPerBlock = (this.blockSize / this.recordSize);
+ this.blockBuffer = new byte[this.blockSize];
+
+ if (this.inStream != null) {
+ this.currBlkIdx = -1;
+ this.currRecIdx = this.recsPerBlock;
+ } else {
+ this.currBlkIdx = 0;
+ this.currRecIdx = 0;
+ }
+ }
+
+ /**
+ * Get the TAR Buffer's block size. Blocks consist of multiple records.
+ * @return the block size
+ */
+ public int getBlockSize() {
+ return this.blockSize;
+ }
+
+ /**
+ * Get the TAR Buffer's record size.
+ * @return the record size
+ */
+ public int getRecordSize() {
+ return this.recordSize;
}
/**
@@ -90,66 +154,20 @@
*
* @param debug If true, print debugging output.
*/
- public void setDebug( final boolean debug )
- {
- m_debug = debug;
+ public void setDebug(boolean debug) {
+ this.debug = debug;
}
/**
- * Get the TAR Buffer's block size. Blocks consist of multiple records.
- *
- * @return The BlockSize value
- */
- public int getBlockSize()
- {
- return m_blockSize;
- }
-
- /**
- * Get the current block number, zero based.
- *
- * @return The current zero based block number.
- */
- public int getCurrentBlockNum()
- {
- return m_currBlkIdx;
- }
-
- /**
- * Get the current record number, within the current block, zero based.
- * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
- *
- * @return The current zero based record number.
- */
- public int getCurrentRecordNum()
- {
- return m_currRecIdx - 1;
- }
-
- /**
- * Get the TAR Buffer's record size.
- *
- * @return The RecordSize value
- */
- public int getRecordSize()
- {
- return m_recordSize;
- }
-
- /**
- * Determine if an archive record indicate End of Archive. End of archive is
- * indicated by a record that consists entirely of null bytes.
+ * Determine if an archive record indicate End of Archive. End of
+ * archive is indicated by a record that consists entirely of null bytes.
*
* @param record The record data to check.
- * @return The EOFRecord value
+ * @return true if the record data is an End of Archive
*/
- public boolean isEOFRecord( final byte[] record )
- {
- final int size = getRecordSize();
- for( int i = 0; i < size; ++i )
- {
- if( record[ i ] != 0 )
- {
+ public boolean isEOFRecord(byte[] record) {
+ for (int i = 0, sz = getRecordSize(); i < sz; ++i) {
+ if (record[i] != 0) {
return false;
}
}
@@ -158,270 +176,81 @@
}
/**
- * Close the TarBuffer. If this is an output buffer, also flush the current
- * block before closing.
+ * Skip over a record on the input stream.
+ * @throws IOException on error
*/
- public void close()
- throws IOException
- {
- if( m_debug )
- {
- debug( "TarBuffer.closeBuffer()." );
+ public void skipRecord() throws IOException {
+ if (debug) {
+ System.err.println("SkipRecord: recIdx = " + currRecIdx
+ + " blkIdx = " + currBlkIdx);
}
- if( null != m_output )
- {
- flushBlock();
+ if (inStream == null) {
+ throw new IOException("reading (via skip) from an output buffer");
+ }
- if( m_output != System.out && m_output != System.err )
- {
- m_output.close();
- m_output = null;
+ if (currRecIdx >= recsPerBlock) {
+ if (!readBlock()) {
+ return; // UNDONE
}
}
- else if( m_input != null )
- {
- if( m_input != System.in )
- {
- m_input.close();
- m_input = null;
- }
- }
+
+ currRecIdx++;
}
/**
* Read a record from the input stream and return the data.
*
* @return The record data.
- * @exception IOException Description of Exception
+ * @throws IOException on error
*/
- public byte[] readRecord()
- throws IOException
- {
- if( m_debug )
- {
- final String message = "ReadRecord: recIdx = " + m_currRecIdx +
- " blkIdx = " + m_currBlkIdx;
- debug( message );
+ public byte[] readRecord() throws IOException {
+ if (debug) {
+ System.err.println("ReadRecord: recIdx = " + currRecIdx
+ + " blkIdx = " + currBlkIdx);
}
- if( null == m_input )
- {
- final String message = "reading from an output buffer";
- throw new IOException( message );
+ if (inStream == null) {
+ throw new IOException("reading from an output buffer");
}
- if( m_currRecIdx >= m_recsPerBlock )
- {
- if( !readBlock() )
- {
+ if (currRecIdx >= recsPerBlock) {
+ if (!readBlock()) {
return null;
}
}
- final byte[] result = new byte[ m_recordSize ];
- System.arraycopy( m_blockBuffer,
- ( m_currRecIdx * m_recordSize ),
- result,
- 0,
- m_recordSize );
+ byte[] result = new byte[recordSize];
- m_currRecIdx++;
+ System.arraycopy(blockBuffer,
+ (currRecIdx * recordSize), result, 0,
+ recordSize);
+
+ currRecIdx++;
return result;
}
/**
- * Skip over a record on the input stream.
- */
- public void skipRecord()
- throws IOException
- {
- if( m_debug )
- {
- final String message = "SkipRecord: recIdx = " + m_currRecIdx +
- " blkIdx = " + m_currBlkIdx;
- debug( message );
- }
-
- if( null == m_input )
- {
- final String message = "reading (via skip) from an output buffer";
- throw new IOException( message );
- }
-
- if( m_currRecIdx >= m_recsPerBlock )
- {
- if( !readBlock() )
- {
- return;// UNDONE
- }
- }
-
- m_currRecIdx++;
- }
-
- /**
- * Write an archive record to the archive.
- *
- * @param record The record data to write to the archive.
- */
- public void writeRecord( final byte[] record )
- throws IOException
- {
- if( m_debug )
- {
- final String message = "WriteRecord: recIdx = " + m_currRecIdx +
- " blkIdx = " + m_currBlkIdx;
- debug( message );
- }
-
- if( null == m_output )
- {
- final String message = "writing to an input buffer";
- throw new IOException( message );
- }
-
- if( record.length != m_recordSize )
- {
- final String message = "record to write has length '" +
- record.length + "' which is not the record size of '" +
- m_recordSize + "'";
- throw new IOException( message );
- }
-
- if( m_currRecIdx >= m_recsPerBlock )
- {
- writeBlock();
- }
-
- System.arraycopy( record,
- 0,
- m_blockBuffer,
- ( m_currRecIdx * m_recordSize ),
- m_recordSize );
-
- m_currRecIdx++;
- }
-
- /**
- * Write an archive record to the archive, where the record may be inside of
- * a larger array buffer. The buffer must be "offset plus record size" long.
- *
- * @param buffer The buffer containing the record data to write.
- * @param offset The offset of the record data within buf.
- */
- public void writeRecord( final byte[] buffer, final int offset )
- throws IOException
- {
- if( m_debug )
- {
- final String message = "WriteRecord: recIdx = " + m_currRecIdx +
- " blkIdx = " + m_currBlkIdx;
- debug( message );
- }
-
- if( null == m_output )
- {
- final String message = "writing to an input buffer";
- throw new IOException( message );
- }
-
- if( ( offset + m_recordSize ) > buffer.length )
- {
- final String message = "record has length '" + buffer.length +
- "' with offset '" + offset + "' which is less than the record size of '" +
- m_recordSize + "'";
- throw new IOException( message );
- }
-
- if( m_currRecIdx >= m_recsPerBlock )
- {
- writeBlock();
- }
-
- System.arraycopy( buffer,
- offset,
- m_blockBuffer,
- ( m_currRecIdx * m_recordSize ),
- m_recordSize );
-
- m_currRecIdx++;
- }
-
- /**
- * Flush the current data block if it has any data in it.
- */
- private void flushBlock()
- throws IOException
- {
- if( m_debug )
- {
- final String message = "TarBuffer.flushBlock() called.";
- debug( message );
- }
-
- if( m_output == null )
- {
- final String message = "writing to an input buffer";
- throw new IOException( message );
- }
-
- if( m_currRecIdx > 0 )
- {
- writeBlock();
- }
- }
-
- /**
- * Initialization common to all constructors.
- */
- private void initialize( final int blockSize, final int recordSize )
- {
- m_debug = false;
- m_blockSize = blockSize;
- m_recordSize = recordSize;
- m_recsPerBlock = ( m_blockSize / m_recordSize );
- m_blockBuffer = new byte[ m_blockSize ];
-
- if( null != m_input )
- {
- m_currBlkIdx = -1;
- m_currRecIdx = m_recsPerBlock;
- }
- else
- {
- m_currBlkIdx = 0;
- m_currRecIdx = 0;
- }
- }
-
- /**
* @return false if End-Of-File, else true
*/
- private boolean readBlock()
- throws IOException
- {
- if( m_debug )
- {
- final String message = "ReadBlock: blkIdx = " + m_currBlkIdx;
- debug( message );
+ private boolean readBlock() throws IOException {
+ if (debug) {
+ System.err.println("ReadBlock: blkIdx = " + currBlkIdx);
}
- if( null == m_input )
- {
- final String message = "reading from an output buffer";
- throw new IOException( message );
+ if (inStream == null) {
+ throw new IOException("reading from an output buffer");
}
- m_currRecIdx = 0;
+ currRecIdx = 0;
int offset = 0;
- int bytesNeeded = m_blockSize;
+ int bytesNeeded = blockSize;
- while( bytesNeeded > 0 )
- {
- final long numBytes = m_input.read( m_blockBuffer, offset, bytesNeeded );
+ while (bytesNeeded > 0) {
+ long numBytes = inStream.read(blockBuffer, offset,
+ bytesNeeded);
//
// NOTE
@@ -436,15 +265,20 @@
//
// Thanks to 'Yohann.Roussel@alcatel.fr' for this fix.
//
- if( numBytes == -1 )
- {
+ if (numBytes == -1) {
+ if (offset == 0) {
+ // Ensure that we do not read gigabytes of zeros
+ // for a corrupt tar file.
+ // See http://issues.apache.org/bugzilla/show_bug.cgi?id=39924
+ return false;
+ }
// However, just leaving the unread portion of the buffer dirty does
// cause problems in some cases. This problem is described in
// http://issues.apache.org/bugzilla/show_bug.cgi?id=29877
//
// The solution is to fill the unused portion of the buffer with zeros.
- Arrays.fill(m_blockBuffer, offset, offset + bytesNeeded, (byte) 0);
+ Arrays.fill(blockBuffer, offset, offset + bytesNeeded, (byte) 0);
break;
}
@@ -452,54 +286,171 @@
offset += numBytes;
bytesNeeded -= numBytes;
- if( numBytes != m_blockSize )
- {
- if( m_debug )
- {
- System.err.println( "ReadBlock: INCOMPLETE READ "
- + numBytes + " of " + m_blockSize
- + " bytes read." );
+ if (numBytes != blockSize) {
+ if (debug) {
+ System.err.println("ReadBlock: INCOMPLETE READ "
+ + numBytes + " of " + blockSize
+ + " bytes read.");
}
}
}
- m_currBlkIdx++;
+ currBlkIdx++;
return true;
}
/**
- * Write a TarBuffer block to the archive.
+ * Get the current block number, zero based.
*
- * @exception IOException Description of Exception
+ * @return The current zero based block number.
*/
- private void writeBlock()
- throws IOException
- {
- if( m_debug )
- {
- final String message = "WriteBlock: blkIdx = " + m_currBlkIdx;
- debug( message );
- }
-
- if( null == m_output )
- {
- final String message = "writing to an input buffer";
- throw new IOException( message );
- }
-
- m_output.write( m_blockBuffer, 0, m_blockSize );
- m_output.flush();
-
- m_currRecIdx = 0;
- m_currBlkIdx++;
+ public int getCurrentBlockNum() {
+ return currBlkIdx;
}
- protected void debug( final String message )
- {
- if( m_debug )
- {
- System.err.println( message );
+ /**
+ * Get the current record number, within the current block, zero based.
+ * Thus, current offset = (currentBlockNum * recsPerBlk) + currentRecNum.
+ *
+ * @return The current zero based record number.
+ */
+ public int getCurrentRecordNum() {
+ return currRecIdx - 1;
+ }
+
+ /**
+ * Write an archive record to the archive.
+ *
+ * @param record The record data to write to the archive.
+ * @throws IOException on error
+ */
+ public void writeRecord(byte[] record) throws IOException {
+ if (debug) {
+ System.err.println("WriteRecord: recIdx = " + currRecIdx
+ + " blkIdx = " + currBlkIdx);
+ }
+
+ if (outStream == null) {
+ throw new IOException("writing to an input buffer");
+ }
+
+ if (record.length != recordSize) {
+ throw new IOException("record to write has length '"
+ + record.length
+ + "' which is not the record size of '"
+ + recordSize + "'");
+ }
+
+ if (currRecIdx >= recsPerBlock) {
+ writeBlock();
+ }
+
+ System.arraycopy(record, 0, blockBuffer,
+ (currRecIdx * recordSize),
+ recordSize);
+
+ currRecIdx++;
+ }
+
+ /**
+ * Write an archive record to the archive, where the record may be
+ * inside of a larger array buffer. The buffer must be "offset plus
+ * record size" long.
+ *
+ * @param buf The buffer containing the record data to write.
+ * @param offset The offset of the record data within buf.
+ * @throws IOException on error
+ */
+ public void writeRecord(byte[] buf, int offset) throws IOException {
+ if (debug) {
+ System.err.println("WriteRecord: recIdx = " + currRecIdx
+ + " blkIdx = " + currBlkIdx);
+ }
+
+ if (outStream == null) {
+ throw new IOException("writing to an input buffer");
+ }
+
+ if ((offset + recordSize) > buf.length) {
+ throw new IOException("record has length '" + buf.length
+ + "' with offset '" + offset
+ + "' which is less than the record size of '"
+ + recordSize + "'");
+ }
+
+ if (currRecIdx >= recsPerBlock) {
+ writeBlock();
+ }
+
+ System.arraycopy(buf, offset, blockBuffer,
+ (currRecIdx * recordSize),
+ recordSize);
+
+ currRecIdx++;
+ }
+
+ /**
+ * Write a TarBuffer block to the archive.
+ */
+ private void writeBlock() throws IOException {
+ if (debug) {
+ System.err.println("WriteBlock: blkIdx = " + currBlkIdx);
+ }
+
+ if (outStream == null) {
+ throw new IOException("writing to an input buffer");
+ }
+
+ outStream.write(blockBuffer, 0, blockSize);
+ outStream.flush();
+
+ currRecIdx = 0;
+ currBlkIdx++;
+ }
+
+ /**
+ * Flush the current data block if it has any data in it.
+ */
+ private void flushBlock() throws IOException {
+ if (debug) {
+ System.err.println("TarBuffer.flushBlock() called.");
+ }
+
+ if (outStream == null) {
+ throw new IOException("writing to an input buffer");
+ }
+
+ if (currRecIdx > 0) {
+ writeBlock();
+ }
+ }
+
+ /**
+ * Close the TarBuffer. If this is an output buffer, also flush the
+ * current block before closing.
+ * @throws IOException on error
+ */
+ public void close() throws IOException {
+ if (debug) {
+ System.err.println("TarBuffer.closeBuffer().");
+ }
+
+ if (outStream != null) {
+ flushBlock();
+
+ if (outStream != System.out
+ && outStream != System.err) {
+ outStream.close();
+
+ outStream = null;
+ }
+ } else if (inStream != null) {
+ if (inStream != System.in) {
+ inStream.close();
+
+ inStream = null;
+ }
}
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarConstants.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarConstants.java
index c56be6e..420dced 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarConstants.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarConstants.java
@@ -20,104 +20,116 @@
/**
* This interface contains all the definitions used in the package.
+ *
*/
-interface TarConstants
-{
+// CheckStyle:InterfaceIsTypeCheck OFF (bc)
+public interface TarConstants {
+
+ /**
+ * The length of the name field in a header buffer.
+ */
+ int NAMELEN = 100;
+
/**
* The length of the mode field in a header buffer.
*/
- int MODELEN = 8;
+ int MODELEN = 8;
/**
* The length of the user id field in a header buffer.
*/
- int UIDLEN = 8;
+ int UIDLEN = 8;
/**
* The length of the group id field in a header buffer.
*/
- int GIDLEN = 8;
+ int GIDLEN = 8;
/**
* The length of the checksum field in a header buffer.
*/
- int CHKSUMLEN = 8;
+ int CHKSUMLEN = 8;
/**
* The length of the size field in a header buffer.
*/
- int SIZELEN = 12;
+ int SIZELEN = 12;
+
+ /**
+ * The maximum size of a file in a tar archive (That's 11 sevens, octal).
+ */
+ long MAXSIZE = 077777777777L;
/**
* The length of the magic field in a header buffer.
*/
- int MAGICLEN = 8;
+ int MAGICLEN = 8;
/**
* The length of the modification time field in a header buffer.
*/
- int MODTIMELEN = 12;
+ int MODTIMELEN = 12;
/**
* The length of the user name field in a header buffer.
*/
- int UNAMELEN = 32;
+ int UNAMELEN = 32;
/**
* The length of the group name field in a header buffer.
*/
- int GNAMELEN = 32;
+ int GNAMELEN = 32;
/**
* The length of the devices field in a header buffer.
*/
- int DEVLEN = 8;
+ int DEVLEN = 8;
/**
* 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.
*/
- byte LF_OLDNORM = 0;
+ byte LF_OLDNORM = 0;
/**
* Normal file type.
*/
- byte LF_NORMAL = (byte)'0';
+ byte LF_NORMAL = (byte) '0';
/**
* Link file type.
*/
- byte LF_LINK = (byte)'1';
+ byte LF_LINK = (byte) '1';
/**
* Symbolic link file type.
*/
- byte LF_SYMLINK = (byte)'2';
+ byte LF_SYMLINK = (byte) '2';
/**
* Character device file type.
*/
- byte LF_CHR = (byte)'3';
+ byte LF_CHR = (byte) '3';
/**
* Block device file type.
*/
- byte LF_BLK = (byte)'4';
+ byte LF_BLK = (byte) '4';
/**
* Directory file type.
*/
- byte LF_DIR = (byte)'5';
+ byte LF_DIR = (byte) '5';
/**
* FIFO (pipe) file type.
*/
- byte LF_FIFO = (byte)'6';
+ byte LF_FIFO = (byte) '6';
/**
* Contiguous file type.
*/
- byte LF_CONTIG = (byte)'7';
+ byte LF_CONTIG = (byte) '7';
/**
* The magic tag representing a POSIX tar archive.
@@ -137,5 +149,5 @@
/**
* Identifies the *next* file on the tape as having a long name.
*/
- byte LF_GNUTYPE_LONGNAME = (byte)'L';
+ byte LF_GNUTYPE_LONGNAME = (byte) 'L';
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarInputStream.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarInputStream.java
index 21e2551..bf05051 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarInputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarInputStream.java
@@ -1,21 +1,26 @@
/*
- * 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
+ * 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
+ * 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.
+ * 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.
+ *
*/
+
+/*
+ * This package is based on the work done by Timothy Gerard Endres
+ * (time@ice.com) to whom the Ant project is very grateful for his great code.
+ */
+
package org.apache.commons.compress.archivers.tar;
import java.io.FilterInputStream;
@@ -24,186 +29,85 @@
import java.io.OutputStream;
/**
- * The TarInputStream reads a UNIX tar archive as an InputStream. methods are
- * provided to position at each successive entry in the archive, and the read
- * each entry as a normal input stream using read().
+ * The TarInputStream reads a UNIX tar archive as an InputStream.
+ * methods are provided to position at each successive entry in
+ * the archive, and the read each entry as a normal input stream
+ * using read().
+ *
*/
-public class TarInputStream
- extends FilterInputStream
-{
- private TarBuffer m_buffer;
- private TarArchiveEntry m_currEntry;
- private boolean m_debug;
- private int m_entryOffset;
- private int m_entrySize;
- private boolean m_hasHitEOF;
- private byte[] m_oneBuf;
- private byte[] m_readBuf;
+public class TarInputStream extends FilterInputStream {
+ private static final int SMALL_BUFFER_SIZE = 256;
+ private static final int BUFFER_SIZE = 8 * 1024;
+ private static final int LARGE_BUFFER_SIZE = 32 * 1024;
+ private static final int BYTE_MASK = 0xFF;
+
+ // CheckStyle:VisibilityModifier OFF - bc
+ protected boolean debug;
+ protected boolean hasHitEOF;
+ protected long entrySize;
+ protected long entryOffset;
+ protected byte[] readBuf;
+ protected TarBuffer buffer;
+ protected TarArchiveEntry currEntry;
/**
- * Construct a TarInputStream using specified input
- * stream and default block and record sizes.
- *
- * @param input stream to create TarInputStream from
- * @see TarBuffer#DEFAULT_BLOCKSIZE
- * @see TarBuffer#DEFAULT_RECORDSIZE
+ * This contents of this array is not used at all in this class,
+ * it is only here to avoid repreated object creation during calls
+ * to the no-arg read method.
*/
- public TarInputStream( final InputStream input )
- {
- this( input, TarBuffer.DEFAULT_BLOCKSIZE, TarBuffer.DEFAULT_RECORDSIZE );
+ protected byte[] oneBuf;
+
+ // CheckStyle:VisibilityModifier ON
+
+ /**
+ * Constructor for TarInputStream.
+ * @param is the input stream to use
+ */
+ public TarInputStream(InputStream is) {
+ this(is, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
}
/**
- * Construct a TarInputStream using specified input
- * stream, block size and default record sizes.
- *
- * @param input stream to create TarInputStream from
+ * Constructor for TarInputStream.
+ * @param is the input stream to use
* @param blockSize the block size to use
- * @see TarBuffer#DEFAULT_RECORDSIZE
*/
- public TarInputStream( final InputStream input,
- final int blockSize )
- {
- this( input, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
+ public TarInputStream(InputStream is, int blockSize) {
+ this(is, blockSize, TarBuffer.DEFAULT_RCDSIZE);
}
/**
- * Construct a TarInputStream using specified input
- * stream, block size and record sizes.
- *
- * @param input stream to create TarInputStream from
+ * 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( final InputStream input,
- final int blockSize,
- final int recordSize )
- {
- super( input );
+ public TarInputStream(InputStream is, int blockSize, int recordSize) {
+ super(is);
- m_buffer = new TarBuffer( input, blockSize, recordSize );
- m_oneBuf = new byte[ 1 ];
+ this.buffer = new TarBuffer(is, blockSize, recordSize);
+ this.readBuf = null;
+ this.oneBuf = new byte[1];
+ this.debug = false;
+ this.hasHitEOF = false;
}
/**
* Sets the debugging flag.
*
- * @param debug The new Debug value
+ * @param debug True to turn on debugging.
*/
- public void setDebug( final boolean debug )
- {
- m_debug = debug;
- m_buffer.setDebug( debug );
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ buffer.setDebug(debug);
}
/**
- * Get the next entry 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, and read the header and instantiate a new
- * TarEntry from the header bytes and return that 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 TarEntry in the archive, or null.
- * @exception IOException Description of Exception
+ * Closes this stream. Calls the TarBuffer's close() method.
+ * @throws IOException on error
*/
- public TarArchiveEntry getNextEntry()
- throws IOException
- {
- if( m_hasHitEOF )
- {
- return null;
- }
-
- if( m_currEntry != null )
- {
- final int numToSkip = m_entrySize - m_entryOffset;
-
- if( m_debug )
- {
- final String message = "TarInputStream: SKIP currENTRY '" +
- m_currEntry.getName() + "' SZ " + m_entrySize +
- " OFF " + m_entryOffset + " skipping " + numToSkip + " bytes";
- debug( message );
- }
-
- if( numToSkip > 0 )
- {
- skip( numToSkip );
- }
-
- m_readBuf = null;
- }
-
- final byte[] headerBuf = m_buffer.readRecord();
- if( headerBuf == null )
- {
- if( m_debug )
- {
- debug( "READ NULL RECORD" );
- }
- m_hasHitEOF = true;
- }
- else if( m_buffer.isEOFRecord( headerBuf ) )
- {
- if( m_debug )
- {
- debug( "READ EOF RECORD" );
- }
- m_hasHitEOF = true;
- }
-
- if( m_hasHitEOF )
- {
- m_currEntry = null;
- }
- else
- {
- m_currEntry = new TarArchiveEntry( headerBuf );
-
- if( !( headerBuf[ 257 ] == 'u' && headerBuf[ 258 ] == 's' &&
- headerBuf[ 259 ] == 't' && headerBuf[ 260 ] == 'a' &&
- headerBuf[ 261 ] == 'r' ) )
- {
- //Must be v7Format
- }
-
- if( m_debug )
- {
- final String message = "TarInputStream: SET CURRENTRY '" +
- m_currEntry.getName() + "' size = " + m_currEntry.getSize();
- debug( message );
- }
-
- m_entryOffset = 0;
-
- // REVIEW How do we resolve this discrepancy?!
- m_entrySize = (int)m_currEntry.getSize();
- }
-
- if( null != m_currEntry && m_currEntry.isGNULongNameEntry() )
- {
- // read in the name
- final StringBuffer longName = new StringBuffer();
- final byte[] buffer = new byte[ 256 ];
- int length = 0;
- while( ( length = read( buffer ) ) >= 0 )
- {
- final String str = new String( buffer, 0, length );
- longName.append( str );
- }
- getNextEntry();
-
- // remove trailing null terminator
- if (longName.length() > 0
- && longName.charAt(longName.length() - 1) == 0) {
- longName.deleteCharAt(longName.length() - 1);
- }
-
- m_currEntry.setName( longName.toString() );
- }
-
- return m_currEntry;
+ public void close() throws IOException {
+ buffer.close();
}
/**
@@ -211,68 +115,55 @@
*
* @return The TarBuffer record size.
*/
- public int getRecordSize()
- {
- return m_buffer.getRecordSize();
+ public int getRecordSize() {
+ return buffer.getRecordSize();
}
/**
- * Get the available data that can be read from the current entry in the
- * archive. This does not indicate how much data is left in the entire
- * archive, only in the current entry. This value is determined from the
- * entry's size header field and the amount of data already read from the
- * current entry.
+ * Get the available data that can be read from the current
+ * entry in the archive. This does not indicate how much data
+ * is left in the entire archive, only in the current entry.
+ * This value is determined from the entry's size header field
+ * and the amount of data already read from the current entry.
+ * Integer.MAX_VALUE is returen in case more than Integer.MAX_VALUE
+ * bytes are left in the current entry in the archive.
*
* @return The number of available bytes for the current entry.
- * @exception IOException when an IO error causes operation to fail
+ * @throws IOException for signature
*/
- public int available()
- throws IOException
- {
- return m_entrySize - m_entryOffset;
+ public int available() throws IOException {
+ if (entrySize - entryOffset > Integer.MAX_VALUE) {
+ return Integer.MAX_VALUE;
+ }
+ return (int) (entrySize - entryOffset);
}
/**
- * Closes this stream. Calls the TarBuffer's close() method.
+ * Skip bytes in the input buffer. This skips bytes in the
+ * current entry's data, not the entire archive, and will
+ * stop at the end of the current entry's data if the number
+ * to skip extends beyond that point.
*
- * @exception IOException when an IO error causes operation to fail
+ * @param numToSkip The number of bytes to skip.
+ * @return the number actually skipped
+ * @throws IOException on error
*/
- public void close()
- throws IOException
- {
- m_buffer.close();
- }
-
- /**
- * Copies the contents of the current tar archive entry directly into an
- * output stream.
- *
- * @param output The OutputStream into which to write the entry's data.
- * @exception IOException when an IO error causes operation to fail
- */
- public void copyEntryContents( final OutputStream output )
- throws IOException
- {
- final byte[] buffer = new byte[ 32 * 1024 ];
- while( true )
- {
- final int numRead = read( buffer, 0, buffer.length );
- if( numRead == -1 )
- {
+ public long skip(long numToSkip) throws IOException {
+ // REVIEW
+ // This is horribly inefficient, but it ensures that we
+ // properly skip over bytes via the TarBuffer...
+ //
+ byte[] skipBuf = new byte[BUFFER_SIZE];
+ long skip = numToSkip;
+ while (skip > 0) {
+ int realSkip = (int) (skip > skipBuf.length ? skipBuf.length : skip);
+ int numRead = read(skipBuf, 0, realSkip);
+ if (numRead == -1) {
break;
}
-
- output.write( buffer, 0, numRead );
+ skip -= numRead;
}
- }
-
- /**
- * Since we do not support marking just yet, we do nothing.
- *
- * @param markLimit The limit to mark.
- */
- public void mark( int markLimit )
- {
+ return (numToSkip - skip);
}
/**
@@ -280,189 +171,227 @@
*
* @return False.
*/
- public boolean markSupported()
- {
+ public boolean markSupported() {
return false;
}
/**
- * Reads a byte from the current tar archive entry. This method simply calls
- * read( byte[], int, int ).
+ * Since we do not support marking just yet, we do nothing.
*
- * @return The byte read, or -1 at EOF.
- * @exception IOException when an IO error causes operation to fail
+ * @param markLimit The limit to mark.
*/
- public int read()
- throws IOException
- {
- final int num = read( m_oneBuf, 0, 1 );
- if( num == -1 )
- {
- return num;
- }
- else
- {
- return (int)m_oneBuf[ 0 ] & 0xFF;
- }
- }
-
- /**
- * Reads bytes from the current tar archive entry. This method simply calls
- * read( byte[], int, int ).
- *
- * @param buffer The buffer into which to place bytes read.
- * @return The number of bytes read, or -1 at EOF.
- * @exception IOException when an IO error causes operation to fail
- */
- public int read( final byte[] buffer )
- throws IOException
- {
- return read( buffer, 0, buffer.length );
- }
-
- /**
- * Reads bytes from the current tar archive entry. This method is aware of
- * the boundaries of the current entry in the archive and will deal with
- * them as if they were this stream's start and EOF.
- *
- * @param buffer The buffer into which to place bytes read.
- * @param offset The offset at which to place bytes read.
- * @param count The number of bytes to read.
- * @return The number of bytes read, or -1 at EOF.
- * @exception IOException when an IO error causes operation to fail
- */
- public int read( final byte[] buffer,
- final int offset,
- final int count )
- throws IOException
- {
- int position = offset;
- int numToRead = count;
- int totalRead = 0;
-
- if( m_entryOffset >= m_entrySize )
- {
- return -1;
- }
-
- if( ( numToRead + m_entryOffset ) > m_entrySize )
- {
- numToRead = ( m_entrySize - m_entryOffset );
- }
-
- if( null != m_readBuf )
- {
- final int size =
- ( numToRead > m_readBuf.length ) ? m_readBuf.length : numToRead;
-
- System.arraycopy( m_readBuf, 0, buffer, position, size );
-
- if( size >= m_readBuf.length )
- {
- m_readBuf = null;
- }
- else
- {
- final int newLength = m_readBuf.length - size;
- final byte[] newBuffer = new byte[ newLength ];
-
- System.arraycopy( m_readBuf, size, newBuffer, 0, newLength );
-
- m_readBuf = newBuffer;
- }
-
- totalRead += size;
- numToRead -= size;
- position += size;
- }
-
- while( numToRead > 0 )
- {
- final byte[] rec = m_buffer.readRecord();
- if( null == rec )
- {
- // Unexpected EOF!
- final String message =
- "unexpected EOF with " + numToRead + " bytes unread";
- throw new IOException( message );
- }
-
- int size = numToRead;
- final int recordLength = rec.length;
-
- if( recordLength > size )
- {
- System.arraycopy( rec, 0, buffer, position, size );
-
- m_readBuf = new byte[ recordLength - size ];
-
- System.arraycopy( rec, size, m_readBuf, 0, recordLength - size );
- }
- else
- {
- size = recordLength;
-
- System.arraycopy( rec, 0, buffer, position, recordLength );
- }
-
- totalRead += size;
- numToRead -= size;
- position += size;
- }
-
- m_entryOffset += totalRead;
-
- return totalRead;
+ public void mark(int markLimit) {
}
/**
* Since we do not support marking just yet, we do nothing.
*/
- public void reset()
- {
+ public void reset() {
}
/**
- * Skip bytes in the input buffer. This skips bytes in the current entry's
- * data, not the entire archive, and will stop at the end of the current
- * entry's data if the number to skip extends beyond that point.
+ * Get the next entry 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, and read the header and instantiate a new
+ * TarEntry from the header bytes and return that 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.
*
- * @param numToSkip The number of bytes to skip.
- * @exception IOException when an IO error causes operation to fail
+ * @return The next TarEntry in the archive, or null.
+ * @throws IOException on error
*/
- public void skip( final int numToSkip )
- throws IOException
- {
- // REVIEW
- // This is horribly inefficient, but it ensures that we
- // properly skip over bytes via the TarBuffer...
- //
- final byte[] skipBuf = new byte[ 8 * 1024 ];
- int num = numToSkip;
- while( num > 0 )
- {
- final int count = ( num > skipBuf.length ) ? skipBuf.length : num;
- final int numRead = read( skipBuf, 0, count );
- if( numRead == -1 )
- {
+ public TarArchiveEntry getNextEntry() throws IOException {
+ if (hasHitEOF) {
+ return null;
+ }
+
+ if (currEntry != null) {
+ long numToSkip = entrySize - entryOffset;
+
+ if (debug) {
+ System.err.println("TarInputStream: SKIP currENTRY '"
+ + currEntry.getName() + "' SZ "
+ + entrySize + " OFF "
+ + entryOffset + " skipping "
+ + numToSkip + " bytes");
+ }
+
+ if (numToSkip > 0) {
+ skip(numToSkip);
+ }
+
+ 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;
+ }
+
+ if (hasHitEOF) {
+ currEntry = null;
+ } else {
+ currEntry = new TarArchiveEntry(headerBuf);
+
+ if (debug) {
+ System.err.println("TarInputStream: SET CURRENTRY '"
+ + currEntry.getName()
+ + "' size = "
+ + currEntry.getSize());
+ }
+
+ entryOffset = 0;
+
+ entrySize = currEntry.getSize();
+ }
+
+ if (currEntry != null && 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));
+ }
+ getNextEntry();
+ if (currEntry == null) {
+ // Bugzilla: 40334
+ // Malformed tar file - long entry name not followed by entry
+ return null;
+ }
+ // remove trailing null terminator
+ if (longName.length() > 0
+ && longName.charAt(longName.length() - 1) == 0) {
+ longName.deleteCharAt(longName.length() - 1);
+ }
+ currEntry.setName(longName.toString());
+ }
+
+ return currEntry;
+ }
+
+ /**
+ * Reads a byte from the current tar archive entry.
+ *
+ * This method simply calls read( byte[], int, int ).
+ *
+ * @return The byte read, or -1 at EOF.
+ * @throws IOException on error
+ */
+ public int read() throws IOException {
+ int num = read(oneBuf, 0, 1);
+ return num == -1 ? -1 : ((int) oneBuf[0]) & BYTE_MASK;
+ }
+
+ /**
+ * Reads bytes from the current tar archive entry.
+ *
+ * This method is aware of the boundaries of the current
+ * entry in the archive and will deal with them as if they
+ * were this stream's start and EOF.
+ *
+ * @param buf The buffer into which to place bytes read.
+ * @param offset The offset at which to place bytes read.
+ * @param numToRead The number of bytes to read.
+ * @return The number of bytes read, or -1 at EOF.
+ * @throws IOException on error
+ */
+ public int read(byte[] buf, int offset, int numToRead) throws IOException {
+ int totalRead = 0;
+
+ if (entryOffset >= entrySize) {
+ return -1;
+ }
+
+ if ((numToRead + entryOffset) > entrySize) {
+ numToRead = (int) (entrySize - entryOffset);
+ }
+
+ if (readBuf != null) {
+ int sz = (numToRead > readBuf.length) ? readBuf.length
+ : numToRead;
+
+ System.arraycopy(readBuf, 0, buf, offset, sz);
+
+ if (sz >= readBuf.length) {
+ readBuf = null;
+ } else {
+ int newLen = readBuf.length - sz;
+ byte[] newBuf = new byte[newLen];
+
+ System.arraycopy(readBuf, sz, newBuf, 0, newLen);
+
+ readBuf = newBuf;
+ }
+
+ totalRead += sz;
+ numToRead -= sz;
+ offset += sz;
+ }
+
+ while (numToRead > 0) {
+ byte[] rec = buffer.readRecord();
+
+ if (rec == null) {
+ // Unexpected EOF!
+ throw new IOException("unexpected EOF with " + numToRead
+ + " bytes unread");
+ }
+
+ int sz = numToRead;
+ int recLen = rec.length;
+
+ if (recLen > sz) {
+ System.arraycopy(rec, 0, buf, offset, sz);
+
+ readBuf = new byte[recLen - sz];
+
+ System.arraycopy(rec, sz, readBuf, 0, recLen - sz);
+ } else {
+ sz = recLen;
+
+ System.arraycopy(rec, 0, buf, offset, recLen);
+ }
+
+ totalRead += sz;
+ numToRead -= sz;
+ offset += sz;
+ }
+
+ entryOffset += totalRead;
+
+ return totalRead;
+ }
+
+ /**
+ * Copies the contents of the current tar archive entry directly into
+ * an output stream.
+ *
+ * @param out The OutputStream into which to write the entry's data.
+ * @throws IOException on error
+ */
+ public void copyEntryContents(OutputStream out) throws IOException {
+ byte[] buf = new byte[LARGE_BUFFER_SIZE];
+
+ while (true) {
+ int numRead = read(buf, 0, buf.length);
+
+ if (numRead == -1) {
break;
}
- num -= numRead;
- }
- }
-
- /**
- * Utility method to do debugging.
- * Capable of being overidden in sub-classes.
- *
- * @param message the message to use in debugging
- */
- protected void debug( final String message )
- {
- if( m_debug )
- {
- System.err.println( message );
+ out.write(buf, 0, numRead);
}
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarOutputStream.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarOutputStream.java
index bf73b9b..49f9d87 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarOutputStream.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarOutputStream.java
@@ -24,120 +24,125 @@
import java.io.OutputStream;
/**
- * The TarOutputStream writes a UNIX tar archive as an OutputStream. Methods are
- * provided to put entries, and then write their contents by writing to this
- * stream using write().
+ * The TarOutputStream writes a UNIX tar archive as an OutputStream.
+ * Methods are provided to put entries, and then write their contents
+ * by writing to this stream using write().
+ *
*/
-public class TarOutputStream
- extends FilterOutputStream
-{
- /**
- * Flag to indicate that an error should be generated if
- * an attempt is made to write an entry that exceeds the 100 char
- * POSIX limit.
- */
+public class TarOutputStream extends FilterOutputStream {
+ /** Fail if a long file name is required in the archive. */
public static final int LONGFILE_ERROR = 0;
- /**
- * Flag to indicate that entry name should be truncated if
- * an attempt is made to write an entry that exceeds the 100 char
- * POSIX limit.
- */
+ /** Long paths will be truncated in the archive. */
public static final int LONGFILE_TRUNCATE = 1;
- /**
- * Flag to indicate that entry name should be formatted
- * according to GNU tar extension if an attempt is made
- * to write an entry that exceeds the 100 char POSIX
- * limit. Note that this makes the jar unreadable by
- * non-GNU tar commands.
- */
+ /** GNU tar extensions are used to store long file names in the archive. */
public static final int LONGFILE_GNU = 2;
- private int m_longFileMode = LONGFILE_ERROR;
- private byte[] m_assemBuf;
- private int m_assemLen;
- private TarBuffer m_buffer;
- private int m_currBytes;
- private int m_currSize;
+ // CheckStyle:VisibilityModifier OFF - bc
+ protected boolean debug;
+ protected long currSize;
+ protected String currName;
+ protected long currBytes;
+ protected byte[] oneBuf;
+ protected byte[] recordBuf;
+ protected int assemLen;
+ protected byte[] assemBuf;
+ protected TarBuffer buffer;
+ protected int longFileMode = LONGFILE_ERROR;
+ // CheckStyle:VisibilityModifier ON
- private byte[] m_oneBuf;
- private byte[] m_recordBuf;
+ private boolean closed = false;
/**
- * Construct a TarOutputStream using specified input
- * stream and default block and record sizes.
- *
- * @param output stream to create TarOutputStream from
- * @see TarBuffer#DEFAULT_BLOCKSIZE
- * @see TarBuffer#DEFAULT_RECORDSIZE
+ * Constructor for TarInputStream.
+ * @param os the output stream to use
*/
- public TarOutputStream( final OutputStream output )
- {
- this( output, TarBuffer.DEFAULT_BLOCKSIZE, TarBuffer.DEFAULT_RECORDSIZE );
+ public TarOutputStream(OutputStream os) {
+ this(os, TarBuffer.DEFAULT_BLKSIZE, TarBuffer.DEFAULT_RCDSIZE);
}
/**
- * Construct a TarOutputStream using specified input
- * stream, block size and default record sizes.
- *
- * @param output stream to create TarOutputStream from
- * @param blockSize the block size
- * @see TarBuffer#DEFAULT_RECORDSIZE
+ * Constructor for TarInputStream.
+ * @param os the output stream to use
+ * @param blockSize the block size to use
*/
- public TarOutputStream( final OutputStream output,
- final int blockSize )
- {
- this( output, blockSize, TarBuffer.DEFAULT_RECORDSIZE );
+ public TarOutputStream(OutputStream os, int blockSize) {
+ this(os, blockSize, TarBuffer.DEFAULT_RCDSIZE);
}
/**
- * Construct a TarOutputStream using specified input
- * stream, block size and record sizes.
- *
- * @param output stream to create TarOutputStream from
- * @param blockSize the block size
- * @param recordSize the record size
+ * 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( final OutputStream output,
- final int blockSize,
- final int recordSize )
- {
- super( output );
+ public TarOutputStream(OutputStream os, int blockSize, int recordSize) {
+ super(os);
- m_buffer = new TarBuffer( output, blockSize, recordSize );
- m_assemLen = 0;
- m_assemBuf = new byte[ recordSize ];
- m_recordBuf = new byte[ recordSize ];
- m_oneBuf = new byte[ 1 ];
+ this.buffer = new TarBuffer(os, blockSize, recordSize);
+ this.debug = false;
+ this.assemLen = 0;
+ this.assemBuf = new byte[recordSize];
+ this.recordBuf = new byte[recordSize];
+ this.oneBuf = new byte[1];
+ }
+
+ /**
+ * Set the long file mode.
+ * This can be LONGFILE_ERROR(0), LONGFILE_TRUNCATE(1) or LONGFILE_GNU(2).
+ * This specifies the treatment of long file names (names >= TarConstants.NAMELEN).
+ * Default is LONGFILE_ERROR.
+ * @param longFileMode the mode to use
+ */
+ public void setLongFileMode(int longFileMode) {
+ this.longFileMode = longFileMode;
+ }
+
+
+ /**
+ * Sets the debugging flag.
+ *
+ * @param debugF True to turn on debugging.
+ */
+ public void setDebug(boolean debugF) {
+ this.debug = debugF;
}
/**
* Sets the debugging flag in this stream's TarBuffer.
*
- * @param debug The new BufferDebug value
+ * @param debug True to turn on debugging.
*/
- public void setBufferDebug( boolean debug )
- {
- m_buffer.setDebug( debug );
+ public void setBufferDebug(boolean debug) {
+ buffer.setDebug(debug);
}
/**
- * Set the mode used to work with entrys exceeding
- * 100 chars (and thus break the POSIX standard).
- * Must be one of the LONGFILE_* constants.
- *
- * @param longFileMode the mode
+ * Ends the TAR archive without closing the underlying OutputStream.
+ * The result is that the two EOF records of nulls are written.
+ * @throws IOException on error
*/
- public void setLongFileMode( final int longFileMode )
- {
- if( LONGFILE_ERROR != longFileMode &&
- LONGFILE_GNU != longFileMode &&
- LONGFILE_TRUNCATE != longFileMode )
- {
- throw new IllegalArgumentException( "longFileMode" );
+ public void finish() throws IOException {
+ // See Bugzilla 28776 for a discussion on this
+ // http://issues.apache.org/bugzilla/show_bug.cgi?id=28776
+ writeEOFRecord();
+ writeEOFRecord();
+ }
+
+ /**
+ * Ends the TAR archive and closes the underlying OutputStream.
+ * This means that finish() is called followed by calling the
+ * TarBuffer's close().
+ * @throws IOException on error
+ */
+ public void close() throws IOException {
+ if (!closed) {
+ finish();
+ buffer.close();
+ out.close();
+ closed = true;
}
- m_longFileMode = longFileMode;
}
/**
@@ -145,202 +150,133 @@
*
* @return The TarBuffer record size.
*/
- public int getRecordSize()
- {
- return m_buffer.getRecordSize();
+ public int getRecordSize() {
+ return buffer.getRecordSize();
}
/**
- * Ends the TAR archive and closes the underlying OutputStream. This means
- * that finish() is called followed by calling the TarBuffer's close().
+ * Put an entry on the output stream. This writes the entry's
+ * header record and positions the output stream for writing
+ * the contents of the entry. Once this method is called, the
+ * stream is ready for calls to write() to write the entry's
+ * contents. Once the contents are written, closeEntry()
+ * <B>MUST</B> be called to ensure that all buffered data
+ * is completely written to the output stream.
*
- * @exception IOException when an IO error causes operation to fail
+ * @param entry The TarEntry to be written to the archive.
+ * @throws IOException on error
*/
- public void close()
- throws IOException
- {
- finish();
- m_buffer.close();
- }
+ public void putNextEntry(TarArchiveEntry entry) throws IOException {
+ if (entry.getName().length() >= TarConstants.NAMELEN) {
- /**
- * Close an entry. This method MUST be called for all file entries that
- * contain data. The reason is that we must buffer data written to the
- * stream in order to satisfy the buffer's record based writes. Thus, there
- * may be data fragments still being assembled that must be written to the
- * output stream before this entry is closed and the next entry written.
- *
- * @exception IOException when an IO error causes operation to fail
- */
- public void closeEntry()
- throws IOException
- {
- if( m_assemLen > 0 )
- {
- for( int i = m_assemLen; i < m_assemBuf.length; ++i )
- {
- m_assemBuf[ i ] = 0;
- }
-
- m_buffer.writeRecord( m_assemBuf );
-
- m_currBytes += m_assemLen;
- m_assemLen = 0;
- }
-
- if( m_currBytes < m_currSize )
- {
- final String message = "entry closed at '" + m_currBytes +
- "' before the '" + m_currSize +
- "' bytes specified in the header were written";
- throw new IOException( message );
- }
- }
-
- /**
- * Ends the TAR archive without closing the underlying OutputStream. The
- * result is that the EOF record of nulls is written.
- *
- * @exception IOException when an IO error causes operation to fail
- */
- public void finish()
- throws IOException
- {
- writeEOFRecord();
- }
-
- /**
- * Put an entry on the output stream. This writes the entry's header record
- * and positions the output stream for writing the contents of the entry.
- * Once this method is called, the stream is ready for calls to write() to
- * write the entry's contents. Once the contents are written, closeEntry()
- * <B>MUST</B> be called to ensure that all buffered data is completely
- * written to the output stream.
- *
- * The entry must be 0 terminated. Maximum filename is 99 chars,
- * according to V7 specification.
- *
- * @param entry The TarArchiveEntry to be written to the archive.
- * @exception IOException when an IO error causes operation to fail
- */
- public void putNextEntry( final TarArchiveEntry entry )
- throws IOException
- {
- if( entry.getName().length() > TarArchiveEntry.NAMELEN )
- {
- if( m_longFileMode == LONGFILE_GNU )
- {
- // create a TarArchiveEntry for the LongLink, the contents
+ if (longFileMode == LONGFILE_GNU) {
+ // create a TarEntry for the LongLink, the contents
// of which are the entry's name
- final TarArchiveEntry longLinkEntry =
- new TarArchiveEntry( TarConstants.GNU_LONGLINK,
- TarConstants.LF_GNUTYPE_LONGNAME );
+ TarArchiveEntry longLinkEntry = new TarArchiveEntry(TarConstants.GNU_LONGLINK,
+ TarConstants.LF_GNUTYPE_LONGNAME);
- longLinkEntry.setSize( entry.getName().length() + 1);
- putNextEntry( longLinkEntry );
- write( entry.getName().getBytes() );
- write( 0 );
+ longLinkEntry.setSize(entry.getName().length() + 1);
+ putNextEntry(longLinkEntry);
+ write(entry.getName().getBytes());
+ write(0);
closeEntry();
- }
- else if( m_longFileMode != LONGFILE_TRUNCATE )
- {
- final String message = "file name '" + entry.getName() +
- "' is too long ( > " + TarArchiveEntry.NAMELEN + " bytes)";
- throw new IOException( message );
+ } else if (longFileMode != LONGFILE_TRUNCATE) {
+ throw new RuntimeException("file name '" + entry.getName()
+ + "' is too long ( > "
+ + TarConstants.NAMELEN + " bytes)");
}
}
- entry.writeEntryHeader( m_recordBuf );
- m_buffer.writeRecord( m_recordBuf );
+ entry.writeEntryHeader(recordBuf);
+ buffer.writeRecord(recordBuf);
- m_currBytes = 0;
+ currBytes = 0;
- if( entry.isDirectory() )
- {
- m_currSize = 0;
+ if (entry.isDirectory()) {
+ currSize = 0;
+ } else {
+ currSize = entry.getSize();
}
- else
- {
- m_currSize = (int)entry.getSize();
- }
+ currName = entry.getName();
}
/**
- * Copies the contents of the specified stream into current tar
- * archive entry.
- *
- * @param input The InputStream from which to read entrys data
- * @exception IOException when an IO error causes operation to fail
+ * Close an entry. This method MUST be called for all file
+ * entries that contain data. The reason is that we must
+ * buffer data written to the stream in order to satisfy
+ * the buffer's record based writes. Thus, there may be
+ * data fragments still being assembled that must be written
+ * to the output stream before this entry is closed and the
+ * next entry written.
+ * @throws IOException on error
*/
- public void copyEntryContents( final InputStream input )
- throws IOException
- {
- final byte[] buffer = new byte[ 32 * 1024 ];
- while( true )
- {
- final int numRead = input.read( buffer, 0, buffer.length );
- if( numRead == -1 )
- {
- break;
+ public void closeEntry() throws IOException {
+ if (assemLen > 0) {
+ for (int i = assemLen; i < assemBuf.length; ++i) {
+ assemBuf[i] = 0;
}
- write( buffer, 0, numRead );
+ buffer.writeRecord(assemBuf);
+
+ currBytes += assemLen;
+ assemLen = 0;
+ }
+
+ if (currBytes < currSize) {
+ throw new IOException("entry '" + currName + "' closed at '"
+ + currBytes
+ + "' before the '" + currSize
+ + "' bytes specified in the header were written");
}
}
/**
- * Writes a byte to the current tar archive entry. This method simply calls
- * read( byte[], int, int ).
+ * Writes a byte to the current tar archive entry.
*
- * @param data The byte written.
- * @exception IOException when an IO error causes operation to fail
+ * This method simply calls read( byte[], int, int ).
+ *
+ * @param b The byte written.
+ * @throws IOException on error
*/
- public void write( final int data )
- throws IOException
- {
- m_oneBuf[ 0 ] = (byte)data;
+ public void write(int b) throws IOException {
+ oneBuf[0] = (byte) b;
- write( m_oneBuf, 0, 1 );
+ write(oneBuf, 0, 1);
}
/**
- * Writes bytes to the current tar archive entry. This method simply calls
- * write( byte[], int, int ).
+ * Writes bytes to the current tar archive entry.
*
- * @param buffer The buffer to write to the archive.
- * @exception IOException when an IO error causes operation to fail
+ * This method simply calls write( byte[], int, int ).
+ *
+ * @param wBuf The buffer to write to the archive.
+ * @throws IOException on error
*/
- public void write( final byte[] buffer )
- throws IOException
- {
- write( buffer, 0, buffer.length );
+ public void write(byte[] wBuf) throws IOException {
+ write(wBuf, 0, wBuf.length);
}
/**
- * Writes bytes to the current tar archive entry. This method is aware of
- * the current entry and will throw an exception if you attempt to write
- * bytes past the length specified for the current entry. The method is also
- * (painfully) aware of the record buffering required by TarBuffer, and
- * manages buffers that are not a multiple of recordsize in length,
- * including assembling records from small buffers.
+ * Writes bytes to the current tar archive entry. This method
+ * is aware of the current entry and will throw an exception if
+ * you attempt to write bytes past the length specified for the
+ * current entry. The method is also (painfully) aware of the
+ * record buffering required by TarBuffer, and manages buffers
+ * that are not a multiple of recordsize in length, including
+ * assembling records from small buffers.
*
- * @param buffer The buffer to write to the archive.
- * @param offset The offset in the buffer from which to get bytes.
- * @param count The number of bytes to write.
- * @exception IOException when an IO error causes operation to fail
+ * @param wBuf The buffer to write to the archive.
+ * @param wOffset The offset in the buffer from which to get bytes.
+ * @param numToWrite The number of bytes to write.
+ * @throws IOException on error
*/
- public void write( final byte[] buffer,
- final int offset,
- final int count )
- throws IOException
- {
- int position = offset;
- int numToWrite = count;
- if( ( m_currBytes + numToWrite ) > m_currSize )
- {
- final String message = "request to write '" + numToWrite +
- "' bytes exceeds size in header of '" + m_currSize + "' bytes";
- throw new IOException( message );
+ public void write(byte[] wBuf, int wOffset, int numToWrite) throws IOException {
+ if ((currBytes + numToWrite) > currSize) {
+ throw new IOException("request to write '" + numToWrite
+ + "' bytes exceeds size in header of '"
+ + currSize + "' bytes for entry '"
+ + currName + "'");
+
//
// We have to deal with assembly!!!
// The programmer can be writing little 32 byte chunks for all
@@ -350,30 +286,26 @@
//
}
- if( m_assemLen > 0 )
- {
- if( ( m_assemLen + numToWrite ) >= m_recordBuf.length )
- {
- final int length = m_recordBuf.length - m_assemLen;
+ if (assemLen > 0) {
+ if ((assemLen + numToWrite) >= recordBuf.length) {
+ int aLen = recordBuf.length - assemLen;
- System.arraycopy( m_assemBuf, 0, m_recordBuf, 0,
- m_assemLen );
- System.arraycopy( buffer, position, m_recordBuf,
- m_assemLen, length );
- m_buffer.writeRecord( m_recordBuf );
+ System.arraycopy(assemBuf, 0, recordBuf, 0,
+ assemLen);
+ System.arraycopy(wBuf, wOffset, recordBuf,
+ assemLen, aLen);
+ buffer.writeRecord(recordBuf);
- m_currBytes += m_recordBuf.length;
- position += length;
- numToWrite -= length;
- m_assemLen = 0;
- }
- else
- {
- System.arraycopy( buffer, position, m_assemBuf, m_assemLen,
- numToWrite );
+ currBytes += recordBuf.length;
+ wOffset += aLen;
+ numToWrite -= aLen;
+ assemLen = 0;
+ } else {
+ System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
+ numToWrite);
- position += numToWrite;
- m_assemLen += numToWrite;
+ wOffset += numToWrite;
+ assemLen += numToWrite;
numToWrite -= numToWrite;
}
}
@@ -383,42 +315,35 @@
// o An empty "assemble" buffer.
// o No bytes to write (numToWrite == 0)
//
- while( numToWrite > 0 )
- {
- if( numToWrite < m_recordBuf.length )
- {
- System.arraycopy( buffer, position, m_assemBuf, m_assemLen,
- numToWrite );
+ while (numToWrite > 0) {
+ if (numToWrite < recordBuf.length) {
+ System.arraycopy(wBuf, wOffset, assemBuf, assemLen,
+ numToWrite);
- m_assemLen += numToWrite;
+ assemLen += numToWrite;
break;
}
- m_buffer.writeRecord( buffer, position );
+ buffer.writeRecord(wBuf, wOffset);
- int num = m_recordBuf.length;
+ int num = recordBuf.length;
- m_currBytes += num;
+ currBytes += num;
numToWrite -= num;
- position += num;
+ wOffset += num;
}
}
/**
- * Write an EOF (end of archive) record to the tar archive. An EOF record
- * consists of a record of all zeros.
- *
- * @exception IOException when an IO error causes operation to fail
+ * Write an EOF (end of archive) record to the tar archive.
+ * An EOF record consists of a record of all zeros.
*/
- private void writeEOFRecord()
- throws IOException
- {
- for( int i = 0; i < m_recordBuf.length; ++i )
- {
- m_recordBuf[ i ] = 0;
+ private void writeEOFRecord() throws IOException {
+ for (int i = 0; i < recordBuf.length; ++i) {
+ recordBuf[i] = 0;
}
- m_buffer.writeRecord( m_recordBuf );
+ buffer.writeRecord(recordBuf);
}
}
diff --git a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
index 523f9f7..05faa64 100644
--- a/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
+++ b/src/main/java/org/apache/commons/compress/archivers/tar/TarUtils.java
@@ -20,142 +20,49 @@
/**
* This class provides static utility methods to work with byte streams.
+ *
*/
-class TarUtils
-{
- /**
- * Parse the checksum octal integer from a header buffer.
- *
- * @param offset The offset into the buffer from which to parse.
- * @param length The number of header bytes to parse.
- * @param value Description of Parameter
- * @param buf Description of Parameter
- * @return The integer value of the entry's checksum.
- */
- public static int getCheckSumOctalBytes( final long value,
- final byte[] buf,
- final int offset,
- final int length )
- {
- getOctalBytes( value, buf, offset, length );
+// CheckStyle:HideUtilityClassConstructorCheck OFF (bc)
+public class TarUtils {
- buf[ offset + length - 1 ] = (byte)' ';
- buf[ offset + length - 2 ] = 0;
-
- return offset + length;
- }
+ private static final int BYTE_MASK = 255;
/**
- * Parse an octal long integer from a header buffer.
+ * Parse an octal string from a header buffer. This is used for the
+ * file permission mode value.
*
+ * @param header The header buffer from which to parse.
* @param offset The offset into the buffer from which to parse.
* @param length The number of header bytes to parse.
- * @param value Description of Parameter
- * @param buf Description of Parameter
- * @return The long value of the octal bytes.
+ * @return The long value of the octal string.
*/
- public static int getLongOctalBytes( final long value,
- final byte[] buf,
- final int offset,
- final int length )
- {
- byte[] temp = new byte[ length + 1 ];
+ public static long parseOctal(byte[] header, int offset, int length) {
+ long result = 0;
+ boolean stillPadding = true;
+ int end = offset + length;
- getOctalBytes( value, temp, 0, length + 1 );
- System.arraycopy( temp, 0, buf, offset, length );
-
- return offset + length;
- }
-
- /**
- * Determine the number of bytes in an entry name.
- *
- * @param offset The offset into the buffer from which to parse.
- * @param length The number of header bytes to parse.
- * @param name Description of Parameter
- * @param buffer Description of Parameter
- * @return The number of bytes in a header's entry name.
- */
- public static int getNameBytes( final StringBuffer name,
- final byte[] buffer,
- final int offset,
- final int length )
- {
- int i;
-
- for( i = 0; i < length && i < name.length(); ++i )
- {
- buffer[ offset + i ] = (byte)name.charAt( i );
- }
-
- for( ; i < length; ++i )
- {
- buffer[ offset + i ] = 0;
- }
-
- return offset + length;
- }
-
- /**
- * Parse an octal integer from a header buffer.
- *
- * @param offset The offset into the buffer from which to parse.
- * @param length The number of header bytes to parse.
- * @return The integer value of the octal bytes.
- */
- public static int getOctalBytes( final long value,
- final byte[] buffer,
- final int offset,
- final int length )
- {
- int idx = length - 1;
-
- buffer[ offset + idx ] = 0;
- --idx;
- buffer[ offset + idx ] = (byte)' ';
- --idx;
-
- if( value == 0 )
- {
- buffer[ offset + idx ] = (byte)'0';
- --idx;
- }
- else
- {
- long val = value;
- while( idx >= 0 && val > 0 )
- {
- buffer[ offset + idx ] = (byte)( (byte)'0' + (byte)( val & 7 ) );
- val = val >> 3;
- idx--;
+ for (int i = offset; i < end; ++i) {
+ if (header[i] == 0) {
+ break;
}
+
+ if (header[i] == (byte) ' ' || header[i] == '0') {
+ if (stillPadding) {
+ continue;
+ }
+
+ if (header[i] == (byte) ' ') {
+ break;
+ }
+ }
+
+ stillPadding = false;
+ // CheckStyle:MagicNumber OFF
+ result = (result << 3) + (header[i] - '0');
+ // CheckStyle:MagicNumber ON
}
- while( idx >= 0 )
- {
- buffer[ offset + idx ] = (byte)' ';
- idx--;
- }
-
- return offset + length;
- }
-
- /**
- * Compute the checksum of a tar entry header.
- *
- * @param buffer The tar entry's header buffer.
- * @return The computed checksum.
- */
- public static long computeCheckSum( final byte[] buffer )
- {
- long sum = 0;
-
- for( int i = 0; i < buffer.length; ++i )
- {
- sum += 255 & buffer[ i ];
- }
-
- return sum;
+ return result;
}
/**
@@ -166,67 +73,129 @@
* @param length The number of header bytes to parse.
* @return The header's entry name.
*/
- public static StringBuffer parseName( final byte[] header,
- final int offset,
- final int length )
- {
- StringBuffer result = new StringBuffer( length );
- int end = offset + length;
+ public static StringBuffer parseName(byte[] header, int offset, int length) {
+ StringBuffer result = new StringBuffer(length);
+ int end = offset + length;
- for( int i = offset; i < end; ++i )
- {
- if( header[ i ] == 0 )
- {
+ for (int i = offset; i < end; ++i) {
+ if (header[i] == 0) {
break;
}
- result.append( (char)header[ i ] );
+ result.append((char) header[i]);
}
return result;
}
/**
- * Parse an octal string from a header buffer. This is used for the file
- * permission mode value.
+ * Determine the number of bytes in an entry name.
*
- * @param header The header buffer from which to parse.
+ * @param name The header name from which to parse.
+ * @param buf The buffer from which to parse.
* @param offset The offset into the buffer from which to parse.
* @param length The number of header bytes to parse.
- * @return The long value of the octal string.
+ * @return The number of bytes in a header's entry name.
*/
- public static long parseOctal( final byte[] header,
- final int offset,
- final int length )
- {
- long result = 0;
- boolean stillPadding = true;
- int end = offset + length;
+ public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
+ int i;
- for( int i = offset; i < end; ++i )
- {
- if( header[ i ] == 0 )
- {
- break;
- }
-
- if( header[ i ] == (byte)' ' || header[ i ] == '0' )
- {
- if( stillPadding )
- {
- continue;
- }
-
- if( header[ i ] == (byte)' ' )
- {
- break;
- }
- }
-
- stillPadding = false;
- result = ( result << 3 ) + ( header[ i ] - '0' );
+ for (i = 0; i < length && i < name.length(); ++i) {
+ buf[offset + i] = (byte) name.charAt(i);
}
- return result;
+ for (; i < length; ++i) {
+ buf[offset + i] = 0;
+ }
+
+ return offset + length;
+ }
+
+ /**
+ * Parse an octal integer from a header buffer.
+ *
+ * @param value The header value
+ * @param buf The buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The integer value of the octal bytes.
+ */
+ public static int getOctalBytes(long value, byte[] buf, int offset, int length) {
+ int idx = length - 1;
+
+ buf[offset + idx] = 0;
+ --idx;
+ buf[offset + idx] = (byte) ' ';
+ --idx;
+
+ if (value == 0) {
+ buf[offset + idx] = (byte) '0';
+ --idx;
+ } else {
+ for (long val = value; idx >= 0 && val > 0; --idx) {
+ // CheckStyle:MagicNumber OFF
+ buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));
+ val = val >> 3;
+ // CheckStyle:MagicNumber ON
+ }
+ }
+
+ for (; idx >= 0; --idx) {
+ buf[offset + idx] = (byte) ' ';
+ }
+
+ return offset + length;
+ }
+
+ /**
+ * Parse an octal long integer from a header buffer.
+ *
+ * @param value The header value
+ * @param buf The buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The long value of the octal bytes.
+ */
+ public static int getLongOctalBytes(long value, byte[] buf, int offset, int length) {
+ byte[] temp = new byte[length + 1];
+
+ getOctalBytes(value, temp, 0, length + 1);
+ System.arraycopy(temp, 0, buf, offset, length);
+
+ return offset + length;
+ }
+
+ /**
+ * Parse the checksum octal integer from a header buffer.
+ *
+ * @param value The header value
+ * @param buf The buffer from which to parse.
+ * @param offset The offset into the buffer from which to parse.
+ * @param length The number of header bytes to parse.
+ * @return The integer value of the entry's checksum.
+ */
+ public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
+ getOctalBytes(value, buf, offset, length);
+
+ buf[offset + length - 1] = (byte) ' ';
+ buf[offset + length - 2] = 0;
+
+ return offset + length;
+ }
+
+ /**
+ * Compute the checksum of a tar entry header.
+ *
+ * @param buf The tar entry's header buffer.
+ * @return The computed checksum.
+ */
+ public static long computeCheckSum(byte[] buf) {
+ long sum = 0;
+
+ for (int i = 0; i < buf.length; ++i) {
+ sum += BYTE_MASK & buf[i];
+ }
+
+ return sum;
}
}
diff --git a/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java b/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java
index 0187d03..2e37952 100644
--- a/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java
+++ b/src/test/java/org/apache/commons/compress/archivers/TarTestCase.java
@@ -38,8 +38,8 @@
final TarArchiveEntry entry = new TarArchiveEntry("testdata/test1.xml");
entry.setModTime(0);
entry.setSize(file1.length());
- entry.setUserID(0);
- entry.setGroupID(0);
+ entry.setUserId(0);
+ entry.setGroupId(0);
entry.setUserName("avalon");
entry.setGroupName("excalibur");
entry.setMode(0100000);
@@ -61,8 +61,8 @@
final TarArchiveEntry entry = new TarArchiveEntry(name);
entry.setModTime(0);
entry.setSize(file1.length());
- entry.setUserID(0);
- entry.setGroupID(0);
+ entry.setUserId(0);
+ entry.setGroupId(0);
entry.setUserName("avalon");
entry.setGroupName("excalibur");
entry.setMode(0100000);
@@ -81,8 +81,8 @@
final TarArchiveEntry entry2 = new TarArchiveEntry(toLongName);
entry2.setModTime(0);
entry2.setSize(file1.length());
- entry2.setUserID(0);
- entry2.setGroupID(0);
+ entry2.setUserId(0);
+ entry2.setGroupId(0);
entry2.setUserName("avalon");
entry2.setGroupName("excalibur");
entry2.setMode(0100000);