/* ** 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; } }