00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031
00032
00033
00034
00035
00036
00037
00038
00039
00040 using System;
00041 using System.Security.Cryptography;
00042 using System.Collections;
00043 using System.IO;
00044 using System.Text;
00045
00046 using ICSharpCode.SharpZipLib.Checksums;
00047 using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
00048 using ICSharpCode.SharpZipLib.Zip.Compression;
00049 using ICSharpCode.SharpZipLib.Encryption;
00050
00051 namespace ICSharpCode.SharpZipLib.Zip
00052 {
00053
00057 public class KeysRequiredEventArgs : EventArgs
00058 {
00059 string fileName;
00060
00064 public string FileName
00065 {
00066 get { return fileName; }
00067 }
00068
00069 byte[] key;
00070
00074 public byte[] Key
00075 {
00076 get { return key; }
00077 set { key = value; }
00078 }
00079
00084 public KeysRequiredEventArgs(string name)
00085 {
00086 fileName = name;
00087 }
00088
00094 public KeysRequiredEventArgs(string name, byte[] keyValue)
00095 {
00096 fileName = name;
00097 key = keyValue;
00098 }
00099 }
00100
00139 public class ZipFile : IEnumerable
00140 {
00141 string name;
00142 string comment;
00143 Stream baseStream;
00144 bool isStreamOwner = true;
00145 long offsetOfFirstEntry = 0;
00146 ZipEntry[] entries;
00147
00148 #region KeyHandling
00149
00153 public delegate void KeysRequiredEventHandler(
00154 object sender,
00155 KeysRequiredEventArgs e
00156 );
00157
00161 public KeysRequiredEventHandler KeysRequired;
00162
00167 void OnKeysRequired(string fileName)
00168 {
00169 if (KeysRequired != null) {
00170 KeysRequiredEventArgs krea = new KeysRequiredEventArgs(fileName, key);
00171 KeysRequired(this, krea);
00172 key = krea.Key;
00173 }
00174 }
00175
00176 byte[] key = null;
00177
00181 byte[] Key
00182 {
00183 get { return key; }
00184 set { key = value; }
00185 }
00186
00191 public string Password
00192 {
00193 set
00194 {
00195 if ( (value == null) || (value.Length == 0) ) {
00196 key = null;
00197 }
00198 else {
00199 key = PkzipClassic.GenerateKeys(Encoding.ASCII.GetBytes(value));
00200 }
00201 }
00202 }
00203
00204 byte[] iv = null;
00205
00206 bool HaveKeys
00207 {
00208 get { return key != null; }
00209 }
00210 #endregion
00211
00221 public ZipFile(string name)
00222 {
00223 this.name = name;
00224 this.baseStream = File.OpenRead(name);
00225 try {
00226 ReadEntries();
00227 }
00228 catch {
00229 Close();
00230 throw;
00231 }
00232 }
00233
00243 public ZipFile(FileStream file)
00244 {
00245 this.baseStream = file;
00246 this.name = file.Name;
00247 try {
00248 ReadEntries();
00249 }
00250 catch {
00251 Close();
00252 throw;
00253 }
00254 }
00255
00266 public ZipFile(Stream baseStream)
00267 {
00268 this.baseStream = baseStream;
00269 this.name = null;
00270 try {
00271 ReadEntries();
00272 }
00273 catch {
00274 Close();
00275 throw;
00276 }
00277 }
00278
00279
00287 bool IsStreamOwner
00288 {
00289 get { return isStreamOwner; }
00290 set { isStreamOwner = value; }
00291 }
00292
00303 int ReadLeShort()
00304 {
00305 return baseStream.ReadByte() | baseStream.ReadByte() << 8;
00306 }
00307
00318 int ReadLeInt()
00319 {
00320 return ReadLeShort() | ReadLeShort() << 16;
00321 }
00322
00323
00324 long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
00325 {
00326 long pos = endLocation - minimumBlockSize;
00327 if (pos < 0) {
00328 return -1;
00329 }
00330
00331 long giveUpMarker = Math.Max(pos - maximumVariableData, 0);
00332
00333
00334 do
00335 {
00336 if (pos < giveUpMarker) {
00337 return -1;
00338 }
00339 baseStream.Seek(pos--, SeekOrigin.Begin);
00340 } while (ReadLeInt() != signature);
00341
00342 return baseStream.Position;
00343 }
00344
00355 void ReadEntries()
00356 {
00357
00358
00359
00360
00361
00362
00363
00364
00365 if (baseStream.CanSeek == false) {
00366 throw new ZipException("ZipFile stream must be seekable");
00367 }
00368
00369 long locatedCentralDirOffset = LocateBlockWithSignature(ZipConstants.ENDSIG, baseStream.Length, ZipConstants.ENDHDR, 0xffff);
00370 if (locatedCentralDirOffset < 0) {
00371 throw new ZipException("Cannot find central directory");
00372 }
00373
00374 int thisDiskNumber = ReadLeShort();
00375 int startCentralDirDisk = ReadLeShort();
00376 int entriesForThisDisk = ReadLeShort();
00377 int entriesForWholeCentralDir = ReadLeShort();
00378 int centralDirSize = ReadLeInt();
00379 int offsetOfCentralDir = ReadLeInt();
00380 int commentSize = ReadLeShort();
00381
00382 byte[] zipComment = new byte[commentSize];
00383 baseStream.Read(zipComment, 0, zipComment.Length);
00384 comment = ZipConstants.ConvertToString(zipComment);
00385
00386
00387
00388
00389
00390
00391
00392 entries = new ZipEntry[entriesForWholeCentralDir];
00393
00394
00395
00396
00397
00398 if (offsetOfCentralDir < locatedCentralDirOffset - (4 + centralDirSize)) {
00399 offsetOfFirstEntry = locatedCentralDirOffset - (4 + centralDirSize + offsetOfCentralDir);
00400 if (offsetOfFirstEntry <= 0) {
00401 throw new ZipException("Invalid SFX file");
00402 }
00403 }
00404
00405 baseStream.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin);
00406
00407 for (int i = 0; i < entriesForThisDisk; i++) {
00408 if (ReadLeInt() != ZipConstants.CENSIG) {
00409 throw new ZipException("Wrong Central Directory signature");
00410 }
00411
00412 int versionMadeBy = ReadLeShort();
00413 int versionToExtract = ReadLeShort();
00414 int bitFlags = ReadLeShort();
00415 int method = ReadLeShort();
00416 int dostime = ReadLeInt();
00417 int crc = ReadLeInt();
00418 int csize = ReadLeInt();
00419 int size = ReadLeInt();
00420 int nameLen = ReadLeShort();
00421 int extraLen = ReadLeShort();
00422 int commentLen = ReadLeShort();
00423
00424 int diskStartNo = ReadLeShort();
00425 int internalAttributes = ReadLeShort();
00426
00427 int externalAttributes = ReadLeInt();
00428 int offset = ReadLeInt();
00429
00430 byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
00431
00432 baseStream.Read(buffer, 0, nameLen);
00433 string name = ZipConstants.ConvertToString(buffer, nameLen);
00434
00435 ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy);
00436 entry.CompressionMethod = (CompressionMethod)method;
00437 entry.Crc = crc & 0xffffffffL;
00438 entry.Size = size & 0xffffffffL;
00439 entry.CompressedSize = csize & 0xffffffffL;
00440 entry.Flags = bitFlags;
00441 entry.DosTime = (uint)dostime;
00442
00443 if (extraLen > 0) {
00444 byte[] extra = new byte[extraLen];
00445 baseStream.Read(extra, 0, extraLen);
00446 entry.ExtraData = extra;
00447 }
00448
00449 if (commentLen > 0) {
00450 baseStream.Read(buffer, 0, commentLen);
00451 entry.Comment = ZipConstants.ConvertToString(buffer, commentLen);
00452 }
00453
00454 entry.ZipFileIndex = i;
00455 entry.Offset = offset;
00456 entry.ExternalFileAttributes = externalAttributes;
00457
00458 entries[i] = entry;
00459 }
00460 }
00461
00469 public void Close()
00470 {
00471 entries = null;
00472 if ( isStreamOwner ) {
00473 lock(baseStream) {
00474 baseStream.Close();
00475 }
00476 }
00477 }
00478
00485 public IEnumerator GetEnumerator()
00486 {
00487 if (entries == null) {
00488 throw new InvalidOperationException("ZipFile has closed");
00489 }
00490
00491 return new ZipEntryEnumeration(entries);
00492 }
00493
00503 public int FindEntry(string name, bool ignoreCase)
00504 {
00505 if (entries == null) {
00506 throw new InvalidOperationException("ZipFile has been closed");
00507 }
00508
00509 for (int i = 0; i < entries.Length; i++) {
00510 if (string.Compare(name, entries[i].Name, ignoreCase) == 0) {
00511 return i;
00512 }
00513 }
00514 return -1;
00515 }
00516
00520 [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")]
00521 public ZipEntry this[int index] {
00522 get {
00523 return (ZipEntry) entries[index].Clone();
00524 }
00525 }
00526
00540 public ZipEntry GetEntry(string name)
00541 {
00542 if (entries == null) {
00543 throw new InvalidOperationException("ZipFile has been closed");
00544 }
00545
00546 int index = FindEntry(name, true);
00547 return index >= 0 ? (ZipEntry) entries[index].Clone() : null;
00548 }
00554 public bool TestArchive(bool testData)
00555 {
00556 bool result = true;
00557 try {
00558 for (int i = 0; i < Size; ++i) {
00559 long offset = TestLocalHeader(this[i], true, true);
00560 if (testData) {
00561 Stream entryStream = this.GetInputStream(this[i]);
00562
00563 Crc32 crc = new Crc32();
00564 byte[] buffer = new byte[4096];
00565 int bytesRead;
00566 while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) {
00567 crc.Update(buffer, 0, bytesRead);
00568 }
00569
00570 if (this[i].Crc != crc.Value) {
00571 result = false;
00572
00573 break;
00574 }
00575 }
00576 }
00577 }
00578 catch {
00579 result = false;
00580 }
00581 return result;
00582 }
00583
00597 long TestLocalHeader(ZipEntry entry, bool fullTest, bool extractTest)
00598 {
00599 lock(baseStream)
00600 {
00601 baseStream.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin);
00602 if (ReadLeInt() != ZipConstants.LOCSIG) {
00603 throw new ZipException("Wrong local header signature");
00604 }
00605
00606 short shortValue = (short)ReadLeShort();
00607 if (extractTest == true && shortValue > ZipConstants.VERSION_MADE_BY) {
00608 throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", shortValue));
00609 }
00610
00611 short localFlags = (short)ReadLeShort();
00612 if (extractTest == true) {
00613 if ((localFlags & (int)(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0) {
00614 throw new ZipException("The library doesnt support the zip version required to extract this entry");
00615 }
00616 }
00617
00618 if (localFlags != entry.Flags) {
00619 throw new ZipException("Central header/local header flags mismatch");
00620 }
00621
00622 if (entry.CompressionMethod != (CompressionMethod)ReadLeShort()) {
00623 throw new ZipException("Central header/local header compression method mismatch");
00624 }
00625
00626 shortValue = (short)ReadLeShort();
00627 shortValue = (short)ReadLeShort();
00628
00629 int intValue = ReadLeInt();
00630
00631 if (fullTest) {
00632 if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0) {
00633 if (intValue != (int)entry.Crc)
00634 throw new ZipException("Central header/local header crc mismatch");
00635 }
00636 }
00637
00638 intValue = ReadLeInt();
00639 intValue = ReadLeInt();
00640
00641
00642
00643 int storedNameLength = ReadLeShort();
00644 if (entry.Name.Length > storedNameLength) {
00645 throw new ZipException("file name length mismatch");
00646 }
00647
00648 int extraLen = storedNameLength + ReadLeShort();
00649 return offsetOfFirstEntry + entry.Offset + ZipConstants.LOCHDR + extraLen;
00650 }
00651 }
00652
00667 long CheckLocalHeader(ZipEntry entry)
00668 {
00669 return TestLocalHeader(entry, false, true);
00670 }
00671
00672
00673 void ReadFully(Stream s, byte[] outBuf)
00674 {
00675 int off = 0;
00676 int len = outBuf.Length;
00677 while (len > 0) {
00678 int count = s.Read(outBuf, off, len);
00679 if (count <= 0) {
00680 throw new ZipException("Unexpected EOF");
00681 }
00682 off += count;
00683 len -= count;
00684 }
00685 }
00686
00687 void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry)
00688 {
00689 byte[] cryptbuffer = new byte[ZipConstants.CRYPTO_HEADER_SIZE];
00690 ReadFully(classicCryptoStream, cryptbuffer);
00691
00692 if ((entry.Flags & (int)GeneralBitFlags.Descriptor) == 0) {
00693 if (cryptbuffer[ZipConstants.CRYPTO_HEADER_SIZE - 1] != (byte)(entry.Crc >> 24)) {
00694 throw new ZipException("Invalid password");
00695 }
00696 }
00697 else {
00698 if (cryptbuffer[ZipConstants.CRYPTO_HEADER_SIZE - 1] != (byte)((entry.DosTime >> 8) & 0xff)) {
00699 throw new ZipException("Invalid password");
00700 }
00701 }
00702 }
00703
00704 Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
00705 {
00706 CryptoStream result = null;
00707
00708 if (entry.Version < ZipConstants.VERSION_STRONG_ENCRYPTION
00709 || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) {
00710 PkzipClassicManaged classicManaged = new PkzipClassicManaged();
00711
00712 OnKeysRequired(entry.Name);
00713 if (HaveKeys == false) {
00714 throw new ZipException("No password available for encrypted stream");
00715 }
00716
00717 result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, iv), CryptoStreamMode.Read);
00718 CheckClassicPassword(result, entry);
00719 }
00720 else {
00721 throw new ZipException("Decryption method not supported");
00722 }
00723
00724 return result;
00725 }
00726
00727 void WriteEncryptionHeader(Stream stream, long crcValue)
00728 {
00729 byte[] cryptBuffer = new byte[ZipConstants.CRYPTO_HEADER_SIZE];
00730 Random rnd = new Random();
00731 rnd.NextBytes(cryptBuffer);
00732 cryptBuffer[11] = (byte)(crcValue >> 24);
00733 stream.Write(cryptBuffer, 0, cryptBuffer.Length);
00734 }
00735
00736 Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
00737 {
00738 CryptoStream result = null;
00739 if (entry.Version < ZipConstants.VERSION_STRONG_ENCRYPTION
00740 || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) {
00741 PkzipClassicManaged classicManaged = new PkzipClassicManaged();
00742
00743 OnKeysRequired(entry.Name);
00744 if (HaveKeys == false) {
00745 throw new ZipException("No password available for encrypted stream");
00746 }
00747
00748 result = new CryptoStream(baseStream, classicManaged.CreateEncryptor(key, iv), CryptoStreamMode.Write);
00749 if (entry.Crc < 0 || (entry.Flags & 8) != 0) {
00750 WriteEncryptionHeader(result, entry.DosTime << 16);
00751 }
00752 else {
00753 WriteEncryptionHeader(result, entry.Crc);
00754 }
00755 }
00756 return result;
00757 }
00758
00765 Stream GetOutputStream(ZipEntry entry, string fileName)
00766 {
00767 baseStream.Seek(0, SeekOrigin.End);
00768 Stream result = File.OpenWrite(fileName);
00769
00770 if (entry.IsCrypted == true)
00771 {
00772 result = CreateAndInitEncryptionStream(result, entry);
00773 }
00774
00775 switch (entry.CompressionMethod)
00776 {
00777 case CompressionMethod.Stored:
00778 break;
00779
00780 case CompressionMethod.Deflated:
00781 result = new DeflaterOutputStream(result);
00782 break;
00783
00784 default:
00785 throw new ZipException("Unknown compression method " + entry.CompressionMethod);
00786 }
00787 return result;
00788 }
00789
00807 public Stream GetInputStream(ZipEntry entry)
00808 {
00809 if (entries == null) {
00810 throw new InvalidOperationException("ZipFile has closed");
00811 }
00812
00813 int index = entry.ZipFileIndex;
00814 if (index < 0 || index >= entries.Length || entries[index].Name != entry.Name) {
00815 index = FindEntry(entry.Name, true);
00816 if (index < 0) {
00817 throw new IndexOutOfRangeException();
00818 }
00819 }
00820 return GetInputStream(index);
00821 }
00822
00839 public Stream GetInputStream(int entryIndex)
00840 {
00841 if (entries == null) {
00842 throw new InvalidOperationException("ZipFile has closed");
00843 }
00844
00845 long start = CheckLocalHeader(entries[entryIndex]);
00846 CompressionMethod method = entries[entryIndex].CompressionMethod;
00847 Stream istr = new PartialInputStream(baseStream, start, entries[entryIndex].CompressedSize);
00848
00849 if (entries[entryIndex].IsCrypted == true) {
00850 istr = CreateAndInitDecryptionStream(istr, entries[entryIndex]);
00851 if (istr == null) {
00852 throw new ZipException("Unable to decrypt this entry");
00853 }
00854 }
00855
00856 switch (method) {
00857 case CompressionMethod.Stored:
00858 return istr;
00859 case CompressionMethod.Deflated:
00860 return new InflaterInputStream(istr, new Inflater(true));
00861 default:
00862 throw new ZipException("Unsupported compression method " + method);
00863 }
00864 }
00865
00866
00870 public string ZipFileComment {
00871 get {
00872 return comment;
00873 }
00874 }
00875
00879 public string Name {
00880 get {
00881 return name;
00882 }
00883 }
00884
00891 public int Size {
00892 get {
00893 if (entries != null) {
00894 return entries.Length;
00895 } else {
00896 throw new InvalidOperationException("ZipFile is closed");
00897 }
00898 }
00899 }
00900
00901 class ZipEntryEnumeration : IEnumerator
00902 {
00903 ZipEntry[] array;
00904 int ptr = -1;
00905
00906 public ZipEntryEnumeration(ZipEntry[] arr)
00907 {
00908 array = arr;
00909 }
00910
00911 public object Current {
00912 get {
00913 return array[ptr];
00914 }
00915 }
00916
00917 public void Reset()
00918 {
00919 ptr = -1;
00920 }
00921
00922 public bool MoveNext()
00923 {
00924 return (++ptr < array.Length);
00925 }
00926 }
00927
00928 class PartialInputStream : InflaterInputStream
00929 {
00930 Stream baseStream;
00931 long filepos, end;
00932
00933 public PartialInputStream(Stream baseStream, long start, long len) : base(baseStream)
00934 {
00935 this.baseStream = baseStream;
00936 filepos = start;
00937 end = start + len;
00938 }
00939
00940 public override int Available
00941 {
00942 get {
00943 long amount = end - filepos;
00944 if (amount > Int32.MaxValue) {
00945 return Int32.MaxValue;
00946 }
00947
00948 return (int) amount;
00949 }
00950 }
00951
00956 public override int ReadByte()
00957 {
00958 if (filepos == end) {
00959 return -1;
00960 }
00961
00962 lock(baseStream) {
00963 baseStream.Seek(filepos++, SeekOrigin.Begin);
00964 return baseStream.ReadByte();
00965 }
00966 }
00967
00968
00975 public override void Close()
00976 {
00977
00978 }
00979
00980 public override int Read(byte[] b, int off, int len)
00981 {
00982 if (len > end - filepos) {
00983 len = (int) (end - filepos);
00984 if (len == 0) {
00985 return 0;
00986 }
00987 }
00988
00989 lock(baseStream) {
00990 baseStream.Seek(filepos, SeekOrigin.Begin);
00991 int count = baseStream.Read(b, off, len);
00992 if (count > 0) {
00993 filepos += len;
00994 }
00995 return count;
00996 }
00997 }
00998
00999 public long SkipBytes(long amount)
01000 {
01001 if (amount < 0) {
01002 throw new ArgumentOutOfRangeException();
01003 }
01004 if (amount > end - filepos) {
01005 amount = end - filepos;
01006 }
01007 filepos += amount;
01008 return amount;
01009 }
01010 }
01011 }
01012 }