// Write the local file header // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage private void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData) { CompressionMethod method = entry.CompressionMethod; bool headerInfoAvailable = true; // How to get this? bool patchEntryHeader = false; this.WriteLEInt(ZipConstants.LocalHeaderSignature); this.WriteLEShort(entry.Version); this.WriteLEShort(entry.Flags); this.WriteLEShort((byte)method); this.WriteLEInt((int)entry.DosTime); if (headerInfoAvailable == true) { this.WriteLEInt((int)entry.Crc); if (entry.LocalHeaderRequiresZip64) { this.WriteLEInt(-1); this.WriteLEInt(-1); } else { this.WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize); this.WriteLEInt((int)entry.Size); } } else { if (patchData != null) { patchData.CrcPatchOffset = this.stream_.Position; } this.WriteLEInt(0); // Crc if (patchData != null) { patchData.SizePatchOffset = this.stream_.Position; } // For local header both sizes appear in Zip64 Extended Information if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { this.WriteLEInt(-1); this.WriteLEInt(-1); } else { this.WriteLEInt(0); // Compressed size this.WriteLEInt(0); // Uncompressed size } } byte[] name = ZipStrings.ConvertToArray(entry.Flags, entry.Name); if (name.Length > 0xFFFF) { throw new ZipException("Entry name too long."); } var ed = new ZipExtraData(entry.ExtraData); if (entry.LocalHeaderRequiresZip64 && (headerInfoAvailable || patchEntryHeader)) { ed.StartNewEntry(); if (headerInfoAvailable) { ed.AddLeLong(entry.Size); ed.AddLeLong(entry.CompressedSize); } else { ed.AddLeLong(-1); ed.AddLeLong(-1); } ed.AddNewEntry(1); if (!ed.Find(1)) { throw new ZipException("Internal error cant find extra data"); } if (patchData != null) { patchData.SizePatchOffset = ed.CurrentReadIndex; } } else { ed.Delete(1); } byte[] extra = ed.GetEntryData(); this.WriteLEShort(name.Length); this.WriteLEShort(extra.Length); if (name.Length > 0) { this.stream_.Write(name, 0, name.Length); } if (entry.LocalHeaderRequiresZip64 && patchEntryHeader) { patchData.SizePatchOffset += this.stream_.Position; } if (extra.Length > 0) { this.stream_.Write(extra, 0, extra.Length); } }
/// <summary> /// Process extra data fields updating the entry based on the contents. /// </summary> /// <param name="localHeader">True if the extra data fields should be handled /// for a local header, rather than for a central header. /// </param> internal void ProcessExtraData(bool localHeader) { var extraData = new ZipExtraData(this.extra); if (extraData.Find(0x0001)) { // Version required to extract is ignored here as some archivers dont set it correctly // in theory it should be version 45 or higher // The recorded size will change but remember that this is zip64. this.forceZip64_ = true; if (extraData.ValueLength < 4) { throw new ZipException("Extra data extended Zip64 information length is invalid"); } // (localHeader ||) was deleted, because actually there is no specific difference with reading sizes between local header & central directory // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT // ... // 4.4 Explanation of fields // ... // 4.4.8 compressed size: (4 bytes) // 4.4.9 uncompressed size: (4 bytes) // // The size of the file compressed (4.4.8) and uncompressed, // (4.4.9) respectively. When a decryption header is present it // will be placed in front of the file data and the value of the // compressed file size will include the bytes of the decryption // header. If bit 3 of the general purpose bit flag is set, // these fields are set to zero in the local header and the // correct values are put in the data descriptor and // in the central directory. If an archive is in ZIP64 format // and the value in this field is 0xFFFFFFFF, the size will be // in the corresponding 8 byte ZIP64 extended information // extra field. When encrypting the central directory, if the // local header is not in ZIP64 format and general purpose bit // flag 13 is set indicating masking, the value stored for the // uncompressed size in the Local Header will be zero. // // Othewise there is problem with minizip implementation if (this.size == uint.MaxValue) { this.size = (ulong)extraData.ReadLong(); } if (this.compressedSize == uint.MaxValue) { this.compressedSize = (ulong)extraData.ReadLong(); } if (!localHeader && (this.offset == uint.MaxValue)) { this.offset = extraData.ReadLong(); } // Disk number on which file starts is ignored } else { if ( ((this.versionToExtract & 0xff) >= ZipConstants.VersionZip64) && ((this.size == uint.MaxValue) || (this.compressedSize == uint.MaxValue)) ) { throw new ZipException("Zip64 Extended information required but is missing."); } } this.DateTime = this.GetDateTime(extraData); if (this.method == CompressionMethod.WinZipAES) { this.ProcessAESExtraData(extraData); } }