From d55456a3be75c628a14c5a58e707f763948edf89 Mon Sep 17 00:00:00 2001 From: Richard Webb Date: Thu, 24 Jan 2019 22:30:31 +0000 Subject: [PATCH] Wire up BZip2 compression support for Zip files. --- .../Zip/ZipConstants.cs | 5 + src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs | 8 +- src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs | 16 +- .../Zip/GeneralHandling.cs | 2 +- .../Zip/ZipFileHandling.cs | 144 ++++++++++++++++++ 5 files changed, 172 insertions(+), 3 deletions(-) diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs index cc2fd27d2..b0f33a764 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipConstants.cs @@ -281,6 +281,11 @@ public static class ZipConstants /// public const int VersionZip64 = 45; + /// + /// The version required for BZip2 compression (4.6 or higher) + /// + public const int VersionBZip2 = 46; + #endregion Versions #region Header Sizes diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs index d6e4b98fa..3baf8415d 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipEntry.cs @@ -585,6 +585,10 @@ public int Version { result = 20; } + else if (CompressionMethod.BZip2 == method) + { + result = ZipConstants.VersionBZip2; + } else if (IsDirectory == true) { result = 20; @@ -616,6 +620,7 @@ public bool CanDecompress (Version == 11) || (Version == 20) || (Version == 45) || + (Version == 46) || (Version == 51)) && IsCompressionMethodSupported(); } @@ -1290,7 +1295,8 @@ public static bool IsCompressionMethodSupported(CompressionMethod method) { return (method == CompressionMethod.Deflated) || - (method == CompressionMethod.Stored); + (method == CompressionMethod.Stored) || + (method == CompressionMethod.BZip2); } /// diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs index c5c05afad..e7fb41978 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs @@ -883,6 +883,10 @@ public Stream GetInputStream(long entryIndex) result = new InflaterInputStream(result, new Inflater(true)); break; + case CompressionMethod.BZip2: + result = new BZip2.BZip2InputStream(result); + break; + default: throw new ZipException("Unsupported compression method " + method); } @@ -1899,7 +1903,7 @@ public void AddDirectory(string directoryName) /// The compression method for the new entry. private void CheckSupportedCompressionMethod(CompressionMethod compressionMethod) { - if (compressionMethod != CompressionMethod.Deflated && compressionMethod != CompressionMethod.Stored) + if (compressionMethod != CompressionMethod.Deflated && compressionMethod != CompressionMethod.Stored && compressionMethod != CompressionMethod.BZip2) { throw new NotImplementedException("Compression method not supported"); } @@ -2629,6 +2633,16 @@ private Stream GetOutputStream(ZipEntry entry) result = dos; break; + case CompressionMethod.BZip2: + var bzos = new BZip2.BZip2OutputStream(result) + { + // If there is an encryption stream in use, then we want that to be disposed when the BZip2OutputStream stream is disposed + // If not, then we don't want it to dispose the base stream + IsStreamOwner = entry.IsCrypted + }; + result = bzos; + break; + default: throw new ZipException("Unknown compression method " + entry.CompressionMethod); } diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs index b0d22c9bc..b74ed1ddc 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs @@ -141,7 +141,7 @@ public void UnsupportedCompressionMethod() var ze = new ZipEntry("HumblePie"); //ze.CompressionMethod = CompressionMethod.BZip2; - Assert.That(() => ze.CompressionMethod = CompressionMethod.BZip2, + Assert.That(() => ze.CompressionMethod = CompressionMethod.Deflate64, Throws.TypeOf()); } diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs index 996b09213..16dad656d 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipFileHandling.cs @@ -1611,5 +1611,149 @@ public void AddFileWithAlternateName() } } } + + /// + /// Test a zip file using BZip2 compression. + /// + [TestCase(true)] + [TestCase(false)] + [Category("Zip")] + public void ZipWithBZip2Compression(bool encryptEntries) + { + string password = "pwd"; + + using (var memStream = new MemoryStream()) + { + using (ZipFile f = new ZipFile(memStream, leaveOpen: true)) + { + if (encryptEntries) + f.Password = password; + + f.BeginUpdate(new MemoryArchiveStorage()); + + var m = new StringMemoryDataSource("BZip2Compressed"); + f.Add(m, "a.dat", CompressionMethod.BZip2); + + var m2 = new StringMemoryDataSource("DeflateCompressed"); + f.Add(m2, "b.dat", CompressionMethod.Deflated); + f.CommitUpdate(); + Assert.IsTrue(f.TestArchive(true)); + } + + memStream.Seek(0, SeekOrigin.Begin); + + using (ZipFile f = new ZipFile(memStream)) + { + if (encryptEntries) + f.Password = password; + + { + var entry = f.GetEntry("a.dat"); + Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.BZip2), "Compression method should be BZip2"); + Assert.That(entry.Version, Is.EqualTo(ZipConstants.VersionBZip2), "Entry version should be 46"); + Assert.That(entry.IsCrypted, Is.EqualTo(encryptEntries)); + + using (var reader = new StreamReader(f.GetInputStream(entry))) + { + string contents = reader.ReadToEnd(); + Assert.That(contents, Is.EqualTo("BZip2Compressed"), "extract string must match original string"); + } + } + + { + var entry = f.GetEntry("b.dat"); + Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.Deflated), "Compression method should be Deflated"); + Assert.That(entry.IsCrypted, Is.EqualTo(encryptEntries)); + + using (var reader = new StreamReader(f.GetInputStream(entry))) + { + string contents = reader.ReadToEnd(); + Assert.That(contents, Is.EqualTo("DeflateCompressed"), "extract string must match original string"); + } + } + } + + // @@TODO@@ verify the archive with 7-zip? + } + } + + /// + /// We should be able to read a bzip2 compressed zip file created by 7-zip. + /// + [Test] + [Category("Zip")] + public void ShouldReadBZip2ZipCreatedBy7Zip() + { + const string BZip2CompressedZipCreatedBy7Zip = + "UEsDBC4AAAAMAIa50U4/rHf5qwAAAK8AAAAJAAAASGVsbG8udHh0QlpoOTFBWSZTWTL8pwYAA" + + "BWfgEhlUAAiLUgQP+feMCAAiCKaeiaBobU9JiaAMGmoak9GmRNqPUDQ9T1PQsz/t9B6YvEdvF" + + "5dhwXzGE1ooO41A6TtATBEFxFUq6trGtUcSJDyWWWj/S2VwY15fy3IqHi3hHUS+K76zdoDzQa" + + "VGE/4YkYZe3JAtv1EsIqIsiTnnktIbBo1R4xY3JZEOm2BvwLuSKcKEgZflODAUEsBAj8ALgAA" + + "AAwAhrnRTj+sd/mrAAAArwAAAAkAJAAAAAAAAAAgAAAAAAAAAEhlbGxvLnR4dAoAIAAAAAAAA" + + "QAYAO97MLZZJdUB73swtlkl1QEK0UTFWCXVAVBLBQYAAAAAAQABAFsAAADSAAAAAAA="; + + const string OriginalText = + "SharpZipLib (#ziplib, formerly NZipLib) is a compression library that supports Zip files using both stored and deflate compression methods, PKZIP 2.0 style and AES encryption."; + + var fileBytes = System.Convert.FromBase64String(BZip2CompressedZipCreatedBy7Zip); + + using (var input = new MemoryStream(fileBytes, false)) + { + using (ZipFile f = new ZipFile(input)) + { + var entry = f.GetEntry("Hello.txt"); + Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.BZip2), "Compression method should be BZip2"); + Assert.That(entry.Version, Is.EqualTo(ZipConstants.VersionBZip2), "Entry version should be 46"); + + using (var reader = new StreamReader(f.GetInputStream(entry))) + { + string contents = reader.ReadToEnd(); + Assert.That(contents, Is.EqualTo(OriginalText), "extract string must match original string"); + } + } + } + } + + /// + /// We should be able to read a bzip2 compressed / AES encrypted zip file created by 7-zip. + /// + [Test] + [Category("Zip")] + public void ShouldReadAESBZip2ZipCreatedBy7Zip() + { + const string BZip2CompressedZipCreatedBy7Zip = + "UEsDBDMAAQBjAIa50U4AAAAAxwAAAK8AAAAJAAsASGVsbG8udHh0AZkHAAIAQUUDDAAYg6jqf" + + "kvZClVMOtgmqKT0/8I9fMPgo96myxw9hLQUhKj1Qczi3fT7QIhAnAKU+u03nA8rCKGWmDI5Qz" + + "qPREy95boQVDPwmwEsWksv3GAWzMfzZUhmB/TgIJlA34a4yP0f2ucy3/QCQYo8QcHjBtjWX5b" + + "dZn0+fwY9Ci7q8JSI8zNSbgQ0Ert/lIJ9MxQ4lzBxMl4LySkd104cDPh/FslTAcPtHoy8Mf1c" + + "vnI1uICMgjWVeTqYrvSvt2uuHnqr4AiehArFiXTnUEsBAj8AMwABAGMAhrnRTgAAAADHAAAAr" + + "wAAAAkALwAAAAAAAAAgAAAAAAAAAEhlbGxvLnR4dAoAIAAAAAAAAQAYAO97MLZZJdUBYdnjul" + + "kl1QEK0UTFWCXVAQGZBwACAEFFAwwAUEsFBgAAAAABAAEAZgAAAPkAAAAAAA=="; + + const string OriginalText = + "SharpZipLib (#ziplib, formerly NZipLib) is a compression library that supports Zip files using both stored and deflate compression methods, PKZIP 2.0 style and AES encryption."; + + var fileBytes = System.Convert.FromBase64String(BZip2CompressedZipCreatedBy7Zip); + + using (var input = new MemoryStream(fileBytes, false)) + { + using (ZipFile f = new ZipFile(input)) + { + f.Password = "password"; + + var entry = f.GetEntry("Hello.txt"); + Assert.That(entry.CompressionMethod, Is.EqualTo(CompressionMethod.BZip2), "Compression method should be BZip2"); + Assert.That(entry.Version, Is.EqualTo(ZipConstants.VERSION_AES), "Entry version should be 51"); + Assert.That(entry.IsCrypted, Is.True, "Entry should be encrypted"); + Assert.That(entry.AESKeySize, Is.EqualTo(256), "AES Keysize should be 256"); + + using (var reader = new StreamReader(f.GetInputStream(entry))) + { + string contents = reader.ReadToEnd(); + Assert.That(contents, Is.EqualTo(OriginalText), "extract string must match original string"); + } + } + } + } } }