From 06ff713469fd6e1c1cdd2ad3b364248e457a1b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= Date: Sun, 21 Feb 2021 17:46:39 +0100 Subject: [PATCH] PR #517: Throw exception on Store+Descriptor entries * fix(zip): indicate that store/desc. entries cant be extracted * style(zip): simplify and fix spelling * fix(zip): check decompress support after specific exception handling * test: use random bad path instead of possible network drive * docs(zip): remove explicit mentions of methods in CompressionMethod * fix(zip): remove superfluous null check --- src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs | 481 +++++------------- .../Zip/ZipEntryExtensions.cs | 32 ++ .../Zip/ZipInputStream.cs | 62 ++- .../Zip/FastZipHandling.cs | 16 +- .../Zip/StreamHandling.cs | 36 ++ 5 files changed, 249 insertions(+), 378 deletions(-) create mode 100644 src/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs index 2f326ab0e..c607cf9f2 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs @@ -263,13 +263,7 @@ public ZipEntry(ZipEntry entry) /// /// Get a value indicating whether the entry has a CRC value available. /// - public bool HasCrc - { - get - { - return (known & Known.Crc) != 0; - } - } + public bool HasCrc => (known & Known.Crc) != 0; /// /// Get/Set flag indicating if entry is encrypted. @@ -278,21 +272,8 @@ public bool HasCrc /// This is an assistant that interprets the flags property. public bool IsCrypted { - get - { - return (flags & 1) != 0; - } - set - { - if (value) - { - flags |= 1; - } - else - { - flags &= ~1; - } - } + get => this.HasFlag(GeneralBitFlags.Encrypted); + set => this.SetFlag(GeneralBitFlags.Encrypted, value); } /// @@ -302,21 +283,8 @@ public bool IsCrypted /// This is an assistant that interprets the flags property. public bool IsUnicodeText { - get - { - return (flags & (int)GeneralBitFlags.UnicodeText) != 0; - } - set - { - if (value) - { - flags |= (int)GeneralBitFlags.UnicodeText; - } - else - { - flags &= ~(int)GeneralBitFlags.UnicodeText; - } - } + get => this.HasFlag(GeneralBitFlags.UnicodeText); + set => this.SetFlag(GeneralBitFlags.UnicodeText, value); } /// @@ -324,15 +292,8 @@ public bool IsUnicodeText /// internal byte CryptoCheckValue { - get - { - return cryptoCheckValue_; - } - - set - { - cryptoCheckValue_ = value; - } + get => cryptoCheckValue_; + set => cryptoCheckValue_ = value; } /// @@ -368,14 +329,8 @@ internal byte CryptoCheckValue /// public int Flags { - get - { - return flags; - } - set - { - flags = value; - } + get => flags; + set => flags = value; } /// @@ -384,14 +339,8 @@ public int Flags /// This is only valid when the entry is part of a public long ZipFileIndex { - get - { - return zipFileIndex; - } - set - { - zipFileIndex = value; - } + get => zipFileIndex; + set => zipFileIndex = value; } /// @@ -399,34 +348,18 @@ public long ZipFileIndex /// public long Offset { - get - { - return offset; - } - set - { - offset = value; - } + get => offset; + set => offset = value; } /// /// Get/Set external file attributes as an integer. - /// The values of this are operating system dependant see + /// The values of this are operating system dependent see /// HostSystem for details /// public int ExternalFileAttributes { - get - { - if ((known & Known.ExternalAttributes) == 0) - { - return -1; - } - else - { - return externalFileAttributes; - } - } + get => (known & Known.ExternalAttributes) == 0 ? -1 : externalFileAttributes; set { @@ -440,25 +373,14 @@ public int ExternalFileAttributes /// The value / 10 indicates the major version number, and /// the value mod 10 is the minor version number /// - public int VersionMadeBy - { - get - { - return (versionMadeBy & 0xff); - } - } + public int VersionMadeBy => versionMadeBy & 0xff; /// /// Get a value indicating this entry is for a DOS/Windows system. /// public bool IsDOSEntry - { - get - { - return ((HostSystem == (int)HostSystemID.Msdos) || - (HostSystem == (int)HostSystemID.WindowsNT)); - } - } + => (HostSystem == (int)HostSystemID.Msdos) + || (HostSystem == (int)HostSystemID.WindowsNT); /// /// Test the external attributes for this to @@ -481,7 +403,7 @@ private bool HasDosAttributes(int attributes) } /// - /// Gets the compatability information for the external file attribute + /// Gets the compatibility information for the external file attribute /// If the external file attributes are compatible with MS-DOS and can be read /// by PKZIP for DOS version 2.04g then this value will be zero. Otherwise the value /// will be non-zero and identify the host system on which the attributes are compatible. @@ -519,10 +441,7 @@ private bool HasDosAttributes(int attributes) /// public int HostSystem { - get - { - return (versionMadeBy >> 8) & 0xff; - } + get => (versionMadeBy >> 8) & 0xff; set { @@ -567,42 +486,26 @@ public int Version { // Return recorded version if known. if (versionToExtract != 0) - { - return versionToExtract & 0x00ff; // Only lower order byte. High order is O/S file system. - } - else - { - int result = 10; - if (AESKeySize > 0) - { - result = ZipConstants.VERSION_AES; // Ver 5.1 = AES - } - else if (CentralHeaderRequiresZip64) - { - result = ZipConstants.VersionZip64; - } - else if (CompressionMethod.Deflated == method) - { - result = 20; - } - else if (CompressionMethod.BZip2 == method) - { - result = ZipConstants.VersionBZip2; - } - else if (IsDirectory == true) - { - result = 20; - } - else if (IsCrypted == true) - { - result = 20; - } - else if (HasDosAttributes(0x08)) - { - result = 11; - } - return result; - } + // Only lower order byte. High order is O/S file system. + return versionToExtract & 0x00ff; + + if (AESKeySize > 0) + // Ver 5.1 = AES + return ZipConstants.VERSION_AES; + + if (CompressionMethod.BZip2 == method) + return ZipConstants.VersionBZip2; + + if (CentralHeaderRequiresZip64) + return ZipConstants.VersionZip64; + + if (CompressionMethod.Deflated == method || IsDirectory || IsCrypted) + return 20; + + if (HasDosAttributes(0x08)) + return 11; + + return 10; } } @@ -611,37 +514,21 @@ public int Version /// /// This is based on the and /// whether the compression method is supported. - public bool CanDecompress - { - get - { - return (Version <= ZipConstants.VersionMadeBy) && - ((Version == 10) || - (Version == 11) || - (Version == 20) || - (Version == 45) || - (Version == 46) || - (Version == 51)) && - IsCompressionMethodSupported(); - } - } + public bool CanDecompress + => Version <= ZipConstants.VersionMadeBy + && (Version == 10 || Version == 11 || Version == 20 || Version == 45 || Version == 46 || Version == 51) + && IsCompressionMethodSupported(); /// /// Force this entry to be recorded using Zip64 extensions. /// - public void ForceZip64() - { - forceZip64_ = true; - } + public void ForceZip64() => forceZip64_ = true; /// /// Get a value indicating whether Zip64 extensions were forced. /// /// A value of true if Zip64 extensions have been forced on; false if not. - public bool IsZip64Forced() - { - return forceZip64_; - } + public bool IsZip64Forced() => forceZip64_; /// /// Gets a value indicating if the entry requires Zip64 extensions @@ -677,13 +564,8 @@ public bool LocalHeaderRequiresZip64 /// /// Get a value indicating whether the central directory entry requires Zip64 extensions to be stored. /// - public bool CentralHeaderRequiresZip64 - { - get - { - return LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); - } - } + public bool CentralHeaderRequiresZip64 + => LocalHeaderRequiresZip64 || (offset >= uint.MaxValue); /// /// Get/Set DosTime value. @@ -699,41 +581,39 @@ public long DosTime { return 0; } - else - { - var year = (uint)DateTime.Year; - var month = (uint)DateTime.Month; - var day = (uint)DateTime.Day; - var hour = (uint)DateTime.Hour; - var minute = (uint)DateTime.Minute; - var second = (uint)DateTime.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; - } - return ((year - 1980) & 0x7f) << 25 | - (month << 21) | - (day << 16) | - (hour << 11) | - (minute << 5) | - (second >> 1); + var year = (uint)DateTime.Year; + var month = (uint)DateTime.Month; + var day = (uint)DateTime.Day; + var hour = (uint)DateTime.Hour; + var minute = (uint)DateTime.Minute; + var second = (uint)DateTime.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; + } + + return ((year - 1980) & 0x7f) << 25 | + (month << 21) | + (day << 16) | + (hour << 11) | + (minute << 5) | + (second >> 1); } set @@ -760,10 +640,7 @@ public long DosTime /// public DateTime DateTime { - get - { - return dateTime; - } + get => dateTime; set { @@ -783,15 +660,8 @@ public DateTime DateTime /// public string Name { - get - { - return name; - } - - internal set - { - name = value; - } + get => name; + internal set => name = value; } /// @@ -801,17 +671,14 @@ internal set /// The size or -1 if unknown. /// /// Setting the size before adding an entry to an archive can help - /// avoid compatability problems with some archivers which dont understand Zip64 extensions. + /// avoid compatibility problems with some archivers which don't understand Zip64 extensions. public long Size { - get - { - return (known & Known.Size) != 0 ? (long)size : -1L; - } + get => (known & Known.Size) != 0 ? (long)size : -1L; set { - this.size = (ulong)value; - this.known |= Known.Size; + size = (ulong)value; + known |= Known.Size; } } @@ -823,14 +690,11 @@ public long Size /// public long CompressedSize { - get - { - return (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; - } + get => (known & Known.CompressedSize) != 0 ? (long)compressedSize : -1L; set { - this.compressedSize = (ulong)value; - this.known |= Known.CompressedSize; + compressedSize = (ulong)value; + known |= Known.CompressedSize; } } @@ -845,13 +709,10 @@ public long CompressedSize /// public long Crc { - get - { - return (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; - } + get => (known & Known.Crc) != 0 ? crc & 0xffffffffL : -1L; set { - if (((ulong)crc & 0xffffffff00000000L) != 0) + if ((crc & 0xffffffff00000000L) != 0) { throw new ArgumentOutOfRangeException(nameof(value)); } @@ -861,28 +722,19 @@ public long Crc } /// - /// Gets/Sets the compression method. Only Deflated and Stored are supported. + /// Gets/Sets the compression method. /// + /// Throws exception when set if the method is not valid as per + /// /// /// The compression method for this entry /// - /// - /// public CompressionMethod CompressionMethod { - get - { - return method; - } - - set - { - if (!IsCompressionMethodSupported(value)) - { - throw new NotSupportedException("Compression method not supported"); - } - this.method = value; - } + get => method; + set => method = !IsCompressionMethodSupported(value) + ? throw new NotSupportedException("Compression method not supported") + : value; } /// @@ -890,13 +742,8 @@ public CompressionMethod CompressionMethod /// Returns same value as CompressionMethod except when AES encrypting, which /// places 99 in the method and places the real method in the extra data. /// - internal CompressionMethod CompressionMethodForHeader - { - get - { - return (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; - } - } + internal CompressionMethod CompressionMethodForHeader + => (AESKeySize > 0) ? CompressionMethod.WinZipAES : method; /// /// Gets/Sets the extra data. @@ -909,12 +756,9 @@ internal CompressionMethod CompressionMethodForHeader /// public byte[] ExtraData { - get - { - // TODO: This is slightly safer but less efficient. Think about whether it should change. - // return (byte[]) extra.Clone(); - return extra; - } + // TODO: This is slightly safer but less efficient. Think about whether it should change. + // return (byte[]) extra.Clone(); + get => extra; set { @@ -986,62 +830,38 @@ public int AESKeySize /// AES Encryption strength for storage in extra data in entry header. /// 1 is 128 bit, 2 is 192 bit, 3 is 256 bit. /// - internal byte AESEncryptionStrength - { - get - { - return (byte)_aesEncryptionStrength; - } - } + internal byte AESEncryptionStrength => (byte)_aesEncryptionStrength; /// /// Returns the length of the salt, in bytes /// - internal int AESSaltLen - { - get - { - // Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. - return AESKeySize / 16; - } - } + /// Key size -> Salt length: 128 bits = 8 bytes, 192 bits = 12 bytes, 256 bits = 16 bytes. + internal int AESSaltLen => AESKeySize / 16; /// /// Number of extra bytes required to hold the AES Header fields (Salt, Pwd verify, AuthCode) /// - internal int AESOverheadSize - { - get - { - // File format: - // Bytes Content - // Variable Salt value - // 2 Password verification value - // Variable Encrypted file data - // 10 Authentication code - return 12 + AESSaltLen; - } - } + /// File format: + /// Bytes | Content + /// ---------+--------------------------- + /// Variable | Salt value + /// 2 | Password verification value + /// Variable | Encrypted file data + /// 10 | Authentication code + internal int AESOverheadSize => 12 + AESSaltLen; /// /// Number of extra bytes required to hold the encryption header fields. /// - internal int EncryptionOverheadSize - { - get - { + internal int EncryptionOverheadSize => + !IsCrypted // Entry is not encrypted - no overhead - if (!this.IsCrypted) - return 0; - - // Entry is encrypted using ZipCrypto - if (_aesEncryptionStrength == 0) - return ZipConstants.CryptoHeaderSize; - - // Entry is encrypted using AES - return this.AESOverheadSize; - } - } + ? 0 + : _aesEncryptionStrength == 0 + // Entry is encrypted using ZipCrypto + ? ZipConstants.CryptoHeaderSize + // Entry is encrypted using AES + : AESOverheadSize; /// /// Process extra data fields updating the entry based on the contents. @@ -1144,7 +964,6 @@ internal void ProcessExtraData(bool localHeader) } // For AES the method in the entry is 99, and the real compression method is in the extradata - // private void ProcessAESExtraData(ZipExtraData extraData) { if (extraData.Find(0x9901)) @@ -1172,7 +991,7 @@ private void ProcessAESExtraData(ZipExtraData extraData) /// /// Gets/Sets the entry comment. /// - /// + /// /// If comment is longer than 0xffff. /// /// @@ -1180,14 +999,11 @@ private void ProcessAESExtraData(ZipExtraData extraData) /// /// /// A comment is only available for entries when read via the class. - /// The class doesnt have the comment data available. + /// The class doesn't have the comment data available. /// public string Comment { - get - { - return comment; - } + get => comment; set { // This test is strictly incorrect as the length is in characters @@ -1196,7 +1012,7 @@ public string Comment // 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. + // isn't available. if ((value != null) && (value.Length > 0xffff)) { throw new ArgumentOutOfRangeException(nameof(value), "cannot exceed 65535"); @@ -1216,19 +1032,9 @@ public string Comment /// Currently only dos/windows attributes are tested in this manner. /// The trailing slash convention should always be followed. /// - public bool IsDirectory - { - get - { - int nameLength = name.Length; - bool result = - ((nameLength > 0) && - ((name[nameLength - 1] == '/') || (name[nameLength - 1] == '\\'))) || - HasDosAttributes(16) - ; - return result; - } - } + public bool IsDirectory + => name.Length > 0 + && (name[name.Length - 1] == '/' || name[name.Length - 1] == '\\') || HasDosAttributes(16); /// /// Get a value of true if the entry appears to be a file; false otherwise @@ -1237,22 +1043,13 @@ public bool IsDirectory /// This only takes account of DOS/Windows attributes. Other operating systems are ignored. /// For linux and others the result may be incorrect. /// - public bool IsFile - { - get - { - return !IsDirectory && !HasDosAttributes(8); - } - } + public bool IsFile => !IsDirectory && !HasDosAttributes(8); /// /// Test entry to see if data can be extracted. /// /// Returns true if data can be extracted for this entry; false otherwise. - public bool IsCompressionMethodSupported() - { - return IsCompressionMethodSupported(CompressionMethod); - } + public bool IsCompressionMethodSupported() => IsCompressionMethodSupported(CompressionMethod); #region ICloneable Members @@ -1280,10 +1077,7 @@ public object Clone() /// Gets a string representation of this ZipEntry. /// /// A readable textual representation of this - public override string ToString() - { - return name; - } + public override string ToString() => name; /// /// Test a compression method to see if this library @@ -1291,13 +1085,10 @@ public override string ToString() /// /// The compression method to test. /// Returns true if the compression method is supported; false otherwise - public static bool IsCompressionMethodSupported(CompressionMethod method) - { - return - (method == CompressionMethod.Deflated) || - (method == CompressionMethod.Stored) || - (method == CompressionMethod.BZip2); - } + public static bool IsCompressionMethodSupported(CompressionMethod method) + => method == CompressionMethod.Deflated + || method == CompressionMethod.Stored + || method == CompressionMethod.BZip2; /// /// Cleans a name making it conform to Zip file conventions. diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs new file mode 100644 index 000000000..927e94cfe --- /dev/null +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipEntryExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ICSharpCode.SharpZipLib.Zip +{ + /// + /// General ZipEntry helper extensions + /// + public static class ZipEntryExtensions + { + /// + /// Efficiently check if a flag is set without enum un-/boxing + /// + /// + /// + /// Returns whether the flag was set + public static bool HasFlag(this ZipEntry entry, GeneralBitFlags flag) + => (entry.Flags & (int) flag) != 0; + + /// + /// Efficiently set a flag without enum un-/boxing + /// + /// + /// + /// Whether the passed flag should be set (1) or cleared (0) + public static void SetFlag(this ZipEntry entry, GeneralBitFlags flag, bool enabled = true) + => entry.Flags = enabled + ? entry.Flags | (int) flag + : entry.Flags & ~(int) flag; + } +} diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs index 66a3fc872..cccac6639 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs @@ -126,14 +126,15 @@ public string Password /// /// The entry can only be decompressed if the library supports the zip features required to extract it. /// See the ZipEntry Version property for more details. + /// + /// Since uses the local headers for extraction, entries with no compression combined with the + /// flag set, cannot be extracted as the end of the entry data cannot be deduced. /// - public bool CanDecompressEntry - { - get - { - return (entry != null) && IsEntryCompressionMethodSupported(entry) && entry.CanDecompress; - } - } + public bool CanDecompressEntry + => entry != null + && IsEntryCompressionMethodSupported(entry) + && entry.CanDecompress + && (!entry.HasFlag(GeneralBitFlags.Descriptor) || entry.CompressionMethod != CompressionMethod.Stored || entry.IsCrypted); /// /// Is the compression method for the specified entry supported? @@ -142,7 +143,7 @@ public bool CanDecompressEntry /// Uses entry.CompressionMethodForHeader so that entries of type WinZipAES will be rejected. /// /// the entry to check. - /// true if the compression methiod is supported, false if not. + /// true if the compression method is supported, false if not. private static bool IsEntryCompressionMethodSupported(ZipEntry entry) { var entryCompressionMethod = entry.CompressionMethodForHeader; @@ -493,6 +494,14 @@ private int ReadingNotSupported(byte[] destination, int offset, int count) throw new ZipException("The compression method for this entry is not supported"); } + /// + /// Handle attempts to read from this entry by throwing an exception + /// + private int StoredDescriptorEntry(byte[] destination, int offset, int count) => + throw new StreamUnsupportedException( + "The combination of Stored compression method and Descriptor flag is not possible to read using ZipInputStream"); + + /// /// Perform the initial read on an entry which may include /// reading encryption headers and setting up inflation. @@ -503,10 +512,7 @@ private int ReadingNotSupported(byte[] destination, int offset, int count) /// The actual number of bytes read. private int InitialRead(byte[] destination, int offset, int count) { - if (!CanDecompressEntry) - { - throw new ZipException("Library cannot extract this entry. Version required is (" + entry.Version + ")"); - } + var usesDescriptor = (entry.Flags & (int)GeneralBitFlags.Descriptor) != 0; // Handle encryption if required. if (entry.IsCrypted) @@ -534,9 +540,9 @@ private int InitialRead(byte[] destination, int offset, int count) { csize -= ZipConstants.CryptoHeaderSize; } - else if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0) + else if (!usesDescriptor) { - throw new ZipException(string.Format("Entry compressed size {0} too small for encryption", csize)); + throw new ZipException($"Entry compressed size {csize} too small for encryption"); } } else @@ -544,21 +550,33 @@ private int InitialRead(byte[] destination, int offset, int count) inputBuffer.CryptoTransform = null; } - if ((csize > 0) || ((flags & (int)GeneralBitFlags.Descriptor) != 0)) + if (csize > 0 || usesDescriptor) { - if ((method == CompressionMethod.Deflated) && (inputBuffer.Available > 0)) + if (method == CompressionMethod.Deflated && inputBuffer.Available > 0) { inputBuffer.SetInflaterInput(inf); } - internalReader = new ReadDataHandler(BodyRead); + // It's not possible to know how many bytes to read when using "Stored" compression (unless using encryption) + if (!entry.IsCrypted && method == CompressionMethod.Stored && usesDescriptor) + { + internalReader = StoredDescriptorEntry; + return StoredDescriptorEntry(destination, offset, count); + } + + if (!CanDecompressEntry) + { + internalReader = ReadingNotSupported; + return ReadingNotSupported(destination, offset, count); + } + + internalReader = BodyRead; return BodyRead(destination, offset, count); } - else - { - internalReader = new ReadDataHandler(ReadingNotAvailable); - return 0; - } + + + internalReader = ReadingNotAvailable; + return 0; } /// diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs index 8be25a4dc..19da3adf6 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/FastZipHandling.cs @@ -227,20 +227,14 @@ public void Encryption(ZipEncryptionMethod encryptionMethod) [Category("Zip")] public void CreateExceptions() { - var fastZip = new FastZip(); - string tempFilePath = GetTempFilePath(); - Assert.IsNotNull(tempFilePath, "No permission to execute this test?"); - Assert.Throws(() => { - string addFile = Path.Combine(tempFilePath, "test.zip"); - try - { - fastZip.CreateZip(addFile, @"z:\doesnt exist", false, null); - } - finally + using (var tempDir = new Utils.TempDir()) { - File.Delete(addFile); + var fastZip = new FastZip(); + var badPath = Path.Combine(Path.GetTempPath(), Utils.GetDummyFileName()); + var addFile = Path.Combine(tempDir.Fullpath, "test.zip"); + fastZip.CreateZip(addFile, badPath, false, null); } }); } diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs index 60d7a5709..7a336592a 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/StreamHandling.cs @@ -520,5 +520,41 @@ public void AddingAnAESEntryWithNoPasswordShouldThrow() } } } + + [Test] + [Category("Zip")] + public void ShouldThrowDescriptiveExceptionOnUncompressedDescriptorEntry() + { + using (var ms = new MemoryStreamWithoutSeek()) + { + using (var zos = new ZipOutputStream(ms)) + { + zos.IsStreamOwner = false; + var entry = new ZipEntry("testentry"); + entry.CompressionMethod = CompressionMethod.Stored; + entry.Flags |= (int)GeneralBitFlags.Descriptor; + zos.PutNextEntry(entry); + zos.Write(new byte[1], 0, 1); + zos.CloseEntry(); + } + + // Patch the Compression Method, since ZipOutputStream automatically changes it to Deflate when descriptors are used + ms.Seek(8, SeekOrigin.Begin); + ms.WriteByte((byte)CompressionMethod.Stored); + ms.Seek(0, SeekOrigin.Begin); + + using (var zis = new ZipInputStream(ms)) + { + zis.IsStreamOwner = false; + var buf = new byte[32]; + zis.GetNextEntry(); + + Assert.Throws(typeof(StreamUnsupportedException), () => + { + zis.Read(buf, 0, buf.Length); + }); + } + } + } } }