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