// ZipFile.cs // Copyright (C) 2001 Mike Krueger // // This file was translated from java, it was part of the GNU Classpath // Copyright (C) 2001 Free Software Foundation, Inc. // // 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.Collections; using System.IO; using System.Text; using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using ICSharpCode.SharpZipLib.Zip.Compression; namespace ICSharpCode.SharpZipLib.Zip { /// /// This class represents a Zip archive. You can ask for the contained /// entries, or get an input stream for a file entry. The entry is /// automatically decompressed. /// /// This class is thread safe: You can open input streams for arbitrary /// entries in different threads. /// /// author of the original java version : Jochen Hoenicke /// /// /// using System; /// using System.Text; /// using System.Collections; /// using System.IO; /// /// using NZlib.Zip; /// /// class MainClass /// { /// static public void Main(string[] args) /// { /// ZipFile zFile = new ZipFile(args[0]); /// //Console.WriteLine("Listing of : " + zFile.Name); /// //Console.WriteLine(""); /// //Console.WriteLine("Raw Size Size Date Time Name"); /// //Console.WriteLine("-------- -------- -------- ------ ---------"); /// foreach (ZipEntry e in zFile) { /// DateTime d = e.DateTime; /// //Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize, /// d.ToString("dd-MM-yy"), d.ToString("t"), /// e.Name); /// } /// } /// } /// public class ZipFile : IEnumerable { string name; string comment; Stream baseStream; ZipEntry[] entries; /// /// Opens a Zip file with the given name for reading. /// /// /// IOException if a i/o error occured. /// /// /// if the file doesn't contain a valid zip archive. /// public ZipFile(string name) : this(File.OpenRead(name)) { } /// /// Opens a Zip file reading the given FileStream /// /// /// IOException if a i/o error occured. /// /// /// if the file doesn't contain a valid zip archive. /// public ZipFile(FileStream file) { this.baseStream = file; this.name = file.Name; ReadEntries(); } /// /// Opens a Zip file reading the given Stream /// /// /// IOException if a i/o error occured. /// /// /// if the file doesn't contain a valid zip archive. /// public ZipFile(Stream baseStream) { this.baseStream = baseStream; this.name = null; ReadEntries(); } /// /// Read an unsigned short in little endian byte order. /// /// /// if a i/o error occured. /// /// /// if the file ends prematurely /// int ReadLeShort() { return baseStream.ReadByte() | baseStream.ReadByte() << 8; } /// /// Read an int in little endian byte order. /// /// /// if a i/o error occured. /// /// /// if the file ends prematurely /// int ReadLeInt() { return ReadLeShort() | ReadLeShort() << 16; } /// /// Read the central directory of a zip file and fill the entries /// array. This is called exactly once by the constructors. /// /// /// if a i/o error occured. /// /// /// if the central directory is malformed /// void ReadEntries() { /* Search for the End Of Central Directory. When a zip comment is * present the directory may start earlier. * FIXME: This searches the whole file in a very slow manner if the * file isn't a zip file. */ long pos = baseStream.Length - ZipConstants.ENDHDR; do { if (pos < 0) { throw new ZipException("central directory not found, probably not a zip file"); } baseStream.Seek(pos--, SeekOrigin.Begin); } while (ReadLeInt() != ZipConstants.ENDSIG); long oldPos = baseStream.Position; baseStream.Position += ZipConstants.ENDTOT - ZipConstants.ENDNRD; if (baseStream.Position - oldPos != ZipConstants.ENDTOT - ZipConstants.ENDNRD) { throw new EndOfStreamException(); } int count = ReadLeShort(); oldPos = baseStream.Position; baseStream.Position += ZipConstants.ENDOFF - ZipConstants.ENDSIZ; if (baseStream.Position - oldPos != ZipConstants.ENDOFF - ZipConstants.ENDSIZ) { throw new EndOfStreamException(); } int centralOffset = ReadLeInt(); // GET COMMENT SIZE (COMES AFTER CENTRALOFFSET) int commentSize = ReadLeShort(); byte[] zipComment = new byte[commentSize]; baseStream.Read(zipComment, 0, zipComment.Length); comment = ZipConstants.ConvertToString(zipComment); entries = new ZipEntry[count]; baseStream.Seek(centralOffset, SeekOrigin.Begin); for (int i = 0; i < count; i++) { if (ReadLeInt() != ZipConstants.CENSIG) { throw new ZipException("Wrong Central Directory signature"); } oldPos = baseStream.Position; baseStream.Position += ZipConstants.CENHOW - ZipConstants.CENVEM; if (baseStream.Position - oldPos != ZipConstants.CENHOW - ZipConstants.CENVEM) { throw new EndOfStreamException(); } int method = ReadLeShort(); int dostime = ReadLeInt(); int crc = ReadLeInt(); int csize = ReadLeInt(); int size = ReadLeInt(); int nameLen = ReadLeShort(); int extraLen = ReadLeShort(); int commentLen = ReadLeShort(); oldPos = baseStream.Position; baseStream.Position += ZipConstants.CENOFF - ZipConstants.CENDSK; if (baseStream.Position - oldPos != ZipConstants.CENOFF - ZipConstants.CENDSK) { throw new EndOfStreamException(); } int offset = ReadLeInt(); byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; baseStream.Read(buffer, 0, nameLen); string name = ZipConstants.ConvertToString(buffer); ZipEntry entry = new ZipEntry(name); entry.CompressionMethod = (CompressionMethod)method; entry.Crc = crc & 0xffffffffL; entry.Size = size & 0xffffffffL; entry.CompressedSize = csize & 0xffffffffL; entry.DosTime = (uint)dostime; if (extraLen > 0) { byte[] extra = new byte[extraLen]; baseStream.Read(extra, 0, extraLen); entry.ExtraData = extra; } if (commentLen > 0) { baseStream.Read(buffer, 0, commentLen); entry.Comment = ZipConstants.ConvertToString(buffer); } entry.ZipFileIndex = i; entry.Offset = offset; entries[i] = entry; } } /// /// Closes the ZipFile. This also closes all input streams given by /// this class. After this is called, no further method should be /// called. /// /// /// if a i/o error occured. /// public void Close() { entries = null; lock(baseStream) { baseStream.Close(); } } /// /// Returns an IEnumerator of all Zip entries in this Zip file. /// public IEnumerator GetEnumerator() { if (entries == null) { throw new InvalidOperationException("ZipFile has closed"); } return new ZipEntryEnumeration(entries); } int GetEntryIndex(string name) { for (int i = 0; i < entries.Length; i++) { if (name.Equals(entries[i].Name)) { return i; } } return -1; // ok } /// /// Searches for a zip entry in this archive with the given name. /// /// /// the name. May contain directory components separated by slashes ('/'). /// /// /// the zip entry, or null if no entry with that name exists. /// public ZipEntry GetEntry(string name) { if (entries == null) { throw new InvalidOperationException("ZipFile has closed"); } int index = GetEntryIndex(name); return index >= 0 ? (ZipEntry) entries[index].Clone() : null; } /// /// Checks, if the local header of the entry at index i matches the /// central directory, and returns the offset to the data. /// /// /// the start offset of the (compressed) data. /// /// /// if a i/o error occured. /// /// /// if the local header doesn't match the central directory header /// long CheckLocalHeader(ZipEntry entry) { lock(baseStream) { baseStream.Seek(entry.Offset, SeekOrigin.Begin); if (ReadLeInt() != ZipConstants.LOCSIG) { throw new ZipException("Wrong Local header signature"); } /* skip version and flags */ long oldPos = baseStream.Position; baseStream.Position += ZipConstants.LOCHOW - ZipConstants.LOCVER; if (baseStream.Position - oldPos != ZipConstants.LOCHOW - ZipConstants.LOCVER) { throw new EndOfStreamException(); } if (entry.CompressionMethod != (CompressionMethod)ReadLeShort()) { throw new ZipException("Compression method mismatch"); } /* Skip time, crc, size and csize */ oldPos = baseStream.Position; baseStream.Position += ZipConstants.LOCNAM - ZipConstants.LOCTIM; if (baseStream.Position - oldPos != ZipConstants.LOCNAM - ZipConstants.LOCTIM) { throw new EndOfStreamException(); } if (entry.Name.Length != ReadLeShort()) { throw new ZipException("file name length mismatch"); } int extraLen = entry.Name.Length + ReadLeShort(); return entry.Offset + ZipConstants.LOCHDR + extraLen; } } /// /// Creates an input stream reading the given zip entry as /// uncompressed data. Normally zip entry should be an entry /// returned by GetEntry(). /// /// /// the input stream. /// /// /// if a i/o error occured. /// /// /// if the Zip archive is malformed. /// public Stream GetInputStream(ZipEntry entry) { if (entries == null) { throw new InvalidOperationException("ZipFile has closed"); } int index = entry.ZipFileIndex; if (index < 0 || index >= entries.Length || entries[index].Name != entry.Name) { index = GetEntryIndex(entry.Name); if (index < 0) { throw new IndexOutOfRangeException(); } } long start = CheckLocalHeader(entries[index]); CompressionMethod method = entries[index].CompressionMethod; Stream istr = new PartialInputStream(baseStream, start, entries[index].CompressedSize); switch (method) { case CompressionMethod.Stored: return istr; case CompressionMethod.Deflated: return new InflaterInputStream(istr, new Inflater(true)); default: throw new ZipException("Unknown compression method " + method); } } /// /// The comment for the whole zip file. /// public string ZipFileComment { get { return comment; } } /// /// Returns the name of this zip file. /// public string Name { get { return name; } } /// /// Returns the number of entries in this zip file. /// public int Size { get { try { return entries.Length; } catch (Exception) { throw new InvalidOperationException("ZipFile has closed"); } } } class ZipEntryEnumeration : IEnumerator { ZipEntry[] array; int ptr = -1; public ZipEntryEnumeration(ZipEntry[] arr) { array = arr; } public object Current { get { return array[ptr]; } } public void Reset() { ptr = -1; } public bool MoveNext() { return (++ptr < array.Length); } } class PartialInputStream : InflaterInputStream { Stream baseStream; long filepos, end; public PartialInputStream(Stream baseStream, long start, long len) : base(baseStream) { this.baseStream = baseStream; filepos = start; end = start + len; } public override int Available { get { long amount = end - filepos; if (amount > Int32.MaxValue) { return Int32.MaxValue; } return (int) amount; } } public override int ReadByte() { if (filepos == end) { return -1; //ok } lock(baseStream) { baseStream.Seek(filepos++, SeekOrigin.Begin); return baseStream.ReadByte(); } } public override int Read(byte[] b, int off, int len) { if (len > end - filepos) { len = (int) (end - filepos); if (len == 0) { return 0; } } lock(baseStream) { baseStream.Seek(filepos, SeekOrigin.Begin); int count = baseStream.Read(b, off, len); if (count > 0) { filepos += len; } return count; } } public long SkipBytes(long amount) { if (amount < 0) { throw new ArgumentOutOfRangeException(); } if (amount > end - filepos) { amount = end - filepos; } filepos += amount; return amount; } } } }