2010年12月31日 星期五

開啟Zip壓縮檔並讀取

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 ICSharpCode.SharpZipLib.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.
/// 
/// 
/// An i/o error occurs
/// 
/// 
/// 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
/// 
/// 
/// An i/o error occurs.
/// 
/// 
/// 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
/// 
/// 
/// An i/o error occurs
/// 
/// 
/// The file doesn't contain a valid zip archive.

/// The stream provided cannot seek
/// 
public ZipFile(Stream baseStream)
{
this.baseStream  = baseStream;
this.name = null;
ReadEntries();
}

/// 
/// Read an unsigned short in little endian byte order.
/// 
/// 
/// An i/o error occurs.
/// 
/// 
/// The file ends prematurely
/// 
int ReadLeShort()
{
return baseStream.ReadByte() | baseStream.ReadByte() << 8;
}

/// 
/// Read an int in little endian byte order.
/// 
/// 
/// An i/o error occurs.
/// 
/// 
/// The file ends prematurely
/// 
int ReadLeInt()
{
return ReadLeShort() | ReadLeShort() << 16;
}

/// 
/// Search for and read the central directory of a zip file filling the entries
/// array.  This is called exactly once by the constructors.
/// 
/// 
/// An i/o error occurs.
/// 
/// 
/// The central directory is malformed or cannot be found
/// 
void ReadEntries()
{
// Search for the End Of Central Directory.  When a zip comment is
// present the directory may start earlier.
//
// TODO: The search is limited to 64K which is the maximum size of a trailing comment field to aid speed.
// This should be compatible with both SFX and ZIP files but has only been tested for Zip files
// Need to confirm this is valid in all cases.
// Could also speed this up by reading memory in larger blocks?

if (baseStream.CanSeek == false) {
throw new ZipException("ZipFile stream must be seekable");
}

long pos = baseStream.Length - ZipConstants.ENDHDR;
if (pos <= 0) {
throw new ZipException("File is too small to be a Zip file");
}

long giveUpMarker = Math.Max(pos - 0x10000, 0);

do {
if (pos < giveUpMarker) {
throw new ZipException("central directory not found, probably not a zip file");
}
baseStream.Seek(pos--, SeekOrigin.Begin);
} while (ReadLeInt() != ZipConstants.ENDSIG);

int thisDiskNumber            = ReadLeShort();
int startCentralDirDisk       = ReadLeShort();
int entriesForThisDisk        = ReadLeShort();
int entriesForWholeCentralDir = ReadLeShort();
int centralDirSize            = ReadLeInt();
int offsetOfCentralDir        = ReadLeInt();
int commentSize               = ReadLeShort();

byte[] zipComment = new byte[commentSize];
baseStream.Read(zipComment, 0, zipComment.Length);
comment = ZipConstants.ConvertToString(zipComment);

/* Its seems possible that this is too strict, more digging required.
if (thisDiskNumber != 0 || startCentralDirDisk != 0 || entriesForThisDisk != entriesForWholeCentralDir) {
throw new ZipException("Spanned archives are not currently handled");
}
*/

entries = new ZipEntry[entriesForWholeCentralDir];
baseStream.Seek(offsetOfCentralDir, SeekOrigin.Begin);

for (int i = 0; i < entriesForWholeCentralDir; i++) {                                 if (ReadLeInt() != ZipConstants.CENSIG) {                                         throw new ZipException("Wrong Central Directory signature");                                 }                                 int versionMadeBy      = ReadLeShort();                                 int versionToExtract   = ReadLeShort();                                 int bitFlags           = ReadLeShort();                                 int method             = ReadLeShort();                                 int dostime            = ReadLeInt();                                 int crc                = ReadLeInt();                                 int csize              = ReadLeInt();                                 int size               = ReadLeInt();                                 int nameLen            = ReadLeShort();                                 int extraLen           = ReadLeShort();                                 int commentLen         = ReadLeShort();                                 int diskStartNo        = ReadLeShort();  // Not currently used                                 int internalAttributes = ReadLeShort();  // Not currently used                                 int externalAttributes = ReadLeInt();                                 int offset             = ReadLeInt();                                 byte[] buffer = new byte[Math.Max(nameLen, commentLen)];                                 baseStream.Read(buffer, 0, nameLen);                                 string name = ZipConstants.ConvertToString(buffer, nameLen);                                 ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy);                                 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, commentLen);
}

entry.ZipFileIndex           = i;
entry.Offset                 = offset;
entry.ExternalFileAttributes = externalAttributes;

entries[i] = entry;
}
}

