Skip to content

Commit 1fc1762

Browse files
committed
Preserve bit 3 and write zeros for CRC/sizes in metadata-only data descriptor rewrites
1 parent 0774808 commit 1fc1762

3 files changed

Lines changed: 193 additions & 14 deletions

File tree

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -316,14 +316,14 @@ private async Task<WrappedStream> OpenInUpdateModeAsync(bool loadExistingContent
316316
}
317317

318318
// return value is true if we allocated an extra field for 64 bit headers, un/compressed size
319-
private async Task<bool> WriteLocalFileHeaderAsync(bool isEmptyFile, bool forceWrite, CancellationToken cancellationToken)
319+
private async Task<bool> WriteLocalFileHeaderAsync(bool isEmptyFile, bool forceWrite, CancellationToken cancellationToken, bool preserveDataDescriptor = false)
320320
{
321321
cancellationToken.ThrowIfCancellationRequested();
322322

323-
if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, out Zip64ExtraField? zip64ExtraField, out uint compressedSizeTruncated, out uint uncompressedSizeTruncated, out ushort extraFieldLength))
323+
if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip64ExtraField, out uint compressedSizeTruncated, out uint uncompressedSizeTruncated, out ushort extraFieldLength, out uint crc32ToWrite))
324324
{
325325
byte[] lfStaticHeader = new byte[ZipLocalFileHeader.SizeOfLocalHeader];
326-
WriteLocalFileHeaderPrepare(lfStaticHeader, compressedSizeTruncated, uncompressedSizeTruncated, extraFieldLength);
326+
WriteLocalFileHeaderPrepare(lfStaticHeader, compressedSizeTruncated, uncompressedSizeTruncated, extraFieldLength, crc32ToWrite);
327327

328328
// write header
329329
await _archive.ArchiveStream.WriteAsync(lfStaticHeader, cancellationToken).ConfigureAwait(false);
@@ -401,7 +401,11 @@ private async Task WriteLocalFileHeaderAndDataIfNeededAsync(bool forceWrite, Can
401401
if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite)
402402
{
403403
_everOpenedForWrite = true;
404-
await WriteLocalFileHeaderAsync(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite, cancellationToken).ConfigureAwait(false);
404+
// If the entry originally used a data descriptor and we are not rewriting the data,
405+
// the descriptor record remains on disk after the compressed bytes. Preserve bit 3
406+
// and write zeros for CRC/sizes so that sequential readers see a consistent entry.
407+
bool preserveDataDescriptor = _originallyInArchive && (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0;
408+
await WriteLocalFileHeaderAsync(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite, cancellationToken, preserveDataDescriptor).ConfigureAwait(false);
405409

406410
// Advance the stream past the compressed data and any trailing data descriptor
407411
// by seeking to the pre-computed end-of-entry boundary.

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

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,7 @@ private static BitFlagValues MapDeflateCompressionOption(BitFlagValues generalPu
10621062

10631063
private bool ShouldUseZIP64 => AreSizesTooLarge || IsOffsetTooLarge;
10641064

1065-
private bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, out Zip64ExtraField? zip64ExtraField, out uint compressedSizeTruncated, out uint uncompressedSizeTruncated, out ushort extraFieldLength)
1065+
private bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, bool preserveDataDescriptor, out Zip64ExtraField? zip64ExtraField, out uint compressedSizeTruncated, out uint uncompressedSizeTruncated, out ushort extraFieldLength, out uint crc32ToWrite)
10661066
{
10671067
// _entryname only gets set when we read in or call moveTo. MoveTo does a check, and
10681068
// reading in should not be able to produce an entryname longer than ushort.MaxValue
@@ -1080,6 +1080,7 @@ private bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, o
10801080
CompressionMethod = ZipCompressionMethod.Stored;
10811081
compressedSizeTruncated = 0;
10821082
uncompressedSizeTruncated = 0;
1083+
crc32ToWrite = 0;
10831084
Debug.Assert(_uncompressedSize == 0);
10841085
Debug.Assert(_crc32 == 0);
10851086
}
@@ -1092,11 +1093,22 @@ private bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, o
10921093
_generalPurposeBitFlag |= BitFlagValues.DataDescriptor;
10931094
compressedSizeTruncated = 0;
10941095
uncompressedSizeTruncated = 0;
1096+
crc32ToWrite = 0;
10951097
// the crc should not have been set if we are in create mode, but clear it just to be sure
10961098
Debug.Assert(_crc32 == 0);
10971099
}
1100+
else if (preserveDataDescriptor)
1101+
{
1102+
// The existing data descriptor bytes will remain on disk. Preserve bit 3 and
1103+
// write zeros for CRC and sizes, matching the original streaming format, so that
1104+
// sequential readers are not confused by the on-disk descriptor record.
1105+
compressedSizeTruncated = 0;
1106+
uncompressedSizeTruncated = 0;
1107+
crc32ToWrite = 0;
1108+
}
10981109
else // if we are not in streaming mode, we have to decide if we want to write zip64 headers
10991110
{
1111+
crc32ToWrite = _crc32;
11001112
if (ShouldUseZIP64
11011113
#if DEBUG_FORCE_ZIP64
11021114
|| (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update)
@@ -1157,37 +1169,38 @@ private bool WriteLocalFileHeaderInitialize(bool isEmptyFile, bool forceWrite, o
11571169
return false;
11581170
}
11591171

1160-
// We are writing the header. For seekable/empty-file paths the sizes are written
1161-
// directly into the header, so a data descriptor is not needed.
1162-
if (isEmptyFile || _archive.ArchiveStream.CanSeek)
1172+
// We are writing the header. Clear the data descriptor bit unless the existing
1173+
// on-disk data descriptor is being preserved, in which case bit 3 must remain set
1174+
// so sequential readers know to expect the descriptor record after the compressed data.
1175+
if (!preserveDataDescriptor && (isEmptyFile || _archive.ArchiveStream.CanSeek))
11631176
{
11641177
_generalPurposeBitFlag &= ~BitFlagValues.DataDescriptor;
11651178
}
11661179

11671180
return true;
11681181
}
11691182

1170-
private void WriteLocalFileHeaderPrepare(Span<byte> lfStaticHeader, uint compressedSizeTruncated, uint uncompressedSizeTruncated, ushort extraFieldLength)
1183+
private void WriteLocalFileHeaderPrepare(Span<byte> lfStaticHeader, uint compressedSizeTruncated, uint uncompressedSizeTruncated, ushort extraFieldLength, uint crc32ToWrite)
11711184
{
11721185
ZipLocalFileHeader.SignatureConstantBytes.CopyTo(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Signature..]);
11731186
BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.VersionNeededToExtract..], (ushort)_versionToExtract);
11741187
BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.GeneralPurposeBitFlags..], (ushort)_generalPurposeBitFlag);
11751188
BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressionMethod..], (ushort)CompressionMethod);
11761189
BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.LastModified..], ZipHelper.DateTimeToDosTime(_lastModified.DateTime));
1177-
BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Crc32..], _crc32);
1190+
BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.Crc32..], crc32ToWrite);
11781191
BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.CompressedSize..], compressedSizeTruncated);
11791192
BinaryPrimitives.WriteUInt32LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.UncompressedSize..], uncompressedSizeTruncated);
11801193
BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.FilenameLength..], (ushort)_storedEntryNameBytes.Length);
11811194
BinaryPrimitives.WriteUInt16LittleEndian(lfStaticHeader[ZipLocalFileHeader.FieldLocations.ExtraFieldLength..], extraFieldLength);
11821195
}
11831196

