/*
 * 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
 *
 * 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.
 */
package org.apache.commons.compress.archivers.tar;

import com.one.ArchiveEntry;
import com.one.PartialInputStream;
import com.one.RandomAccessInputStream;
import com.one.TextProcessor;
import com.one.file.Connector;
import com.one.file.FileConnection;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;

import org.apache.commons.compress.utils.ArchiveUtils;

/**
 * This class represents an entry in a Tar archive. It consists
 * of the entry's header, as well as the entry's FileConnection. 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 FileConnection
 * 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( FileConnection )
 * constructor. These entries have their header filled in using
 * the FileConnection's information. They also keep a reference to the FileConnection
 * 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 FileConnection is set to null.
 *
 * <p>
 * The C structure for a Tar Entry's header is:
 * <pre>
 * struct header {
 * char name[100];     // TarConstants.NAMELEN    - offset   0
 * char mode[8];       // TarConstants.MODELEN    - offset 100
 * char uid[8];        // TarConstants.UIDLEN     - offset 108
 * char gid[8];        // TarConstants.GIDLEN     - offset 116
 * char size[12];      // TarConstants.SIZELEN    - offset 124
 * char mtime[12];     // TarConstants.MODTIMELEN - offset 136
 * char chksum[8];     // TarConstants.CHKSUMLEN  - offset 148
 * char linkflag[1];   //                         - offset 156
 * char linkname[100]; // TarConstants.NAMELEN    - offset 157
 * The following fields are only present in new-style POSIX tar archives:
 * char magic[6];      // TarConstants.MAGICLEN   - offset 257
 * char version[2];    // TarConstants.VERSIONLEN - offset 263
 * char uname[32];     // TarConstants.UNAMELEN   - offset 265
 * char gname[32];     // TarConstants.GNAMELEN   - offset 297
 * char devmajor[8];   // TarConstants.DEVLEN     - offset 329
 * char devminor[8];   // TarConstants.DEVLEN     - offset 337
 * char prefix[155];   // TarConstants.PREFIXLEN  - offset 345
 * // Used if "name" field is not long enough to hold the path
 * char pad[12];       // NULs                    - offset 500
 * } header;
 * All unused bytes are set to null.
 * New-style GNU tar files are slightly different from the above.
 * </pre>
 * 
 * @NotThreadSafe
 */
public class TarEntry extends ArchiveEntry
{
	private static final int SMALL_BUFFER_SIZE = 256;
	private static final int BUFFER_SIZE = 8 * 1024;

	/** Maximum length of a user's name in the tar file */
	public static final int MAX_NAMELEN = 31;
	/** Default permissions bits for directories */
	public static final int DEFAULT_DIR_MODE = 040755;
	/** Default permissions bits for files */
	public static final int DEFAULT_FILE_MODE = 0100644;
	/** Convert millis to seconds */
	public static final int MILLIS_PER_SECOND = 1000;
	
	/** The entry's permission mode. */
	private int mode;
	/** The entry's user id. */
	private int userId;
	/** The entry's group id. */
	private int groupId;
	/** The entry's modification time. */
	private long modTime;
	/** The entry's link flag. */
	private byte linkFlag;
	/** The entry's link name. */
	private String linkName;
	/** The entry's magic tag. */
	private String magic;
	/** The version of the format */
	private String version;
	/** The entry's user name. */
	private String userName;
	/** The entry's group name. */
	private String groupName;
	/** The entry's major device number. */
	private int devMajor;
	/** The entry's minor device number. */
	private int devMinor;
	/** The entry's file reference */
	private FileConnection file;

	public int headerOffset;

	/**
	 * Construct an empty entry and prepares the header values.
	 */
	private TarEntry()
	{
		this.magic = TarConstants.MAGIC_POSIX;
		this.version = TarConstants.VERSION_POSIX;
		this.name = "";
		this.linkName = "";

		String user = System.getProperty("user.name");

		if(user == null)
		{
			user = "";
		}

		if(user.length() > MAX_NAMELEN)
		{
			user = user.substring(0, MAX_NAMELEN);
		}

		this.userId = 0;
		this.groupId = 0;
		this.userName = user;
		this.groupName = "";
		this.file = null;
	}

