| | 1 | | using System; |
| | 2 | | using System.IO; |
| | 3 | |
|
| | 4 | | namespace ICSharpCode.SharpZipLib.Zip |
| | 5 | | { |
| | 6 | | /// <summary> |
| | 7 | | /// Defines known values for the <see cref="HostSystemID"/> property. |
| | 8 | | /// </summary> |
| | 9 | | public enum HostSystemID |
| | 10 | | { |
| | 11 | | /// <summary> |
| | 12 | | /// Host system = MSDOS |
| | 13 | | /// </summary> |
| | 14 | | Msdos = 0, |
| | 15 | | /// <summary> |
| | 16 | | /// Host system = Amiga |
| | 17 | | /// </summary> |
| | 18 | | Amiga = 1, |
| | 19 | | /// <summary> |
| | 20 | | /// Host system = Open VMS |
| | 21 | | /// </summary> |
| | 22 | | OpenVms = 2, |
| | 23 | | /// <summary> |
| | 24 | | /// Host system = Unix |
| | 25 | | /// </summary> |
| | 26 | | Unix = 3, |
| | 27 | | /// <summary> |
| | 28 | | /// Host system = VMCms |
| | 29 | | /// </summary> |
| | 30 | | VMCms = 4, |
| | 31 | | /// <summary> |
| | 32 | | /// Host system = Atari ST |
| | 33 | | /// </summary> |
| | 34 | | AtariST = 5, |
| | 35 | | /// <summary> |
| | 36 | | /// Host system = OS2 |
| | 37 | | /// </summary> |
| | 38 | | OS2 = 6, |
| | 39 | | /// <summary> |
| | 40 | | /// Host system = Macintosh |
| | 41 | | /// </summary> |
| | 42 | | Macintosh = 7, |
| | 43 | | /// <summary> |
| | 44 | | /// Host system = ZSystem |
| | 45 | | /// </summary> |
| | 46 | | ZSystem = 8, |
| | 47 | | /// <summary> |
| | 48 | | /// Host system = Cpm |
| | 49 | | /// </summary> |
| | 50 | | Cpm = 9, |
| | 51 | | /// <summary> |
| | 52 | | /// Host system = Windows NT |
| | 53 | | /// </summary> |
| | 54 | | WindowsNT = 10, |
| | 55 | | /// <summary> |
| | 56 | | /// Host system = MVS |
| | 57 | | /// </summary> |
| | 58 | | MVS = 11, |
| | 59 | | /// <summary> |
| | 60 | | /// Host system = VSE |
| | 61 | | /// </summary> |
| | 62 | | Vse = 12, |
| | 63 | | /// <summary> |
| | 64 | | /// Host system = Acorn RISC |
| | 65 | | /// </summary> |
| | 66 | | AcornRisc = 13, |
| | 67 | | /// <summary> |
| | 68 | | /// Host system = VFAT |
| | 69 | | /// </summary> |
| | 70 | | Vfat = 14, |
| | 71 | | /// <summary> |
| | 72 | | /// Host system = Alternate MVS |
| | 73 | | /// </summary> |
| | 74 | | AlternateMvs = 15, |
| | 75 | | /// <summary> |
| | 76 | | /// Host system = BEOS |
| | 77 | | /// </summary> |
| | 78 | | BeOS = 16, |
| | 79 | | /// <summary> |
| | 80 | | /// Host system = Tandem |
| | 81 | | /// </summary> |
| | 82 | | Tandem = 17, |
| | 83 | | /// <summary> |
| | 84 | | /// Host system = OS400 |
| | 85 | | /// </summary> |
| | 86 | | OS400 = 18, |
| | 87 | | /// <summary> |
| | 88 | | /// Host system = OSX |
| | 89 | | /// </summary> |
| | 90 | | OSX = 19, |
| | 91 | | /// <summary> |
| | 92 | | /// Host system = WinZIP AES |
| | 93 | | /// </summary> |
| | 94 | | WinZipAES = 99, |
| | 95 | | } |
| | 96 | |
|
| | 97 | | /// <summary> |
| | 98 | | /// This class represents an entry in a zip archive. This can be a file |
| | 99 | | /// or a directory |
| | 100 | | /// ZipFile and ZipInputStream will give you instances of this class as |
| | 101 | | /// information about the members in an archive. ZipOutputStream |
| | 102 | | /// uses an instance of this class when creating an entry in a Zip file. |
| | 103 | | /// <br/> |
| | 104 | | /// <br/>Author of the original java version : Jochen Hoenicke |
| | 105 | | /// </summary> |
| | 106 | | public class ZipEntry : ICloneable |
| | 107 | | { |
| | 108 | | [Flags] |
| | 109 | | enum Known : byte |
| | 110 | | { |
| | 111 | | None = 0, |
| | 112 | | Size = 0x01, |
| | 113 | | CompressedSize = 0x02, |
| | 114 | | Crc = 0x04, |
| | 115 | | Time = 0x08, |
| | 116 | | ExternalAttributes = 0x10, |
| | 117 | | } |
| | 118 | |
|
| | 119 | | #region Constructors |
| | 120 | | /// <summary> |
| | 121 | | /// Creates a zip entry with the given name. |
| | 122 | | /// </summary> |
| | 123 | | /// <param name="name"> |
| | 124 | | /// The name for this entry. Can include directory components. |
| | 125 | | /// The convention for names is 'unix' style paths with relative names only. |
| | 126 | | /// There are with no device names and path elements are separated by '/' characters. |
| | 127 | | /// </param> |
| | 128 | | /// <exception cref="ArgumentNullException"> |
| | 129 | | /// The name passed is null |
| | 130 | | /// </exception> |
| | 131 | | public ZipEntry(string name) |
| 65841 | 132 | | : this(name, 0, ZipConstants.VersionMadeBy, CompressionMethod.Deflated) |
| | 133 | | { |
| 65840 | 134 | | } |
| | 135 | |
|
| | 136 | | /// <summary> |
| | 137 | | /// Creates a zip entry with the given name and version required to extract |
| | 138 | | /// </summary> |
| | 139 | | /// <param name="name"> |
| | 140 | | /// The name for this entry. Can include directory components. |
| | 141 | | /// The convention for names is 'unix' style paths with no device names and |
| | 142 | | /// path elements separated by '/' characters. This is not enforced see <see cref="CleanName(string)">CleanName</se |
| | 143 | | /// on how to ensure names are valid if this is desired. |
| | 144 | | /// </param> |
| | 145 | | /// <param name="versionRequiredToExtract"> |
| | 146 | | /// The minimum 'feature version' required this entry |
| | 147 | | /// </param> |
| | 148 | | /// <exception cref="ArgumentNullException"> |
| | 149 | | /// The name passed is null |
| | 150 | | /// </exception> |
| | 151 | | internal ZipEntry(string name, int versionRequiredToExtract) |
| 81 | 152 | | : this(name, versionRequiredToExtract, ZipConstants.VersionMadeBy, |
| 81 | 153 | | CompressionMethod.Deflated) |
| | 154 | | { |
| 81 | 155 | | } |
| | 156 | |
|
| | 157 | | /// <summary> |
| | 158 | | /// Initializes an entry with the given name and made by information |
| | 159 | | /// </summary> |
| | 160 | | /// <param name="name">Name for this entry</param> |
| | 161 | | /// <param name="madeByInfo">Version and HostSystem Information</param> |
| | 162 | | /// <param name="versionRequiredToExtract">Minimum required zip feature version required to extract this entry</para |
| | 163 | | /// <param name="method">Compression method for this entry.</param> |
| | 164 | | /// <exception cref="ArgumentNullException"> |
| | 165 | | /// The name passed is null |
| | 166 | | /// </exception> |
| | 167 | | /// <exception cref="ArgumentOutOfRangeException"> |
| | 168 | | /// versionRequiredToExtract should be 0 (auto-calculate) or > 10 |
| | 169 | | /// </exception> |
| | 170 | | /// <remarks> |
| | 171 | | /// This constructor is used by the ZipFile class when reading from the central header |
| | 172 | | /// It is not generally useful, use the constructor specifying the name only. |
| | 173 | | /// </remarks> |
| 131878 | 174 | | internal ZipEntry(string name, int versionRequiredToExtract, int madeByInfo, |
| 131878 | 175 | | CompressionMethod method) |
| | 176 | | { |
| 131878 | 177 | | if (name == null) { |
| 1 | 178 | | throw new ArgumentNullException(nameof(name)); |
| | 179 | | } |
| | 180 | |
|
| 131877 | 181 | | if (name.Length > 0xffff) { |
| 0 | 182 | | throw new ArgumentException("Name is too long", nameof(name)); |
| | 183 | | } |
| | 184 | |
|
| 131877 | 185 | | if ((versionRequiredToExtract != 0) && (versionRequiredToExtract < 10)) { |
| 0 | 186 | | throw new ArgumentOutOfRangeException(nameof(versionRequiredToExtract)); |
| | 187 | | } |
| | 188 | |
|
| 131877 | 189 | | this.DateTime = DateTime.Now; |
| 131877 | 190 | | this.name = CleanName(name); |
| 131877 | 191 | | this.versionMadeBy = (ushort)madeByInfo; |
| 131877 | 192 | | this.versionToExtract = (ushort)versionRequiredToExtract; |
| 131877 | 193 | | this.method = method; |
| 131877 | 194 | | } |
| | 195 | |
|
| | 196 | | /// <summary> |
| | 197 | | /// Creates a deep copy of the given zip entry. |
| | 198 | | /// </summary> |
| | 199 | | /// <param name="entry"> |
| | 200 | | /// The entry to copy. |
| | 201 | | /// </param> |
| | 202 | | [Obsolete("Use Clone instead")] |
| 1 | 203 | | public ZipEntry(ZipEntry entry) |
| | 204 | | { |
| 1 | 205 | | if (entry == null) { |
| 0 | 206 | | throw new ArgumentNullException(nameof(entry)); |
| | 207 | | } |
| | 208 | |
|
| 1 | 209 | | known = entry.known; |
| 1 | 210 | | name = entry.name; |
| 1 | 211 | | size = entry.size; |
| 1 | 212 | | compressedSize = entry.compressedSize; |
| 1 | 213 | | crc = entry.crc; |
| 1 | 214 | | dosTime = entry.dosTime; |
| 1 | 215 | | dateTime = entry.dateTime; |
| 1 | 216 | | method = entry.method; |
| 1 | 217 | | comment = entry.comment; |
| 1 | 218 | | versionToExtract = entry.versionToExtract; |
| 1 | 219 | | versionMadeBy = entry.versionMadeBy; |
| 1 | 220 | | externalFileAttributes = entry.externalFileAttributes; |
| 1 | 221 | | flags = entry.flags; |
| | 222 | |
|
| 1 | 223 | | zipFileIndex = entry.zipFileIndex; |
| 1 | 224 | | offset = entry.offset; |
| | 225 | |
|
| 1 | 226 | | forceZip64_ = entry.forceZip64_; |
| | 227 | |
|
| 1 | 228 | | if (entry.extra != null) { |
| 1 | 229 | | extra = new byte[entry.extra.Length]; |
| 1 | 230 | | Array.Copy(entry.extra, 0, extra, 0, entry.extra.Length); |
| | 231 | | } |
| 1 | 232 | | } |
| | 233 | |
|
| | 234 | | #endregion |
| | 235 | |
|
| | 236 | | /// <summary> |
| | 237 | | /// Get a value indicating wether the entry has a CRC value available. |
| | 238 | | /// </summary> |
| | 239 | | public bool HasCrc { |
| | 240 | | get { |
| 65754 | 241 | | return (known & Known.Crc) != 0; |
| | 242 | | } |
| | 243 | | } |
| | 244 | |
|
| | 245 | | /// <summary> |
| | 246 | | /// Get/Set flag indicating if entry is encrypted. |
| | 247 | | /// A simple helper routine to aid interpretation of <see cref="Flags">flags</see> |
| | 248 | | /// </summary> |
| | 249 | | /// <remarks>This is an assistant that interprets the <see cref="Flags">flags</see> property.</remarks> |
| | 250 | | public bool IsCrypted { |
| | 251 | | get { |
| 592001 | 252 | | return (flags & 1) != 0; |
| | 253 | | } |
| | 254 | | set { |
| 65738 | 255 | | if (value) { |
| 38 | 256 | | flags |= 1; |
| 38 | 257 | | } else { |
| 65700 | 258 | | flags &= ~1; |
| | 259 | | } |
| 65700 | 260 | | } |
| | 261 | | } |
| | 262 | |
|
| | 263 | | /// <summary> |
| | 264 | | /// Get / set a flag indicating wether entry name and comment text are |
| | 265 | | /// encoded in <a href="http://www.unicode.org">unicode UTF8</a>. |
| | 266 | | /// </summary> |
| | 267 | | /// <remarks>This is an assistant that interprets the <see cref="Flags">flags</see> property.</remarks> |
| | 268 | | public bool IsUnicodeText { |
| | 269 | | get { |
| 4 | 270 | | return (flags & (int)GeneralBitFlags.UnicodeText) != 0; |
| | 271 | | } |
| | 272 | | set { |
| 177 | 273 | | if (value) { |
| 8 | 274 | | flags |= (int)GeneralBitFlags.UnicodeText; |
| 8 | 275 | | } else { |
| 169 | 276 | | flags &= ~(int)GeneralBitFlags.UnicodeText; |
| | 277 | | } |
| 169 | 278 | | } |
| | 279 | | } |
| | 280 | |
|
| | 281 | | /// <summary> |
| | 282 | | /// Value used during password checking for PKZIP 2.0 / 'classic' encryption. |
| | 283 | | /// </summary> |
| | 284 | | internal byte CryptoCheckValue { |
| | 285 | | get { |
| 34 | 286 | | return cryptoCheckValue_; |
| | 287 | | } |
| | 288 | |
|
| | 289 | | set { |
| 66037 | 290 | | cryptoCheckValue_ = value; |
| 66037 | 291 | | } |
| | 292 | | } |
| | 293 | |
|
| | 294 | | /// <summary> |
| | 295 | | /// Get/Set general purpose bit flag for entry |
| | 296 | | /// </summary> |
| | 297 | | /// <remarks> |
| | 298 | | /// General purpose bit flag<br/> |
| | 299 | | /// <br/> |
| | 300 | | /// Bit 0: If set, indicates the file is encrypted<br/> |
| | 301 | | /// Bit 1-2 Only used for compression type 6 Imploding, and 8, 9 deflating<br/> |
| | 302 | | /// Imploding:<br/> |
| | 303 | | /// Bit 1 if set indicates an 8K sliding dictionary was used. If clear a 4k dictionary was used<br/> |
| | 304 | | /// Bit 2 if set indicates 3 Shannon-Fanno trees were used to encode the sliding dictionary, 2 otherwise<br/> |
| | 305 | | /// <br/> |
| | 306 | | /// Deflating:<br/> |
| | 307 | | /// Bit 2 Bit 1<br/> |
| | 308 | | /// 0 0 Normal compression was used<br/> |
| | 309 | | /// 0 1 Maximum compression was used<br/> |
| | 310 | | /// 1 0 Fast compression was used<br/> |
| | 311 | | /// 1 1 Super fast compression was used<br/> |
| | 312 | | /// <br/> |
| | 313 | | /// Bit 3: If set, the fields crc-32, compressed size |
| | 314 | | /// and uncompressed size are were not able to be written during zip file creation |
| | 315 | | /// The correct values are held in a data descriptor immediately following the compressed data. <br/> |
| | 316 | | /// Bit 4: Reserved for use by PKZIP for enhanced deflating<br/> |
| | 317 | | /// Bit 5: If set indicates the file contains compressed patch data<br/> |
| | 318 | | /// Bit 6: If set indicates strong encryption was used.<br/> |
| | 319 | | /// Bit 7-10: Unused or reserved<br/> |
| | 320 | | /// Bit 11: If set the name and comments for this entry are in <a href="http://www.unicode.org">unicode</a>.<br/> |
| | 321 | | /// Bit 12-15: Unused or reserved<br/> |
| | 322 | | /// </remarks> |
| | 323 | | /// <seealso cref="IsUnicodeText"></seealso> |
| | 324 | | /// <seealso cref="IsCrypted"></seealso> |
| | 325 | | public int Flags { |
| | 326 | | get { |
| 395994 | 327 | | return flags; |
| | 328 | | } |
| | 329 | | set { |
| 66249 | 330 | | flags = value; |
| 66249 | 331 | | } |
| | 332 | | } |
| | 333 | |
|
| | 334 | | /// <summary> |
| | 335 | | /// Get/Set index of this entry in Zip file |
| | 336 | | /// </summary> |
| | 337 | | /// <remarks>This is only valid when the entry is part of a <see cref="ZipFile"></see></remarks> |
| | 338 | | public long ZipFileIndex { |
| | 339 | | get { |
| 65933 | 340 | | return zipFileIndex; |
| | 341 | | } |
| | 342 | | set { |
| 65956 | 343 | | zipFileIndex = value; |
| 65956 | 344 | | } |
| | 345 | | } |
| | 346 | |
|
| | 347 | | /// <summary> |
| | 348 | | /// Get/set offset for use in central header |
| | 349 | | /// </summary> |
| | 350 | | public long Offset { |
| | 351 | | get { |
| 396693 | 352 | | return offset; |
| | 353 | | } |
| | 354 | | set { |
| 131833 | 355 | | offset = value; |
| 131833 | 356 | | } |
| | 357 | | } |
| | 358 | |
|
| | 359 | | /// <summary> |
| | 360 | | /// Get/Set external file attributes as an integer. |
| | 361 | | /// The values of this are operating system dependant see |
| | 362 | | /// <see cref="HostSystem">HostSystem</see> for details |
| | 363 | | /// </summary> |
| | 364 | | public int ExternalFileAttributes { |
| | 365 | | get { |
| 527438 | 366 | | if ((known & Known.ExternalAttributes) == 0) { |
| 65824 | 367 | | return -1; |
| | 368 | | } else { |
| 461614 | 369 | | return externalFileAttributes; |
| | 370 | | } |
| | 371 | | } |
| | 372 | |
|
| | 373 | | set { |
| 65965 | 374 | | externalFileAttributes = value; |
| 65965 | 375 | | known |= Known.ExternalAttributes; |
| 65965 | 376 | | } |
| | 377 | | } |
| | 378 | |
|
| | 379 | | /// <summary> |
| | 380 | | /// Get the version made by for this entry or zero if unknown. |
| | 381 | | /// The value / 10 indicates the major version number, and |
| | 382 | | /// the value mod 10 is the minor version number |
| | 383 | | /// </summary> |
| | 384 | | public int VersionMadeBy { |
| | 385 | | get { |
| 0 | 386 | | return (versionMadeBy & 0xff); |
| | 387 | | } |
| | 388 | | } |
| | 389 | |
|
| | 390 | | /// <summary> |
| | 391 | | /// Get a value indicating this entry is for a DOS/Windows system. |
| | 392 | | /// </summary> |
| | 393 | | public bool IsDOSEntry { |
| | 394 | | get { |
| 2 | 395 | | return ((HostSystem == (int)HostSystemID.Msdos) || |
| 2 | 396 | | (HostSystem == (int)HostSystemID.WindowsNT)); |
| | 397 | | } |
| | 398 | | } |
| | 399 | |
|
| | 400 | | /// <summary> |
| | 401 | | /// Test the external attributes for this <see cref="ZipEntry"/> to |
| | 402 | | /// see if the external attributes are Dos based (including WINNT and variants) |
| | 403 | | /// and match the values |
| | 404 | | /// </summary> |
| | 405 | | /// <param name="attributes">The attributes to test.</param> |
| | 406 | | /// <returns>Returns true if the external attributes are known to be DOS/Windows |
| | 407 | | /// based and have the same attributes set as the value passed.</returns> |
| | 408 | | bool HasDosAttributes(int attributes) |
| | 409 | | { |
| 920886 | 410 | | bool result = false; |
| 920886 | 411 | | if ((known & Known.ExternalAttributes) != 0) { |
| 461462 | 412 | | result |= (((HostSystem == (int)HostSystemID.Msdos) || |
| 461462 | 413 | | (HostSystem == (int)HostSystemID.WindowsNT)) && |
| 461462 | 414 | | (ExternalFileAttributes & attributes) == attributes); |
| | 415 | | } |
| 920886 | 416 | | return result; |
| | 417 | | } |
| | 418 | |
|
| | 419 | | /// <summary> |
| | 420 | | /// Gets the compatability information for the <see cref="ExternalFileAttributes">external file attribute</see> |
| | 421 | | /// If the external file attributes are compatible with MS-DOS and can be read |
| | 422 | | /// by PKZIP for DOS version 2.04g then this value will be zero. Otherwise the value |
| | 423 | | /// will be non-zero and identify the host system on which the attributes are compatible. |
| | 424 | | /// </summary> |
| | 425 | | /// |
| | 426 | | /// <remarks> |
| | 427 | | /// The values for this as defined in the Zip File format and by others are shown below. The values are somewhat |
| | 428 | | /// misleading in some cases as they are not all used as shown. You should consult the relevant documentation |
| | 429 | | /// to obtain up to date and correct information. The modified appnote by the infozip group is |
| | 430 | | /// particularly helpful as it documents a lot of peculiarities. The document is however a little dated. |
| | 431 | | /// <list type="table"> |
| | 432 | | /// <item>0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems)</item> |
| | 433 | | /// <item>1 - Amiga</item> |
| | 434 | | /// <item>2 - OpenVMS</item> |
| | 435 | | /// <item>3 - Unix</item> |
| | 436 | | /// <item>4 - VM/CMS</item> |
| | 437 | | /// <item>5 - Atari ST</item> |
| | 438 | | /// <item>6 - OS/2 HPFS</item> |
| | 439 | | /// <item>7 - Macintosh</item> |
| | 440 | | /// <item>8 - Z-System</item> |
| | 441 | | /// <item>9 - CP/M</item> |
| | 442 | | /// <item>10 - Windows NTFS</item> |
| | 443 | | /// <item>11 - MVS (OS/390 - Z/OS)</item> |
| | 444 | | /// <item>12 - VSE</item> |
| | 445 | | /// <item>13 - Acorn Risc</item> |
| | 446 | | /// <item>14 - VFAT</item> |
| | 447 | | /// <item>15 - Alternate MVS</item> |
| | 448 | | /// <item>16 - BeOS</item> |
| | 449 | | /// <item>17 - Tandem</item> |
| | 450 | | /// <item>18 - OS/400</item> |
| | 451 | | /// <item>19 - OS/X (Darwin)</item> |
| | 452 | | /// <item>99 - WinZip AES</item> |
| | 453 | | /// <item>remainder - unused</item> |
| | 454 | | /// </list> |
| | 455 | | /// </remarks> |
| | 456 | | public int HostSystem { |
| | 457 | | get { |
| 461464 | 458 | | return (versionMadeBy >> 8) & 0xff; |
| | 459 | | } |
| | 460 | |
|
| | 461 | | set { |
| 0 | 462 | | versionMadeBy &= 0xff; |
| 0 | 463 | | versionMadeBy |= (ushort)((value & 0xff) << 8); |
| 0 | 464 | | } |
| | 465 | | } |
| | 466 | |
|
| | 467 | | /// <summary> |
| | 468 | | /// Get minimum Zip feature version required to extract this entry |
| | 469 | | /// </summary> |
| | 470 | | /// <remarks> |
| | 471 | | /// Minimum features are defined as:<br/> |
| | 472 | | /// 1.0 - Default value<br/> |
| | 473 | | /// 1.1 - File is a volume label<br/> |
| | 474 | | /// 2.0 - File is a folder/directory<br/> |
| | 475 | | /// 2.0 - File is compressed using Deflate compression<br/> |
| | 476 | | /// 2.0 - File is encrypted using traditional encryption<br/> |
| | 477 | | /// 2.1 - File is compressed using Deflate64<br/> |
| | 478 | | /// 2.5 - File is compressed using PKWARE DCL Implode<br/> |
| | 479 | | /// 2.7 - File is a patch data set<br/> |
| | 480 | | /// 4.5 - File uses Zip64 format extensions<br/> |
| | 481 | | /// 4.6 - File is compressed using BZIP2 compression<br/> |
| | 482 | | /// 5.0 - File is encrypted using DES<br/> |
| | 483 | | /// 5.0 - File is encrypted using 3DES<br/> |
| | 484 | | /// 5.0 - File is encrypted using original RC2 encryption<br/> |
| | 485 | | /// 5.0 - File is encrypted using RC4 encryption<br/> |
| | 486 | | /// 5.1 - File is encrypted using AES encryption<br/> |
| | 487 | | /// 5.1 - File is encrypted using corrected RC2 encryption<br/> |
| | 488 | | /// 5.1 - File is encrypted using corrected RC2-64 encryption<br/> |
| | 489 | | /// 6.1 - File is encrypted using non-OAEP key wrapping<br/> |
| | 490 | | /// 6.2 - Central directory encryption (not confirmed yet)<br/> |
| | 491 | | /// 6.3 - File is compressed using LZMA<br/> |
| | 492 | | /// 6.3 - File is compressed using PPMD+<br/> |
| | 493 | | /// 6.3 - File is encrypted using Blowfish<br/> |
| | 494 | | /// 6.3 - File is encrypted using Twofish<br/> |
| | 495 | | /// </remarks> |
| | 496 | | /// <seealso cref="CanDecompress"></seealso> |
| | 497 | | public int Version { |
| | 498 | | get { |
| | 499 | | // Return recorded version if known. |
| 198066 | 500 | | if (versionToExtract != 0) { |
| 66394 | 501 | | return versionToExtract & 0x00ff; // Only lower order byte. High order is O/S file system. |
| | 502 | | } else { |
| 131672 | 503 | | int result = 10; |
| 131672 | 504 | | if (AESKeySize > 0) { |
| 0 | 505 | | result = ZipConstants.VERSION_AES; // Ver 5.1 = AES |
| 131672 | 506 | | } else if (CentralHeaderRequiresZip64) { |
| 247 | 507 | | result = ZipConstants.VersionZip64; |
| 131672 | 508 | | } else if (CompressionMethod.Deflated == method) { |
| 321 | 509 | | result = 20; |
| 131425 | 510 | | } else if (IsDirectory == true) { |
| 0 | 511 | | result = 20; |
| 131104 | 512 | | } else if (IsCrypted == true) { |
| 0 | 513 | | result = 20; |
| 131104 | 514 | | } else if (HasDosAttributes(0x08)) { |
| 0 | 515 | | result = 11; |
| | 516 | | } |
| 131672 | 517 | | return result; |
| | 518 | | } |
| | 519 | | } |
| | 520 | | } |
| | 521 | |
|
| | 522 | | /// <summary> |
| | 523 | | /// Get a value indicating whether this entry can be decompressed by the library. |
| | 524 | | /// </summary> |
| | 525 | | /// <remarks>This is based on the <see cref="Version"></see> and |
| | 526 | | /// wether the <see cref="IsCompressionMethodSupported()">compression method</see> is supported.</remarks> |
| | 527 | | public bool CanDecompress { |
| | 528 | | get { |
| 73 | 529 | | return (Version <= ZipConstants.VersionMadeBy) && |
| 73 | 530 | | ((Version == 10) || |
| 73 | 531 | | (Version == 11) || |
| 73 | 532 | | (Version == 20) || |
| 73 | 533 | | (Version == 45) || |
| 73 | 534 | | (Version == 51)) && |
| 73 | 535 | | IsCompressionMethodSupported(); |
| | 536 | | } |
| | 537 | | } |
| | 538 | |
|
| | 539 | | /// <summary> |
| | 540 | | /// Force this entry to be recorded using Zip64 extensions. |
| | 541 | | /// </summary> |
| | 542 | | public void ForceZip64() |
| | 543 | | { |
| 124 | 544 | | forceZip64_ = true; |
| 124 | 545 | | } |
| | 546 | |
|
| | 547 | | /// <summary> |
| | 548 | | /// Get a value indicating wether Zip64 extensions were forced. |
| | 549 | | /// </summary> |
| | 550 | | /// <returns>A <see cref="bool"/> value of true if Zip64 extensions have been forced on; false if not.</returns> |
| | 551 | | public bool IsZip64Forced() |
| | 552 | | { |
| 132042 | 553 | | return forceZip64_; |
| | 554 | | } |
| | 555 | |
|
| | 556 | | /// <summary> |
| | 557 | | /// Gets a value indicating if the entry requires Zip64 extensions |
| | 558 | | /// to store the full entry values. |
| | 559 | | /// </summary> |
| | 560 | | /// <value>A <see cref="bool"/> value of true if a local header requires Zip64 extensions; false if not.</value> |
| | 561 | | public bool LocalHeaderRequiresZip64 { |
| | 562 | | get { |
| 395536 | 563 | | bool result = forceZip64_; |
| | 564 | |
|
| 395536 | 565 | | if (!result) { |
| 394651 | 566 | | ulong trueCompressedSize = compressedSize; |
| | 567 | |
|
| 394651 | 568 | | if ((versionToExtract == 0) && IsCrypted) { |
| 68 | 569 | | trueCompressedSize += ZipConstants.CryptoHeaderSize; |
| | 570 | | } |
| | 571 | |
|
| | 572 | | // TODO: A better estimation of the true limit based on compression overhead should be used |
| | 573 | | // to determine when an entry should use Zip64. |
| 394651 | 574 | | result = |
| 394651 | 575 | | ((this.size >= uint.MaxValue) || (trueCompressedSize >= uint.MaxValue)) && |
| 394651 | 576 | | ((versionToExtract == 0) || (versionToExtract >= ZipConstants.VersionZip64)); |
| | 577 | | } |
| | 578 | |
|
| 395536 | 579 | | return result; |
| | 580 | | } |
| | 581 | | } |
| | 582 | |
|
| | 583 | | /// <summary> |
| | 584 | | /// Get a value indicating wether the central directory entry requires Zip64 extensions to be stored. |
| | 585 | | /// </summary> |
| | 586 | | public bool CentralHeaderRequiresZip64 { |
| | 587 | | get { |
| 197572 | 588 | | return LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); |
| | 589 | | } |
| | 590 | | } |
| | 591 | |
|
| | 592 | | /// <summary> |
| | 593 | | /// Get/Set DosTime value. |
| | 594 | | /// </summary> |
| | 595 | | /// <remarks> |
| | 596 | | /// The MS-DOS date format can only represent dates between 1/1/1980 and 12/31/2107. |
| | 597 | | /// </remarks> |
| | 598 | | public long DosTime { |
| | 599 | | get { |
| 131812 | 600 | | if ((known & Known.Time) == 0) { |
| 0 | 601 | | return 0; |
| | 602 | | } else { |
| 131812 | 603 | | return dosTime; |
| | 604 | | } |
| | 605 | | } |
| | 606 | |
|
| | 607 | | set { |
| | 608 | | unchecked { |
| 197925 | 609 | | dosTime = (uint)value; |
| | 610 | | } |
| | 611 | |
|
| 197925 | 612 | | known |= Known.Time; |
| 197925 | 613 | | } |
| | 614 | | } |
| | 615 | |
|
| | 616 | | /// <summary> |
| | 617 | | /// Gets/Sets the time of last modification of the entry. |
| | 618 | | /// </summary> |
| | 619 | | /// <remarks> |
| | 620 | | /// The <see cref="DosTime"></see> property is updated to match this as far as possible. |
| | 621 | | /// </remarks> |
| | 622 | | public DateTime DateTime |
| | 623 | | { |
| 2 | 624 | | get { return dateTime; } |
| | 625 | |
|
| | 626 | | set { |
| 131886 | 627 | | var year = (uint)value.Year; |
| 131886 | 628 | | var month = (uint)value.Month; |
| 131886 | 629 | | var day = (uint)value.Day; |
| 131886 | 630 | | var hour = (uint)value.Hour; |
| 131886 | 631 | | var minute = (uint)value.Minute; |
| 131886 | 632 | | var second = (uint)value.Second; |
| | 633 | |
|
| 131886 | 634 | | if (year < 1980) { |
| 0 | 635 | | year = 1980; |
| 0 | 636 | | month = 1; |
| 0 | 637 | | day = 1; |
| 0 | 638 | | hour = 0; |
| 0 | 639 | | minute = 0; |
| 0 | 640 | | second = 0; |
| 131886 | 641 | | } else if (year > 2107) { |
| 0 | 642 | | year = 2107; |
| 0 | 643 | | month = 12; |
| 0 | 644 | | day = 31; |
| 0 | 645 | | hour = 23; |
| 0 | 646 | | minute = 59; |
| 0 | 647 | | second = 59; |
| | 648 | | } |
| | 649 | |
|
| 131886 | 650 | | DosTime = ((year - 1980) & 0x7f) << 25 | |
| 131886 | 651 | | (month << 21) | |
| 131886 | 652 | | (day << 16) | |
| 131886 | 653 | | (hour << 11) | |
| 131886 | 654 | | (minute << 5) | |
| 131886 | 655 | | (second >> 1); |
| 131886 | 656 | | } |
| | 657 | | } |
| | 658 | |
|
| | 659 | | /// <summary> |
| | 660 | | /// Returns the entry name. |
| | 661 | | /// </summary> |
| | 662 | | /// <remarks> |
| | 663 | | /// The unix naming convention is followed. |
| | 664 | | /// Path components in the entry should always separated by forward slashes ('/'). |
| | 665 | | /// Dos device names like C: should also be removed. |
| | 666 | | /// See the <see cref="ZipNameTransform"/> class, or <see cref="CleanName(string)"/> |
| | 667 | | ///</remarks> |
| | 668 | | public string Name { |
| | 669 | | get { |
| 527086 | 670 | | return name; |
| | 671 | | } |
| | 672 | | } |
| | 673 | |
|
| | 674 | | /// <summary> |
| | 675 | | /// Gets/Sets the size of the uncompressed data. |
| | 676 | | /// </summary> |
| | 677 | | /// <returns> |
| | 678 | | /// The size or -1 if unknown. |
| | 679 | | /// </returns> |
| | 680 | | /// <remarks>Setting the size before adding an entry to an archive can help |
| | 681 | | /// avoid compatability problems with some archivers which dont understand Zip64 extensions.</remarks> |
| | 682 | | public long Size { |
| | 683 | | get { |
| 593205 | 684 | | return (known & Known.Size) != 0 ? (long)size : -1L; |
| | 685 | | } |
| | 686 | | set { |
| 131877 | 687 | | this.size = (ulong)value; |
| 131877 | 688 | | this.known |= Known.Size; |
| 131877 | 689 | | } |
| | 690 | | } |
| | 691 | |
|
| | 692 | | /// <summary> |
| | 693 | | /// Gets/Sets the size of the compressed data. |
| | 694 | | /// </summary> |
| | 695 | | /// <returns> |
| | 696 | | /// The compressed entry size or -1 if unknown. |
| | 697 | | /// </returns> |
| | 698 | | public long CompressedSize { |
| | 699 | | get { |
| 462089 | 700 | | return (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; |
| | 701 | | } |
| | 702 | | set { |
| 262984 | 703 | | this.compressedSize = (ulong)value; |
| 262984 | 704 | | this.known |= Known.CompressedSize; |
| 262984 | 705 | | } |
| | 706 | | } |
| | 707 | |
|
| | 708 | | /// <summary> |
| | 709 | | /// Gets/Sets the crc of the uncompressed data. |
| | 710 | | /// </summary> |
| | 711 | | /// <exception cref="System.ArgumentOutOfRangeException"> |
| | 712 | | /// Crc is not in the range 0..0xffffffffL |
| | 713 | | /// </exception> |
| | 714 | | /// <returns> |
| | 715 | | /// The crc value or -1 if unknown. |
| | 716 | | /// </returns> |
| | 717 | | public long Crc { |
| | 718 | | get { |
| 329635 | 719 | | return (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; |
| | 720 | | } |
| | 721 | | set { |
| 131852 | 722 | | if (((ulong)crc & 0xffffffff00000000L) != 0) { |
| 0 | 723 | | throw new ArgumentOutOfRangeException(nameof(value)); |
| | 724 | | } |
| 131852 | 725 | | this.crc = (uint)value; |
| 131852 | 726 | | this.known |= Known.Crc; |
| 131852 | 727 | | } |
| | 728 | | } |
| | 729 | |
|
| | 730 | | /// <summary> |
| | 731 | | /// Gets/Sets the compression method. Only Deflated and Stored are supported. |
| | 732 | | /// </summary> |
| | 733 | | /// <returns> |
| | 734 | | /// The compression method for this entry |
| | 735 | | /// </returns> |
| | 736 | | /// <see cref="ICSharpCode.SharpZipLib.Zip.CompressionMethod.Deflated"/> |
| | 737 | | /// <see cref="ICSharpCode.SharpZipLib.Zip.CompressionMethod.Stored"/> |
| | 738 | | public CompressionMethod CompressionMethod { |
| | 739 | | get { |
| 461414 | 740 | | return method; |
| | 741 | | } |
| | 742 | |
|
| | 743 | | set { |
| 65837 | 744 | | if (!IsCompressionMethodSupported(value)) { |
| 1 | 745 | | throw new NotSupportedException("Compression method not supported"); |
| | 746 | | } |
| 65836 | 747 | | this.method = value; |
| 65836 | 748 | | } |
| | 749 | | } |
| | 750 | |
|
| | 751 | | /// <summary> |
| | 752 | | /// Gets the compression method for outputting to the local or central header. |
| | 753 | | /// Returns same value as CompressionMethod except when AES encrypting, which |
| | 754 | | /// places 99 in the method and places the real method in the extra data. |
| | 755 | | /// </summary> |
| | 756 | | internal CompressionMethod CompressionMethodForHeader { |
| | 757 | | get { |
| 257 | 758 | | return (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; |
| | 759 | | } |
| | 760 | | } |
| | 761 | |
|
| | 762 | | /// <summary> |
| | 763 | | /// Gets/Sets the extra data. |
| | 764 | | /// </summary> |
| | 765 | | /// <exception cref="System.ArgumentOutOfRangeException"> |
| | 766 | | /// Extra data is longer than 64KB (0xffff) bytes. |
| | 767 | | /// </exception> |
| | 768 | | /// <returns> |
| | 769 | | /// Extra data or null if not set. |
| | 770 | | /// </returns> |
| | 771 | | public byte[] ExtraData { |
| | 772 | |
|
| | 773 | | get { |
| | 774 | | // TODO: This is slightly safer but less efficient. Think about wether it should change. |
| | 775 | | // return (byte[]) extra.Clone(); |
| 263288 | 776 | | return extra; |
| | 777 | | } |
| | 778 | |
|
| | 779 | | set { |
| 65874 | 780 | | if (value == null) { |
| 0 | 781 | | extra = null; |
| 0 | 782 | | } else { |
| 65874 | 783 | | if (value.Length > 0xffff) { |
| 0 | 784 | | throw new System.ArgumentOutOfRangeException(nameof(value)); |
| | 785 | | } |
| | 786 | |
|
| 65874 | 787 | | extra = new byte[value.Length]; |
| 65874 | 788 | | Array.Copy(value, 0, extra, 0, value.Length); |
| | 789 | | } |
| 65874 | 790 | | } |
| | 791 | | } |
| | 792 | |
|
| | 793 | |
|
| | 794 | | /// <summary> |
| | 795 | | /// For AES encrypted files returns or sets the number of bits of encryption (128, 192 or 256). |
| | 796 | | /// When setting, only 0 (off), 128 or 256 is supported. |
| | 797 | | /// </summary> |
| | 798 | | public int AESKeySize { |
| | 799 | | get { |
| | 800 | | // the strength (1 or 3) is in the entry header |
| 132508 | 801 | | switch (_aesEncryptionStrength) { |
| | 802 | | case 0: |
| 132508 | 803 | | return 0; // Not AES |
| | 804 | | case 1: |
| 0 | 805 | | return 128; |
| | 806 | | case 2: |
| 0 | 807 | | return 192; // Not used by WinZip |
| | 808 | | case 3: |
| 0 | 809 | | return 256; |
| | 810 | | default: |
| 0 | 811 | | throw new ZipException("Invalid AESEncryptionStrength " + _aesEncryptionStrength); |
| | 812 | | } |
| | 813 | | } |
| | 814 | | set { |
| 0 | 815 | | switch (value) { |
| | 816 | | case 0: |
| 0 | 817 | | _aesEncryptionStrength = 0; |
| 0 | 818 | | break; |
| | 819 | | case 128: |
| 0 | 820 | | _aesEncryptionStrength = 1; |
| 0 | 821 | | break; |
| | 822 | | case 256: |
| 0 | 823 | | _aesEncryptionStrength = 3; |
| 0 | 824 | | break; |
| | 825 | | default: |
| 0 | 826 | | throw new ZipException("AESKeySize must be 0, 128 or 256: " + value); |
| | 827 | | } |
| | 828 | | } |
| | 829 | | } |
| | 830 | |
|
| | 831 | | /// <summary> |
| | 832 | | /// AES Encryption strength for storage in extra data in entry header. |
| | 833 | | /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit. |
| | 834 | | /// </summary> |
| | 835 | | internal byte AESEncryptionStrength { |
| | 836 | | get { |
| 0 | 837 | | return (byte)_aesEncryptionStrength; |
| | 838 | | } |
| | 839 | | } |
| | 840 | |
|
| | 841 | | /// <summary> |
| | 842 | | /// Returns the length of the salt, in bytes |
| | 843 | | /// </summary> |
| | 844 | | internal int AESSaltLen { |
| | 845 | | get { |
| | 846 | | // Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. |
| 0 | 847 | | return AESKeySize / 16; |
| | 848 | | } |
| | 849 | | } |
| | 850 | |
|
| | 851 | | /// <summary> |
| | 852 | | /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode) |
| | 853 | | /// </summary> |
| | 854 | | internal int AESOverheadSize { |
| | 855 | | get { |
| | 856 | | // File format: |
| | 857 | | // Bytes Content |
| | 858 | | // Variable Salt value |
| | 859 | | // 2 Password verification value |
| | 860 | | // Variable Encrypted file data |
| | 861 | | // 10 Authentication code |
| 0 | 862 | | return 12 + AESSaltLen; |
| | 863 | | } |
| | 864 | | } |
| | 865 | |
|
| | 866 | | /// <summary> |
| | 867 | | /// Process extra data fields updating the entry based on the contents. |
| | 868 | | /// </summary> |
| | 869 | | /// <param name="localHeader">True if the extra data fields should be handled |
| | 870 | | /// for a local header, rather than for a central header. |
| | 871 | | /// </param> |
| | 872 | | internal void ProcessExtraData(bool localHeader) |
| | 873 | | { |
| 66037 | 874 | | var extraData = new ZipExtraData(this.extra); |
| | 875 | |
|
| 66037 | 876 | | if (extraData.Find(0x0001)) { |
| | 877 | | // Version required to extract is ignored here as some archivers dont set it correctly |
| | 878 | | // in theory it should be version 45 or higher |
| | 879 | |
|
| | 880 | | // The recorded size will change but remember that this is zip64. |
| 123 | 881 | | forceZip64_ = true; |
| | 882 | |
|
| 123 | 883 | | if (extraData.ValueLength < 4) { |
| 0 | 884 | | throw new ZipException("Extra data extended Zip64 information length is invalid"); |
| | 885 | | } |
| | 886 | |
|
| | 887 | | // (localHeader ||) was deleted, because actually there is no specific difference with reading sizes between loc |
| | 888 | | // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT |
| | 889 | | // ... |
| | 890 | | // 4.4 Explanation of fields |
| | 891 | | // ... |
| | 892 | | // 4.4.8 compressed size: (4 bytes) |
| | 893 | | // 4.4.9 uncompressed size: (4 bytes) |
| | 894 | | // |
| | 895 | | // The size of the file compressed (4.4.8) and uncompressed, |
| | 896 | | // (4.4.9) respectively. When a decryption header is present it |
| | 897 | | // will be placed in front of the file data and the value of the |
| | 898 | | // compressed file size will include the bytes of the decryption |
| | 899 | | // header. If bit 3 of the general purpose bit flag is set, |
| | 900 | | // these fields are set to zero in the local header and the |
| | 901 | | // correct values are put in the data descriptor and |
| | 902 | | // in the central directory. If an archive is in ZIP64 format |
| | 903 | | // and the value in this field is 0xFFFFFFFF, the size will be |
| | 904 | | // in the corresponding 8 byte ZIP64 extended information |
| | 905 | | // extra field. When encrypting the central directory, if the |
| | 906 | | // local header is not in ZIP64 format and general purpose bit |
| | 907 | | // flag 13 is set indicating masking, the value stored for the |
| | 908 | | // uncompressed size in the Local Header will be zero. |
| | 909 | | // |
| | 910 | | // Othewise there is problem with minizip implementation |
| 123 | 911 | | if (size == uint.MaxValue) { |
| 123 | 912 | | size = (ulong)extraData.ReadLong(); |
| | 913 | | } |
| | 914 | |
|
| 123 | 915 | | if (compressedSize == uint.MaxValue) { |
| 123 | 916 | | compressedSize = (ulong)extraData.ReadLong(); |
| | 917 | | } |
| | 918 | |
|
| 123 | 919 | | if (!localHeader && (offset == uint.MaxValue)) { |
| 0 | 920 | | offset = extraData.ReadLong(); |
| | 921 | | } |
| | 922 | |
|
| | 923 | | // Disk number on which file starts is ignored |
| 0 | 924 | | } else { |
| 65914 | 925 | | if ( |
| 65914 | 926 | | ((versionToExtract & 0xff) >= ZipConstants.VersionZip64) && |
| 65914 | 927 | | ((size == uint.MaxValue) || (compressedSize == uint.MaxValue)) |
| 65914 | 928 | | ) { |
| 0 | 929 | | throw new ZipException("Zip64 Extended information required but is missing."); |
| | 930 | | } |
| | 931 | | } |
| | 932 | |
|
| 66037 | 933 | | dateTime = GetDateTime(extraData); |
| 66037 | 934 | | if (method == CompressionMethod.WinZipAES) { |
| 0 | 935 | | ProcessAESExtraData(extraData); |
| | 936 | | } |
| 66037 | 937 | | } |
| | 938 | |
|
| | 939 | | private DateTime GetDateTime(ZipExtraData extraData) { |
| | 940 | | // Check for NT timestamp |
| | 941 | | // NOTE: Disable by default to match behavior of InfoZIP |
| | 942 | | #if RESPECT_NT_TIMESTAMP |
| | 943 | | NTTaggedData ntData = extraData.GetData<NTTaggedData>(); |
| | 944 | | if (ntData != null) |
| | 945 | | return ntData.LastModificationTime; |
| | 946 | | #endif |
| | 947 | |
|
| | 948 | | // Check for Unix timestamp |
| 66037 | 949 | | ExtendedUnixData unixData = extraData.GetData<ExtendedUnixData>(); |
| 66037 | 950 | | if (unixData != null && |
| 66037 | 951 | | // Only apply modification time, but require all other values to be present |
| 66037 | 952 | | // This is done to match InfoZIP's behaviour |
| 66037 | 953 | | ((unixData.Include & ExtendedUnixData.Flags.ModificationTime) != 0) && |
| 66037 | 954 | | ((unixData.Include & ExtendedUnixData.Flags.AccessTime) != 0) && |
| 66037 | 955 | | ((unixData.Include & ExtendedUnixData.Flags.CreateTime) != 0)) |
| 0 | 956 | | return unixData.ModificationTime; |
| | 957 | |
|
| | 958 | | // Fall back to DOS time |
| 66037 | 959 | | uint sec = Math.Min(59, 2 * (dosTime & 0x1f)); |
| 66037 | 960 | | uint min = Math.Min(59, (dosTime >> 5) & 0x3f); |
| 66037 | 961 | | uint hrs = Math.Min(23, (dosTime >> 11) & 0x1f); |
| 66037 | 962 | | uint mon = Math.Max(1, Math.Min(12, ((dosTime >> 21) & 0xf))); |
| 66037 | 963 | | uint year = ((dosTime >> 25) & 0x7f) + 1980; |
| 66037 | 964 | | int day = Math.Max(1, Math.Min(DateTime.DaysInMonth((int)year, (int)mon), (int)((dosTime >> 16) & 0x1f))); |
| 66037 | 965 | | return new DateTime((int)year, (int)mon, day, (int)hrs, (int)min, (int)sec, DateTimeKind.Utc); |
| | 966 | | } |
| | 967 | |
|
| | 968 | | // For AES the method in the entry is 99, and the real compression method is in the extradata |
| | 969 | | // |
| | 970 | | private void ProcessAESExtraData(ZipExtraData extraData) |
| | 971 | | { |
| | 972 | |
|
| 0 | 973 | | if (extraData.Find(0x9901)) { |
| | 974 | | // Set version and flag for Zipfile.CreateAndInitDecryptionStream |
| 0 | 975 | | versionToExtract = ZipConstants.VERSION_AES; // Ver 5.1 = AES see "Version" getter |
| | 976 | | // Set StrongEncryption flag for ZipFile.CreateAndInitDecryptionStream |
| 0 | 977 | | Flags = Flags | (int)GeneralBitFlags.StrongEncryption; |
| | 978 | | // |
| | 979 | | // Unpack AES extra data field see http://www.winzip.com/aes_info.htm |
| 0 | 980 | | int length = extraData.ValueLength; // Data size currently 7 |
| 0 | 981 | | if (length < 7) |
| 0 | 982 | | throw new ZipException("AES Extra Data Length " + length + " invalid."); |
| 0 | 983 | | int ver = extraData.ReadShort(); // Version number (1=AE-1 2=AE-2) |
| 0 | 984 | | int vendorId = extraData.ReadShort(); // 2-character vendor ID 0x4541 = "AE" |
| 0 | 985 | | int encrStrength = extraData.ReadByte(); // encryption strength 1 = 128 2 = 192 3 = 256 |
| 0 | 986 | | int actualCompress = extraData.ReadShort(); // The actual compression method used to compress the file |
| 0 | 987 | | _aesVer = ver; |
| 0 | 988 | | _aesEncryptionStrength = encrStrength; |
| 0 | 989 | | method = (CompressionMethod)actualCompress; |
| 0 | 990 | | } else |
| 0 | 991 | | throw new ZipException("AES Extra Data missing"); |
| | 992 | | } |
| | 993 | |
|
| | 994 | | /// <summary> |
| | 995 | | /// Gets/Sets the entry comment. |
| | 996 | | /// </summary> |
| | 997 | | /// <exception cref="System.ArgumentOutOfRangeException"> |
| | 998 | | /// If comment is longer than 0xffff. |
| | 999 | | /// </exception> |
| | 1000 | | /// <returns> |
| | 1001 | | /// The comment or null if not set. |
| | 1002 | | /// </returns> |
| | 1003 | | /// <remarks> |
| | 1004 | | /// A comment is only available for entries when read via the <see cref="ZipFile"/> class. |
| | 1005 | | /// The <see cref="ZipInputStream"/> class doesnt have the comment data available. |
| | 1006 | | /// </remarks> |
| | 1007 | | public string Comment { |
| | 1008 | | get { |
| 131673 | 1009 | | return comment; |
| | 1010 | | } |
| | 1011 | | set { |
| | 1012 | | // This test is strictly incorrect as the length is in characters |
| | 1013 | | // while the storage limit is in bytes. |
| | 1014 | | // While the test is partially correct in that a comment of this length or greater |
| | 1015 | | // is definitely invalid, shorter comments may also have an invalid length |
| | 1016 | | // where there are multi-byte characters |
| | 1017 | | // The full test is not possible here however as the code page to apply conversions with |
| | 1018 | | // isnt available. |
| 3 | 1019 | | if ((value != null) && (value.Length > 0xffff)) { |
| 0 | 1020 | | throw new ArgumentOutOfRangeException(nameof(value), "cannot exceed 65535"); |
| | 1021 | | } |
| | 1022 | |
|
| 3 | 1023 | | comment = value; |
| 3 | 1024 | | } |
| | 1025 | | } |
| | 1026 | |
|
| | 1027 | | /// <summary> |
| | 1028 | | /// Gets a value indicating if the entry is a directory. |
| | 1029 | | /// however. |
| | 1030 | | /// </summary> |
| | 1031 | | /// <remarks> |
| | 1032 | | /// A directory is determined by an entry name with a trailing slash '/'. |
| | 1033 | | /// The external file attributes can also indicate an entry is for a directory. |
| | 1034 | | /// Currently only dos/windows attributes are tested in this manner. |
| | 1035 | | /// The trailing slash convention should always be followed. |
| | 1036 | | /// </remarks> |
| | 1037 | | public bool IsDirectory { |
| | 1038 | | get { |
| 526334 | 1039 | | int nameLength = name.Length; |
| 526334 | 1040 | | bool result = |
| 526334 | 1041 | | ((nameLength > 0) && |
| 526334 | 1042 | | ((name[nameLength - 1] == '/') || (name[nameLength - 1] == '\\'))) || |
| 526334 | 1043 | | HasDosAttributes(16) |
| 526334 | 1044 | | ; |
| 27 | 1045 | | return result; |
| | 1046 | | } |
| | 1047 | | } |
| | 1048 | |
|
| | 1049 | | /// <summary> |
| | 1050 | | /// Get a value of true if the entry appears to be a file; false otherwise |
| | 1051 | | /// </summary> |
| | 1052 | | /// <remarks> |
| | 1053 | | /// This only takes account of DOS/Windows attributes. Other operating systems are ignored. |
| | 1054 | | /// For linux and others the result may be incorrect. |
| | 1055 | | /// </remarks> |
| | 1056 | | public bool IsFile { |
| | 1057 | | get { |
| 263486 | 1058 | | return !IsDirectory && !HasDosAttributes(8); |
| | 1059 | | } |
| | 1060 | | } |
| | 1061 | |
|
| | 1062 | | /// <summary> |
| | 1063 | | /// Test entry to see if data can be extracted. |
| | 1064 | | /// </summary> |
| | 1065 | | /// <returns>Returns true if data can be extracted for this entry; false otherwise.</returns> |
| | 1066 | | public bool IsCompressionMethodSupported() |
| | 1067 | | { |
| 132012 | 1068 | | return IsCompressionMethodSupported(CompressionMethod); |
| | 1069 | | } |
| | 1070 | |
|
| | 1071 | | #region ICloneable Members |
| | 1072 | | /// <summary> |
| | 1073 | | /// Creates a copy of this zip entry. |
| | 1074 | | /// </summary> |
| | 1075 | | /// <returns>An <see cref="Object"/> that is a copy of the current instance.</returns> |
| | 1076 | | public object Clone() |
| | 1077 | | { |
| 461093 | 1078 | | var result = (ZipEntry)this.MemberwiseClone(); |
| | 1079 | |
|
| | 1080 | | // Ensure extra data is unique if it exists. |
| 461093 | 1081 | | if (extra != null) { |
| 72 | 1082 | | result.extra = new byte[extra.Length]; |
| 72 | 1083 | | Array.Copy(extra, 0, result.extra, 0, extra.Length); |
| | 1084 | | } |
| | 1085 | |
|
| 461093 | 1086 | | return result; |
| | 1087 | | } |
| | 1088 | |
|
| | 1089 | | #endregion |
| | 1090 | |
|
| | 1091 | | /// <summary> |
| | 1092 | | /// Gets a string representation of this ZipEntry. |
| | 1093 | | /// </summary> |
| | 1094 | | /// <returns>A readable textual representation of this <see cref="ZipEntry"/></returns> |
| | 1095 | | public override string ToString() |
| | 1096 | | { |
| 0 | 1097 | | return name; |
| | 1098 | | } |
| | 1099 | |
|
| | 1100 | | /// <summary> |
| | 1101 | | /// Test a <see cref="CompressionMethod">compression method</see> to see if this library |
| | 1102 | | /// supports extracting data compressed with that method |
| | 1103 | | /// </summary> |
| | 1104 | | /// <param name="method">The compression method to test.</param> |
| | 1105 | | /// <returns>Returns true if the compression method is supported; false otherwise</returns> |
| | 1106 | | public static bool IsCompressionMethodSupported(CompressionMethod method) |
| | 1107 | | { |
| 197849 | 1108 | | return |
| 197849 | 1109 | | (method == CompressionMethod.Deflated) || |
| 197849 | 1110 | | (method == CompressionMethod.Stored); |
| | 1111 | | } |
| | 1112 | |
|
| | 1113 | | /// <summary> |
| | 1114 | | /// Cleans a name making it conform to Zip file conventions. |
| | 1115 | | /// Devices names ('c:\') and UNC share names ('\\server\share') are removed |
| | 1116 | | /// and forward slashes ('\') are converted to back slashes ('/'). |
| | 1117 | | /// Names are made relative by trimming leading slashes which is compatible |
| | 1118 | | /// with the ZIP naming convention. |
| | 1119 | | /// </summary> |
| | 1120 | | /// <param name="name">The name to clean</param> |
| | 1121 | | /// <returns>The 'cleaned' name.</returns> |
| | 1122 | | /// <remarks> |
| | 1123 | | /// The <seealso cref="ZipNameTransform">Zip name transform</seealso> class is more flexible. |
| | 1124 | | /// </remarks> |
| | 1125 | | public static string CleanName(string name) |
| | 1126 | | { |
| 131877 | 1127 | | if (name == null) { |
| 0 | 1128 | | return string.Empty; |
| | 1129 | | } |
| | 1130 | |
|
| 131877 | 1131 | | if (Path.IsPathRooted(name)) { |
| | 1132 | | // NOTE: |
| | 1133 | | // for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt |
| 0 | 1134 | | name = name.Substring(Path.GetPathRoot(name).Length); |
| | 1135 | | } |
| | 1136 | |
|
| 131877 | 1137 | | name = name.Replace(@"\", "/"); |
| | 1138 | |
|
| 131877 | 1139 | | while ((name.Length > 0) && (name[0] == '/')) { |
| 0 | 1140 | | name = name.Remove(0, 1); |
| | 1141 | | } |
| 131877 | 1142 | | return name; |
| | 1143 | | } |
| | 1144 | |
|
| | 1145 | | #region Instance Fields |
| | 1146 | | Known known; |
| 131879 | 1147 | | int externalFileAttributes = -1; // contains external attributes (O/S dependant) |
| | 1148 | |
|
| | 1149 | | ushort versionMadeBy; // Contains host system and version information |
| | 1150 | | // only relevant for central header entries |
| | 1151 | |
|
| | 1152 | | string name; |
| | 1153 | | ulong size; |
| | 1154 | | ulong compressedSize; |
| | 1155 | | ushort versionToExtract; // Version required to extract (library handles <= 2.0) |
| | 1156 | | uint crc; |
| | 1157 | | uint dosTime; |
| | 1158 | | DateTime dateTime; |
| | 1159 | |
|
| 131879 | 1160 | | CompressionMethod method = CompressionMethod.Deflated; |
| | 1161 | | byte[] extra; |
| | 1162 | | string comment; |
| | 1163 | |
|
| | 1164 | | int flags; // general purpose bit flags |
| | 1165 | |
|
| 131879 | 1166 | | long zipFileIndex = -1; // used by ZipFile |
| | 1167 | | long offset; // used by ZipFile and ZipOutputStream |
| | 1168 | |
|
| | 1169 | | bool forceZip64_; |
| | 1170 | | byte cryptoCheckValue_; |
| | 1171 | | int _aesVer; // Version number (2 = AE-2 ?). Assigned but not used. |
| | 1172 | | int _aesEncryptionStrength; // Encryption strength 1 = 128 2 = 192 3 = 256 |
| | 1173 | | #endregion |
| | 1174 | | } |
| | 1175 | | } |