From 7a62468ffbb90008e08b30471bfa94b2d4c58726 Mon Sep 17 00:00:00 2001 From: Badre BSAILA <54767641+pedrobsaila@users.noreply.github.com> Date: Wed, 1 Dec 2021 21:16:59 +0100 Subject: [PATCH] throw exception when creating an entry name that already exists in ZipArchive (#60973) * throw exception when creating an entry name that already exists in ZipArchive * secure code at the top of CreateEntry call * specify entry name in exception message * fix exception message, make AddEntry retrocompatible, and fix test issue with globalization * restore the EmptyEntryTest code as before --- .../src/Resources/Strings.resx | 3 ++ .../src/System/IO/Compression/ZipArchive.cs | 12 +++---- .../tests/ZipArchive/zip_CreateTests.cs | 17 ++++++++++ .../tests/ZipArchive/zip_UpdateTests.cs | 32 +++++++++++++++---- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.IO.Compression/src/Resources/Strings.resx b/src/libraries/System.IO.Compression/src/Resources/Strings.resx index 286ff6ab7c723..cac801aa8fcc4 100644 --- a/src/libraries/System.IO.Compression/src/Resources/Strings.resx +++ b/src/libraries/System.IO.Compression/src/Resources/Strings.resx @@ -308,4 +308,7 @@ Zip 64 End of Central Directory Record not where indicated. + + An entry named '{0}' already exists in the archive. + diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs index 5fbd00c47006c..58b65209caf0e 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipArchive.cs @@ -388,6 +388,11 @@ private ZipArchiveEntry DoCreateEntry(string entryName, CompressionLevel? compre if (_mode == ZipArchiveMode.Read) throw new NotSupportedException(SR.CreateInReadMode); + if (_entriesDictionary.ContainsKey(entryName)) + { + throw new InvalidOperationException(string.Format(SR.EntryNameAlreadyExists, entryName)); + } + ThrowIfDisposed(); @@ -421,12 +426,7 @@ internal void AcquireArchiveStream(ZipArchiveEntry entry) private void AddEntry(ZipArchiveEntry entry) { _entries.Add(entry); - - string entryName = entry.FullName; - if (!_entriesDictionary.ContainsKey(entryName)) - { - _entriesDictionary.Add(entryName, entry); - } + _entriesDictionary.TryAdd(entry.FullName, entry); } [Conditional("DEBUG")] diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_CreateTests.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_CreateTests.cs index b5075cd8ba594..f0c36d5967626 100644 --- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_CreateTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_CreateTests.cs @@ -178,6 +178,23 @@ public static void CreateNormal_VerifyDataDescriptor() AssertDataDescriptor(memoryStream, false); } + [Fact] + public static void CreateNormal_With2SameEntries_ThrowException() + { + using var memoryStream = new MemoryStream(); + // We need an non-seekable stream so the data descriptor bit is turned on when saving + var wrappedStream = new WrappedStream(memoryStream); + + // Creation will go through the path that sets the data descriptor bit when the stream is unseekable + using (var archive = new ZipArchive(wrappedStream, ZipArchiveMode.Create)) + { + string entryName = "duplicate.txt"; + CreateEntry(archive, entryName, "xxx"); + AssertExtensions.ThrowsContains(() => CreateEntry(archive, entryName, "yyy"), + entryName); + } + } + private static string ReadStringFromSpan(Span input) { return Text.Encoding.UTF8.GetString(input.ToArray()); diff --git a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_UpdateTests.cs b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_UpdateTests.cs index af97a9d3ab960..3cb9934fb3a7c 100644 --- a/src/libraries/System.IO.Compression/tests/ZipArchive/zip_UpdateTests.cs +++ b/src/libraries/System.IO.Compression/tests/ZipArchive/zip_UpdateTests.cs @@ -92,20 +92,38 @@ public static void EmptyEntryTest(ZipArchiveMode mode) baseline = baseline.Clone(); using (ZipArchive archive = new ZipArchive(baseline, mode)) { - AddEntry(archive, "data1.txt", data1, lastWrite); + if (mode == ZipArchiveMode.Create) + { + AddEntry(archive, "data1.txt", data1, lastWrite); - ZipArchiveEntry e = archive.CreateEntry("empty.txt"); - e.LastWriteTime = lastWrite; - using (Stream s = e.Open()) { } + ZipArchiveEntry e = archive.CreateEntry("empty.txt"); + e.LastWriteTime = lastWrite; + using (Stream s = e.Open()) { } + } + else + { + Assert.Throws(() => AddEntry(archive, "data1.txt", data1, lastWrite)); + + Assert.Throws(() => archive.CreateEntry("empty.txt")); + } } test = test.Clone(); using (ZipArchive archive = new ZipArchive(test, mode)) { - AddEntry(archive, "data1.txt", data1, lastWrite); + if (mode == ZipArchiveMode.Create) + { + AddEntry(archive, "data1.txt", data1, lastWrite); - ZipArchiveEntry e = archive.CreateEntry("empty.txt"); - e.LastWriteTime = lastWrite; + ZipArchiveEntry e = archive.CreateEntry("empty.txt"); + e.LastWriteTime = lastWrite; + } + else + { + Assert.Throws(() => AddEntry(archive, "data1.txt", data1, lastWrite)); + + Assert.Throws(() => archive.CreateEntry("empty.txt")); + } } //compare Assert.True(ArraysEqual(baseline.ToArray(), test.ToArray()), "Arrays didn't match after update");