	/**
	 * Construct an entry with only a name. This allows the programmer
	 * to construct the entry's header "by hand". FileConnection is set to null.
	 *
	 * @param name the entry name
	 */
	public TarEntry(String name)
	{
		this(name, false);
	}

	/**
	 * Construct an entry with only a name. This allows the programmer
	 * to construct the entry's header "by hand". FileConnection is set to null.
	 *
	 * @param name the entry name
	 * @param preserveLeadingSlashes whether to allow leading slashes
	 * in the name.
	 *
	 * @since Apache Commons Compress 1.1
	 */
	public TarEntry(String name, boolean preserveLeadingSlashes)
	{
		this();

		name = normalizeFileName(name, preserveLeadingSlashes);
		boolean isDir = name.endsWith("/");

		this.devMajor = 0;
		this.devMinor = 0;
		this.name = name;
		this.mode = isDir ? DEFAULT_DIR_MODE : DEFAULT_FILE_MODE;
		this.linkFlag = isDir ? TarConstants.LF_DIR : TarConstants.LF_NORMAL;
		this.userId = 0;
		this.groupId = 0;
		this.size = 0;
		this.modTime = (new Date()).getTime() / MILLIS_PER_SECOND;
		this.linkName = "";
		this.userName = "";
		this.groupName = "";
		this.devMajor = 0;
		this.devMinor = 0;

	}

	/**
	 * Construct an entry with a name and a link flag.
	 *
	 * @param name the entry name
	 * @param linkFlag the entry link flag.
	 */
	public TarEntry(String name, byte linkFlag)
	{
		this(name);
		this.linkFlag = linkFlag;
		if(linkFlag == TarConstants.LF_GNUTYPE_LONGNAME)
		{
			magic = TarConstants.MAGIC_GNU;
			version = TarConstants.VERSION_GNU_SPACE;
		}
	}

	/**
	 * Construct an entry for a file. FileConnection is set to file, and the
	 * header is constructed from information from the file.
	 * The name is set from the normalized file path.
	 *
	 * @param file The file that the entry represents.
	 */
	public TarEntry(FileConnection file) throws IOException
	{
		this(file, normalizeFileName(file.getPath(), false));
	}

	/**
	 * Construct an entry for a file. FileConnection is set to file, and the
	 * header is constructed from information from the file.
	 *
	 * @param file The file that the entry represents.
	 * @param fileName the name to be used for the entry.
	 */
	public TarEntry(FileConnection file, String fileName) throws IOException
	{
		this();

		this.file = file;

		this.linkName = "";

		if(file.isDirectory())
		{
			this.mode = DEFAULT_DIR_MODE;
			this.linkFlag = TarConstants.LF_DIR;

			int nameLength = fileName.length();
			if(nameLength == 0 || fileName.charAt(nameLength - 1) != '/')
			{
				this.name = fileName + "/";
			}
			else
			{
				this.name = fileName;
			}
			this.size = 0;
		}
		else
		{
			this.mode = DEFAULT_FILE_MODE;
			this.linkFlag = TarConstants.LF_NORMAL;
			this.size = (int)file.fileSize();
			this.name = fileName;
		}

		this.modTime = file.lastModified() / MILLIS_PER_SECOND;
		this.devMajor = 0;
		this.devMinor = 0;
	}

	/**
	 * Construct an entry from an archive's header bytes. FileConnection is set
	 * to null.
	 *
	 * @param headerBuf The header bytes from a tar archive entry.
	 */
	public TarEntry(byte[] headerBuf)
	{
		this();
		parseTarHeader(headerBuf);
	}

