1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 
8 namespace System.IO.Compression
9 {
10     // All blocks.TryReadBlock do a check to see if signature is correct. Generic extra field is slightly different
11     // all of the TryReadBlocks will throw if there are not enough bytes in the stream
12 
13     internal struct ZipGenericExtraField
14     {
15         private const int SizeOfHeader = 4;
16 
17         private ushort _tag;
18         private ushort _size;
19         private byte[] _data;
20 
21         public ushort Tag => _tag;
22         // returns size of data, not of the entire block
23         public ushort Size => _size;
24         public byte[] Data => _data;
25 
WriteBlockSystem.IO.Compression.ZipGenericExtraField26         public void WriteBlock(Stream stream)
27         {
28             BinaryWriter writer = new BinaryWriter(stream);
29             writer.Write(Tag);
30             writer.Write(Size);
31             writer.Write(Data);
32         }
33 
34         // shouldn't ever read the byte at position endExtraField
35         // assumes we are positioned at the beginning of an extra field subfield
TryReadBlockSystem.IO.Compression.ZipGenericExtraField36         public static bool TryReadBlock(BinaryReader reader, long endExtraField, out ZipGenericExtraField field)
37         {
38             field = new ZipGenericExtraField();
39 
40             // not enough bytes to read tag + size
41             if (endExtraField - reader.BaseStream.Position < 4)
42                 return false;
43 
44             field._tag = reader.ReadUInt16();
45             field._size = reader.ReadUInt16();
46 
47             // not enough bytes to read the data
48             if (endExtraField - reader.BaseStream.Position < field._size)
49                 return false;
50 
51             field._data = reader.ReadBytes(field._size);
52             return true;
53         }
54 
55         // shouldn't ever read the byte at position endExtraField
ParseExtraFieldSystem.IO.Compression.ZipGenericExtraField56         public static List<ZipGenericExtraField> ParseExtraField(Stream extraFieldData)
57         {
58             List<ZipGenericExtraField> extraFields = new List<ZipGenericExtraField>();
59 
60             using (BinaryReader reader = new BinaryReader(extraFieldData))
61             {
62                 ZipGenericExtraField field;
63                 while (TryReadBlock(reader, extraFieldData.Length, out field))
64                 {
65                     extraFields.Add(field);
66                 }
67             }
68 
69             return extraFields;
70         }
71 
TotalSizeSystem.IO.Compression.ZipGenericExtraField72         public static int TotalSize(List<ZipGenericExtraField> fields)
73         {
74             int size = 0;
75             foreach (ZipGenericExtraField field in fields)
76                 size += field.Size + SizeOfHeader; //size is only size of data
77             return size;
78         }
79 
WriteAllBlocksSystem.IO.Compression.ZipGenericExtraField80         public static void WriteAllBlocks(List<ZipGenericExtraField> fields, Stream stream)
81         {
82             foreach (ZipGenericExtraField field in fields)
83                 field.WriteBlock(stream);
84         }
85     }
86 
87     internal struct Zip64ExtraField
88     {
89         // Size is size of the record not including the tag or size fields
90         // If the extra field is going in the local header, it cannot include only
91         // one of uncompressed/compressed size
92 
93         public const int OffsetToFirstField = 4;
94         private const ushort TagConstant = 1;
95 
96         private ushort _size;
97         private long? _uncompressedSize;
98         private long? _compressedSize;
99         private long? _localHeaderOffset;
100         private int? _startDiskNumber;
101 
102         public ushort TotalSize => (ushort)(_size + 4);
103 
104         public long? UncompressedSize
105         {
106             get { return _uncompressedSize; }
107             set { _uncompressedSize = value; UpdateSize(); }
108         }
109         public long? CompressedSize
110         {
111             get { return _compressedSize; }
112             set { _compressedSize = value; UpdateSize(); }
113         }
114         public long? LocalHeaderOffset
115         {
116             get { return _localHeaderOffset; }
117             set { _localHeaderOffset = value; UpdateSize(); }
118         }
119         public int? StartDiskNumber => _startDiskNumber;
120 
UpdateSizeSystem.IO.Compression.Zip64ExtraField121         private void UpdateSize()
122         {
123             _size = 0;
124             if (_uncompressedSize != null) _size += 8;
125             if (_compressedSize != null) _size += 8;
126             if (_localHeaderOffset != null) _size += 8;
127             if (_startDiskNumber != null) _size += 4;
128         }
129 
130         // There is a small chance that something very weird could happen here. The code calling into this function
131         // will ask for a value from the extra field if the field was masked with FF's. It's theoretically possible
132         // that a field was FF's legitimately, and the writer didn't decide to write the corresponding extra field.
133         // Also, at the same time, other fields were masked with FF's to indicate looking in the zip64 record.
134         // Then, the search for the zip64 record will fail because the expected size is wrong,
135         // and a nulled out Zip64ExtraField will be returned. Thus, even though there was Zip64 data,
136         // it will not be used. It is questionable whether this situation is possible to detect
137 
138         // unlike the other functions that have try-pattern semantics, these functions always return a
139         // Zip64ExtraField. If a Zip64 extra field actually doesn't exist, all of the fields in the
140         // returned struct will be null
141         //
142         // If there are more than one Zip64 extra fields, we take the first one that has the expected size
143         //
GetJustZip64BlockSystem.IO.Compression.Zip64ExtraField144         public static Zip64ExtraField GetJustZip64Block(Stream extraFieldStream,
145             bool readUncompressedSize, bool readCompressedSize,
146             bool readLocalHeaderOffset, bool readStartDiskNumber)
147         {
148             Zip64ExtraField zip64Field;
149             using (BinaryReader reader = new BinaryReader(extraFieldStream))
150             {
151                 ZipGenericExtraField currentExtraField;
152                 while (ZipGenericExtraField.TryReadBlock(reader, extraFieldStream.Length, out currentExtraField))
153                 {
154                     if (TryGetZip64BlockFromGenericExtraField(currentExtraField, readUncompressedSize,
155                                 readCompressedSize, readLocalHeaderOffset, readStartDiskNumber, out zip64Field))
156                     {
157                         return zip64Field;
158                     }
159                 }
160             }
161 
162             zip64Field = new Zip64ExtraField();
163 
164             zip64Field._compressedSize = null;
165             zip64Field._uncompressedSize = null;
166             zip64Field._localHeaderOffset = null;
167             zip64Field._startDiskNumber = null;
168 
169             return zip64Field;
170         }
171 
TryGetZip64BlockFromGenericExtraFieldSystem.IO.Compression.Zip64ExtraField172         private static bool TryGetZip64BlockFromGenericExtraField(ZipGenericExtraField extraField,
173             bool readUncompressedSize, bool readCompressedSize,
174             bool readLocalHeaderOffset, bool readStartDiskNumber,
175             out Zip64ExtraField zip64Block)
176         {
177             zip64Block = new Zip64ExtraField();
178 
179             zip64Block._compressedSize = null;
180             zip64Block._uncompressedSize = null;
181             zip64Block._localHeaderOffset = null;
182             zip64Block._startDiskNumber = null;
183 
184             if (extraField.Tag != TagConstant)
185                 return false;
186 
187             // this pattern needed because nested using blocks trigger CA2202
188             MemoryStream ms = null;
189             try
190             {
191                 ms = new MemoryStream(extraField.Data);
192                 using (BinaryReader reader = new BinaryReader(ms))
193                 {
194                     ms = null;
195 
196                     zip64Block._size = extraField.Size;
197 
198                     ushort expectedSize = 0;
199 
200                     if (readUncompressedSize) expectedSize += 8;
201                     if (readCompressedSize) expectedSize += 8;
202                     if (readLocalHeaderOffset) expectedSize += 8;
203                     if (readStartDiskNumber) expectedSize += 4;
204 
205                     // if it is not the expected size, perhaps there is another extra field that matches
206                     if (expectedSize != zip64Block._size)
207                         return false;
208 
209                     if (readUncompressedSize) zip64Block._uncompressedSize = reader.ReadInt64();
210                     if (readCompressedSize) zip64Block._compressedSize = reader.ReadInt64();
211                     if (readLocalHeaderOffset) zip64Block._localHeaderOffset = reader.ReadInt64();
212                     if (readStartDiskNumber) zip64Block._startDiskNumber = reader.ReadInt32();
213 
214                     // original values are unsigned, so implies value is too big to fit in signed integer
215                     if (zip64Block._uncompressedSize < 0) throw new InvalidDataException(SR.FieldTooBigUncompressedSize);
216                     if (zip64Block._compressedSize < 0) throw new InvalidDataException(SR.FieldTooBigCompressedSize);
217                     if (zip64Block._localHeaderOffset < 0) throw new InvalidDataException(SR.FieldTooBigLocalHeaderOffset);
218                     if (zip64Block._startDiskNumber < 0) throw new InvalidDataException(SR.FieldTooBigStartDiskNumber);
219 
220                     return true;
221                 }
222             }
223             finally
224             {
225                 if (ms != null)
226                     ms.Dispose();
227             }
228         }
229 
GetAndRemoveZip64BlockSystem.IO.Compression.Zip64ExtraField230         public static Zip64ExtraField GetAndRemoveZip64Block(List<ZipGenericExtraField> extraFields,
231             bool readUncompressedSize, bool readCompressedSize,
232             bool readLocalHeaderOffset, bool readStartDiskNumber)
233         {
234             Zip64ExtraField zip64Field = new Zip64ExtraField();
235 
236             zip64Field._compressedSize = null;
237             zip64Field._uncompressedSize = null;
238             zip64Field._localHeaderOffset = null;
239             zip64Field._startDiskNumber = null;
240 
241             List<ZipGenericExtraField> markedForDelete = new List<ZipGenericExtraField>();
242             bool zip64FieldFound = false;
243 
244             foreach (ZipGenericExtraField ef in extraFields)
245             {
246                 if (ef.Tag == TagConstant)
247                 {
248                     markedForDelete.Add(ef);
249                     if (!zip64FieldFound)
250                     {
251                         if (TryGetZip64BlockFromGenericExtraField(ef, readUncompressedSize, readCompressedSize,
252                                     readLocalHeaderOffset, readStartDiskNumber, out zip64Field))
253                         {
254                             zip64FieldFound = true;
255                         }
256                     }
257                 }
258             }
259 
260             foreach (ZipGenericExtraField ef in markedForDelete)
261                 extraFields.Remove(ef);
262 
263             return zip64Field;
264         }
265 
RemoveZip64BlocksSystem.IO.Compression.Zip64ExtraField266         public static void RemoveZip64Blocks(List<ZipGenericExtraField> extraFields)
267         {
268             List<ZipGenericExtraField> markedForDelete = new List<ZipGenericExtraField>();
269             foreach (ZipGenericExtraField field in extraFields)
270                 if (field.Tag == TagConstant)
271                     markedForDelete.Add(field);
272 
273             foreach (ZipGenericExtraField field in markedForDelete)
274                 extraFields.Remove(field);
275         }
276 
WriteBlockSystem.IO.Compression.Zip64ExtraField277         public void WriteBlock(Stream stream)
278         {
279             BinaryWriter writer = new BinaryWriter(stream);
280             writer.Write(TagConstant);
281             writer.Write(_size);
282             if (_uncompressedSize != null) writer.Write(_uncompressedSize.Value);
283             if (_compressedSize != null) writer.Write(_compressedSize.Value);
284             if (_localHeaderOffset != null) writer.Write(_localHeaderOffset.Value);
285             if (_startDiskNumber != null) writer.Write(_startDiskNumber.Value);
286         }
287     }
288 
289     internal struct Zip64EndOfCentralDirectoryLocator
290     {
291         public const uint SignatureConstant = 0x07064B50;
292         public const int SizeOfBlockWithoutSignature = 16;
293 
294         public uint NumberOfDiskWithZip64EOCD;
295         public ulong OffsetOfZip64EOCD;
296         public uint TotalNumberOfDisks;
297 
TryReadBlockSystem.IO.Compression.Zip64EndOfCentralDirectoryLocator298         public static bool TryReadBlock(BinaryReader reader, out Zip64EndOfCentralDirectoryLocator zip64EOCDLocator)
299         {
300             zip64EOCDLocator = new Zip64EndOfCentralDirectoryLocator();
301 
302             if (reader.ReadUInt32() != SignatureConstant)
303                 return false;
304 
305             zip64EOCDLocator.NumberOfDiskWithZip64EOCD = reader.ReadUInt32();
306             zip64EOCDLocator.OffsetOfZip64EOCD = reader.ReadUInt64();
307             zip64EOCDLocator.TotalNumberOfDisks = reader.ReadUInt32();
308             return true;
309         }
310 
WriteBlockSystem.IO.Compression.Zip64EndOfCentralDirectoryLocator311         public static void WriteBlock(Stream stream, long zip64EOCDRecordStart)
312         {
313             BinaryWriter writer = new BinaryWriter(stream);
314             writer.Write(SignatureConstant);
315             writer.Write((uint)0); // number of disk with start of zip64 eocd
316             writer.Write(zip64EOCDRecordStart);
317             writer.Write((uint)1); // total number of disks
318         }
319     }
320 
321     internal struct Zip64EndOfCentralDirectoryRecord
322     {
323         private const uint SignatureConstant = 0x06064B50;
324         private const ulong NormalSize = 0x2C; // the size of the data excluding the size/signature fields if no extra data included
325 
326         public ulong SizeOfThisRecord;
327         public ushort VersionMadeBy;
328         public ushort VersionNeededToExtract;
329         public uint NumberOfThisDisk;
330         public uint NumberOfDiskWithStartOfCD;
331         public ulong NumberOfEntriesOnThisDisk;
332         public ulong NumberOfEntriesTotal;
333         public ulong SizeOfCentralDirectory;
334         public ulong OffsetOfCentralDirectory;
335 
TryReadBlockSystem.IO.Compression.Zip64EndOfCentralDirectoryRecord336         public static bool TryReadBlock(BinaryReader reader, out Zip64EndOfCentralDirectoryRecord zip64EOCDRecord)
337         {
338             zip64EOCDRecord = new Zip64EndOfCentralDirectoryRecord();
339 
340             if (reader.ReadUInt32() != SignatureConstant)
341                 return false;
342 
343             zip64EOCDRecord.SizeOfThisRecord = reader.ReadUInt64();
344             zip64EOCDRecord.VersionMadeBy = reader.ReadUInt16();
345             zip64EOCDRecord.VersionNeededToExtract = reader.ReadUInt16();
346             zip64EOCDRecord.NumberOfThisDisk = reader.ReadUInt32();
347             zip64EOCDRecord.NumberOfDiskWithStartOfCD = reader.ReadUInt32();
348             zip64EOCDRecord.NumberOfEntriesOnThisDisk = reader.ReadUInt64();
349             zip64EOCDRecord.NumberOfEntriesTotal = reader.ReadUInt64();
350             zip64EOCDRecord.SizeOfCentralDirectory = reader.ReadUInt64();
351             zip64EOCDRecord.OffsetOfCentralDirectory = reader.ReadUInt64();
352 
353             return true;
354         }
355 
WriteBlockSystem.IO.Compression.Zip64EndOfCentralDirectoryRecord356         public static void WriteBlock(Stream stream, long numberOfEntries, long startOfCentralDirectory, long sizeOfCentralDirectory)
357         {
358             BinaryWriter writer = new BinaryWriter(stream);
359 
360             // write Zip 64 EOCD record
361             writer.Write(SignatureConstant);
362             writer.Write(NormalSize);
363             writer.Write((ushort)ZipVersionNeededValues.Zip64); // version needed is 45 for zip 64 support
364             writer.Write((ushort)ZipVersionNeededValues.Zip64); // version made by: high byte is 0 for MS DOS, low byte is version needed
365             writer.Write((uint)0); // number of this disk is 0
366             writer.Write((uint)0); // number of disk with start of central directory is 0
367             writer.Write(numberOfEntries); // number of entries on this disk
368             writer.Write(numberOfEntries); // number of entries total
369             writer.Write(sizeOfCentralDirectory);
370             writer.Write(startOfCentralDirectory);
371         }
372     }
373 
374     internal readonly struct ZipLocalFileHeader
375     {
376         public const uint DataDescriptorSignature = 0x08074B50;
377         public const uint SignatureConstant = 0x04034B50;
378         public const int OffsetToCrcFromHeaderStart = 14;
379         public const int OffsetToBitFlagFromHeaderStart = 6;
380         public const int SizeOfLocalHeader = 30;
381 
GetExtraFieldsSystem.IO.Compression.ZipLocalFileHeader382         public static List<ZipGenericExtraField> GetExtraFields(BinaryReader reader)
383         {
384             // assumes that TrySkipBlock has already been called, so we don't have to validate twice
385 
386             List<ZipGenericExtraField> result;
387 
388             const int OffsetToFilenameLength = 26; // from the point before the signature
389 
390             reader.BaseStream.Seek(OffsetToFilenameLength, SeekOrigin.Current);
391 
392             ushort filenameLength = reader.ReadUInt16();
393             ushort extraFieldLength = reader.ReadUInt16();
394 
395             reader.BaseStream.Seek(filenameLength, SeekOrigin.Current);
396 
397 
398             using (Stream str = new SubReadStream(reader.BaseStream, reader.BaseStream.Position, extraFieldLength))
399             {
400                 result = ZipGenericExtraField.ParseExtraField(str);
401             }
402             Zip64ExtraField.RemoveZip64Blocks(result);
403 
404             return result;
405         }
406 
407         // will not throw end of stream exception
TrySkipBlockSystem.IO.Compression.ZipLocalFileHeader408         public static bool TrySkipBlock(BinaryReader reader)
409         {
410             const int OffsetToFilenameLength = 22; // from the point after the signature
411 
412             if (reader.ReadUInt32() != SignatureConstant)
413                 return false;
414 
415 
416             if (reader.BaseStream.Length < reader.BaseStream.Position + OffsetToFilenameLength)
417                 return false;
418 
419             reader.BaseStream.Seek(OffsetToFilenameLength, SeekOrigin.Current);
420 
421             ushort filenameLength = reader.ReadUInt16();
422             ushort extraFieldLength = reader.ReadUInt16();
423 
424             if (reader.BaseStream.Length < reader.BaseStream.Position + filenameLength + extraFieldLength)
425                 return false;
426 
427             reader.BaseStream.Seek(filenameLength + extraFieldLength, SeekOrigin.Current);
428 
429             return true;
430         }
431     }
432 
433     internal struct ZipCentralDirectoryFileHeader
434     {
435         public const uint SignatureConstant = 0x02014B50;
436         public byte VersionMadeByCompatibility;
437         public byte VersionMadeBySpecification;
438         public ushort VersionNeededToExtract;
439         public ushort GeneralPurposeBitFlag;
440         public ushort CompressionMethod;
441         public uint LastModified; // convert this on the fly
442         public uint Crc32;
443         public long CompressedSize;
444         public long UncompressedSize;
445         public ushort FilenameLength;
446         public ushort ExtraFieldLength;
447         public ushort FileCommentLength;
448         public int DiskNumberStart;
449         public ushort InternalFileAttributes;
450         public uint ExternalFileAttributes;
451         public long RelativeOffsetOfLocalHeader;
452 
453         public byte[] Filename;
454         public byte[] FileComment;
455         public List<ZipGenericExtraField> ExtraFields;
456 
457         // if saveExtraFieldsAndComments is false, FileComment and ExtraFields will be null
458         // in either case, the zip64 extra field info will be incorporated into other fields
TryReadBlockSystem.IO.Compression.ZipCentralDirectoryFileHeader459         public static bool TryReadBlock(BinaryReader reader, bool saveExtraFieldsAndComments, out ZipCentralDirectoryFileHeader header)
460         {
461             header = new ZipCentralDirectoryFileHeader();
462 
463             if (reader.ReadUInt32() != SignatureConstant)
464                 return false;
465             header.VersionMadeBySpecification = reader.ReadByte();
466             header.VersionMadeByCompatibility = reader.ReadByte();
467             header.VersionNeededToExtract = reader.ReadUInt16();
468             header.GeneralPurposeBitFlag = reader.ReadUInt16();
469             header.CompressionMethod = reader.ReadUInt16();
470             header.LastModified = reader.ReadUInt32();
471             header.Crc32 = reader.ReadUInt32();
472             uint compressedSizeSmall = reader.ReadUInt32();
473             uint uncompressedSizeSmall = reader.ReadUInt32();
474             header.FilenameLength = reader.ReadUInt16();
475             header.ExtraFieldLength = reader.ReadUInt16();
476             header.FileCommentLength = reader.ReadUInt16();
477             ushort diskNumberStartSmall = reader.ReadUInt16();
478             header.InternalFileAttributes = reader.ReadUInt16();
479             header.ExternalFileAttributes = reader.ReadUInt32();
480             uint relativeOffsetOfLocalHeaderSmall = reader.ReadUInt32();
481 
482             header.Filename = reader.ReadBytes(header.FilenameLength);
483 
484             bool uncompressedSizeInZip64 = uncompressedSizeSmall == ZipHelper.Mask32Bit;
485             bool compressedSizeInZip64 = compressedSizeSmall == ZipHelper.Mask32Bit;
486             bool relativeOffsetInZip64 = relativeOffsetOfLocalHeaderSmall == ZipHelper.Mask32Bit;
487             bool diskNumberStartInZip64 = diskNumberStartSmall == ZipHelper.Mask16Bit;
488 
489             Zip64ExtraField zip64;
490 
491             long endExtraFields = reader.BaseStream.Position + header.ExtraFieldLength;
492             using (Stream str = new SubReadStream(reader.BaseStream, reader.BaseStream.Position, header.ExtraFieldLength))
493             {
494                 if (saveExtraFieldsAndComments)
495                 {
496                     header.ExtraFields = ZipGenericExtraField.ParseExtraField(str);
497                     zip64 = Zip64ExtraField.GetAndRemoveZip64Block(header.ExtraFields,
498                             uncompressedSizeInZip64, compressedSizeInZip64,
499                             relativeOffsetInZip64, diskNumberStartInZip64);
500                 }
501                 else
502                 {
503                     header.ExtraFields = null;
504                     zip64 = Zip64ExtraField.GetJustZip64Block(str,
505                             uncompressedSizeInZip64, compressedSizeInZip64,
506                             relativeOffsetInZip64, diskNumberStartInZip64);
507                 }
508             }
509 
510             // There are zip files that have malformed ExtraField blocks in which GetJustZip64Block() silently bails out without reading all the way to the end
511             // of the ExtraField block. Thus we must force the stream's position to the proper place.
512             reader.BaseStream.AdvanceToPosition(endExtraFields);
513 
514             if (saveExtraFieldsAndComments)
515                 header.FileComment = reader.ReadBytes(header.FileCommentLength);
516             else
517             {
518                 reader.BaseStream.Position += header.FileCommentLength;
519                 header.FileComment = null;
520             }
521 
522             header.UncompressedSize = zip64.UncompressedSize == null
523                                                     ? uncompressedSizeSmall
524                                                     : zip64.UncompressedSize.Value;
525             header.CompressedSize = zip64.CompressedSize == null
526                                                     ? compressedSizeSmall
527                                                     : zip64.CompressedSize.Value;
528             header.RelativeOffsetOfLocalHeader = zip64.LocalHeaderOffset == null
529                                                     ? relativeOffsetOfLocalHeaderSmall
530                                                     : zip64.LocalHeaderOffset.Value;
531             header.DiskNumberStart = zip64.StartDiskNumber == null
532                                                     ? diskNumberStartSmall
533                                                     : zip64.StartDiskNumber.Value;
534 
535             return true;
536         }
537     }
538 
539     internal struct ZipEndOfCentralDirectoryBlock
540     {
541         public const uint SignatureConstant = 0x06054B50;
542         public const int SizeOfBlockWithoutSignature = 18;
543         public uint Signature;
544         public ushort NumberOfThisDisk;
545         public ushort NumberOfTheDiskWithTheStartOfTheCentralDirectory;
546         public ushort NumberOfEntriesInTheCentralDirectoryOnThisDisk;
547         public ushort NumberOfEntriesInTheCentralDirectory;
548         public uint SizeOfCentralDirectory;
549         public uint OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber;
550         public byte[] ArchiveComment;
551 
WriteBlockSystem.IO.Compression.ZipEndOfCentralDirectoryBlock552         public static void WriteBlock(Stream stream, long numberOfEntries, long startOfCentralDirectory, long sizeOfCentralDirectory, byte[] archiveComment)
553         {
554             BinaryWriter writer = new BinaryWriter(stream);
555 
556             ushort numberOfEntriesTruncated = numberOfEntries > ushort.MaxValue ?
557                                                         ZipHelper.Mask16Bit : (ushort)numberOfEntries;
558             uint startOfCentralDirectoryTruncated = startOfCentralDirectory > uint.MaxValue ?
559                                                         ZipHelper.Mask32Bit : (uint)startOfCentralDirectory;
560             uint sizeOfCentralDirectoryTruncated = sizeOfCentralDirectory > uint.MaxValue ?
561                                                         ZipHelper.Mask32Bit : (uint)sizeOfCentralDirectory;
562 
563             writer.Write(SignatureConstant);
564             writer.Write((ushort)0); // number of this disk
565             writer.Write((ushort)0); // number of disk with start of CD
566             writer.Write(numberOfEntriesTruncated); // number of entries on this disk's cd
567             writer.Write(numberOfEntriesTruncated); // number of entries in entire CD
568             writer.Write(sizeOfCentralDirectoryTruncated);
569             writer.Write(startOfCentralDirectoryTruncated);
570 
571             // Should be valid because of how we read archiveComment in TryReadBlock:
572             Debug.Assert((archiveComment == null) || (archiveComment.Length < ushort.MaxValue));
573 
574             writer.Write(archiveComment != null ? (ushort)archiveComment.Length : (ushort)0); // zip file comment length
575             if (archiveComment != null)
576                 writer.Write(archiveComment);
577         }
578 
TryReadBlockSystem.IO.Compression.ZipEndOfCentralDirectoryBlock579         public static bool TryReadBlock(BinaryReader reader, out ZipEndOfCentralDirectoryBlock eocdBlock)
580         {
581             eocdBlock = new ZipEndOfCentralDirectoryBlock();
582             if (reader.ReadUInt32() != SignatureConstant)
583                 return false;
584 
585             eocdBlock.Signature = SignatureConstant;
586             eocdBlock.NumberOfThisDisk = reader.ReadUInt16();
587             eocdBlock.NumberOfTheDiskWithTheStartOfTheCentralDirectory = reader.ReadUInt16();
588             eocdBlock.NumberOfEntriesInTheCentralDirectoryOnThisDisk = reader.ReadUInt16();
589             eocdBlock.NumberOfEntriesInTheCentralDirectory = reader.ReadUInt16();
590             eocdBlock.SizeOfCentralDirectory = reader.ReadUInt32();
591             eocdBlock.OffsetOfStartOfCentralDirectoryWithRespectToTheStartingDiskNumber = reader.ReadUInt32();
592 
593             ushort commentLength = reader.ReadUInt16();
594             eocdBlock.ArchiveComment = reader.ReadBytes(commentLength);
595 
596             return true;
597         }
598     }
599 }
600