/*
** Authored by Timothy Gerard Endres
** <mailto:time@gjt.org> <http://www.trustice.com>
**
** This work has been placed into the public domain.
** You may use this work in any way and for any purpose you wish.
**
** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
** REDISTRIBUTION OF THIS SOFTWARE.
**
*/
package org.jboss.shrinkwrap.impl.base.io.tar;
/**
* This class encapsulates the Tar Entry Header used in Tar Archives. The class also holds a number of tar constants,
* used mostly in headers.
*
* @author Timothy Gerard Endres, <time@gjt.org>
*/
public class TarHeader extends Object implements Cloneable {
/**
* The length of the name field in a header buffer.
*/
public static final int NAMELEN = 100;
/**
* The offset of the name field in a header buffer.
*/
public static final int NAMEOFFSET = 0;
/**
* The length of the name prefix field in a header buffer.
*/
public static final int PREFIXLEN = 155;
/**
* The offset of the name prefix field in a header buffer.
*/
public static final int PREFIXOFFSET = 345;
/**
* The length of the mode field in a header buffer.
*/
public static final int MODELEN = 8;
/**
* The length of the user id field in a header buffer.
*/
public static final int UIDLEN = 8;
/**
* The length of the group id field in a header buffer.
*/
public static final int GIDLEN = 8;
/**
* The length of the checksum field in a header buffer.
*/
public static final int CHKSUMLEN = 8;
/**
* The length of the size field in a header buffer.
*/
public static final int SIZELEN = 12;
/**
* The length of the magic field in a header buffer.
*/
public static final int MAGICLEN = 8;
/**
* The length of the modification time field in a header buffer.
*/
public static final int MODTIMELEN = 12;
/**
* The length of the user name field in a header buffer.
*/
public static final int UNAMELEN = 32;
/**
* The length of the group name field in a header buffer.
*/
public static final int GNAMELEN = 32;
/**
* The length of the devices field in a header buffer.
*/
public static final 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.
*/
public static final byte LF_OLDNORM = 0;
/**
* Normal file type.
*/
public static final byte LF_NORMAL = (byte) '0';
/**
* Link file type.
*/
public static final byte LF_LINK = (byte) '1';
/**
* Symbolic link file type.
*/
public static final byte LF_SYMLINK = (byte) '2';
/**
* Character device file type.
*/
public static final byte LF_CHR = (byte) '3';
/**
* Block device file type.
*/
public static final byte LF_BLK = (byte) '4';
/**
* Directory file type.
*/
public static final byte LF_DIR = (byte) '5';
/**
* FIFO (pipe) file type.
*/
public static final byte LF_FIFO = (byte) '6';
/**
* Contiguous file type.
*/
public static final byte LF_CONTIG = (byte) '7';
/**
* The magic tag representing a POSIX tar archive.
*/
public static final String TMAGIC = "ustar";
/**
* The magic tag representing a GNU tar archive.
*/
public static final String GNU_TMAGIC = "ustar ";
/**
* The entry's name.
*/
public StringBuffer name;
/**
* The entry's permission mode.
*/
public int mode;
/**
* The entry's user id.
*/
public int userId;
/**
* The entry's group id.
*/
public int groupId;
/**
* The entry's size.
*/
public long size;
/**
* The entry's modification time.
*/
public long modTime;
/**
* The entry's checksum.
*/
public int checkSum;
/**
* The entry's link flag.
*/
public byte linkFlag;
/**
* The entry's link name.
*/
public StringBuffer linkName;
/**
* The entry's magic tag.
*/
public StringBuffer magic;
/**
* The entry's user name.
*/
public StringBuffer userName;
/**
* The entry's group name.
*/
public StringBuffer groupName;
/**
* The entry's major device number.
*/
public int devMajor;
/**
* The entry's minor device number.
*/
public int devMinor;
public TarHeader() {
this.magic = new StringBuffer(TarHeader.TMAGIC);
this.name = new StringBuffer();
this.linkName = new StringBuffer();
String user = System.getProperty("user.name", "");
if (user.length() > 31) {
user = user.substring(0, 31);
}
this.userId = 0;
this.groupId = 0;
this.userName = new StringBuffer(user);
this.groupName = new StringBuffer("");
}
/**
* TarHeaders can be cloned.
*/
public Object clone() {
TarHeader hdr = null;
try {
hdr = (TarHeader) super.clone();
hdr.name = (this.name == null) ? null : new StringBuffer(this.name.toString());
hdr.mode = this.mode;
hdr.userId = this.userId;
hdr.groupId = this.groupId;
hdr.size = this.size;
hdr.modTime = this.modTime;
hdr.checkSum = this.checkSum;
hdr.linkFlag = this.linkFlag;
hdr.linkName = (this.linkName == null) ? null : new StringBuffer(this.linkName.toString());
hdr.magic = (this.magic == null) ? null : new StringBuffer(this.magic.toString());
hdr.userName = (this.userName == null) ? null : new StringBuffer(this.userName.toString());
hdr.groupName = (this.groupName == null) ? null : new StringBuffer(this.groupName.toString());
hdr.devMajor = this.devMajor;
hdr.devMinor = this.devMinor;
} catch (CloneNotSupportedException ex) {
ex.printStackTrace(System.err);
}
return hdr;
}
/**
* Get the name of this entry.
*
* @return Teh entry's name.
*/
public String getName() {
return this.name.toString();
}
/**
* 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.
* @return The long value of the octal string.
*/
public static long parseOctal(byte[] header, int offset, int length) throws InvalidHeaderException {
long result = 0;
boolean stillPadding = true;
int end = offset + length;
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');
}
return result;
}
/**
* Parse a file name from a header buffer. This is different from parseName() in that is recognizes 'ustar' names
* and will handle adding on the "prefix" field to the name.
*
* Contributed by Dmitri Tikhonov <dxt2431@yahoo.com>
*
* @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.
* @return The header's entry name.
*/
public static StringBuffer parseFileName(byte[] header) {
StringBuffer result = new StringBuffer(256);
// If header[345] is not equal to zero, then it is the "prefix"
// that 'ustar' defines. It must be prepended to the "normal"
// name field. We are responsible for the separating '/'.
//
if (header[345] != 0) {
for (int i = 345; i < 500 && header[i] != 0; ++i) {
result.append((char) header[i]);
}
result.append("/");
}
for (int i = 0; i < 100 && header[i] != 0; ++i) {
result.append((char) header[i]);
}
return result;
}
/**
* Parse an entry name from a header buffer.
*
* @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.
* @return The header's entry name.
*/
public static StringBuffer parseName(byte[] header, int offset, int length) throws InvalidHeaderException {
StringBuffer result = new StringBuffer(length);
int end = offset + length;
for (int i = offset; i < end; ++i) {
if (header[i] == 0) {
break;
}
result.append((char) header[i]);
}
return result;
}
/**
* This method, like getNameBytes(), is intended to place a name into a TarHeader's buffer. However, this method is
* sophisticated enough to recognize long names (name.length() > NAMELEN). In these cases, the method will break the
* name into a prefix and suffix and place the name in the header in 'ustar' format. It is up to the TarEntry to
* manage the "entry header format". This method assumes the name is valid for the type of archive being generated.
*
* @param outbuf
* The buffer containing the entry header to modify.
* @param newName
* The new name to place into the header buffer.
* @return The current offset in the tar header (always TarHeader.NAMELEN).
* @throws InvalidHeaderException
* If the name will not fit in the header.
*/
public static int getFileNameBytes(String newName, byte[] outbuf) throws InvalidHeaderException {
if (newName.length() > 100) {
// Locate a pathname "break" prior to the maximum name length...
int index = newName.indexOf("/", newName.length() - 100);
if (index == -1) {
throw new InvalidHeaderException("file name is greater than 100 characters, " + newName);
}
// Get the "suffix subpath" of the name.
String name = newName.substring(index + 1);
// Get the "prefix subpath", or "prefix", of the name.
String prefix = newName.substring(0, index);
if (prefix.length() > TarHeader.PREFIXLEN) {
throw new InvalidHeaderException("file prefix is greater than 155 characters");
}
TarHeader.getNameBytes(new StringBuffer(name), outbuf, TarHeader.NAMEOFFSET, TarHeader.NAMELEN);
TarHeader.getNameBytes(new StringBuffer(prefix), outbuf, TarHeader.PREFIXOFFSET, TarHeader.PREFIXLEN);
} else {
TarHeader.getNameBytes(new StringBuffer(newName), outbuf, TarHeader.NAMEOFFSET, TarHeader.NAMELEN);
}
// The offset, regardless of the format, is now the end of the
// original name field.
//
return TarHeader.NAMELEN;
}
/**
* Move the bytes from the name StringBuffer into the header's buffer.
*
* @param header
* The header buffer into which to copy the name.
* @param offset
* The offset into the buffer at which to store.
* @param length
* The number of header bytes to store.
* @return The new offset (offset + length).
*/
public static int getNameBytes(StringBuffer name, byte[] buf, int offset, int length) {
int i;
for (i = 0; i < length && i < name.length(); ++i) {
buf[offset + i] = (byte) name.charAt(i);
}
for (; i < length; ++i) {
buf[offset + i] = 0;
}
return offset + length;
}
/**
* Parse an octal integer from a header buffer.
*
* @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.
* @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) {
buf[offset + idx] = (byte) ((byte) '0' + (byte) (val & 7));
val = val >> 3;
}
}
for (; idx >= 0; --idx) {
buf[offset + idx] = (byte) ' ';
}
return offset + length;
}
/**
* Parse an octal long integer from a header buffer.
*
* @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.
* @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];
TarHeader.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 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.
* @return The integer value of the entry's checksum.
*/
public static int getCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
TarHeader.getOctalBytes(value, buf, offset, length);
buf[offset + length - 1] = (byte) ' ';
buf[offset + length - 2] = 0;
return offset + length;
}
}