	/**
	 * Determine if the two entries are equal. Equality is determined
	 * by the header names being equal.
	 *
	 * @param it Entry to be checked for equality.
	 * @return True if the entries are equal.
	 */
	public boolean equals(TarEntry it)
	{
		return getName().equals(it.getName());
	}

	/**
	 * Determine if the two entries are equal. Equality is determined
	 * by the header names being equal.
	 *
	 * @param it Entry to be checked for equality.
	 * @return True if the entries are equal.
	 */
	public boolean equals(Object it)
	{
		if(it == null || getClass() != it.getClass())
		{
			return false;
		}
		return equals((TarEntry) it);
	}

	/**
	 * Hashcodes are based on entry names.
	 *
	 * @return the entry hashcode
	 */
	public int hashCode()
	{
		return getName().hashCode();
	}

	/**
	 * 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.
	 *
	 * @param desc Entry to be checked as a descendent of this.
	 * @return True if entry is a descendant of this.
	 */
	public boolean isDescendent(TarEntry desc)
	{
		return desc.getName().startsWith(getName());
	}

	/**
	 * Get this entry's name.
	 *
	 * @return This entry's name.
	 */
	public String getName()
	{
		return name.toString();
	}

	/**
	 * Set this entry's name.
	 *
	 * @param name This entry's new name.
	 */
	public void setName(String name)
	{
		this.name = normalizeFileName(name, false);
	}

	/**
	 * Set the mode for this entry
	 *
	 * @param mode the mode for this entry
	 */
	public void setMode(int mode)
	{
		this.mode = mode;
	}

	/**
	 * Get this entry's link name.
	 *
	 * @return This entry's link name.
	 */
	public String getLinkName()
	{
		return linkName.toString();
	}

	/**
	 * Set this entry's link name.
	 *
	 * @param link the link name to use.
	 *
	 * @since Apache Commons Compress 1.1
	 */
	public void setLinkName(String link)
	{
		this.linkName = link;
	}