11841197
// return value is true if we allocated an extra field for 64 bit headers, un/compressed size
1185-
private bool WriteLocalFileHeader(bool isEmptyFile, bool forceWrite)
1198+
private bool WriteLocalFileHeader(bool isEmptyFile, bool forceWrite, bool preserveDataDescriptor = false)
11861199
{
1187-
if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, out Zip64ExtraField? zip64ExtraField, out uint compressedSizeTruncated, out uint uncompressedSizeTruncated, out ushort extraFieldLength))
1200+
if (WriteLocalFileHeaderInitialize(isEmptyFile, forceWrite, preserveDataDescriptor, out Zip64ExtraField? zip64ExtraField, out uint compressedSizeTruncated, out uint uncompressedSizeTruncated, out ushort extraFieldLength, out uint crc32ToWrite))
11881201
{
11891202
Span<byte> lfStaticHeader = stackalloc byte[ZipLocalFileHeader.SizeOfLocalHeader];
1190-
WriteLocalFileHeaderPrepare(lfStaticHeader, compressedSizeTruncated, uncompressedSizeTruncated, extraFieldLength);
1203+
WriteLocalFileHeaderPrepare(lfStaticHeader, compressedSizeTruncated, uncompressedSizeTruncated, extraFieldLength, crc32ToWrite);
11911204

11921205
// write header
11931206
_archive.ArchiveStream.Write(lfStaticHeader);
@@ -1261,7 +1274,11 @@ private void WriteLocalFileHeaderAndDataIfNeeded(bool forceWrite)
12611274
if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite)
12621275
{
12631276
_everOpenedForWrite = true;
1264-
WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite);
1277+
// If the entry originally used a data descriptor and we are not rewriting the data,
1278+
// the descriptor record remains on disk after the compressed bytes. Preserve bit 3
1279+
// and write zeros for CRC/sizes so that sequential readers see a consistent entry.
1280+
bool preserveDataDescriptor = _originallyInArchive && (_generalPurposeBitFlag & BitFlagValues.DataDescriptor) != 0;
1281+
WriteLocalFileHeader(isEmptyFile: _uncompressedSize == 0, forceWrite: forceWrite, preserveDataDescriptor: preserveDataDescriptor);
12651282

12661283
// Advance the stream past the compressed data and any trailing data descriptor
12671284
// by seeking to the pre-computed end-of-entry boundary.

0 commit comments

Comments
 (0)