Mercurial > repos > SharpZipLib
diff Zip/ZipEntry.cs @ 1:94e25b786321
Re #311: can't read ZIP file packed by Linux app Archive Manager/File Roller
Initial commit of clean SharpZipLib 0860 source. Only change is build paths.
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 30 Oct 2010 14:03:17 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Zip/ZipEntry.cs Sat Oct 30 14:03:17 2010 +0000 @@ -0,0 +1,1252 @@ +// ZipEntry.cs +// +// Copyright (C) 2001 Mike Krueger +// Copyright (C) 2004 John Reilly +// +// This file was translated from java, it was part of the GNU Classpath +// Copyright (C) 2001 Free Software Foundation, Inc. +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +// +// Linking this library statically or dynamically with other modules is +// making a combined work based on this library. Thus, the terms and +// conditions of the GNU General Public License cover the whole +// combination. +// +// As a special exception, the copyright holders of this library give you +// permission to link this library with independent modules to produce an +// executable, regardless of the license terms of these independent +// modules, and to copy and distribute the resulting executable under +// terms of your choice, provided that you also meet, for each linked +// independent module, the terms and conditions of the license of that +// module. An independent module is a module which is not derived from +// or based on this library. If you modify this library, you may extend +// this exception to your version of the library, but you are not +// obligated to do so. If you do not wish to do so, delete this +// exception statement from your version. + +// HISTORY +// 22-12-2009 DavidPierson Added AES support +// 02-02-2010 DavidPierson Changed NTFS Extra Data min length to 4 + +using System; +using System.IO; + +namespace ICSharpCode.SharpZipLib.Zip +{ + + /// <summary> + /// Defines known values for the <see cref="HostSystemID"/> property. + /// </summary> + public enum HostSystemID + { + /// <summary> + /// Host system = MSDOS + /// </summary> + Msdos = 0, + /// <summary> + /// Host system = Amiga + /// </summary> + Amiga = 1, + /// <summary> + /// Host system = Open VMS + /// </summary> + OpenVms = 2, + /// <summary> + /// Host system = Unix + /// </summary> + Unix = 3, + /// <summary> + /// Host system = VMCms + /// </summary> + VMCms = 4, + /// <summary> + /// Host system = Atari ST + /// </summary> + AtariST = 5, + /// <summary> + /// Host system = OS2 + /// </summary> + OS2 = 6, + /// <summary> + /// Host system = Macintosh + /// </summary> + Macintosh = 7, + /// <summary> + /// Host system = ZSystem + /// </summary> + ZSystem = 8, + /// <summary> + /// Host system = Cpm + /// </summary> + Cpm = 9, + /// <summary> + /// Host system = Windows NT + /// </summary> + WindowsNT = 10, + /// <summary> + /// Host system = MVS + /// </summary> + MVS = 11, + /// <summary> + /// Host system = VSE + /// </summary> + Vse = 12, + /// <summary> + /// Host system = Acorn RISC + /// </summary> + AcornRisc = 13, + /// <summary> + /// Host system = VFAT + /// </summary> + Vfat = 14, + /// <summary> + /// Host system = Alternate MVS + /// </summary> + AlternateMvs = 15, + /// <summary> + /// Host system = BEOS + /// </summary> + BeOS = 16, + /// <summary> + /// Host system = Tandem + /// </summary> + Tandem = 17, + /// <summary> + /// Host system = OS400 + /// </summary> + OS400 = 18, + /// <summary> + /// Host system = OSX + /// </summary> + OSX = 19, + /// <summary> + /// Host system = WinZIP AES + /// </summary> + WinZipAES = 99, + } + + /// <summary> + /// 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. + /// <br/> + /// <br/>Author of the original java version : Jochen Hoenicke + /// </summary> + public class ZipEntry : ICloneable + { + [Flags] + enum Known : byte + { + None = 0, + Size = 0x01, + CompressedSize = 0x02, + Crc = 0x04, + Time = 0x08, + ExternalAttributes = 0x10, + } + + #region Constructors + /// <summary> + /// Creates a zip entry with the given name. + /// </summary> + /// <param name="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. + /// </param> + /// <exception cref="ArgumentNullException"> + /// The name passed is null + /// </exception> + public ZipEntry(string name) + : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated) + { + } + + /// <summary> + /// Creates a zip entry with the given name and version required to extract + /// </summary> + /// <param name="name"> + /// 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 <see cref="CleanName(string)">CleanName</see> + /// on how to ensure names are valid if this is desired. + /// </param> + /// <param name="versionRequiredToExtract"> + /// The minimum 'feature version' required this entry + /// </param> + /// <exception cref="ArgumentNullException"> + /// The name passed is null + /// </exception> + internal ZipEntry(string name, int versionRequiredToExtract) + : this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, + CompressionMethod.Deflated) + { + } + + /// <summary> + /// Initializes an entry with the given name and made by information + /// </summary> + /// <param name="name">Name for this entry</param> + /// <param name="madeByInfo">Version and HostSystem Information</param> + /// <param name="versionRequiredToExtract">Minimum required zip feature version required to extract this entry</param> + /// <param name="method">Compression method for this entry.</param> + /// <exception cref="ArgumentNullException"> + /// The name passed is null + /// </exception> + /// <exception cref="ArgumentOutOfRangeException"> + /// versionRequiredToExtract should be 0 (auto-calculate) or > 10 + /// </exception> + /// <remarks> + /// 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. + /// </remarks> + internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo, + CompressionMethod method) + { + if (name == null) { + throw new System.ArgumentNullException("name"); + } + + if ( name.Length > 0xffff ) { + throw new ArgumentException("Name is too long", "name"); + } + + if ( (versionRequiredToExtract != 0) && (versionRequiredToExtract < 10) ) { + throw new ArgumentOutOfRangeException("versionRequiredToExtract"); + } + + this.DateTime = System.DateTime.Now; + this.name = name; + this.versionMadeBy = (ushort)madeByInfo; + this.versionToExtract = (ushort)versionRequiredToExtract; + this.method = method; + } + + /// <summary> + /// Creates a deep copy of the given zip entry. + /// </summary> + /// <param name="entry"> + /// The entry to copy. + /// </param> + [Obsolete("Use Clone instead")] + public ZipEntry(ZipEntry entry) + { + if ( entry == null ) { + throw new ArgumentNullException("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 + + /// <summary> + /// Get a value indicating wether the entry has a CRC value available. + /// </summary> + public bool HasCrc + { + get { + return (known & Known.Crc) != 0; + } + } + + /// <summary> + /// Get/Set flag indicating if entry is encrypted. + /// A simple helper routine to aid interpretation of <see cref="Flags">flags</see> + /// </summary> + /// <remarks>This is an assistant that interprets the <see cref="Flags">flags</see> property.</remarks> + public bool IsCrypted + { + get { + return (flags & 1) != 0; + } + set { + if (value) { + flags |= 1; + } + else { + flags &= ~1; + } + } + } + + /// <summary> + /// Get / set a flag indicating wether entry name and comment text are + /// encoded in <a href="http://www.unicode.org">unicode UTF8</a>. + /// </summary> + /// <remarks>This is an assistant that interprets the <see cref="Flags">flags</see> property.</remarks> + public bool IsUnicodeText + { + get { + return ( flags & (int)GeneralBitFlags.UnicodeText ) != 0; + } + set { + if ( value ) { + flags |= (int)GeneralBitFlags.UnicodeText; + } + else { + flags &= ~(int)GeneralBitFlags.UnicodeText; + } + } + } + + /// <summary> + /// Value used during password checking for PKZIP 2.0 / 'classic' encryption. + /// </summary> + internal byte CryptoCheckValue + { + get { + return cryptoCheckValue_; + } + + set { + cryptoCheckValue_ = value; + } + } + + /// <summary> + /// Get/Set general purpose bit flag for entry + /// </summary> + /// <remarks> + /// General purpose bit flag<br/> + /// <br/> + /// Bit 0: If set, indicates the file is encrypted<br/> + /// Bit 1-2 Only used for compression type 6 Imploding, and 8, 9 deflating<br/> + /// Imploding:<br/> + /// Bit 1 if set indicates an 8K sliding dictionary was used. If clear a 4k dictionary was used<br/> + /// Bit 2 if set indicates 3 Shannon-Fanno trees were used to encode the sliding dictionary, 2 otherwise<br/> + /// <br/> + /// Deflating:<br/> + /// Bit 2 Bit 1<br/> + /// 0 0 Normal compression was used<br/> + /// 0 1 Maximum compression was used<br/> + /// 1 0 Fast compression was used<br/> + /// 1 1 Super fast compression was used<br/> + /// <br/> + /// 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. <br/> + /// Bit 4: Reserved for use by PKZIP for enhanced deflating<br/> + /// Bit 5: If set indicates the file contains compressed patch data<br/> + /// Bit 6: If set indicates strong encryption was used.<br/> + /// Bit 7-10: Unused or reserved<br/> + /// Bit 11: If set the name and comments for this entry are in <a href="http://www.unicode.org">unicode</a>.<br/> + /// Bit 12-15: Unused or reserved<br/> + /// </remarks> + /// <seealso cref="IsUnicodeText"></seealso> + /// <seealso cref="IsCrypted"></seealso> + public int Flags + { + get { + return flags; + } + set { + flags = value; + } + } + + /// <summary> + /// Get/Set index of this entry in Zip file + /// </summary> + /// <remarks>This is only valid when the entry is part of a <see cref="ZipFile"></see></remarks> + public long ZipFileIndex + { + get { + return zipFileIndex; + } + set { + zipFileIndex = value; + } + } + + /// <summary> + /// Get/set offset for use in central header + /// </summary> + public long Offset + { + get { + return offset; + } + set { + offset = value; + } + } + + /// <summary> + /// Get/Set external file attributes as an integer. + /// The values of this are operating system dependant see + /// <see cref="HostSystem">HostSystem</see> for details + /// </summary> + public int ExternalFileAttributes + { + get { + if ((known & Known.ExternalAttributes) == 0) { + return -1; + } + else { + return externalFileAttributes; + } + } + + set { + externalFileAttributes = value; + known |= Known.ExternalAttributes; + } + } + + /// <summary> + /// 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 + /// </summary> + public int VersionMadeBy + { + get { + return (versionMadeBy & 0xff); + } + } + + /// <summary> + /// Get a value indicating this entry is for a DOS/Windows system. + /// </summary> + public bool IsDOSEntry + { + get { + return ((HostSystem == ( int )HostSystemID.Msdos) || + (HostSystem == ( int )HostSystemID.WindowsNT)); + } + } + + /// <summary> + /// Test the external attributes for this <see cref="ZipEntry"/> to + /// see if the external attributes are Dos based (including WINNT and variants) + /// and match the values + /// </summary> + /// <param name="attributes">The attributes to test.</param> + /// <returns>Returns true if the external attributes are known to be DOS/Windows + /// based and have the same attributes set as the value passed.</returns> + bool HasDosAttributes(int attributes) + { + bool result = false; + if ( (known & Known.ExternalAttributes) != 0 ) { + if ( ((HostSystem == (int)HostSystemID.Msdos) || + (HostSystem == (int)HostSystemID.WindowsNT)) && + (ExternalFileAttributes & attributes) == attributes) { + result = true; + } + } + return result; + } + + /// <summary> + /// Gets the compatability information for the <see cref="ExternalFileAttributes">external file attribute</see> + /// 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. + /// </summary> + /// + /// <remarks> + /// 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. + /// <list type="table"> + /// <item>0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)</item> + /// <item>1 - Amiga</item> + /// <item>2 - OpenVMS</item> + /// <item>3 - Unix</item> + /// <item>4 - VM/CMS</item> + /// <item>5 - Atari ST</item> + /// <item>6 - OS/2 HPFS</item> + /// <item>7 - Macintosh</item> + /// <item>8 - Z-System</item> + /// <item>9 - CP/M</item> + /// <item>10 - Windows NTFS</item> + /// <item>11 - MVS (OS/390 - Z/OS)</item> + /// <item>12 - VSE</item> + /// <item>13 - Acorn Risc</item> + /// <item>14 - VFAT</item> + /// <item>15 - Alternate MVS</item> + /// <item>16 - BeOS</item> + /// <item>17 - Tandem</item> + /// <item>18 - OS/400</item> + /// <item>19 - OS/X (Darwin)</item> + /// <item>99 - WinZip AES</item> + /// <item>remainder - unused</item> + /// </list> + /// </remarks> + public int HostSystem + { + get { + return (versionMadeBy >> 8) & 0xff; + } + + set { + versionMadeBy &= 0xff; + versionMadeBy |= (ushort)((value & 0xff) << 8); + } + } + + /// <summary> + /// Get minimum Zip feature version required to extract this entry + /// </summary> + /// <remarks> + /// Minimum features are defined as:<br/> + /// 1.0 - Default value<br/> + /// 1.1 - File is a volume label<br/> + /// 2.0 - File is a folder/directory<br/> + /// 2.0 - File is compressed using Deflate compression<br/> + /// 2.0 - File is encrypted using traditional encryption<br/> + /// 2.1 - File is compressed using Deflate64<br/> + /// 2.5 - File is compressed using PKWARE DCL Implode<br/> + /// 2.7 - File is a patch data set<br/> + /// 4.5 - File uses Zip64 format extensions<br/> + /// 4.6 - File is compressed using BZIP2 compression<br/> + /// 5.0 - File is encrypted using DES<br/> + /// 5.0 - File is encrypted using 3DES<br/> + /// 5.0 - File is encrypted using original RC2 encryption<br/> + /// 5.0 - File is encrypted using RC4 encryption<br/> + /// 5.1 - File is encrypted using AES encryption<br/> + /// 5.1 - File is encrypted using corrected RC2 encryption<br/> + /// 5.1 - File is encrypted using corrected RC2-64 encryption<br/> + /// 6.1 - File is encrypted using non-OAEP key wrapping<br/> + /// 6.2 - Central directory encryption (not confirmed yet)<br/> + /// 6.3 - File is compressed using LZMA<br/> + /// 6.3 - File is compressed using PPMD+<br/> + /// 6.3 - File is encrypted using Blowfish<br/> + /// 6.3 - File is encrypted using Twofish<br/> + /// </remarks> + /// <seealso cref="CanDecompress"></seealso> + public int Version + { + get { + // Return recorded version if known. + if (versionToExtract != 0) { + return versionToExtract; + } + 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; + } + } + } + + /// <summary> + /// Get a value indicating whether this entry can be decompressed by the library. + /// </summary> + /// <remarks>This is based on the <see cref="Version"></see> and + /// wether the <see cref="IsCompressionMethodSupported()">compression method</see> is supported.</remarks> + public bool CanDecompress + { + get { + return (Version <= ZipConstants.VersionMadeBy) && + ((Version == 10) || + (Version == 11) || + (Version == 20) || + (Version == 45) || + (Version == 51)) && + IsCompressionMethodSupported(); + } + } + + /// <summary> + /// Force this entry to be recorded using Zip64 extensions. + /// </summary> + public void ForceZip64() + { + forceZip64_ = true; + } + + /// <summary> + /// Get a value indicating wether Zip64 extensions were forced. + /// </summary> + /// <returns>A <see cref="bool"/> value of true if Zip64 extensions have been forced on; false if not.</returns> + public bool IsZip64Forced() + { + return forceZip64_; + } + + /// <summary> + /// Gets a value indicating if the entry requires Zip64 extensions + /// to store the full entry values. + /// </summary> + /// <value>A <see cref="bool"/> value of true if a local header requires Zip64 extensions; false if not.</value> + 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; + } + } + + /// <summary> + /// Get a value indicating wether the central directory entry requires Zip64 extensions to be stored. + /// </summary> + public bool CentralHeaderRequiresZip64 + { + get { + return LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); + } + } + + /// <summary> + /// Get/Set DosTime value. + /// </summary> + /// <remarks> + /// The MS-DOS date format can only represent dates between 1/1/1980 and 12/31/2107. + /// </remarks> + public long DosTime + { + get { + if ((known & Known.Time) == 0) { + return 0; + } + else { + return dosTime; + } + } + + set { + unchecked { + dosTime = (uint)value; + } + + known |= Known.Time; + } + } + + /// <summary> + /// Gets/Sets the time of last modification of the entry. + /// </summary> + /// <remarks> + /// The <see cref="DosTime"></see> property is updated to match this as far as possible. + /// </remarks> + 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 { + uint year = (uint) value.Year; + uint month = (uint) value.Month; + uint day = (uint) value.Day; + uint hour = (uint) value.Hour; + uint minute = (uint) value.Minute; + uint 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); + } + } + + /// <summary> + /// Returns the entry name. + /// </summary> + /// <remarks> + /// 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 <see cref="ZipNameTransform"/> class, or <see cref="CleanName(string)"/> + ///</remarks> + public string Name + { + get { + return name; + } + } + + /// <summary> + /// Gets/Sets the size of the uncompressed data. + /// </summary> + /// <returns> + /// The size or -1 if unknown. + /// </returns> + /// <remarks>Setting the size before adding an entry to an archive can help + /// avoid compatability problems with some archivers which dont understand Zip64 extensions.</remarks> + public long Size + { + get { + return (known & Known.Size) != 0 ? (long)size : -1L; + } + set { + this.size = (ulong)value; + this.known |= Known.Size; + } + } + + /// <summary> + /// Gets/Sets the size of the compressed data. + /// </summary> + /// <returns> + /// The compressed entry size or -1 if unknown. + /// </returns> + public long CompressedSize + { + get { + return (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; + } + set { + this.compressedSize = (ulong)value; + this.known |= Known.CompressedSize; + } + } + + /// <summary> + /// Gets/Sets the crc of the uncompressed data. + /// </summary> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Crc is not in the range 0..0xffffffffL + /// </exception> + /// <returns> + /// The crc value or -1 if unknown. + /// </returns> + public long Crc + { + get { + return (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; + } + set { + if (((ulong)crc & 0xffffffff00000000L) != 0) { + throw new ArgumentOutOfRangeException("value"); + } + this.crc = (uint)value; + this.known |= Known.Crc; + } + } + + /// <summary> + /// Gets/Sets the compression method. Only Deflated and Stored are supported. + /// </summary> + /// <returns> + /// The compression method for this entry + /// </returns> + /// <see cref="ICSharpCode.SharpZipLib.Zip.CompressionMethod.Deflated"/> + /// <see cref="ICSharpCode.SharpZipLib.Zip.CompressionMethod.Stored"/> + public CompressionMethod CompressionMethod { + get { + return method; + } + + set { + if ( !IsCompressionMethodSupported(value) ) { + throw new NotSupportedException("Compression method not supported"); + } + this.method = value; + } + } + + /// <summary> + /// 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. + /// </summary> + internal CompressionMethod CompressionMethodForHeader { + get { + return (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; + } + } + + /// <summary> + /// Gets/Sets the extra data. + /// </summary> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// Extra data is longer than 64KB (0xffff) bytes. + /// </exception> + /// <returns> + /// Extra data or null if not set. + /// </returns> + 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("value"); + } + + extra = new byte[value.Length]; + Array.Copy(value, 0, extra, 0, value.Length); + } + } + } + + +#if !NET_1_1 && !NETCF_2_0 + /// <summary> + /// 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. + /// </summary> + 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); + } + } + } + + /// <summary> + /// AES Encryption strength for storage in extra data in entry header. + /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit. + /// </summary> + internal byte AESEncryptionStrength { + get { + return (byte)_aesEncryptionStrength; + } + } +#else + /// <summary> + /// AES unsupported prior to .NET 2.0 + /// </summary> + internal int AESKeySize; +#endif + + /// <summary> + /// Returns the length of the salt, in bytes + /// </summary> + internal int AESSaltLen { + get { + // Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. + return AESKeySize / 16; + } + } + + /// <summary> + /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode) + /// </summary> + 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; + } + } + + /// <summary> + /// Process extra data fields updating the entry based on the contents. + /// </summary> + /// <param name="localHeader">True if the extra data fields should be handled + /// for a local header, rather than for a central header. + /// </param> + internal void ProcessExtraData(bool localHeader) + { + ZipExtraData 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"); + } + + if ( localHeader || (size == uint.MaxValue) ) { + size = (ulong)extraData.ReadLong(); + } + + if ( localHeader || (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."); + } + } + + if ( extraData.Find(10) ) { + // No room for any tags. + if ( extraData.ValueLength < 4 ) { + throw new ZipException("NTFS Extra data invalid"); + } + + extraData.ReadInt(); // Reserved + + while ( extraData.UnreadCount >= 4 ) { + int ntfsTag = extraData.ReadShort(); + int ntfsLength = extraData.ReadShort(); + if ( ntfsTag == 1 ) { + if ( ntfsLength >= 24 ) { + long lastModification = extraData.ReadLong(); + long lastAccess = extraData.ReadLong(); + long createTime = extraData.ReadLong(); + + DateTime = System.DateTime.FromFileTime(lastModification); + } + break; + } + else { + // An unknown NTFS tag so simply skip it. + extraData.Skip(ntfsLength); + } + } + } + else if ( extraData.Find(0x5455) ) { + int length = extraData.ValueLength; + int flags = extraData.ReadByte(); + + // Can include other times but these are ignored. Length of data should + // actually be 1 + 4 * no of bits in flags. + if ( ((flags & 1) != 0) && (length >= 5) ) { + int iTime = extraData.ReadInt(); + + DateTime = (new System.DateTime ( 1970, 1, 1, 0, 0, 0 ).ToUniversalTime() + + new TimeSpan ( 0, 0, 0, iTime, 0 )).ToLocalTime(); + } + } + if (method == CompressionMethod.WinZipAES) { + ProcessAESExtraData(extraData); + } + } + + // For AES the method in the entry is 99, and the real compression method is in the extradata + // + private void ProcessAESExtraData(ZipExtraData extraData) { + +#if !NET_1_1 && !NETCF_2_0 + 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) + int vendorId = extraData.ReadShort(); // 2-character vendor ID 0x4541 = "AE" + 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"); +#else + throw new ZipException("AES unsupported"); +#endif + } + + /// <summary> + /// Gets/Sets the entry comment. + /// </summary> + /// <exception cref="System.ArgumentOutOfRangeException"> + /// If comment is longer than 0xffff. + /// </exception> + /// <returns> + /// The comment or null if not set. + /// </returns> + /// <remarks> + /// A comment is only available for entries when read via the <see cref="ZipFile"/> class. + /// The <see cref="ZipInputStream"/> class doesnt have the comment data available. + /// </remarks> + 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) ) { +#if NETCF_1_0 + throw new ArgumentOutOfRangeException("value"); +#else + throw new ArgumentOutOfRangeException("value", "cannot exceed 65535"); +#endif + } + + comment = value; + } + } + + /// <summary> + /// Gets a value indicating if the entry is a directory. + /// however. + /// </summary> + /// <remarks> + /// 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. + /// </remarks> + public bool IsDirectory + { + get { + int nameLength = name.Length; + bool result = + ((nameLength > 0) && + ((name[nameLength - 1] == '/') || (name[nameLength - 1] == '\\'))) || + HasDosAttributes(16) + ; + return result; + } + } + + /// <summary> + /// Get a value of true if the entry appears to be a file; false otherwise + /// </summary> + /// <remarks> + /// This only takes account of DOS/Windows attributes. Other operating systems are ignored. + /// For linux and others the result may be incorrect. + /// </remarks> + public bool IsFile + { + get { + return !IsDirectory && !HasDosAttributes(8); + } + } + + /// <summary> + /// Test entry to see if data can be extracted. + /// </summary> + /// <returns>Returns true if data can be extracted for this entry; false otherwise.</returns> + public bool IsCompressionMethodSupported() + { + return IsCompressionMethodSupported(CompressionMethod); + } + + #region ICloneable Members + /// <summary> + /// Creates a copy of this zip entry. + /// </summary> + /// <returns>An <see cref="Object"/> that is a copy of the current instance.</returns> + public object Clone() + { + ZipEntry 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 + + /// <summary> + /// Gets a string representation of this ZipEntry. + /// </summary> + /// <returns>A readable textual representation of this <see cref="ZipEntry"/></returns> + public override string ToString() + { + return name; + } + + /// <summary> + /// Test a <see cref="CompressionMethod">compression method</see> to see if this library + /// supports extracting data compressed with that method + /// </summary> + /// <param name="method">The compression method to test.</param> + /// <returns>Returns true if the compression method is supported; false otherwise</returns> + public static bool IsCompressionMethodSupported(CompressionMethod method) + { + return + ( method == CompressionMethod.Deflated ) || + ( method == CompressionMethod.Stored ); + } + + /// <summary> + /// 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. + /// </summary> + /// <param name="name">The name to clean</param> + /// <returns>The 'cleaned' name.</returns> + /// <remarks> + /// The <seealso cref="ZipNameTransform">Zip name transform</seealso> class is more flexible. + /// </remarks> + public static string CleanName(string name) + { + if (name == null) { + return string.Empty; + } + + if (Path.IsPathRooted(name) == true) { + // 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_; +#if !NET_1_1 && !NETCF_2_0 + int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used. + int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256 +#endif + #endregion + } +}