Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 05ada9b

Browse files
authored
Fix bug in ZipArchiveEntry data descriptor (#37601)
* Prevent data corruption (can't drag drop files using external zip tools) when data descriptor bit is turned on in a seekable file that is being updated. * Add unit test to verify data descriptor is off after updating a zip file that was created with an unseekable stream.
1 parent bffb2a1 commit 05ada9b

2 files changed

Lines changed: 53 additions & 1 deletion

File tree

src/System.IO.Compression/src/System/IO/Compression/ZipArchiveEntry.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,8 @@ private bool WriteLocalFileHeader(bool isEmptyFile)
823823
}
824824
else // if we are not in streaming mode, we have to decide if we want to write zip64 headers
825825
{
826+
// We are in seekable mode so we will not need to write a data descriptor
827+
_generalPurposeBitFlag &= ~BitFlagValues.DataDescriptor;
826828
if (SizesTooLarge()
827829
#if DEBUG_FORCE_ZIP64
828830
|| (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update)
@@ -1020,6 +1022,9 @@ private void WriteCrcAndSizesInLocalHeader(bool zip64HeaderUsed)
10201022

10211023
private void WriteDataDescriptor()
10221024
{
1025+
// We enter here because we cannot seek, so the data descriptor bit should be on
1026+
Debug.Assert((_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0);
1027+
10231028
// data descriptor can be 32-bit or 64-bit sizes. 32-bit is more compatible, so use that if possible
10241029
// signature is optional but recommended by the spec
10251030

@@ -1231,7 +1236,6 @@ protected override void Dispose(bool disposing)
12311236
// go back and finish writing
12321237
if (_entry._archive.ArchiveStream.CanSeek)
12331238
// finish writing local header if we have seek capabilities
1234-
12351239
_entry.WriteCrcAndSizesInLocalHeader(_usedZip64inLH);
12361240
else
12371241
// write out data descriptor if we don't have seek capabilities

src/System.IO.Compression/tests/ZipArchive/zip_CreateTests.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,58 @@ public static void CreateUncompressedArchive()
149149
}
150150
}
151151

152+
[Fact]
153+
public static void CreateNormal_VerifyDataDescriptor()
154+
{
155+
using var memoryStream = new MemoryStream();
156+
// We need an non-seekable stream so the data descriptor bit is turned on when saving
157+
var wrappedStream = new WrappedStream(memoryStream, true, true, false, null);
158+
159+
// Creation will go through the path that sets the data descriptor bit when the stream is unseekable
160+
using (var archive = new ZipArchive(wrappedStream, ZipArchiveMode.Create))
161+
{
162+
CreateEntry(archive, "A", "xxx");
163+
CreateEntry(archive, "B", "yyy");
164+
}
165+
166+
AssertDataDescriptor(memoryStream, true);
167+
168+
// Update should flip the data descriptor bit to zero on save
169+
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Update))
170+
{
171+
ZipArchiveEntry entry = archive.Entries[0];
172+
using Stream entryStream = entry.Open();
173+
StreamReader reader = new StreamReader(entryStream);
174+
string content = reader.ReadToEnd();
175+
176+
// Append a string to this entry
177+
entryStream.Seek(0, SeekOrigin.End);
178+
StreamWriter writer = new StreamWriter(entryStream);
179+
writer.Write("zzz");
180+
writer.Flush();
181+
}
182+
183+
AssertDataDescriptor(memoryStream, false);
184+
}
185+
152186
private static string ReadStringFromSpan(Span<byte> input)
153187
{
154188
return Text.Encoding.UTF8.GetString(input.ToArray());
155189
}
190+
191+
private static void CreateEntry(ZipArchive archive, string fileName, string fileContents)
192+
{
193+
ZipArchiveEntry entry = archive.CreateEntry(fileName);
194+
using StreamWriter writer = new StreamWriter(entry.Open());
195+
writer.Write(fileContents);
196+
}
197+
198+
private static void AssertDataDescriptor(MemoryStream memoryStream, bool hasDataDescriptor)
199+
{
200+
byte[] fileBytes = memoryStream.ToArray();
201+
Assert.Equal(hasDataDescriptor ? 8 : 0, fileBytes[6]);
202+
Assert.Equal(0, fileBytes[7]);
203+
}
156204
}
157205
}
158206

0 commit comments

Comments
 (0)