// ZipHelperStream.cs // // Copyright 2006, 2007 John Reilly // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // Linking this library statically or dynamically with other modules is // making a combined work based on this library. Thus, the terms and // conditions of the GNU General Public License cover the whole // combination. // // As a special exception, the copyright holders of this library give you // permission to link this library with independent modules to produce an // executable, regardless of the license terms of these independent // modules, and to copy and distribute the resulting executable under // terms of your choice, provided that you also meet, for each linked // independent module, the terms and conditions of the license of that // module. An independent module is a module which is not derived from // or based on this library. If you modify this library, you may extend // this exception to your version of the library, but you are not // obligated to do so. If you do not wish to do so, delete this // exception statement from your version. using System; using System.IO; using System.Text; namespace ICSharpCode.SharpZipLib.Zip { /// /// Holds data pertinent to a data descriptor. /// public class DescriptorData { /// /// Get /set the compressed size of data. /// public long CompressedSize { get { return compressedSize; } set { compressedSize = value; } } /// /// Get / set the uncompressed size of data /// public long Size { get { return size; } set { size = value; } } /// /// Get /set the crc value. /// public long Crc { get { return crc; } set { crc = (value & 0xffffffff); } } #region Instance Fields long size; long compressedSize; long crc; #endregion } class EntryPatchData { public long SizePatchOffset { get { return sizePatchOffset_; } set { sizePatchOffset_ = value; } } public long CrcPatchOffset { get { return crcPatchOffset_; } set { crcPatchOffset_ = value; } } #region Instance Fields long sizePatchOffset_; long crcPatchOffset_; #endregion } /// /// This class assists with writing/reading from Zip files. /// internal class ZipHelperStream : Stream { #region Constructors /// /// Initialise an instance of this class. /// /// The name of the file to open. public ZipHelperStream(string name) { stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite); isOwner_ = true; } /// /// Initialise a new instance of . /// /// The stream to use. public ZipHelperStream(Stream stream) { stream_ = stream; } #endregion /// /// Get / set a value indicating wether the the underlying stream is owned or not. /// /// If the stream is owned it is closed when this instance is closed. public bool IsStreamOwner { get { return isOwner_; } set { isOwner_ = value; } } #region Base Stream Methods public override bool CanRead { get { return stream_.CanRead; } } public override bool CanSeek { get { return stream_.CanSeek; } } #if !NET_1_0 && !NET_1_1 && !NETCF_1_0 public override bool CanTimeout { get { return stream_.CanTimeout; } } #endif public override long Length { get { return stream_.Length; } } public override long Position { get { return stream_.Position; } set { stream_.Position = value; } } public override bool CanWrite { get { return stream_.CanWrite; } } public override void Flush() { stream_.Flush(); } public override long Seek(long offset, SeekOrigin origin) { return stream_.Seek(offset, origin); } public override void SetLength(long value) { stream_.SetLength(value); } public override int Read(byte[] buffer, int offset, int count) { return stream_.Read(buffer, offset, count); } public override void Write(byte[] buffer, int offset, int count) { stream_.Write(buffer, offset, count); } /// /// Close the stream. /// /// /// The underlying stream is closed only if is true. /// override public void Close() { Stream toClose = stream_; stream_ = null; if (isOwner_ && (toClose != null)) { isOwner_ = false; toClose.Close(); } } #endregion // Write the local file header // TODO: ZipHelperStream.WriteLocalHeader is not yet used and needs checking for ZipFile and ZipOuptutStream usage void WriteLocalHeader(ZipEntry entry, EntryPatchData patchData) { CompressionMethod method = entry.CompressionMethod; bool headerInfoAvailable = true; // How to get this? bool patchEntryHeader = false; WriteLEInt(ZipConstants.LocalHeaderSignature); WriteLEShort(entry.Version); WriteLEShort(entry.Flags); WriteLEShort((byte)method); WriteLEInt((int)entry.DosTime); if (headerInfoAvailable == true) { WriteLEInt((int)entry.Crc); if ( entry.LocalHeaderRequiresZip64 ) { WriteLEInt(-1); WriteLEInt(-1); } else { WriteLEInt(entry.IsCrypted ? (int)entry.CompressedSize + ZipConstants.CryptoHeaderSize : (int)entry.CompressedSize); WriteLEInt((int)entry.Size); } } else { if (patchData != null) { patchData.CrcPatchOffset = stream_.Position; } WriteLEInt(0); // Crc if ( patchData != null ) { patchData.SizePatchOffset = stream_.Position; } // For local header both sizes appear in Zip64 Extended Information if ( entry.LocalHeaderRequiresZip64 && patchEntryHeader ) { WriteLEInt(-1); WriteLEInt(-1); } else { WriteLEInt(0); // Compressed size WriteLEInt(0); // Uncompressed size } } byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name); if (name.Length > 0xFFFF) { throw new ZipException("Entry name too long."); } ZipExtraData 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(); WriteLEShort(name.Length); WriteLEShort(extra.Length); if ( name.Length > 0 ) { stream_.Write(name, 0, name.Length); } if ( entry.LocalHeaderRequiresZip64 && patchEntryHeader ) { patchData.SizePatchOffset += stream_.Position; } if ( extra.Length > 0 ) { stream_.Write(extra, 0, extra.Length); } } /// /// Locates a block with the desired . /// /// The signature to find. /// Location, marking the end of block. /// Minimum size of the block. /// The maximum variable data. /// Eeturns the offset of the first byte after the signature; -1 if not found public long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData) { long pos = endLocation - minimumBlockSize; if ( pos < 0 ) { return -1; } long giveUpMarker = Math.Max(pos - maximumVariableData, 0); // TODO: This loop could be optimised for speed. do { if ( pos < giveUpMarker ) { return -1; } Seek(pos--, SeekOrigin.Begin); } while ( ReadLEInt() != signature ); return Position; } /// /// Write Zip64 end of central directory records (File header and locator). /// /// The number of entries in the central directory. /// The size of entries in the central directory. /// The offset of the dentral directory. public void WriteZip64EndOfCentralDirectory(long noOfEntries, long sizeEntries, long centralDirOffset) { long centralSignatureOffset = stream_.Position; WriteLEInt(ZipConstants.Zip64CentralFileHeaderSignature); WriteLELong(44); // Size of this record (total size of remaining fields in header or full size - 12) WriteLEShort(ZipConstants.VersionMadeBy); // Version made by WriteLEShort(ZipConstants.VersionZip64); // Version to extract WriteLEInt(0); // Number of this disk WriteLEInt(0); // number of the disk with the start of the central directory WriteLELong(noOfEntries); // No of entries on this disk WriteLELong(noOfEntries); // Total No of entries in central directory WriteLELong(sizeEntries); // Size of the central directory WriteLELong(centralDirOffset); // offset of start of central directory // zip64 extensible data sector not catered for here (variable size) // Write the Zip64 end of central directory locator WriteLEInt(ZipConstants.Zip64CentralDirLocatorSignature); // no of the disk with the start of the zip64 end of central directory WriteLEInt(0); // relative offset of the zip64 end of central directory record WriteLELong(centralSignatureOffset); // total number of disks WriteLEInt(1); } /// /// Write the required records to end the central directory. /// /// The number of entries in the directory. /// The size of the entries in the directory. /// The start of the central directory. /// The archive comment. (This can be null). public void WriteEndOfCentralDirectory(long noOfEntries, long sizeEntries, long startOfCentralDirectory, byte[] comment) { if ( (noOfEntries >= 0xffff) || (startOfCentralDirectory >= 0xffffffff) || (sizeEntries >= 0xffffffff) ) { WriteZip64EndOfCentralDirectory(noOfEntries, sizeEntries, startOfCentralDirectory); } WriteLEInt(ZipConstants.EndOfCentralDirectorySignature); // TODO: ZipFile Multi disk handling not done WriteLEShort(0); // number of this disk WriteLEShort(0); // no of disk with start of central dir // Number of entries if ( noOfEntries >= 0xffff ) { WriteLEUshort(0xffff); // Zip64 marker WriteLEUshort(0xffff); } else { WriteLEShort(( short )noOfEntries); // entries in central dir for this disk WriteLEShort(( short )noOfEntries); // total entries in central directory } // Size of the central directory if ( sizeEntries >= 0xffffffff ) { WriteLEUint(0xffffffff); // Zip64 marker } else { WriteLEInt(( int )sizeEntries); } // offset of start of central directory if ( startOfCentralDirectory >= 0xffffffff ) { WriteLEUint(0xffffffff); // Zip64 marker } else { WriteLEInt(( int )startOfCentralDirectory); } int commentLength = (comment != null) ? comment.Length : 0; if ( commentLength > 0xffff ) { throw new ZipException(string.Format("Comment length({0}) is too long can only be 64K", commentLength)); } WriteLEShort(commentLength); if ( commentLength > 0 ) { Write(comment, 0, comment.Length); } } #region LE value reading/writing /// /// Read an unsigned short in little endian byte order. /// /// Returns the value read. /// /// An i/o error occurs. /// /// /// The file ends prematurely /// public int ReadLEShort() { int byteValue1 = stream_.ReadByte(); if (byteValue1 < 0) { throw new EndOfStreamException(); } int byteValue2 = stream_.ReadByte(); if (byteValue2 < 0) { throw new EndOfStreamException(); } return byteValue1 | (byteValue2 << 8); } /// /// Read an int in little endian byte order. /// /// Returns the value read. /// /// An i/o error occurs. /// /// /// The file ends prematurely /// public int ReadLEInt() { return ReadLEShort() | (ReadLEShort() << 16); } /// /// Read a long in little endian byte order. /// /// The value read. public long ReadLELong() { return (uint)ReadLEInt() | ((long)ReadLEInt() << 32); } /// /// Write an unsigned short in little endian byte order. /// /// The value to write. public void WriteLEShort(int value) { stream_.WriteByte(( byte )(value & 0xff)); stream_.WriteByte(( byte )((value >> 8) & 0xff)); } /// /// Write a ushort in little endian byte order. /// /// The value to write. public void WriteLEUshort(ushort value) { stream_.WriteByte(( byte )(value & 0xff)); stream_.WriteByte(( byte )(value >> 8)); } /// /// Write an int in little endian byte order. /// /// The value to write. public void WriteLEInt(int value) { WriteLEShort(value); WriteLEShort(value >> 16); } /// /// Write a uint in little endian byte order. /// /// The value to write. public void WriteLEUint(uint value) { WriteLEUshort(( ushort )(value & 0xffff)); WriteLEUshort(( ushort )(value >> 16)); } /// /// Write a long in little endian byte order. /// /// The value to write. public void WriteLELong(long value) { WriteLEInt(( int )value); WriteLEInt(( int )(value >> 32)); } /// /// Write a ulong in little endian byte order. /// /// The value to write. public void WriteLEUlong(ulong value) { WriteLEUint(( uint )(value & 0xffffffff)); WriteLEUint(( uint )(value >> 32)); } #endregion /// /// Write a data descriptor. /// /// The entry to write a descriptor for. /// Returns the number of descriptor bytes written. public int WriteDataDescriptor(ZipEntry entry) { if (entry == null) { throw new ArgumentNullException("entry"); } int result=0; // Add data descriptor if flagged as required if ((entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) { // The signature is not PKZIP originally but is now described as optional // in the PKZIP Appnote documenting trhe format. WriteLEInt(ZipConstants.DataDescriptorSignature); WriteLEInt(unchecked((int)(entry.Crc))); result+=8; if (entry.LocalHeaderRequiresZip64) { WriteLELong(entry.CompressedSize); WriteLELong(entry.Size); result+=16; } else { WriteLEInt((int)entry.CompressedSize); WriteLEInt((int)entry.Size); result+=8; } } return result; } /// /// Read data descriptor at the end of compressed data. /// /// if set to true [zip64]. /// The data to fill in. /// Returns the number of bytes read in the descriptor. public void ReadDataDescriptor(bool zip64, DescriptorData data) { int intValue = ReadLEInt(); // In theory this may not be a descriptor according to PKZIP appnote. // In practise its always there. if (intValue != ZipConstants.DataDescriptorSignature) { throw new ZipException("Data descriptor signature not found"); } data.Crc = ReadLEInt(); if (zip64) { data.CompressedSize = ReadLELong(); data.Size = ReadLELong(); } else { data.CompressedSize = ReadLEInt(); data.Size = ReadLEInt(); } } #region Instance Fields bool isOwner_; Stream stream_; #endregion } }