Mercurial > repos > SharpZipLib
view Zip/ZipHelperStream.cs @ 1:94e25b786321
Re #311: can't read ZIP file packed by Linux app Archive Manager/File Roller
Initial commit of clean SharpZipLib 0860 source. Only change is build paths.
author | IBBoard <dev@ibboard.co.uk> |
---|---|
date | Sat, 30 Oct 2010 14:03:17 +0000 |
parents | |
children |
line wrap: on
line source
// 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 { /// <summary> /// Holds data pertinent to a data descriptor. /// </summary> public class DescriptorData { /// <summary> /// Get /set the compressed size of data. /// </summary> public long CompressedSize { get { return compressedSize; } set { compressedSize = value; } } /// <summary> /// Get / set the uncompressed size of data /// </summary> public long Size { get { return size; } set { size = value; } } /// <summary> /// Get /set the crc value. /// </summary> 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 } /// <summary> /// This class assists with writing/reading from Zip files. /// </summary> internal class ZipHelperStream : Stream { #region Constructors /// <summary> /// Initialise an instance of this class. /// </summary> /// <param name="name">The name of the file to open.</param> public ZipHelperStream(string name) { stream_ = new FileStream(name, FileMode.Open, FileAccess.ReadWrite); isOwner_ = true; } /// <summary> /// Initialise a new instance of <see cref="ZipHelperStream"/>. /// </summary> /// <param name="stream">The stream to use.</param> public ZipHelperStream(Stream stream) { stream_ = stream; } #endregion /// <summary> /// Get / set a value indicating wether the the underlying stream is owned or not. /// </summary> /// <remarks>If the stream is owned it is closed when this instance is closed.</remarks> 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); } /// <summary> /// Close the stream. /// </summary> /// <remarks> /// The underlying stream is closed only if <see cref="IsStreamOwner"/> is true. /// </remarks> 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); } } /// <summary> /// Locates a block with the desired <paramref name="signature"/>. /// </summary> /// <param name="signature">The signature to find.</param> /// <param name="endLocation">Location, marking the end of block.</param> /// <param name="minimumBlockSize">Minimum size of the block.</param> /// <param name="maximumVariableData">The maximum variable data.</param> /// <returns>Eeturns the offset of the first byte after the signature; -1 if not found</returns> 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; } /// <summary> /// Write Zip64 end of central directory records (File header and locator). /// </summary> /// <param name="noOfEntries">The number of entries in the central directory.</param> /// <param name="sizeEntries">The size of entries in the central directory.</param> /// <param name="centralDirOffset">The offset of the dentral directory.</param> 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); } /// <summary> /// Write the required records to end the central directory. /// </summary> /// <param name="noOfEntries">The number of entries in the directory.</param> /// <param name="sizeEntries">The size of the entries in the directory.</param> /// <param name="startOfCentralDirectory">The start of the central directory.</param> /// <param name="comment">The archive comment. (This can be null).</param> 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 /// <summary> /// Read an unsigned short in little endian byte order. /// </summary> /// <returns>Returns the value read.</returns> /// <exception cref="IOException"> /// An i/o error occurs. /// </exception> /// <exception cref="EndOfStreamException"> /// The file ends prematurely /// </exception> 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); } /// <summary> /// Read an int in little endian byte order. /// </summary> /// <returns>Returns the value read.</returns> /// <exception cref="IOException"> /// An i/o error occurs. /// </exception> /// <exception cref="System.IO.EndOfStreamException"> /// The file ends prematurely /// </exception> public int ReadLEInt() { return ReadLEShort() | (ReadLEShort() << 16); } /// <summary> /// Read a long in little endian byte order. /// </summary> /// <returns>The value read.</returns> public long ReadLELong() { return (uint)ReadLEInt() | ((long)ReadLEInt() << 32); } /// <summary> /// Write an unsigned short in little endian byte order. /// </summary> /// <param name="value">The value to write.</param> public void WriteLEShort(int value) { stream_.WriteByte(( byte )(value & 0xff)); stream_.WriteByte(( byte )((value >> 8) & 0xff)); } /// <summary> /// Write a ushort in little endian byte order. /// </summary> /// <param name="value">The value to write.</param> public void WriteLEUshort(ushort value) { stream_.WriteByte(( byte )(value & 0xff)); stream_.WriteByte(( byte )(value >> 8)); } /// <summary> /// Write an int in little endian byte order. /// </summary> /// <param name="value">The value to write.</param> public void WriteLEInt(int value) { WriteLEShort(value); WriteLEShort(value >> 16); } /// <summary> /// Write a uint in little endian byte order. /// </summary> /// <param name="value">The value to write.</param> public void WriteLEUint(uint value) { WriteLEUshort(( ushort )(value & 0xffff)); WriteLEUshort(( ushort )(value >> 16)); } /// <summary> /// Write a long in little endian byte order. /// </summary> /// <param name="value">The value to write.</param> public void WriteLELong(long value) { WriteLEInt(( int )value); WriteLEInt(( int )(value >> 32)); } /// <summary> /// Write a ulong in little endian byte order. /// </summary> /// <param name="value">The value to write.</param> public void WriteLEUlong(ulong value) { WriteLEUint(( uint )(value & 0xffffffff)); WriteLEUint(( uint )(value >> 32)); } #endregion /// <summary> /// Write a data descriptor. /// </summary> /// <param name="entry">The entry to write a descriptor for.</param> /// <returns>Returns the number of descriptor bytes written.</returns> 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; } /// <summary> /// Read data descriptor at the end of compressed data. /// </summary> /// <param name="zip64">if set to <c>true</c> [zip64].</param> /// <param name="data">The data to fill in.</param> /// <returns>Returns the number of bytes read in the descriptor.</returns> 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 } }