// Initializes, attaches it to archive internal ZipArchiveEntry(ZipArchive archive, ZipCentralDirectoryFileHeader cd) { Archive = archive; _diskNumberStart = cd.DiskNumberStart; _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag; _compressionMethod = (CompressionMethodValues)cd.CompressionMethod; // Leave this as a uint and let the caller convert it if it wants (perf optimization) LastWriteTime = cd.LastModified; CompressedLength = cd.CompressedSize; Length = cd.UncompressedSize; ExternalAttributes = (int)cd.ExternalFileAttributes; _offsetOfLocalHeader = cd.RelativeOffsetOfLocalHeader; // we don't know this yet: should be _offsetOfLocalHeader + 30 + _storedEntryNameBytes.Length + extrafieldlength // but entryname/extra length could be different in LH _storedOffsetOfCompressedData = null; Crc32 = cd.Crc32; // Sacrifice a slight amount of time for safety. Zips entry names are emphatically NOT supposed to // have backslashes according to the spec, but they might anyway, so normalize them all to forward slashes. FullName = DecodeEntryName(cd.Filename)?.Replace('\\', '/') ?? throw new ArgumentNullException(nameof(FullName)); Name = ParseFileName(FullName, (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility); }
// Initializes, attaches it to archive internal ZipArchiveEntry(ZipArchiveFast archive, ZipCentralDirectoryFileHeader cd) { Archive = archive; _diskNumberStart = cd.DiskNumberStart; _generalPurposeBitFlag = (BitFlagValues)cd.GeneralPurposeBitFlag; _compressionMethod = (CompressionMethodValues)cd.CompressionMethod; // Leave this as a uint and let the caller convert it if it wants (perf optimization) LastWriteTime = cd.LastModified; CompressedLength = cd.CompressedSize; Length = cd.UncompressedSize; _offsetOfLocalHeader = cd.RelativeOffsetOfLocalHeader; // we don't know this yet: should be _offsetOfLocalHeader + 30 + _storedEntryNameBytes.Length + extrafieldlength // but entryname/extra length could be different in LH _storedOffsetOfCompressedData = null; // Sacrifice a slight amount of time for safety. Zips entry names are emphatically NOT supposed to // have backslashes according to the spec, but they might anyway, so normalize them all to forward slashes. FullName = DecodeEntryName(cd.Filename).Replace('\\', '/'); // Lazy-load Name so we don't preemptively do a ton of Substring() calls when we don't need to. _versionMadeByCompatibility = (ZipVersionMadeByPlatform)cd.VersionMadeByCompatibility; }
private Stream GetDataDecompressor(Stream compressedStreamToRead) { CompressionMethodValues compressionMethod = CompressionMethod; if ((compressionMethod != CompressionMethodValues.Stored) && (compressionMethod == CompressionMethodValues.Deflate)) { return(new DeflateStream(compressedStreamToRead, CompressionMode.Decompress)); } return(compressedStreamToRead); }
private void WriteLocalFileHeaderAndDataIfNeeded() { // _storedUncompressedData gets frozen here, and is what gets written to the file if (_storedUncompressedData != null || _compressedBytes != null) { if (_storedUncompressedData != null) { _uncompressedSize = _storedUncompressedData.Length; //The compressor fills in CRC and sizes //The DirectToArchiveWriterStream writes headers and such using (Stream entryWriter = new DirectToArchiveWriterStream( GetDataCompressor(_archive.ArchiveStream, true, null), this)) { _storedUncompressedData.Seek(0, SeekOrigin.Begin); _storedUncompressedData.CopyTo(entryWriter); _storedUncompressedData.Dispose(); _storedUncompressedData = null; } } else { // we know the sizes at this point, so just go ahead and write the headers if (_uncompressedSize == 0) { CompressionMethod = CompressionMethodValues.Stored; } WriteLocalFileHeader(isEmptyFile: false); foreach (byte[] compressedBytes in _compressedBytes) { _archive.ArchiveStream.Write(compressedBytes, 0, compressedBytes.Length); } } } else // there is no data in the file, but if we are in update mode, we still need to write a header { if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite) { _everOpenedForWrite = true; WriteLocalFileHeader(isEmptyFile: true); } } }
private void WriteLocalFileHeaderAndDataIfNeeded() { //_storedUncompressedData gets frozen here, and is what gets written to the file if (_storedUncompressedData != null || _compressedBytes != null) { if (_storedUncompressedData != null) { _uncompressedSize = _storedUncompressedData.Length; //The compressor fills in CRC and sizes //The DirectToArchiveWriterStream writes headers and such using (Stream entryWriter = new DirectToArchiveWriterStream( GetDataCompressor(_archive.ArchiveStream, true, null), this)) { _storedUncompressedData.Seek(0, SeekOrigin.Begin); _storedUncompressedData.CopyTo(entryWriter); _storedUncompressedData.Dispose(); _storedUncompressedData = null; } } else { // we know the sizes at this point, so just go ahead and write the headers if (_uncompressedSize == 0) CompressionMethod = CompressionMethodValues.Stored; WriteLocalFileHeader(false); foreach (Byte[] compressedBytes in _compressedBytes) { _archive.ArchiveStream.Write(compressedBytes, 0, compressedBytes.Length); } } } else //there is no data in the file, but if we are in update mode, we still need to write a header { if (_archive.Mode == ZipArchiveMode.Update || !_everOpenedForWrite) { _everOpenedForWrite = true; WriteLocalFileHeader(true); } } }
//return value is true if we allocated an extra field for 64 bit headers, un/compressed size private Boolean WriteLocalFileHeader(Boolean isEmptyFile) { BinaryWriter writer = new BinaryWriter(_archive.ArchiveStream); //_entryname only gets set when we read in or call moveTo. MoveTo does a check, and //reading in should not be able to produce a entryname longer than UInt16.MaxValue Debug.Assert(_storedEntryNameBytes.Length <= UInt16.MaxValue); //decide if we need the Zip64 extra field: Zip64ExtraField zip64ExtraField = new Zip64ExtraField(); Boolean zip64Used = false; UInt32 compressedSizeTruncated, uncompressedSizeTruncated; //if we already know that we have an empty file don't worry about anything, just do a straight shot of the header if (isEmptyFile) { CompressionMethod = CompressionMethodValues.Stored; compressedSizeTruncated = 0; uncompressedSizeTruncated = 0; Debug.Assert(_compressedSize == 0); Debug.Assert(_uncompressedSize == 0); Debug.Assert(_crc32 == 0); } else { //if we have a non-seekable stream, don't worry about sizes at all, and just set the right bit //if we are using the data descriptor, then sizes and crc should be set to 0 in the header if (_archive.Mode == ZipArchiveMode.Create && _archive.ArchiveStream.CanSeek == false && !isEmptyFile) { _generalPurposeBitFlag |= BitFlagValues.DataDescriptor; zip64Used = false; compressedSizeTruncated = 0; uncompressedSizeTruncated = 0; //the crc should not have been set if we are in create mode, but clear it just to be sure Debug.Assert(_crc32 == 0); } else //if we are not in streaming mode, we have to decide if we want to write zip64 headers { if (SizesTooLarge() #if DEBUG_FORCE_ZIP64 || (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update) #endif ) { zip64Used = true; compressedSizeTruncated = ZipHelper.Mask32Bit; uncompressedSizeTruncated = ZipHelper.Mask32Bit; //prepare Zip64 extra field object. If we have one of the sizes, the other must go in there zip64ExtraField.CompressedSize = _compressedSize; zip64ExtraField.UncompressedSize = _uncompressedSize; VersionToExtractAtLeast(ZipVersionNeededValues.Zip64); } else { zip64Used = false; compressedSizeTruncated = (UInt32)_compressedSize; uncompressedSizeTruncated = (UInt32)_uncompressedSize; } } } //save offset _offsetOfLocalHeader = writer.BaseStream.Position; //calculate extra field. if zip64 stuff + original extraField aren't going to fit, dump the original extraField, because this is more important Int32 bigExtraFieldLength = (zip64Used ? zip64ExtraField.TotalSize : 0) + (_lhUnknownExtraFields != null ? ZipGenericExtraField.TotalSize(_lhUnknownExtraFields) : 0); UInt16 extraFieldLength; if (bigExtraFieldLength > UInt16.MaxValue) { extraFieldLength = (UInt16)(zip64Used ? zip64ExtraField.TotalSize : 0); _lhUnknownExtraFields = null; } else { extraFieldLength = (UInt16)bigExtraFieldLength; } //write header writer.Write(ZipLocalFileHeader.SignatureConstant); writer.Write((UInt16)_versionToExtract); writer.Write((UInt16)_generalPurposeBitFlag); writer.Write((UInt16)CompressionMethod); writer.Write(ZipHelper.DateTimeToDosTime(_lastModified.DateTime)); //UInt32 writer.Write(_crc32); //UInt32 writer.Write(compressedSizeTruncated); //UInt32 writer.Write(uncompressedSizeTruncated); //UInt32 writer.Write((UInt16)_storedEntryNameBytes.Length); writer.Write(extraFieldLength); //UInt16 writer.Write(_storedEntryNameBytes); if (zip64Used) zip64ExtraField.WriteBlock(_archive.ArchiveStream); if (_lhUnknownExtraFields != null) ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _archive.ArchiveStream); return zip64Used; }
// return value is true if we allocated an extra field for 64 bit headers, un/compressed size private bool WriteLocalFileHeader(bool isEmptyFile) { BinaryWriter writer = new BinaryWriter(_archive.ArchiveStream); // _entryname only gets set when we read in or call moveTo. MoveTo does a check, and // reading in should not be able to produce an entryname longer than ushort.MaxValue Debug.Assert(_storedEntryNameBytes.Length <= ushort.MaxValue); // decide if we need the Zip64 extra field: Zip64ExtraField zip64ExtraField = new Zip64ExtraField(); bool zip64Used = false; uint compressedSizeTruncated, uncompressedSizeTruncated; // if we already know that we have an empty file don't worry about anything, just do a straight shot of the header if (isEmptyFile) { CompressionMethod = CompressionMethodValues.Stored; compressedSizeTruncated = 0; uncompressedSizeTruncated = 0; Debug.Assert(_compressedSize == 0); Debug.Assert(_uncompressedSize == 0); Debug.Assert(_crc32 == 0); } else { // if we have a non-seekable stream, don't worry about sizes at all, and just set the right bit // if we are using the data descriptor, then sizes and crc should be set to 0 in the header if (_archive.Mode == ZipArchiveMode.Create && _archive.ArchiveStream.CanSeek == false && !isEmptyFile) { _generalPurposeBitFlag |= BitFlagValues.DataDescriptor; zip64Used = false; compressedSizeTruncated = 0; uncompressedSizeTruncated = 0; // the crc should not have been set if we are in create mode, but clear it just to be sure Debug.Assert(_crc32 == 0); } else // if we are not in streaming mode, we have to decide if we want to write zip64 headers { if (SizesTooLarge() #if DEBUG_FORCE_ZIP64 || (_archive._forceZip64 && _archive.Mode == ZipArchiveMode.Update) #endif ) { zip64Used = true; compressedSizeTruncated = ZipHelper.Mask32Bit; uncompressedSizeTruncated = ZipHelper.Mask32Bit; // prepare Zip64 extra field object. If we have one of the sizes, the other must go in there zip64ExtraField.CompressedSize = _compressedSize; zip64ExtraField.UncompressedSize = _uncompressedSize; VersionToExtractAtLeast(ZipVersionNeededValues.Zip64); } else { zip64Used = false; compressedSizeTruncated = (uint)_compressedSize; uncompressedSizeTruncated = (uint)_uncompressedSize; } } } // save offset _offsetOfLocalHeader = writer.BaseStream.Position; // calculate extra field. if zip64 stuff + original extraField aren't going to fit, dump the original extraField, because this is more important int bigExtraFieldLength = (zip64Used ? zip64ExtraField.TotalSize : 0) + (_lhUnknownExtraFields != null ? ZipGenericExtraField.TotalSize(_lhUnknownExtraFields) : 0); ushort extraFieldLength; if (bigExtraFieldLength > ushort.MaxValue) { extraFieldLength = (ushort)(zip64Used ? zip64ExtraField.TotalSize : 0); _lhUnknownExtraFields = null; } else { extraFieldLength = (ushort)bigExtraFieldLength; } // write header writer.Write(ZipLocalFileHeader.SignatureConstant); writer.Write((ushort)_versionToExtract); writer.Write((ushort)_generalPurposeBitFlag); writer.Write((ushort)CompressionMethod); writer.Write(ZipHelper.DateTimeToDosTime(_lastModified.DateTime)); // uint writer.Write(_crc32); // uint writer.Write(compressedSizeTruncated); // uint writer.Write(uncompressedSizeTruncated); // uint writer.Write((ushort)_storedEntryNameBytes.Length); writer.Write(extraFieldLength); // ushort writer.Write(_storedEntryNameBytes); if (zip64Used) { zip64ExtraField.WriteBlock(_archive.ArchiveStream); } if (_lhUnknownExtraFields != null) { ZipGenericExtraField.WriteAllBlocks(_lhUnknownExtraFields, _archive.ArchiveStream); } return(zip64Used); }