From a389d9f521dc5ddeae575511dc918b22baab22a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nils=20m=C3=A5s=C3=A9n?= Date: Thu, 20 Oct 2022 14:14:45 +0200 Subject: [PATCH] fix(zip): cleanup/fix of StringCodec (#778) --- .../Streams/DeflaterOutputStream.cs | 10 +- src/ICSharpCode.SharpZipLib/Zip/FastZip.cs | 7 +- src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs | 23 +- src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs | 2 +- .../Zip/ZipInputStream.cs | 18 ++ .../Zip/ZipOutputStream.cs | 14 +- src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs | 141 +++++++---- .../Zip/GeneralHandling.cs | 61 +---- .../Zip/ZipStringsTests.cs | 239 ++++++++++++++++++ .../Zip/ZipTests.cs | 2 + 10 files changed, 399 insertions(+), 118 deletions(-) create mode 100644 test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStringsTests.cs diff --git a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs index 0afd438c6..f448e0fad 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/Compression/Streams/DeflaterOutputStream.cs @@ -205,7 +205,12 @@ public bool CanPatchEntries protected byte[] AESAuthCode; /// - public Encoding ZipCryptoEncoding { get; set; } = StringCodec.DefaultZipCryptoEncoding; + public Encoding ZipCryptoEncoding { + get => _stringCodec.ZipCryptoEncoding; + set { + _stringCodec = _stringCodec.WithZipCryptoEncoding(value); + } + } /// /// Encrypt a block of data @@ -508,6 +513,9 @@ public override void Write(byte[] buffer, int offset, int count) private bool isClosed_; + /// + protected StringCodec _stringCodec = ZipStrings.GetStringCodec(); + #endregion Instance Fields } } diff --git a/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs b/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs index 29185cbec..baa1771cb 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/FastZip.cs @@ -358,7 +358,7 @@ public bool UseUnicode public int LegacyCodePage { get => _stringCodec.CodePage; - set => _stringCodec.CodePage = value; + set => _stringCodec = StringCodec.FromCodePage(value); } /// @@ -579,7 +579,7 @@ public void ExtractZip(string zipFileName, string targetDirectory, string fileFi directoryFilter_ = new NameFilter(directoryFilter); restoreDateTimeOnExtract_ = restoreDateTime; - using (zipFile_ = new ZipFile(inputStream, !isStreamOwner)) + using (zipFile_ = new ZipFile(inputStream, !isStreamOwner, _stringCodec)) { if (password_ != null) { @@ -994,8 +994,7 @@ private static bool NameIsValid(string name) private INameTransform extractNameTransform_; private UseZip64 useZip64_ = UseZip64.Dynamic; private CompressionLevel compressionLevel_ = CompressionLevel.DEFAULT_COMPRESSION; - private StringCodec _stringCodec = new StringCodec(); - + private StringCodec _stringCodec = ZipStrings.GetStringCodec(); private string password_; #endregion Instance Fields diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs index 3abe9516b..69bb9f6a9 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFile.cs @@ -7,6 +7,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Security.Cryptography; using System.Text; @@ -504,6 +505,7 @@ public ZipFile(FileStream file, bool leaveOpen) /// /// The to read archive data from. /// true to leave the stream open when the ZipFile is disposed, false to dispose of it + /// /// /// An i/o error occurs /// @@ -516,7 +518,7 @@ public ZipFile(FileStream file, bool leaveOpen) /// /// The stream argument is null. /// - public ZipFile(Stream stream, bool leaveOpen) + public ZipFile(Stream stream, bool leaveOpen, StringCodec stringCodec = null) { if (stream == null) { @@ -531,6 +533,11 @@ public ZipFile(Stream stream, bool leaveOpen) baseStream_ = stream; isStreamOwner = !leaveOpen; + if (stringCodec != null) + { + _stringCodec = stringCodec; + } + if (baseStream_.Length > 0) { try @@ -736,14 +743,20 @@ public long Count public Encoding ZipCryptoEncoding { get => _stringCodec.ZipCryptoEncoding; - set => _stringCodec.ZipCryptoEncoding = value; + set => _stringCodec = _stringCodec.WithZipCryptoEncoding(value); } /// public StringCodec StringCodec { - get => _stringCodec; - set => _stringCodec = value; + set { + _stringCodec = value; + if (!isNewArchive_) + { + // Since the string codec was changed + ReadEntries(); + } + } } #endregion Properties @@ -1592,7 +1605,7 @@ public void CommitUpdate() { RunUpdates(); } - else if (commentEdited_) + else if (commentEdited_ && !isNewArchive_) { UpdateCommentOnly(); } diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs index 905d856c8..ec63d7943 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipFormat.cs @@ -95,7 +95,7 @@ internal static class ZipFormat } } - byte[] name = stringCodec.ZipOutputEncoding.GetBytes(entry.Name); + byte[] name = stringCodec.ZipEncoding(entry.IsUnicodeText).GetBytes(entry.Name); if (name.Length > 0xFFFF) { diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs index e49ebddfb..ddfd9086b 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipInputStream.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; namespace ICSharpCode.SharpZipLib.Zip { @@ -104,6 +105,21 @@ public ZipInputStream(Stream baseInputStream, int bufferSize) internalReader = new ReadDataHandler(ReadingNotAvailable); } + /// + /// Creates a new Zip input stream, for reading a zip archive. + /// + /// The underlying providing data. + /// + public ZipInputStream(Stream baseInputStream, StringCodec stringCodec) + : base(baseInputStream, new Inflater(true)) + { + internalReader = new ReadDataHandler(ReadingNotAvailable); + if (stringCodec != null) + { + _stringCodec = stringCodec; + } + } + #endregion Constructors /// @@ -558,7 +574,9 @@ private int InitialRead(byte[] destination, int offset, int count) // Generate and set crypto transform... var managed = new PkzipClassicManaged(); + Console.WriteLine($"Input Encoding: {_stringCodec.ZipCryptoEncoding.EncodingName}"); byte[] key = PkzipClassic.GenerateKeys(_stringCodec.ZipCryptoEncoding.GetBytes(password)); + Console.WriteLine($"Input Bytes: {string.Join(", ", key.Select(b => $"{b:x2}").ToArray())}"); inputBuffer.CryptoTransform = managed.CreateDecryptor(key, null); diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs index e21d7fb54..3f4d0240b 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipOutputStream.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; @@ -80,7 +81,12 @@ public ZipOutputStream(Stream baseOutputStream, int bufferSize) { } - internal ZipOutputStream(Stream baseOutputStream, StringCodec stringCodec) : this(baseOutputStream) + /// + /// Creates a new Zip output stream, writing a zip archive. + /// + /// The output stream to which the archive contents are written. + /// + public ZipOutputStream(Stream baseOutputStream, StringCodec stringCodec) : this(baseOutputStream) { _stringCodec = stringCodec; } @@ -160,7 +166,7 @@ public UseZip64 UseZip64 /// Defaults to , set to null to disable transforms and use names as supplied. /// public INameTransform NameTransform { get; set; } = new PathTransformer(); - + /// /// Get/set the password used for encryption. /// @@ -742,7 +748,9 @@ private byte[] CreateZipCryptoHeader(long crcValue) private void InitializeZipCryptoPassword(string password) { var pkManaged = new PkzipClassicManaged(); + Console.WriteLine($"Output Encoding: {ZipCryptoEncoding.EncodingName}"); byte[] key = PkzipClassic.GenerateKeys(ZipCryptoEncoding.GetBytes(password)); + Console.WriteLine($"Output Bytes: {string.Join(", ", key.Select(b => $"{b:x2}").ToArray())}"); cryptoTransform_ = pkManaged.CreateEncryptor(key, null); } @@ -970,8 +978,6 @@ public override void Flush() /// private string password; - private readonly StringCodec _stringCodec = ZipStrings.GetStringCodec(); - #endregion Instance Fields #region Static Fields diff --git a/src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs b/src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs index 29fa98014..3eab416ef 100644 --- a/src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs +++ b/src/ICSharpCode.SharpZipLib/Zip/ZipStrings.cs @@ -20,7 +20,7 @@ public static bool IsZipUnicode(this Encoding e) /// public static class ZipStrings { - static readonly StringCodec CompatCodec = new StringCodec(); + static StringCodec CompatCodec = StringCodec.Default; private static bool compatibilityMode; @@ -29,7 +29,7 @@ public static class ZipStrings /// /// public static StringCodec GetStringCodec() - => compatibilityMode ? CompatCodec : new StringCodec(); + => compatibilityMode ? CompatCodec : StringCodec.Default; /// [Obsolete("Use ZipFile/Zip*Stream StringCodec instead")] @@ -38,7 +38,11 @@ public static int CodePage get => CompatCodec.CodePage; set { - CompatCodec.CodePage = value; + CompatCodec = new StringCodec(CompatCodec.ForceZipLegacyEncoding, Encoding.GetEncoding(value)) + { + ZipArchiveCommentEncoding = CompatCodec.ZipArchiveCommentEncoding, + ZipCryptoEncoding = CompatCodec.ZipCryptoEncoding, + }; compatibilityMode = true; } } @@ -54,7 +58,11 @@ public static bool UseUnicode get => !CompatCodec.ForceZipLegacyEncoding; set { - CompatCodec.ForceZipLegacyEncoding = !value; + CompatCodec = new StringCodec(!value, CompatCodec.LegacyEncoding) + { + ZipArchiveCommentEncoding = CompatCodec.ZipArchiveCommentEncoding, + ZipCryptoEncoding = CompatCodec.ZipCryptoEncoding, + }; compatibilityMode = true; } } @@ -102,25 +110,42 @@ public static byte[] ConvertToArray(int flags, string str) /// public class StringCodec { - static StringCodec() + internal StringCodec(bool forceLegacyEncoding, Encoding legacyEncoding) { - try - { - var platformCodepage = Encoding.Default.CodePage; - SystemDefaultCodePage = (platformCodepage == 1 || platformCodepage == 2 || platformCodepage == 3 || platformCodepage == 42) ? FallbackCodePage : platformCodepage; - } - catch - { - SystemDefaultCodePage = FallbackCodePage; - } - - SystemDefaultEncoding = Encoding.GetEncoding(SystemDefaultCodePage); + LegacyEncoding = legacyEncoding; + ForceZipLegacyEncoding = forceLegacyEncoding; + ZipArchiveCommentEncoding = legacyEncoding; + ZipCryptoEncoding = legacyEncoding; } + /// + /// Creates a StringCodec that uses the system default encoder or UTF-8 depending on whether the zip entry Unicode flag is set + /// + public static StringCodec Default + => new StringCodec(false, SystemDefaultEncoding); + + /// + /// Creates a StringCodec that uses an encoding from the specified code page except for zip entries with the Unicode flag + /// + public static StringCodec FromCodePage(int codePage) + => new StringCodec(false, Encoding.GetEncoding(codePage)); + + /// + /// Creates a StringCodec that uses an the specified encoding, except for zip entries with the Unicode flag + /// + public static StringCodec FromEncoding(Encoding encoding) + => new StringCodec(false, encoding); + + /// + /// Creates a StringCodec that uses the zip specification encoder or UTF-8 depending on whether the zip entry Unicode flag is set + /// + public static StringCodec WithStrictSpecEncoding() + => new StringCodec(false, Encoding.GetEncoding(ZipSpecCodePage)); + /// /// If set, use the encoding set by for zip entries instead of the defaults /// - public bool ForceZipLegacyEncoding { get; set; } + public bool ForceZipLegacyEncoding { get; internal set; } /// /// The default encoding used for ZipCrypto passwords in zip files, set to @@ -137,7 +162,8 @@ static StringCodec() /// /// Returns if is set, otherwise it returns the encoding indicated by /// - public Encoding ZipEncoding(bool unicode) => unicode ? UnicodeZipEncoding : _legacyEncoding; + public Encoding ZipEncoding(bool unicode) + => unicode ? UnicodeZipEncoding : LegacyEncoding; /// /// Returns the appropriate encoding for an input according to . @@ -145,22 +171,19 @@ static StringCodec() /// /// /// - public Encoding ZipInputEncoding(GeneralBitFlags flags) => ZipInputEncoding((int)flags); + public Encoding ZipInputEncoding(GeneralBitFlags flags) + => ZipEncoding(!ForceZipLegacyEncoding && flags.HasAny(GeneralBitFlags.UnicodeText)); /// - public Encoding ZipInputEncoding(int flags) => ZipEncoding(!ForceZipLegacyEncoding && (flags & (int)GeneralBitFlags.UnicodeText) != 0); + public Encoding ZipInputEncoding(int flags) => ZipInputEncoding((GeneralBitFlags)flags); /// Code page encoding, used for non-unicode strings /// /// The original Zip specification (https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) states /// that file names should only be encoded with IBM Code Page 437 or UTF-8. /// In practice, most zip apps use OEM or system encoding (typically cp437 on Windows). - /// Let's be good citizens and default to UTF-8 http://utf8everywhere.org/ /// - private Encoding _legacyEncoding = SystemDefaultEncoding; - - private Encoding _zipArchiveCommentEncoding; - private Encoding _zipCryptoEncoding; + public Encoding LegacyEncoding { get; internal set; } /// /// Returns the UTF-8 code page (65001) used for zip entries with unicode flag set @@ -171,43 +194,67 @@ static StringCodec() /// Code page used for non-unicode strings and legacy zip encoding (if is set). /// Default value is /// - public int CodePage - { - get => _legacyEncoding.CodePage; - set => _legacyEncoding = (value < 4 || value > 65535 || value == 42) - ? throw new ArgumentOutOfRangeException(nameof(value)) - : Encoding.GetEncoding(value); - } + public int CodePage => LegacyEncoding.CodePage; - private const int FallbackCodePage = 437; + /// + /// The non-unicode code page that should be used according to the zip specification + /// + public const int ZipSpecCodePage = 437; /// - /// Operating system default codepage, or if it could not be retrieved, the fallback code page IBM 437. + /// Operating system default codepage. /// - public static int SystemDefaultCodePage { get; } + public static int SystemDefaultCodePage => SystemDefaultEncoding.CodePage; /// - /// The system default encoding, based on + /// The system default encoding. /// - public static Encoding SystemDefaultEncoding { get; } + public static Encoding SystemDefaultEncoding => Encoding.GetEncoding(0); /// /// The encoding used for the zip archive comment. Defaults to the encoding for , since /// no unicode flag can be set for it in the files. /// - public Encoding ZipArchiveCommentEncoding - { - get => _zipArchiveCommentEncoding ?? _legacyEncoding; - set => _zipArchiveCommentEncoding = value; - } + public Encoding ZipArchiveCommentEncoding { get; internal set; } /// /// The encoding used for the ZipCrypto passwords. Defaults to . /// - public Encoding ZipCryptoEncoding - { - get => _zipCryptoEncoding ?? DefaultZipCryptoEncoding; - set => _zipCryptoEncoding = value; - } + public Encoding ZipCryptoEncoding { get; internal set; } + + /// + /// Create a copy of this StringCodec with the specified zip archive comment encoding + /// + /// + /// + public StringCodec WithZipArchiveCommentEncoding(Encoding commentEncoding) + => new StringCodec(ForceZipLegacyEncoding, LegacyEncoding) + { + ZipArchiveCommentEncoding = commentEncoding, + ZipCryptoEncoding = ZipCryptoEncoding + }; + + /// + /// Create a copy of this StringCodec with the specified zip crypto password encoding + /// + /// + /// + public StringCodec WithZipCryptoEncoding(Encoding cryptoEncoding) + => new StringCodec(ForceZipLegacyEncoding, LegacyEncoding) + { + ZipArchiveCommentEncoding = ZipArchiveCommentEncoding, + ZipCryptoEncoding = cryptoEncoding + }; + + /// + /// Create a copy of this StringCodec that ignores the Unicode flag when reading entries + /// + /// + public StringCodec WithForcedLegacyEncoding() + => new StringCodec(true, LegacyEncoding) + { + ZipArchiveCommentEncoding = ZipArchiveCommentEncoding, + ZipCryptoEncoding = ZipCryptoEncoding + }; } } diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs index 8026668ff..f3bf9a995 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/GeneralHandling.cs @@ -73,57 +73,6 @@ public class GeneralHandling : ZipBase } } - private string DescribeAttributes(FieldAttributes attributes) - { - string att = string.Empty; - if ((FieldAttributes.Public & attributes) != 0) - { - att = att + "Public,"; - } - - if ((FieldAttributes.Static & attributes) != 0) - { - att = att + "Static,"; - } - - if ((FieldAttributes.Literal & attributes) != 0) - { - att = att + "Literal,"; - } - - if ((FieldAttributes.HasDefault & attributes) != 0) - { - att = att + "HasDefault,"; - } - - if ((FieldAttributes.InitOnly & attributes) != 0) - { - att = att + "InitOnly,"; - } - - if ((FieldAttributes.Assembly & attributes) != 0) - { - att = att + "Assembly,"; - } - - if ((FieldAttributes.FamANDAssem & attributes) != 0) - { - att = att + "FamANDAssembly,"; - } - - if ((FieldAttributes.FamORAssem & attributes) != 0) - { - att = att + "FamORAssembly,"; - } - - if ((FieldAttributes.HasFieldMarshal & attributes) != 0) - { - att = att + "HasFieldMarshal,"; - } - - return att; - } - /// /// Invalid passwords should be detected early if possible, seekable stream /// Note: Have a 1/255 chance of failing due to CRC collision (hence retried once) @@ -803,7 +752,7 @@ private object UnZipZeroLength(byte[] zipped) [TestCase("a/b/c/d/e/f/g/h/SomethingLikeAnArchiveName.txt")] public void LegacyNameConversion(string name) { - var encoding = new StringCodec().ZipEncoding(false); + var encoding = StringCodec.Default.ZipEncoding(false); byte[] intermediate = encoding.GetBytes(name); string final = encoding.GetString(intermediate); @@ -816,22 +765,22 @@ public void UnicodeNameConversion() { Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); - var codec = new StringCodec() {CodePage = 850}; + var codec = StringCodec.FromCodePage(850); string sample = "Hello world"; byte[] rawData = Encoding.ASCII.GetBytes(sample); - var converted = codec.ZipInputEncoding(0).GetString(rawData); + var converted = codec.LegacyEncoding.GetString(rawData); Assert.AreEqual(sample, converted); - converted = codec.ZipInputEncoding((int)GeneralBitFlags.UnicodeText).GetString(rawData); + converted = codec.ZipInputEncoding(GeneralBitFlags.UnicodeText).GetString(rawData); Assert.AreEqual(sample, converted); // This time use some greek characters sample = "\u03A5\u03d5\u03a3"; rawData = Encoding.UTF8.GetBytes(sample); - converted = codec.ZipInputEncoding((int)GeneralBitFlags.UnicodeText).GetString(rawData); + converted = codec.ZipInputEncoding(GeneralBitFlags.UnicodeText).GetString(rawData); Assert.AreEqual(sample, converted); } diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStringsTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStringsTests.cs new file mode 100644 index 000000000..cd213df6e --- /dev/null +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipStringsTests.cs @@ -0,0 +1,239 @@ +using ICSharpCode.SharpZipLib.Tests.TestSupport; +using ICSharpCode.SharpZipLib.Tests.Zip; +using ICSharpCode.SharpZipLib.Zip; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Does = ICSharpCode.SharpZipLib.Tests.TestSupport.Does; + +// As there is no way to order the test namespace execution order we use a name that should be alphabetically sorted before any other namespace +// This is because we have one test that only works when no encoding provider has been loaded which is not reversable once done. +namespace ICSharpCode.SharpZipLib.Tests._Zip +{ + [TestFixture] + [Order(1)] + public class ZipStringsTests + { + [Test] + [Order(1)] + // NOTE: This test needs to be run before any test registering CodePagesEncodingProvider.Instance + public void TestSystemDefaultEncoding() + { + Console.WriteLine($"Default encoding before registering provider: {Encoding.GetEncoding(0).EncodingName}"); + Encoding.RegisterProvider(new TestEncodingProvider()); + Console.WriteLine($"Default encoding after registering provider: {Encoding.GetEncoding(0).EncodingName}"); + + // Initialize a default StringCodec + var sc = StringCodec.Default; + + var legacyEncoding = sc.ZipEncoding(false); + Assert.That(legacyEncoding.EncodingName, Is.EqualTo(TestEncodingProvider.DefaultEncodingName)); + Assert.That(legacyEncoding.CodePage, Is.EqualTo(TestEncodingProvider.DefaultEncodingCodePage)); + } + + [Test] + [Order(2)] + public void TestFastZipRoundTripWithCodePage() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + using var ms = new MemoryStream(); + using var zipFile = new TempFile(); + using var srcDir = new TempDir(); + using var dstDir = new TempDir(); + + srcDir.CreateDummyFile("file1"); + srcDir.CreateDummyFile("слово"); + + foreach(var f in Directory.EnumerateFiles(srcDir.FullName)) + { + Console.WriteLine(f); + } + + var fzCreate = new FastZip() { StringCodec = StringCodec.FromCodePage(866), UseUnicode = false }; + fzCreate.CreateZip(zipFile, srcDir.FullName, true, null); + + var fzExtract = new FastZip() { StringCodec = StringCodec.FromCodePage(866) }; + fzExtract.ExtractZip(zipFile, dstDir.FullName, null); + + foreach (var f in Directory.EnumerateFiles(dstDir.FullName)) + { + Console.WriteLine(f); + } + + Assert.That(dstDir.GetFile("file1").FullName, Does.Exist); + Assert.That(dstDir.GetFile("слово").FullName, Does.Exist); + } + + + [Test] + [Order(2)] + public void TestZipFileRoundTripWithCodePage() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + using var ms = new MemoryStream(); + using (var zf = ZipFile.Create(ms)) + { + zf.StringCodec = StringCodec.FromCodePage(866); + zf.BeginUpdate(); + zf.Add(MemoryDataSource.Empty, "file1", CompressionMethod.Stored, useUnicodeText: false); + zf.Add(MemoryDataSource.Empty, "слово", CompressionMethod.Stored, useUnicodeText: false); + zf.CommitUpdate(); + } + + ms.Seek(0, SeekOrigin.Begin); + + using (var zf = new ZipFile(ms, false, StringCodec.FromCodePage(866)) { IsStreamOwner = false }) + { + Assert.That(zf.GetEntry("file1"), Is.Not.Null); + Assert.That(zf.GetEntry("слово"), Is.Not.Null); + } + + } + + [Test] + [Order(2)] + public void TestZipStreamRoundTripWithCodePage() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + using var ms = new MemoryStream(); + using (var zos = new ZipOutputStream(ms, StringCodec.FromCodePage(866)) { IsStreamOwner = false }) + { + zos.PutNextEntry(new ZipEntry("file1") { IsUnicodeText = false }); + zos.PutNextEntry(new ZipEntry("слово") { IsUnicodeText = false }); + } + + ms.Seek(0, SeekOrigin.Begin); + + using (var zis = new ZipInputStream(ms, StringCodec.FromCodePage(866)) { IsStreamOwner = false }) + { + Assert.That(zis.GetNextEntry().Name, Is.EqualTo("file1")); + Assert.That(zis.GetNextEntry().Name, Is.EqualTo("слово")); + } + + } + + [Test] + [Order(2)] + public void TestZipCryptoPasswordEncodingRoundtrip() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + var content = Utils.GetDummyBytes(32); + + using var ms = new MemoryStream(); + using (var zos = new ZipOutputStream(ms, StringCodec.FromCodePage(866)) { IsStreamOwner = false }) + { + zos.Password = "слово"; + zos.PutNextEntry(new ZipEntry("file1")); + zos.Write(content, 0, content.Length); + } + + ms.Seek(0, SeekOrigin.Begin); + + using (var zis = new ZipInputStream(ms, StringCodec.FromCodePage(866)) { IsStreamOwner = false }) + { + zis.Password = "слово"; + var entry = zis.GetNextEntry(); + var output = new byte[32]; + Assert.That(zis.Read(output, 0, 32), Is.EqualTo(32)); + Assert.That(output, Is.EqualTo(content)); + } + + } + + [Test] + [Order(2)] + public void TestZipStreamCommentEncodingRoundtrip() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + var content = Utils.GetDummyBytes(32); + + using var ms = new MemoryStream(); + using (var zos = new ZipOutputStream(ms, StringCodec.FromCodePage(866)) { IsStreamOwner = false }) + { + zos.SetComment("слово"); + } + + ms.Seek(0, SeekOrigin.Begin); + + using var zf = new ZipFile(ms, false, StringCodec.FromCodePage(866)); + Assert.That(zf.ZipFileComment, Is.EqualTo("слово")); + } + + + [Test] + [Order(2)] + public void TestZipFileCommentEncodingRoundtrip() + { + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + var content = Utils.GetDummyBytes(32); + + using var ms = new MemoryStream(); + using (var zf = ZipFile.Create(ms)) + { + zf.StringCodec = StringCodec.FromCodePage(866); + zf.BeginUpdate(); + zf.SetComment("слово"); + zf.CommitUpdate(); + } + + ms.Seek(0, SeekOrigin.Begin); + + using (var zf = new ZipFile(ms, false, StringCodec.FromCodePage(866))) + { + Assert.That(zf.ZipFileComment, Is.EqualTo("слово")); + } + } + } + + + internal class TestEncodingProvider : EncodingProvider + { + internal static string DefaultEncodingName = "TestDefaultEncoding"; + internal static int DefaultEncodingCodePage = -37; + + class TestDefaultEncoding : Encoding + { + public override string EncodingName => DefaultEncodingName; + public override int CodePage => DefaultEncodingCodePage; + + public override int GetByteCount(char[] chars, int index, int count) + => UTF8.GetByteCount(chars, index, count); + + public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) + => UTF8.GetBytes(chars, charIndex, charCount, bytes, byteIndex); + + public override int GetCharCount(byte[] bytes, int index, int count) + => UTF8.GetCharCount(bytes, index, count); + + public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) + => UTF8.GetChars(bytes, byteIndex, byteCount, chars, charIndex); + + public override int GetMaxByteCount(int charCount) => UTF8.GetMaxByteCount(charCount); + + public override int GetMaxCharCount(int byteCount) => UTF8.GetMaxCharCount(byteCount); + } + + TestDefaultEncoding testDefaultEncoding = new TestDefaultEncoding(); + + public override Encoding GetEncoding(int codepage) + => (codepage == 0 || codepage == DefaultEncodingCodePage) ? testDefaultEncoding : null; + + public override Encoding GetEncoding(string name) + => DefaultEncodingName == name ? testDefaultEncoding : null; + +#if NET6_0_OR_GREATER + public override IEnumerable GetEncodings() + { + yield return new EncodingInfo(this, DefaultEncodingCodePage, DefaultEncodingName, DefaultEncodingName); + } +#endif + } +} diff --git a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipTests.cs index dc398000e..885c976f3 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipTests.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Zip/ZipTests.cs @@ -121,6 +121,8 @@ public MemoryDataSource(byte[] data) data_ = data; } + public static MemoryDataSource Empty => new MemoryDataSource(Array.Empty()); + #endregion Constructors #region IDataSource Members