	/**
	 * Get this entry's user id.
	 *
	 * @return This entry's user id.
	 */
	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;
	}

	/**
	 * Get this entry's user name.
	 *
	 * @return This entry's user name.
	 */
	public String getUserName()
	{
		return userName.toString();
	}

	/**
	 * Set this entry's user name.
	 *
	 * @param userName This entry's new user name.
	 */
	public void setUserName(String userName)
	{
		this.userName = 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 = 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);
	}

	/** {@inheritDoc} */
	public Date getLastModifiedDate()
	{
		return getModTime();
	}

	/**
	 * Get this entry's file.
	 *
	 * @return This entry's file.
	 */
	public FileConnection 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 int getSize()
	{
		return size;
	}

	/**
	 * Get the number of records needed to store this entry.
	 * This does not account header record.
	 */
	public int getRecordCount()
	{
		return (size + TarArchive.RECORD_SIZE - 1) / TarArchive.RECORD_SIZE;
	}

	/**
	 * Get this entry's "compressed" size,
	 * which would actually be larger because of padding.
	 */
	public int getCompressedSize()
	{
		return getRecordCount() * TarArchive.RECORD_SIZE;
	}

	/**
	 * Set this entry's file size.
	 *
	 * @param size This entry's new file size.
	 * @throws IllegalArgumentException if the size is < 0
	 * or > {@link TarConstants#MAXSIZE} (077777777777L).
	 */
	public void setSize(int size)
	{
		if(size > TarConstants.MAXSIZE || size < 0)
		{
			throw new IllegalArgumentException("Size is out of range: " + 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 == TarConstants.LF_GNUTYPE_LONGNAME
			&& name.toString().equals(TarConstants.GNU_LONGLINK);
	}

	/**
	 * Check if this is a Pax header.
	 *
	 * @return <code>true</code> if this is a Pax header.
	 *
	 * @since Apache Commons Compress 1.1
	 */
	public boolean isPaxHeader()
	{
		return linkFlag == TarConstants.LF_PAX_EXTENDED_HEADER_LC
			|| linkFlag == TarConstants.LF_PAX_EXTENDED_HEADER_UC;
	}

	/**
	 * Check if this is a Pax header.
	 *
	 * @return <code>true</code> if this is a Pax header.
	 *
	 * @since Apache Commons Compress 1.1
	 */
	public boolean isGlobalPaxHeader()
	{
		return linkFlag == TarConstants.LF_PAX_GLOBAL_EXTENDED_HEADER;
	}

	/**
	 * Return whether or not this entry represents a directory.
	 *
	 * @return True if this entry is a directory.
	 */
	public boolean isDirectory()
	{
		if(file != null)
		{
			return file.isDirectory();
		}

		if(linkFlag == TarConstants.LF_DIR)
		{
			return true;
		}

		if(getName().endsWith("/"))
		{
			return true;
		}

		return false;
	}

	/**
	 * 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 TarEntry[] getDirectoryEntries() throws IOException
	{
		if(file == null || !file.isDirectory())
		{
			return new TarEntry[0];
		}

		String[] list = TextProcessor.enumerationToStringArray(file.list());
		TarEntry[] result = new TarEntry[list.length];

		for(int i = 0; i < list.length; ++i)
		{
			result[i] = new TarEntry((FileConnection) Connector.open("file:///" + file), list[i]);
		}

		return result;
	}

	/**
	 * Parse an entry's header information from a header buffer.
	 *
	 * @param header The tar entry header buffer to get information from.
	 */
	public void parseTarHeader(byte[] header)
	{
		int pos = 0;

		name = TarUtils.parseName(header, pos, TarConstants.NAMELEN);
		pos += TarConstants.NAMELEN;
		mode = (int) TarUtils.parseOctal(header, pos, TarConstants.MODELEN);
		pos += TarConstants.MODELEN;
		userId = (int) TarUtils.parseOctal(header, pos, TarConstants.UIDLEN);
		pos += TarConstants.UIDLEN;
		groupId = (int) TarUtils.parseOctal(header, pos, TarConstants.GIDLEN);
		pos += TarConstants.GIDLEN;
		size = (int) TarUtils.parseOctal(header, pos, TarConstants.SIZELEN);
		pos += TarConstants.SIZELEN;
		modTime = TarUtils.parseOctal(header, pos, TarConstants.MODTIMELEN);
		pos += TarConstants.MODTIMELEN;
		pos += TarConstants.CHKSUMLEN;
		linkFlag = header[pos++];
		linkName = TarUtils.parseName(header, pos, TarConstants.NAMELEN);
		pos += TarConstants.NAMELEN;
		magic = TarUtils.parseName(header, pos, TarConstants.MAGICLEN);
		pos += TarConstants.MAGICLEN;
		version = TarUtils.parseName(header, pos, TarConstants.VERSIONLEN);
		pos += TarConstants.VERSIONLEN;
		userName = TarUtils.parseName(header, pos, TarConstants.UNAMELEN);
		pos += TarConstants.UNAMELEN;
		groupName = TarUtils.parseName(header, pos, TarConstants.GNAMELEN);
		pos += TarConstants.GNAMELEN;
		devMajor = (int) TarUtils.parseOctal(header, pos, TarConstants.DEVLEN);
		pos += TarConstants.DEVLEN;
		devMinor = (int) TarUtils.parseOctal(header, pos, TarConstants.DEVLEN);
		pos += TarConstants.DEVLEN;
		String prefix = TarUtils.parseName(header, pos, TarConstants.PREFIXLEN);
		// SunOS tar -E does not add / to directory names, so fix up to be consistent
		if(isDirectory() && !name.endsWith("/"))
		{
			name = name + "/";
		}
		if(prefix.length() > 0)
		{
			name = prefix + "/" + name;
		}
	}

	/**
	 * Strips Windows' drive letter as well as any leading slashes,
	 * turns path separators into forward slahes.
	 */
	private static String normalizeFileName(String fileName, boolean preserveLeadingSlashes)
	{
		String osname = System.getProperty("os.name");

		if(osname != null)
		{
			osname = osname.toLowerCase();

			// Strip off drive letters!
			// REVIEW Would a better check be "(FileConnection.separator == '\')"?

			if(osname.startsWith("windows"))
			{
				if(fileName.length() > 2)
				{
					char ch1 = fileName.charAt(0);
					char ch2 = fileName.charAt(1);

					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);
				}
			}
		}

		//fileName = fileName.replace(FileConnection.separatorChar, '/');

		// No absolute pathnames
		// Windows (and Posix?) paths can start with "\\NetworkDrive\",
		// so we loop on starting /'s.

		while(!preserveLeadingSlashes && fileName.startsWith("/"))
		{
			fileName = fileName.substring(1);
		}
		
		return fileName;
	}

	/**
     * 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 true if the record data is an End of Archive
     */
    public static boolean isEOFRecord(byte[] record)
	{
		for(int i = 0; i < TarArchive.RECORD_SIZE; ++i)
		{
			if(record[i] != 0)
			{
				return false;
			}
		}

		return true;
	}

	/**
	 * Get the next entry in this tar archive.
	 * 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.
	 * @throws IOException on error
	 */
	public static TarEntry readEntryHeader(RandomAccessInputStream rais) throws IOException
	{
		int pos = rais.getPosition();

		byte[] headerBuf = new byte[TarArchive.RECORD_SIZE];
		rais.read(headerBuf);

		if(isEOFRecord(headerBuf))
		{
			return null;
		}

		TarEntry currEntry = new TarEntry(headerBuf);
		currEntry.offset = rais.getPosition();

		if(currEntry.isGNULongNameEntry())
		{
			// read in the name
			StringBuffer longName = new StringBuffer();
			PartialInputStream inp = new PartialInputStream(rais, currEntry.offset, currEntry.getSize());

			byte[] buf = new byte[SMALL_BUFFER_SIZE];
			int length = 0;

			while((length = inp.read(buf)) >= 0)
			{
				longName.append(new String(buf, 0, length));
			}

			currEntry = readEntryHeader(rais);

			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());
		}

		if(currEntry != null && currEntry.isPaxHeader())
		{
			// Process Pax headers

			InputStreamReader br = new InputStreamReader(rais, "UTF-8");
			Hashtable headers = new Hashtable();

			// Format is "length keyword=value\n";

			while(true)
			{ // get length
				int ch;
				int len = 0;
				int read = 0;
				while((ch = br.read()) != -1)
				{
					read++;
					if(ch == ' ')
					{ // End of length string
						// Get keyword
						StringBuffer sb = new StringBuffer();
						while((ch = br.read()) != -1)
						{
							read++;
							if(ch == '=')
							{ // end of keyword
								String keyword = sb.toString();
								// Get rest of entry
								char[] cbuf = new char[len - read];
								int got = br.read(cbuf);
								if(got != len - read)
								{
									throw new IOException("Failed to read Paxheader. Expected " + (len - read) + " chars, read " + got);
								}
								String value = new String(cbuf, 0, len - read - 1); // Drop trailing NL
								headers.put(keyword, value);
								break;
							}
							sb.append((char) ch);
						}
						break; // Processed single header
					}
					len *= 10;
					len += ch - '0';
				}
				if(ch == -1)
				{ // EOF
					break;
				}
			}

			currEntry = readEntryHeader(rais); // Get the actual file entry
			
			/*
			 * The following headers are defined for Pax.
			 * atime, ctime, mtime, charset: cannot use these without changing TarArchiveEntry fields
			 * comment
			 * gid, gname
			 * linkpath
			 * size
			 * uid,uname
			 */
			Enumeration hdrs = headers.keys();
			String key, val;
			while(hdrs.hasMoreElements())
			{
				key = (String)hdrs.nextElement();
				val = (String)headers.get(key);

				if("path".equals(key))
				{
					currEntry.setName(val);
				}
				else if("linkpath".equals(key))
				{
					currEntry.setLinkName(val);
				}
				else if("gid".equals(key))
				{
					currEntry.setGroupId(Integer.parseInt(val));
				}
				else if("gname".equals(key))
				{
					currEntry.setGroupName(val);
				}
				else if("uid".equals(key))
				{
					currEntry.setUserId(Integer.parseInt(val));
				}
				else if("uname".equals(key))
				{
					currEntry.setUserName(val);
				}
				else if("size".equals(key))
				{
					currEntry.setSize(Integer.parseInt(val));
				}
			}
		}

		currEntry.headerOffset = pos;

		return currEntry;
	}

	public void writeEntryHeader(OutputStream os) throws IOException
	{
		byte[] buf = new byte[TarArchive.RECORD_SIZE];

		if(getName().length() >= TarConstants.NAMELEN)
		{
			// create a TarEntry for the LongLink, the contents
			// of which are the entry's name

			TarEntry longLinkEntry = new TarEntry(TarConstants.GNU_LONGLINK, TarConstants.LF_GNUTYPE_LONGNAME);

			final byte[] nameBytes = ArchiveUtils.toAsciiBytes(getName());

			longLinkEntry.setSize(nameBytes.length + 1); // +1 for NUL
			longLinkEntry.writeEntryHeader(os);

			os.write(nameBytes);
			os.write(0); // NUL terminator

			int left = TarArchive.RECORD_SIZE - longLinkEntry.getSize() % TarArchive.RECORD_SIZE;

			if(left < TarArchive.RECORD_SIZE)
			{
				os.write(buf, 0, left);
			}
		}

		int pos = 0;

		pos = TarUtils.formatNameBytes(name, buf, pos, TarConstants.NAMELEN);
		pos = TarUtils.formatOctalBytes(mode, buf, pos, TarConstants.MODELEN);
		pos = TarUtils.formatOctalBytes(userId, buf, pos, TarConstants.UIDLEN);
		pos = TarUtils.formatOctalBytes(groupId, buf, pos, TarConstants.GIDLEN);
		pos = TarUtils.formatLongOctalBytes(size, buf, pos, TarConstants.SIZELEN);
		pos = TarUtils.formatLongOctalBytes(modTime, buf, pos, TarConstants.MODTIMELEN);

		int cspos = pos;

		for(int c = 0; c < TarConstants.CHKSUMLEN; ++c)
		{
			buf[pos++] = (byte)' ';
		}

		buf[pos++] = linkFlag;
		pos = TarUtils.formatNameBytes(linkName, buf, pos, TarConstants.NAMELEN);
		pos = TarUtils.formatNameBytes(magic, buf, pos, TarConstants.MAGICLEN);
		pos = TarUtils.formatNameBytes(version, buf, pos, TarConstants.VERSIONLEN);
		pos = TarUtils.formatNameBytes(userName, buf, pos, TarConstants.UNAMELEN);
		pos = TarUtils.formatNameBytes(groupName, buf, pos, TarConstants.GNAMELEN);
		pos = TarUtils.formatOctalBytes(devMajor, buf, pos, TarConstants.DEVLEN);
		pos = TarUtils.formatOctalBytes(devMinor, buf, pos, TarConstants.DEVLEN);

		while(pos < buf.length)
		{
			buf[pos++] = 0;
		}

		TarUtils.formatCheckSumOctalBytes(TarUtils.computeCheckSum(buf), buf, cspos, TarConstants.CHKSUMLEN);

		os.write(buf);
	}

	public String toString()
	{
		return Integer.toString(headerOffset) + "\t" + Integer.toString(offset) + "\t" + Integer.toString(getCompressedSize()) + "\t" + getName();
	}
}