/// 
/// Closes the ZipFile.  This also closes all input streams managed by
/// this class.  Once closed, no further instance methods should be
/// called.
/// 
/// 
/// An i/o error occurs.
/// 
public void Close()
{
entries = null;
lock(baseStream) {
baseStream.Close();
}
}

/// 
/// Returns an enumerator for the Zip entries in this Zip file.
/// 
/// 
/// The Zip file has been closed.
/// 
public IEnumerator GetEnumerator()
{
if (entries == null) {
throw new InvalidOperationException("ZipFile has closed");
}

return new ZipEntryEnumeration(entries);
}

/// 
/// Return the index of the entry with a matching name
/// 
///
Entry name to find
///
If true the comparison is case insensitive
/// The index position of the matching entry or -1 if not found
/// 
/// The Zip file has been closed.
/// 
public int FindEntry(string name, bool ignoreCase)
{
if (entries == null) {
throw new InvalidOperationException("ZipFile has been closed");
}

for (int i = 0; i < entries.Length; i++) {
if (string.Compare(name, entries[i].Name, ignoreCase) == 0) {
return i;
}
}
return -1;
}

/// 
/// Indexer property for ZipEntries
/// 
[System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")]
public ZipEntry this[int index] {
get {
return (ZipEntry) entries[index].Clone();
}
}

/// 
/// Searches for a zip entry in this archive with the given name.
/// String comparisons are case insensitive
/// 
///

/// The name to find. May contain directory components separated by slashes ('/').
/// 
/// 
/// The zip entry, or null if no entry with that name exists.
/// 
/// 
/// The Zip file has been closed.
/// 
public ZipEntry GetEntry(string name)
{
if (entries == null) {
throw new InvalidOperationException("ZipFile has been closed");
}

int index = FindEntry(name, true);
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.
/// 
/// 
/// The stream ends prematurely
/// 
/// 
/// The local header signature is invalid, the entry and central header file name lengths are different
/// or the local and entry compression methods dont match
/// 
long CheckLocalHeader(ZipEntry entry)
{
lock(baseStream) {
baseStream.Seek(entry.Offset, SeekOrigin.Begin);
if (ReadLeInt() != ZipConstants.LOCSIG) {
throw new ZipException("Wrong Local header signature");
}

short shortValue = (short)ReadLeShort();     // version required to extract
if (shortValue > ZipConstants.VERSION_MADE_BY) {
throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", shortValue));
}

shortValue = (short)ReadLeShort();                  // general purpose bit flags.
if ((shortValue & 0x30) != 0) {
throw new ZipException("The library doesnt support the zip version required to extract this entry");
}

if (entry.CompressionMethod != (CompressionMethod)ReadLeShort()) {
throw new ZipException("Compression method mismatch");
}

// Skip time, crc, size and csize
long oldPos = baseStream.Position;
baseStream.Position += ZipConstants.LOCNAM - ZipConstants.LOCTIM;

if (baseStream.Position - oldPos != ZipConstants.LOCNAM - ZipConstants.LOCTIM) {
throw new EndOfStreamException();
}

// TODO make test more correct...  cant compare lengths as was done originally as this can fail for MBCS strings
int storedNameLength = ReadLeShort();
if (entry.Name.Length > storedNameLength) {
throw new ZipException("file name length mismatch");
}

int extraLen = storedNameLength + 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.
/// 
/// 
/// The ZipFile has already been closed
/// 
/// 
/// The compression method for the entry is unknown
/// 
/// 
/// The entry is not found in the ZipFile
/// 
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 = FindEntry(entry.Name, true);
if (index < 0) {
throw new IndexOutOfRangeException();
}
}
return GetInputStream(index);
}

/// 
/// Creates an input stream reading the zip entry based on the index passed
/// 
/// 
/// An input stream.
/// 
/// 
/// The ZipFile has already been closed
/// 
/// 
/// The compression method for the entry is unknown
/// 
/// 
/// The entry is not found in the ZipFile
/// 
public Stream GetInputStream(int entryIndex)
{
if (entries == null) {
throw new InvalidOperationException("ZipFile has closed");
}

long start = CheckLocalHeader(entries[entryIndex]);
CompressionMethod method = entries[entryIndex].CompressionMethod;
Stream istr = new PartialInputStream(baseStream, start, entries[entryIndex].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);
}
}

/// 
/// Gets the comment for the zip file.
/// 
public string ZipFileComment {
get {
return comment;
}
}

/// 
/// Gets the name of this zip file.
/// 
public string Name {
get {
return name;
}
}

/// 
/// Gets the number of entries in this zip file.
/// 
/// 
/// The Zip file has been closed.
/// 
public int Size {
get {
if (entries != null) {
return entries.Length;
} else {
throw new InvalidOperationException("ZipFile is 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;
}
}
}
}

沒有留言: