// RemoteZipFile.cs // Copyright (C) 2003 Emanuele Ruffaldi // // ZipEntry parsing code taken from ZipFile.cs in SharpLibZip // Copyright (C) 2001 Mike Krueger // // The original SharpLibZip code 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 ICSharpCode.SharpZipLib; using ICSharpCode.SharpZipLib.Zip; using ICSharpCode.SharpZipLib.Zip.Compression.Streams; using ICSharpCode.SharpZipLib.Zip.Compression; using ICSharpCode.SharpZipLib.BZip2; using System.Collections; using System.IO; using System.Text; using ICSharpCode.SharpZipLib.Checksums; using MyDownloader.Core; namespace MyDownloader.Extension.Zip { public class ZipRemoteFile : IEnumerable { private ZipEntry[] entries; private ResourceLocation url; private int MaxFileOffset; private IProtocolProvider protocolProvider; public ZipRemoteFile(ResourceLocation url) : this(url, ProtocolProviderFactory.GetProvider(url.URL)) { } public ZipRemoteFile(ResourceLocation url, IProtocolProvider protocolProvider) { if (protocolProvider == null) { throw new ArgumentNullException("protocolProvider"); } this.url = url; this.protocolProvider = protocolProvider; } /* end of central dir signature 4 bytes (0x06054b50) number of this disk 2 bytes number of the disk with the start of the central directory 2 bytes total number of entries in the central directory on this disk 2 bytes total number of entries in the central directory 2 bytes size of the central directory 4 bytes offset of start of central directory with respect to the starting disk number 4 bytes .ZIP file comment length 2 bytes .ZIP file comment (variable size) */ /// /// TODO: case when the whole file is smaller than 64K /// TODO: handle non HTTP case /// /// /// public bool Load() { int CentralOffset, CentralSize; int TotalEntries; if(!FindCentralDirectory(out CentralOffset, out CentralSize, out TotalEntries)) return false; MaxFileOffset = CentralOffset; // now retrieve the Central Directory entries = new ZipEntry[TotalEntries]; //HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); //req.AddRange(CentralOffset, CentralOffset+CentralSize); //HttpWebResponse res = (HttpWebResponse)req.GetResponse(); using (Stream s = protocolProvider.CreateStream(this.url, CentralOffset, CentralOffset + CentralSize)) { // code taken from SharpZipLib with modification for not seekable stream // and adjustement for Central Directory entry for (int i = 0; i < TotalEntries; i++) { if (ReadLeInt(s) != ZipConstants.CentralHeaderSignature) { throw new ZipException("Wrong Central Directory signature"); } // skip 6 bytes: version made (W), version ext (W), flags (W) ReadLeInt(s); ReadLeShort(s); int method = ReadLeShort(s); int dostime = ReadLeInt(s); int crc = ReadLeInt(s); int csize = ReadLeInt(s); int size = ReadLeInt(s); int nameLen = ReadLeShort(s); int extraLen = ReadLeShort(s); int commentLen = ReadLeShort(s); // skip 8 bytes: disk number start, internal file attribs, external file attribs (DW) ReadLeInt(s); ReadLeInt(s); int offset = ReadLeInt(s); byte[] buffer = new byte[Math.Max(nameLen, commentLen)]; StreamHelper.ReadAll(buffer, 0, nameLen, s); 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]; StreamHelper.ReadAll(extra, 0, extraLen, s); entry.ExtraData = extra; } if (commentLen > 0) { StreamHelper.ReadAll(buffer, 0, commentLen, s); entry.Comment = ZipConstants.ConvertToString(buffer); } entry.ZipFileIndex = i; entry.Offset = offset; entries[i] = entry; OnProgress((i*100)/TotalEntries); } } OnProgress(100); return true; } /// /// OnProgress during Central Header loading /// /// public virtual void OnProgress(int pct) { } /// /// Checks if there is a local header at the current position in the stream and skips it /// /// /// void SkipLocalHeader(Stream baseStream, ZipEntry entry) { lock(baseStream) { if (ReadLeInt(baseStream) != ZipConstants.LocalHeaderSignature) { throw new ZipException("Wrong Local header signature"); } Skip(baseStream, 10+12); int namelen = ReadLeShort(baseStream); int extralen = ReadLeShort(baseStream); Skip(baseStream, namelen+extralen); } } /// /// Finds the Central Header in the Zip file. We can minimize the number of requests and /// the bytes taken /// /// Actually we do: 256, 1024, 65536 /// /// /// bool FindCentralDirectory(out int Offset, out int Size, out int Entries) { int currentLength = 256; Entries = 0; Size = 0; Offset = -1; while(true) { //HttpWebRequest req = (HttpWebRequest)WebRequest.Create(url); //req.AddRange(-(currentLength+22)); //HttpWebResponse res = (HttpWebResponse)req.GetResponse(); // bb = new byte[res.ContentLength]; // endSize = StreamHelper.ReadAll(bb, 0, (int)res.ContentLength, res.getResponseStream()); int endSize; byte[] bb; using (BinaryReader headerStream = new BinaryReader(protocolProvider.CreateStream( this.url, -(currentLength + 22), 0))) { using (MemoryStream data = new MemoryStream()) { int count = 256; byte[] buffer = new byte[count]; do { int readCount = headerStream.Read(buffer, 0, count); if (readCount == 0) { break; } data.Write(buffer, 0, readCount); } while (count > 0); bb = data.ToArray(); endSize = (int)data.Length; } } // scan for the central block. The position of the central block // is end-comment-22 //< // 50 4B 05 06 int pos = endSize-22; //int state = 0; while(pos >= 0) { if(bb[pos] == 0x50) { if(bb[pos+1] == 0x4b && bb[pos+2] == 0x05 && bb[pos+3] == 0x06) break; // found!! pos -= 4; } else pos --; } if(pos < 0) { if(currentLength == 65536) break; if(currentLength == 1024) currentLength = 65536; else if(currentLength == 256) currentLength = 1024; else break; } else { // found it!! so at offset pos+3*4 there is Size, and pos+4*4 // BinaryReader is so elegant but now it's too much Size = MakeInt(bb, pos+12); Offset = MakeInt(bb, pos+16); Entries = MakeShort(bb, pos+10); return true; } } return false; } /// /// Get a Stream for reading the specified entry /// /// /// public Stream GetInputStream(ZipEntry entry) { if(entry.Size == 0) return null; if (entries == null) { throw new InvalidOperationException("ZipFile has closed"); } int index = (int)entry.ZipFileIndex; if (index < 0 || index >= entries.Length || entries[index].Name != entry.Name) { throw new IndexOutOfRangeException(); } // WARNING // should parse the Local Header to get the data address // Maximum Size of the Local Header is ... 16+64K*2 // // So the HTTP request should ask for the big local header, but actually the // additional data is not downloaded. // Optionally use an additional Request to be really precise //HttpWebRequest req = (HttpWebRequest)WebRequest.Create(baseUrl); int limit = (int)(entry.Offset+entry.CompressedSize+16+65536*2); if(limit >= MaxFileOffset) limit = MaxFileOffset-1; //req.AddRange((int)entry.Offset, limit); //HttpWebResponse res = (HttpWebResponse)req.GetResponse(); //Stream baseStream = res.GetResponseStream(); Stream baseStream = protocolProvider.CreateStream(this.url, (int)entry.Offset, limit); // skips all the header SkipLocalHeader(baseStream, entries[index]); CompressionMethod method = entries[index].CompressionMethod; Stream istr = new PartialInputStream(baseStream, entries[index].CompressedSize); switch (method) { case CompressionMethod.Stored: return istr; case CompressionMethod.Deflated: return new InflaterInputStream(istr, new Inflater(true)); case (CompressionMethod)12: return new BZip2InputStream(istr); default: throw new ZipException("Unknown compression method " + method); } } #region Helper methods /// /// Read an unsigned short in little endian byte order. /// /// /// if a i/o error occured. /// /// /// if the file ends prematurely /// private static int ReadLeShort(Stream s) { return s.ReadByte() | s.ReadByte() << 8; } /// /// Read an int in little endian byte order. /// /// /// if a i/o error occured. /// /// /// if the file ends prematurely /// private static int ReadLeInt(Stream s) { return ReadLeShort(s) | ReadLeShort(s) << 16; } private static void Skip(Stream s, int n) { for(int i = 0; i < n; i++) s.ReadByte(); } private static int MakeInt(byte [] bb, int pos) { return bb[pos+0]|(bb[pos+1]<<8)|(bb[pos+2]<<16)|(bb[pos+3]<<24); } private static int MakeShort(byte[] bb, int pos) { return bb[pos+0]|(bb[pos+1]<<8); } #endregion public int Size { get { return entries == null ? 0 : entries.Length; } } public ZipEntry this[string entryName] { get { for (int i = 0; i < entries.Length; i++) { if (entries[i].Name == entryName) { return entries[i]; } } throw new ArgumentException("entryName"); } } public ZipEntry this[int index] { get { return entries[index]; } } /// /// 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); } 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); } } } }