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