using System;
using System.IO;
namespace ICSharpCode.SharpZipLib.Zip
{
	/// 
	/// Defines known values for the  property.
	/// 
	public enum HostSystemID
	{
		/// 
		/// Host system = MSDOS
		/// 
		Msdos = 0,
		/// 
		/// Host system = Amiga
		/// 
		Amiga = 1,
		/// 
		/// Host system = Open VMS
		/// 
		OpenVms = 2,
		/// 
		/// Host system = Unix
		/// 
		Unix = 3,
		/// 
		/// Host system = VMCms
		/// 
		VMCms = 4,
		/// 
		/// Host system = Atari ST
		/// 
		AtariST = 5,
		/// 
		/// Host system = OS2
		/// 
		OS2 = 6,
		/// 
		/// Host system = Macintosh
		/// 
		Macintosh = 7,
		/// 
		/// Host system = ZSystem
		/// 
		ZSystem = 8,
		/// 
		/// Host system = Cpm
		/// 
		Cpm = 9,
		/// 
		/// Host system = Windows NT
		/// 
		WindowsNT = 10,
		/// 
		/// Host system = MVS
		/// 
		MVS = 11,
		/// 
		/// Host system = VSE
		/// 
		Vse = 12,
		/// 
		/// Host system = Acorn RISC
		/// 
		AcornRisc = 13,
		/// 
		/// Host system = VFAT
		/// 
		Vfat = 14,
		/// 
		/// Host system = Alternate MVS
		/// 
		AlternateMvs = 15,
		/// 
		/// Host system = BEOS
		/// 
		BeOS = 16,
		/// 
		/// Host system = Tandem
		/// 
		Tandem = 17,
		/// 
		/// Host system = OS400
		/// 
		OS400 = 18,
		/// 
		/// Host system = OSX
		/// 
		OSX = 19,
		/// 
		/// Host system = WinZIP AES
		/// 
		WinZipAES = 99,
	}
	/// 
	/// This class represents an entry in a zip archive.  This can be a file
	/// or a directory
	/// ZipFile and ZipInputStream will give you instances of this class as
	/// information about the members in an archive.  ZipOutputStream
	/// uses an instance of this class when creating an entry in a Zip file.
	/// 
	/// 
Author of the original java version : Jochen Hoenicke
	/// 
	public class ZipEntry
	{
		[Flags]
		enum Known : byte
		{
			None = 0,
			Size = 0x01,
			CompressedSize = 0x02,
			Crc = 0x04,
			Time = 0x08,
			ExternalAttributes = 0x10,
		}
		#region Constructors
		/// 
		/// Creates a zip entry with the given name.
		/// 
		/// 
		/// The name for this entry. Can include directory components.
		/// The convention for names is 'unix' style paths with relative names only.
		/// There are with no device names and path elements are separated by '/' characters.
		/// 
		/// 
		/// The name passed is null
		/// 
		public ZipEntry(string name)
			: this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated)
		{
		}
		/// 
		/// Creates a zip entry with the given name and version required to extract
		/// 
		/// 
		/// The name for this entry. Can include directory components.
		/// The convention for names is 'unix'  style paths with no device names and
		/// path elements separated by '/' characters.  This is not enforced see CleanName
		/// on how to ensure names are valid if this is desired.
		/// 
		/// 
		/// The minimum 'feature version' required this entry
		/// 
		/// 
		/// The name passed is null
		/// 
		internal ZipEntry(string name, int versionRequiredToExtract)
			: this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy,
			CompressionMethod.Deflated)
		{
		}
		/// 
		/// Initializes an entry with the given name and made by information
		/// 
		/// Name for this entry
		/// Version and HostSystem Information
		/// Minimum required zip feature version required to extract this entry
		/// Compression method for this entry.
		/// 
		/// The name passed is null
		/// 
		/// 
		/// versionRequiredToExtract should be 0 (auto-calculate) or > 10
		/// 
		/// 
		/// This constructor is used by the ZipFile class when reading from the central header
		/// It is not generally useful, use the constructor specifying the name only.
		/// 
		internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo,
			CompressionMethod method)
		{
			if (name == null) {
				throw new ArgumentNullException("nameof(name)");
			}
			if (name.Length > 0xffff) {
				throw new ArgumentException("Name is too long", "nameof(name)");
			}
			if ((versionRequiredToExtract != 0) && (versionRequiredToExtract < 10)) {
				throw new ArgumentOutOfRangeException("nameof(versionRequiredToExtract)");
			}
			this.DateTime = DateTime.Now;
			this.name = CleanName(name);
			this.versionMadeBy = (ushort)madeByInfo;
			this.versionToExtract = (ushort)versionRequiredToExtract;
			this.method = method;
		}
		/// 
		/// Creates a deep copy of the given zip entry.
		/// 
		/// 
		/// The entry to copy.
		/// 
		[Obsolete("Use Clone instead")]
		public ZipEntry(ZipEntry entry)
		{
			if (entry == null) {
				throw new ArgumentNullException("nameof(entry)");
			}
			known = entry.known;
			name = entry.name;
			size = entry.size;
			compressedSize = entry.compressedSize;
			crc = entry.crc;
			dosTime = entry.dosTime;
			method = entry.method;
			comment = entry.comment;
			versionToExtract = entry.versionToExtract;
			versionMadeBy = entry.versionMadeBy;
			externalFileAttributes = entry.externalFileAttributes;
			flags = entry.flags;
			zipFileIndex = entry.zipFileIndex;
			offset = entry.offset;
			forceZip64_ = entry.forceZip64_;
			if (entry.extra != null) {
				extra = new byte[entry.extra.Length];
				Array.Copy(entry.extra, 0, extra, 0, entry.extra.Length);
			}
		}
		#endregion
		/// 
		/// Get a value indicating wether the entry has a CRC value available.
		/// 
		public bool HasCrc {
			get {
				return (known & Known.Crc) != 0;
			}
		}
		/// 
		/// Get/Set flag indicating if entry is encrypted.
		/// A simple helper routine to aid interpretation of flags
		/// 
		/// This is an assistant that interprets the flags property.
		public bool IsCrypted {
			get {
				return (flags & 1) != 0;
			}
			set {
				if (value) {
					flags |= 1;
				} else {
					flags &= ~1;
				}
			}
		}
		/// 
		/// Get / set a flag indicating wether entry name and comment text are
		/// encoded in unicode UTF8.
		/// 
		/// This is an assistant that interprets the flags property.
		public bool IsUnicodeText {
			get {
				return (flags & (int)GeneralBitFlags.UnicodeText) != 0;
			}
			set {
				if (value) {
					flags |= (int)GeneralBitFlags.UnicodeText;
				} else {
					flags &= ~(int)GeneralBitFlags.UnicodeText;
				}
			}
		}
		/// 
		/// Value used during password checking for PKZIP 2.0 / 'classic' encryption.
		/// 
		internal byte CryptoCheckValue {
			get {
				return cryptoCheckValue_;
			}
			set {
				cryptoCheckValue_ = value;
			}
		}
		/// 
		/// Get/Set general purpose bit flag for entry
		/// 
		/// 
		/// General purpose bit flag
		/// 
		/// Bit 0: If set, indicates the file is encrypted
		/// Bit 1-2 Only used for compression type 6 Imploding, and 8, 9 deflating
		/// Imploding:
		/// Bit 1 if set indicates an 8K sliding dictionary was used.  If clear a 4k dictionary was used
		/// Bit 2 if set indicates 3 Shannon-Fanno trees were used to encode the sliding dictionary, 2 otherwise
		/// 
		/// Deflating:
		///   Bit 2    Bit 1
		///     0        0       Normal compression was used
		///     0        1       Maximum compression was used
		///     1        0       Fast compression was used
		///     1        1       Super fast compression was used
		/// 
		/// Bit 3: If set, the fields crc-32, compressed size
		/// and uncompressed size are were not able to be written during zip file creation
		/// The correct values are held in a data descriptor immediately following the compressed data. 
		/// Bit 4: Reserved for use by PKZIP for enhanced deflating
		/// Bit 5: If set indicates the file contains compressed patch data
		/// Bit 6: If set indicates strong encryption was used.
		/// Bit 7-10: Unused or reserved
		/// Bit 11: If set the name and comments for this entry are in unicode.
		/// Bit 12-15: Unused or reserved
		/// 
		/// 
		/// 
		public int Flags {
			get {
				return flags;
			}
			set {
				flags = value;
			}
		}
		/// 
		/// Get/Set index of this entry in Zip file
		/// 
		/// This is only valid when the entry is part of a 
		public long ZipFileIndex {
			get {
				return zipFileIndex;
			}
			set {
				zipFileIndex = value;
			}
		}
		/// 
		/// Get/set offset for use in central header
		/// 
		public long Offset {
			get {
				return offset;
			}
			set {
				offset = value;
			}
		}
		/// 
		/// Get/Set external file attributes as an integer.
		/// The values of this are operating system dependant see
		/// HostSystem for details
		/// 
		public int ExternalFileAttributes {
			get {
				if ((known & Known.ExternalAttributes) == 0) {
					return -1;
				} else {
					return externalFileAttributes;
				}
			}
			set {
				externalFileAttributes = value;
				known |= Known.ExternalAttributes;
			}
		}
		/// 
		/// Get the version made by for this entry or zero if unknown.
		/// The value / 10 indicates the major version number, and
		/// the value mod 10 is the minor version number
		/// 
		public int VersionMadeBy {
			get {
				return (versionMadeBy & 0xff);
			}
		}
		/// 
		/// Get a value indicating this entry is for a DOS/Windows system.
		/// 
		public bool IsDOSEntry {
			get {
				return ((HostSystem == (int)HostSystemID.Msdos) ||
					(HostSystem == (int)HostSystemID.WindowsNT));
			}
		}
		/// 
		/// Test the external attributes for this  to
		/// see if the external attributes are Dos based (including WINNT and variants)
		/// and match the values
		/// 
		/// The attributes to test.
		/// Returns true if the external attributes are known to be DOS/Windows
		/// based and have the same attributes set as the value passed.
		bool HasDosAttributes(int attributes)
		{
			bool result = false;
			if ((known & Known.ExternalAttributes) != 0) {
				result |= (((HostSystem == (int)HostSystemID.Msdos) ||
					(HostSystem == (int)HostSystemID.WindowsNT)) &&
					(ExternalFileAttributes & attributes) == attributes);
			}
			return result;
		}
		/// 
		/// Gets the compatability information for the external file attribute
		/// If the external file attributes are compatible with MS-DOS and can be read
		/// by PKZIP for DOS version 2.04g then this value will be zero.  Otherwise the value
		/// will be non-zero and identify the host system on which the attributes are compatible.
		/// 
		///
		/// 
		/// The values for this as defined in the Zip File format and by others are shown below.  The values are somewhat
		/// misleading in some cases as they are not all used as shown.  You should consult the relevant documentation
		/// to obtain up to date and correct information.  The modified appnote by the infozip group is
		/// particularly helpful as it documents a lot of peculiarities.  The document is however a little dated.
		/// 
		/// - 0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)
 
		/// - 1 - Amiga
 
		/// - 2 - OpenVMS
 
		/// - 3 - Unix
 
		/// - 4 - VM/CMS
 
		/// - 5 - Atari ST
 
		/// - 6 - OS/2 HPFS
 
		/// - 7 - Macintosh
 
		/// - 8 - Z-System
 
		/// - 9 - CP/M
 
		/// - 10 - Windows NTFS
 
		/// - 11 - MVS (OS/390 - Z/OS)
 
		/// - 12 - VSE
 
		/// - 13 - Acorn Risc
 
		/// - 14 - VFAT
 
		/// - 15 - Alternate MVS
 
		/// - 16 - BeOS
 
		/// - 17 - Tandem
 
		/// - 18 - OS/400
 
		/// - 19 - OS/X (Darwin)
 
		/// - 99 - WinZip AES
 
		/// - remainder - unused
 
		/// 
		/// 
		public int HostSystem {
			get {
				return (versionMadeBy >> 8) & 0xff;
			}
			set {
				versionMadeBy &= 0xff;
				versionMadeBy |= (ushort)((value & 0xff) << 8);
			}
		}
		/// 
		/// Get minimum Zip feature version required to extract this entry
		/// 
		/// 
		/// Minimum features are defined as:
		/// 1.0 - Default value
		/// 1.1 - File is a volume label
		/// 2.0 - File is a folder/directory
		/// 2.0 - File is compressed using Deflate compression
		/// 2.0 - File is encrypted using traditional encryption
		/// 2.1 - File is compressed using Deflate64
		/// 2.5 - File is compressed using PKWARE DCL Implode
		/// 2.7 - File is a patch data set
		/// 4.5 - File uses Zip64 format extensions
		/// 4.6 - File is compressed using BZIP2 compression
		/// 5.0 - File is encrypted using DES
		/// 5.0 - File is encrypted using 3DES
		/// 5.0 - File is encrypted using original RC2 encryption
		/// 5.0 - File is encrypted using RC4 encryption
		/// 5.1 - File is encrypted using AES encryption
		/// 5.1 - File is encrypted using corrected RC2 encryption
		/// 5.1 - File is encrypted using corrected RC2-64 encryption
		/// 6.1 - File is encrypted using non-OAEP key wrapping
		/// 6.2 - Central directory encryption (not confirmed yet)
		/// 6.3 - File is compressed using LZMA
		/// 6.3 - File is compressed using PPMD+
		/// 6.3 - File is encrypted using Blowfish
		/// 6.3 - File is encrypted using Twofish
		/// 
		/// 
		public int Version {
			get {
				// Return recorded version if known.
				if (versionToExtract != 0) {
					return versionToExtract & 0x00ff;               // Only lower order byte. High order is O/S file system.
				} else {
					int result = 10;
					if (AESKeySize > 0) {
						result = ZipConstants.VERSION_AES;          // Ver 5.1 = AES
					} else if (CentralHeaderRequiresZip64) {
						result = ZipConstants.VersionZip64;
					} else if (CompressionMethod.Deflated == method) {
						result = 20;
					} else if (IsDirectory == true) {
						result = 20;
					} else if (IsCrypted == true) {
						result = 20;
					} else if (HasDosAttributes(0x08)) {
						result = 11;
					}
					return result;
				}
			}
		}
		/// 
		/// Get a value indicating whether this entry can be decompressed by the library.
		/// 
		/// This is based on the  and
		/// wether the compression method is supported.
		public bool CanDecompress {
			get {
				return (Version <= ZipConstants.VersionMadeBy) &&
					((Version == 10) ||
					(Version == 11) ||
					(Version == 20) ||
					(Version == 45) ||
					(Version == 51)) &&
					IsCompressionMethodSupported();
			}
		}
		/// 
		/// Force this entry to be recorded using Zip64 extensions.
		/// 
		public void ForceZip64()
		{
			forceZip64_ = true;
		}
		/// 
		/// Get a value indicating wether Zip64 extensions were forced.
		/// 
		/// A  value of true if Zip64 extensions have been forced on; false if not.
		public bool IsZip64Forced()
		{
			return forceZip64_;
		}
		/// 
		/// Gets a value indicating if the entry requires Zip64 extensions
		/// to store the full entry values.
		/// 
		/// A  value of true if a local header requires Zip64 extensions; false if not.
		public bool LocalHeaderRequiresZip64 {
			get {
				bool result = forceZip64_;
				if (!result) {
					ulong trueCompressedSize = compressedSize;
					if ((versionToExtract == 0) && IsCrypted) {
						trueCompressedSize += ZipConstants.CryptoHeaderSize;
					}
					// TODO: A better estimation of the true limit based on compression overhead should be used
					// to determine when an entry should use Zip64.
					result =
						((this.size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) &&
						((versionToExtract == 0) || (versionToExtract >= ZipConstants.VersionZip64));
				}
				return result;
			}
		}
		/// 
		/// Get a value indicating wether the central directory entry requires Zip64 extensions to be stored.
		/// 
		public bool CentralHeaderRequiresZip64 {
			get {
				return LocalHeaderRequiresZip64 || (offset >= uint.MaxValue);
			}
		}
		/// 
		/// Get/Set DosTime value.
		/// 
		/// 
		/// The MS-DOS date format can only represent dates between 1/1/1980 and 12/31/2107.
		/// 
		public long DosTime {
			get {
				if ((known & Known.Time) == 0) {
					return 0;
				} else {
					return dosTime;
				}
			}
			set {
				unchecked {
					dosTime = (uint)value;
				}
				known |= Known.Time;
			}
		}
		/// 
		/// Gets/Sets the time of last modification of the entry.
		/// 
		/// 
		/// The  property is updated to match this as far as possible.
		/// 
		public DateTime DateTime
		{
			get
			{
				uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
				uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
				uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
				uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
				uint year = ((dosTime >> 25) & 0x7f) + 1980;
				int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
				return new System.DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec);
			}
			set {
				var year = (uint)value.Year;
				var month = (uint)value.Month;
				var day = (uint)value.Day;
				var hour = (uint)value.Hour;
				var minute = (uint)value.Minute;
				var second = (uint)value.Second;
				if (year < 1980) {
					year = 1980;
					month = 1;
					day = 1;
					hour = 0;
					minute = 0;
					second = 0;
				} else if (year > 2107) {
					year = 2107;
					month = 12;
					day = 31;
					hour = 23;
					minute = 59;
					second = 59;
				}
				DosTime = ((year - 1980) & 0x7f) << 25 |
					(month << 21) |
					(day << 16) |
					(hour << 11) |
					(minute << 5) |
					(second >> 1);
			}
		}
		/// 
		/// Returns the entry name.
		/// 
		/// 
		/// The unix naming convention is followed.
		/// Path components in the entry should always separated by forward slashes ('/').
		/// Dos device names like C: should also be removed.
		/// See the  class, or 
		///
		public string Name {
			get {
				return name;
			}
		}
		/// 
		/// Gets/Sets the size of the uncompressed data.
		/// 
		/// 
		/// The size or -1 if unknown.
		/// 
		/// Setting the size before adding an entry to an archive can help
		/// avoid compatability problems with some archivers which dont understand Zip64 extensions.
		public long Size {
			get {
				return (known & Known.Size) != 0 ? (long)size : -1L;
			}
			set {
				this.size = (ulong)value;
				this.known |= Known.Size;
			}
		}
		/// 
		/// Gets/Sets the size of the compressed data.
		/// 
		/// 
		/// The compressed entry size or -1 if unknown.
		/// 
		public long CompressedSize {
			get {
				return (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L;
			}
			set {
				this.compressedSize = (ulong)value;
				this.known |= Known.CompressedSize;
			}
		}
		/// 
		/// Gets/Sets the crc of the uncompressed data.
		/// 
		/// 
		/// Crc is not in the range 0..0xffffffffL
		/// 
		/// 
		/// The crc value or -1 if unknown.
		/// 
		public long Crc {
			get {
				return (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L;
			}
			set {
				if (((ulong)crc & 0xffffffff00000000L) != 0) {
					throw new ArgumentOutOfRangeException("nameof(value)");
				}
				this.crc = (uint)value;
				this.known |= Known.Crc;
			}
		}
		/// 
		/// Gets/Sets the compression method. Only Deflated and Stored are supported.
		/// 
		/// 
		/// The compression method for this entry
		/// 
		/// 
		/// 
		public CompressionMethod CompressionMethod {
			get {
				return method;
			}
			set {
				if (!IsCompressionMethodSupported(value)) {
					throw new NotSupportedException("Compression method not supported");
				}
				this.method = value;
			}
		}
		/// 
		/// Gets the compression method for outputting to the local or central header.
		/// Returns same value as CompressionMethod except when AES encrypting, which
		/// places 99 in the method and places the real method in the extra data.
		/// 
		internal CompressionMethod CompressionMethodForHeader {
			get {
				return (AESKeySize > 0) ? CompressionMethod.WinZipAES : method;
			}
		}
		/// 
		/// Gets/Sets the extra data.
		/// 
		/// 
		/// Extra data is longer than 64KB (0xffff) bytes.
		/// 
		/// 
		/// Extra data or null if not set.
		/// 
		public byte[] ExtraData {
			get {
				// TODO: This is slightly safer but less efficient.  Think about wether it should change.
				//				return (byte[]) extra.Clone();
				return extra;
			}
			set {
				if (value == null) {
					extra = null;
				} else {
					if (value.Length > 0xffff) {
						throw new System.ArgumentOutOfRangeException("nameof(value)");
					}
					extra = new byte[value.Length];
					Array.Copy(value, 0, extra, 0, value.Length);
				}
			}
		}
		/// 
		/// For AES encrypted files returns or sets the number of bits of encryption (128, 192 or 256).
		/// When setting, only 0 (off), 128 or 256 is supported.
		/// 
		public int AESKeySize {
			get {
				// the strength (1 or 3) is in the entry header
				switch (_aesEncryptionStrength) {
					case 0:
						return 0;   // Not AES
					case 1:
						return 128;
					case 2:
						return 192; // Not used by WinZip
					case 3:
						return 256;
					default:
						throw new ZipException("Invalid AESEncryptionStrength " + _aesEncryptionStrength);
				}
			}
			set {
				switch (value) {
					case 0:
						_aesEncryptionStrength = 0;
						break;
					case 128:
						_aesEncryptionStrength = 1;
						break;
					case 256:
						_aesEncryptionStrength = 3;
						break;
					default:
						throw new ZipException("AESKeySize must be 0, 128 or 256: " + value);
				}
			}
		}
		/// 
		/// AES Encryption strength for storage in extra data in entry header.
		/// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit.
		/// 
		internal byte AESEncryptionStrength {
			get {
				return (byte)_aesEncryptionStrength;
			}
		}
		/// 
		/// Returns the length of the salt, in bytes
		/// 
		internal int AESSaltLen {
			get {
				// Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes.
				return AESKeySize / 16;
			}
		}
		/// 
		/// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode)
		/// 
		internal int AESOverheadSize {
			get {
				// File format:
				//   Bytes		Content
				// Variable		Salt value
				//     2		Password verification value
				// Variable		Encrypted file data
				//    10		Authentication code
				return 12 + AESSaltLen;
			}
		}
		/// 
		/// Process extra data fields updating the entry based on the contents.
		/// 
		/// True if the extra data fields should be handled
		/// for a local header, rather than for a central header.
		/// 
		internal void ProcessExtraData(bool localHeader)
		{
			var extraData = new ZipExtraData(this.extra);
			if (extraData.Find(0x0001)) {
				// Version required to extract is ignored here as some archivers dont set it correctly
				// in theory it should be version 45 or higher
				// The recorded size will change but remember that this is zip64.
				forceZip64_ = true;
				if (extraData.ValueLength < 4) {
					throw new ZipException("Extra data extended Zip64 information length is invalid");
				}
				// (localHeader ||) was deleted, because actually there is no specific difference with reading sizes between local header & central directory
				// https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
				// ...
				// 4.4  Explanation of fields
				// ...
				//	4.4.8 compressed size: (4 bytes)
				//	4.4.9 uncompressed size: (4 bytes)
				//
				//		The size of the file compressed (4.4.8) and uncompressed,
				//		(4.4.9) respectively.  When a decryption header is present it
				//		will be placed in front of the file data and the value of the
				//		compressed file size will include the bytes of the decryption
				//		header.  If bit 3 of the general purpose bit flag is set,
				//		these fields are set to zero in the local header and the
				//		correct values are put in the data descriptor and
				//		in the central directory.  If an archive is in ZIP64 format
				//		and the value in this field is 0xFFFFFFFF, the size will be
				//		in the corresponding 8 byte ZIP64 extended information
				//		extra field.  When encrypting the central directory, if the
				//		local header is not in ZIP64 format and general purpose bit
				//		flag 13 is set indicating masking, the value stored for the
				//		uncompressed size in the Local Header will be zero.
				//
				// Othewise there is problem with minizip implementation
				if (size == uint.MaxValue) {
					size = (ulong)extraData.ReadLong();
				}
				if (compressedSize == uint.MaxValue) {
					compressedSize = (ulong)extraData.ReadLong();
				}
				if (!localHeader && (offset == uint.MaxValue)) {
					offset = extraData.ReadLong();
				}
				// Disk number on which file starts is ignored
			} else {
				if (
					((versionToExtract & 0xff) >= ZipConstants.VersionZip64) &&
					((size == uint.MaxValue) || (compressedSize == uint.MaxValue))
				) {
					throw new ZipException("Zip64 Extended information required but is missing.");
				}
			}
			DateTime = GetDateTime(extraData);
			if (method == CompressionMethod.WinZipAES) {
				ProcessAESExtraData(extraData);
			}
		}
		private DateTime GetDateTime(ZipExtraData extraData) {
			// Check for NT timestamp
            // NOTE: Disable by default to match behavior of InfoZIP
#if RESPECT_NT_TIMESTAMP
			NTTaggedData ntData = extraData.GetData();
			if (ntData != null)
				return ntData.LastModificationTime;
#endif
			// Check for Unix timestamp
			ExtendedUnixData unixData = extraData.GetData();
			if (unixData != null &&
				// Only apply modification time, but require all other values to be present
				// This is done to match InfoZIP's behaviour
				((unixData.Include & ExtendedUnixData.Flags.ModificationTime) != 0) &&
				((unixData.Include & ExtendedUnixData.Flags.AccessTime) != 0) &&
				((unixData.Include & ExtendedUnixData.Flags.CreateTime) != 0))
				return unixData.ModificationTime;
			// Fall back to DOS time
			uint sec = Math.Min(59, 2 * (dosTime & 0x1f));
			uint min = Math.Min(59, (dosTime >> 5) & 0x3f);
			uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f);
			uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf)));
			uint year = ((dosTime >> 25) & 0x7f) + 1980;
			int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f)));
			return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Utc);
		}
		// For AES the method in the entry is 99, and the real compression method is in the extradata
		//
		private void ProcessAESExtraData(ZipExtraData extraData)
		{
			if (extraData.Find(0x9901)) {
				// Set version and flag for Zipfile.CreateAndInitDecryptionStream
				versionToExtract = ZipConstants.VERSION_AES;            // Ver 5.1 = AES see "Version" getter
																		// Set StrongEncryption flag for ZipFile.CreateAndInitDecryptionStream
				Flags = Flags | (int)GeneralBitFlags.StrongEncryption;
				//
				// Unpack AES extra data field see http://www.winzip.com/aes_info.htm
				int length = extraData.ValueLength;         // Data size currently 7
				if (length < 7)
					throw new ZipException("AES Extra Data Length " + length + " invalid.");
				int ver = extraData.ReadShort();            // Version number (1=AE-1 2=AE-2)
                #pragma warning disable 0219
				int vendorId = extraData.ReadShort();       // 2-character vendor ID 0x4541 = "AE"
                #pragma warning restore 0219
				int encrStrength = extraData.ReadByte();    // encryption strength 1 = 128 2 = 192 3 = 256
				int actualCompress = extraData.ReadShort(); // The actual compression method used to compress the file
				_aesVer = ver;
				_aesEncryptionStrength = encrStrength;
				method = (CompressionMethod)actualCompress;
			} else
				throw new ZipException("AES Extra Data missing");
		}
		/// 
		/// Gets/Sets the entry comment.
		/// 
		/// 
		/// If comment is longer than 0xffff.
		/// 
		/// 
		/// The comment or null if not set.
		/// 
		/// 
		/// A comment is only available for entries when read via the  class.
		/// The  class doesnt have the comment data available.
		/// 
		public string Comment {
			get {
				return comment;
			}
			set {
				// This test is strictly incorrect as the length is in characters
				// while the storage limit is in bytes.
				// While the test is partially correct in that a comment of this length or greater
				// is definitely invalid, shorter comments may also have an invalid length
				// where there are multi-byte characters
				// The full test is not possible here however as the code page to apply conversions with
				// isnt available.
				if ((value != null) && (value.Length > 0xffff)) {
					throw new ArgumentOutOfRangeException("nameof(value)", "cannot exceed 65535");
				}
				comment = value;
			}
		}
		/// 
		/// Gets a value indicating if the entry is a directory.
		/// however.
		/// 
		/// 
		/// A directory is determined by an entry name with a trailing slash '/'.
		/// The external file attributes can also indicate an entry is for a directory.
		/// Currently only dos/windows attributes are tested in this manner.
		/// The trailing slash convention should always be followed.
		/// 
		public bool IsDirectory {
			get {
				int nameLength = name.Length;
				bool result =
					((nameLength > 0) &&
					((name[nameLength - 1] == '/') || (name[nameLength - 1] == '\\'))) ||
					HasDosAttributes(16)
					;
				return result;
			}
		}
		/// 
		/// Get a value of true if the entry appears to be a file; false otherwise
		/// 
		/// 
		/// This only takes account of DOS/Windows attributes.  Other operating systems are ignored.
		/// For linux and others the result may be incorrect.
		/// 
		public bool IsFile {
			get {
				return !IsDirectory && !HasDosAttributes(8);
			}
		}
		/// 
		/// Test entry to see if data can be extracted.
		/// 
		/// Returns true if data can be extracted for this entry; false otherwise.
		public bool IsCompressionMethodSupported()
		{
			return IsCompressionMethodSupported(CompressionMethod);
		}
		#region ICloneable Members
		/// 
		/// Creates a copy of this zip entry.
		/// 
		/// An  that is a copy of the current instance.
		public object Clone()
		{
			var result = (ZipEntry)this.MemberwiseClone();
			// Ensure extra data is unique if it exists.
			if (extra != null) {
				result.extra = new byte[extra.Length];
				Array.Copy(extra, 0, result.extra, 0, extra.Length);
			}
			return result;
		}
		#endregion
		/// 
		/// Gets a string representation of this ZipEntry.
		/// 
		/// A readable textual representation of this 
		public override string ToString()
		{
			return name;
		}
		/// 
		/// Test a compression method to see if this library
		/// supports extracting data compressed with that method
		/// 
		/// The compression method to test.
		/// Returns true if the compression method is supported; false otherwise
		public static bool IsCompressionMethodSupported(CompressionMethod method)
		{
			return
				(method == CompressionMethod.Deflated) ||
				(method == CompressionMethod.Stored);
		}
		/// 
		/// Cleans a name making it conform to Zip file conventions.
		/// Devices names ('c:\') and UNC share names ('\\server\share') are removed
		/// and forward slashes ('\') are converted to back slashes ('/').
		/// Names are made relative by trimming leading slashes which is compatible
		/// with the ZIP naming convention.
		/// 
		/// The name to clean
		/// The 'cleaned' name.
		/// 
		/// The Zip name transform class is more flexible.
		/// 
		public static string CleanName(string name)
		{
			if (name == null) {
				return string.Empty;
			}
			if (Path.IsPathRooted(name)) {
				// NOTE:
				// for UNC names...  \\machine\share\zoom\beet.txt gives \zoom\beet.txt
				name = name.Substring(Path.GetPathRoot(name).Length);
			}
			name = name.Replace(@"\", "/");
			while ((name.Length > 0) && (name[0] == '/')) {
				name = name.Remove(0, 1);
			}
			return name;
		}
		#region Instance Fields
		Known known;
		int externalFileAttributes = -1;     // contains external attributes (O/S dependant)
		ushort versionMadeBy;                   // Contains host system and version information
												// only relevant for central header entries
		string name;
		ulong size;
		ulong compressedSize;
		ushort versionToExtract;                // Version required to extract (library handles <= 2.0)
		uint crc;
		uint dosTime;
		CompressionMethod method = CompressionMethod.Deflated;
		byte[] extra;
		string comment;
		int flags;                             // general purpose bit flags
		long zipFileIndex = -1;                // used by ZipFile
		long offset;                           // used by ZipFile and ZipOutputStream
		bool forceZip64_;
		byte cryptoCheckValue_;
		#pragma warning disable 0414
		int _aesVer;                            // Version number (2 = AE-2 ?). Assigned but not used.
		#pragma warning restore 0414
		int _aesEncryptionStrength;             // Encryption strength 1 = 128 2 = 192 3 = 256
		#endregion
	}
}