3 // Copyright (C) 2001 Mike Krueger
4 // Copyright (C) 2004 John Reilly
6 // This file was translated from java, it was part of the GNU Classpath
7 // Copyright (C) 2001 Free Software Foundation, Inc.
9 // This program is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU General Public License
11 // as published by the Free Software Foundation; either version 2
12 // of the License, or (at your option) any later version.
14 // This program is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
19 // You should have received a copy of the GNU General Public License
20 // along with this program; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 // Linking this library statically or dynamically with other modules is
24 // making a combined work based on this library. Thus, the terms and
25 // conditions of the GNU General Public License cover the whole
28 // As a special exception, the copyright holders of this library give you
29 // permission to link this library with independent modules to produce an
30 // executable, regardless of the license terms of these independent
31 // modules, and to copy and distribute the resulting executable under
32 // terms of your choice, provided that you also meet, for each linked
33 // independent module, the terms and conditions of the license of that
34 // module. An independent module is a module which is not derived from
35 // or based on this library. If you modify this library, you may extend
36 // this exception to your version of the library, but you are not
37 // obligated to do so. If you do not wish to do so, delete this
38 // exception statement from your version.
41 using System.Collections;
42 using System.Collections.Generic;
45 using System.Globalization;
47 using System.Security.Cryptography;
48 using ICSharpCode.SharpZipLib.Silverlight.Checksums;
49 using ICSharpCode.SharpZipLib.Silverlight.Core;
50 using ICSharpCode.SharpZipLib.Silverlight.Compat;
51 using ICSharpCode.SharpZipLib.Silverlight.Encryption;
52 using ICSharpCode.SharpZipLib.Silverlight.Zip;
53 using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression;
54 using ICSharpCode.SharpZipLib.Silverlight.Zip.Compression.Streams;
55 using ICSharpCode.SharpZipLib.Zip;
57 namespace ICSharpCode.SharpZipLib.Silverlight.Zip
60 /// Arguments used with KeysRequiredEvent
62 public class KeysRequiredEventArgs : EventArgs
66 /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see>
68 /// <param name="name">The name of the file for which keys are required.</param>
69 public KeysRequiredEventArgs(string name)
75 /// Initialise a new instance of <see cref="KeysRequiredEventArgs"></see>
77 /// <param name="name">The name of the file for which keys are required.</param>
78 /// <param name="keyValue">The current key value.</param>
79 public KeysRequiredEventArgs(string name, byte[] keyValue)
88 /// Get the name of the file for which keys are required.
90 public string FileName
92 get { return fileName; }
96 /// Get/set the key value
104 #region Instance Fields
111 /// The strategy to apply to testing.
113 public enum TestStrategy
116 /// Find the first error only.
120 /// Find all possible errors.
126 /// The operation in progress reported by a <see cref="ZipTestResultHandler"/> during testing.
128 /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso>
129 public enum TestOperation
132 /// Setting up testing.
137 /// Testing an individual entries header
142 /// Testing an individual entries data
147 /// Testing an individual entry has completed.
152 /// Running miscellaneous tests
157 /// Testing is complete
163 /// Status returned returned by <see cref="ZipTestResultHandler"/> during testing.
165 /// <seealso cref="ZipFile.TestArchive(bool)">TestArchive</seealso>
166 public class TestStatus
170 /// Initialise a new instance of <see cref="TestStatus"/>
172 /// <param name="file">The <see cref="ZipFile"/> this status applies to.</param>
173 public TestStatus(ZipFile file)
182 /// Get the current <see cref="TestOperation"/> in progress.
184 public TestOperation Operation
186 get { return operation_; }
190 /// Get the <see cref="ZipFile"/> this status is applicable to.
194 get { return file_; }
198 /// Get the current/last entry tested.
200 public ZipEntry Entry
202 get { return entry_; }
206 /// Get the number of errors detected so far.
208 public int ErrorCount
210 get { return errorCount_; }
214 /// Get the number of bytes tested so far for the current entry.
216 public long BytesTested
218 get { return bytesTested_; }
222 /// Get a value indicating wether the last entry test was valid.
224 public bool EntryValid
226 get { return entryValid_; }
231 internal void AddError()
237 internal void SetOperation(TestOperation operation)
239 operation_ = operation;
242 internal void SetEntry(ZipEntry entry)
249 internal void SetBytesTested(long value)
251 bytesTested_ = value;
255 #region Instance Fields
261 TestOperation operation_;
266 /// Delegate invoked during <see cref="ZipFile.TestArchive(bool, TestStrategy, ZipTestResultHandler)">testing</see> if supplied indicating current progress and status.
268 /// <remarks>If the message is non-null an error has occured. If the message is null
269 /// the operation as found in <see cref="TestStatus">status</see> has started.</remarks>
270 public delegate void ZipTestResultHandler(TestStatus status, string message);
273 /// The possible ways of <see cref="ZipFile.CommitUpdate()">applying updates</see> to an archive.
275 public enum FileUpdateMode
278 /// Perform all updates on temporary files ensuring that the original file is saved.
282 /// Update the archive directly, which is faster but less safe.
288 /// This class represents a Zip archive. You can ask for the contained
289 /// entries, or get an input stream for a file entry. The entry is
290 /// automatically decompressed.
292 /// You can also update the archive adding or deleting entries.
294 /// This class is thread safe for input: You can open input streams for arbitrary
295 /// entries in different threads.
297 /// <br/>Author of the original java version : Jochen Hoenicke
302 /// using System.Text;
303 /// using System.Collections;
306 /// using ICSharpCode.SharpZipLib.Zip;
310 /// static public void Main(string[] args)
312 /// using (ZipFile zFile = new ZipFile(args[0])) {
313 /// Console.WriteLine("Listing of : " + zFile.Name);
314 /// Console.WriteLine("");
315 /// Console.WriteLine("Raw Size Size Date Time Name");
316 /// Console.WriteLine("-------- -------- -------- ------ ---------");
317 /// foreach (ZipEntry e in zFile) {
318 /// if ( e.IsFile ) {
319 /// DateTime d = e.DateTime;
320 /// Console.WriteLine("{0, -10}{1, -10}{2} {3} {4}", e.Size, e.CompressedSize,
321 /// d.ToString("dd-MM-yy"), d.ToString("HH:mm"),
330 public class ZipFile : IEnumerable, IDisposable
335 /// Delegate for handling keys/password setting during compresion/decompression.
337 public delegate void KeysRequiredEventHandler(
339 KeysRequiredEventArgs e
343 /// Event handler for handling encryption keys.
345 public KeysRequiredEventHandler KeysRequired;
348 /// Handles getting of encryption keys when required.
350 /// <param name="fileName">The file for which encryption keys are required.</param>
351 void OnKeysRequired(string fileName)
353 if (KeysRequired != null) {
354 KeysRequiredEventArgs krea = new KeysRequiredEventArgs(fileName, key);
355 KeysRequired(this, krea);
362 /// Get/set the encryption key value.
371 /// Password to be used for encrypting/decrypting files.
373 /// <remarks>Set to null if no password is required.</remarks>
374 public string Password
378 if ( (value == null) || (value.Length == 0) ) {
382 key = PkzipClassic.GenerateKeys(ZipConstants.ConvertToArray(value));
388 /// Get a value indicating wether encryption keys are currently available.
392 get { return key != null; }
398 /// Opens a Zip file with the given name for reading.
400 /// <param name="name">The name of the file to open.</param>
401 /// <exception cref="IOException">
402 /// An i/o error occurs
404 /// <exception cref="ZipException">
405 /// The file doesn't contain a valid zip archive.
407 public ZipFile(string name)
411 baseStream_ = File.OpenRead(name);
412 isStreamOwner = true;
418 DisposeInternal(true);
424 /// Opens a Zip file reading the given <see cref="FileStream"/>.
426 /// <param name="file">The <see cref="FileStream"/> to read archive data from.</param>
427 /// <exception cref="IOException">
428 /// An i/o error occurs.
430 /// <exception cref="ZipException">
431 /// The file doesn't contain a valid zip archive.
433 public ZipFile(FileStream file)
435 if ( file == null ) {
436 throw new ArgumentNullException("file");
439 if ( !file.CanSeek ) {
440 throw new ArgumentException("Stream is not seekable", "file");
445 isStreamOwner = true;
451 DisposeInternal(true);
457 /// Opens a Zip file reading the given <see cref="Stream"/>.
459 /// <param name="stream">The <see cref="Stream"/> to read archive data from.</param>
460 /// <exception cref="IOException">
461 /// An i/o error occurs
463 /// <exception cref="ZipException">
464 /// The file doesn't contain a valid zip archive.<br/>
465 /// The stream provided cannot seek
467 public ZipFile(Stream stream)
469 if ( stream == null ) {
470 throw new ArgumentNullException("stream");
473 if ( !stream.CanSeek ) {
474 throw new ArgumentException("Stream is not seekable", "stream");
477 baseStream_ = stream;
478 isStreamOwner = true;
480 if ( baseStream_.Length > 0 ) {
485 DisposeInternal(true);
489 entries_ = new ZipEntry[0];
490 isNewArchive_ = true;
495 /// Initialises a default <see cref="ZipFile"/> instance with no entries and no file storage.
499 entries_ = new ZipEntry[0];
500 isNewArchive_ = true;
505 #region Destructors and Closing
507 /// Finalize this instance.
515 /// Closes the ZipFile. If the stream is <see cref="IsStreamOwner">owned</see> then this also closes the underlying input stream.
516 /// Once closed, no further instance methods should be called.
518 /// <exception cref="System.IO.IOException">
519 /// An i/o error occurs.
523 DisposeInternal(true);
524 GC.SuppressFinalize(this);
531 /// Create a new <see cref="ZipFile"/> whose data will be stored in a file.
533 /// <param name="fileName">The name of the archive to create.</param>
534 /// <returns>Returns the newly created <see cref="ZipFile"/></returns>
535 public static ZipFile Create(string fileName)
537 if ( fileName == null ) {
538 throw new ArgumentNullException("fileName");
541 FileStream fs = File.Create(fileName);
543 ZipFile result = new ZipFile();
544 result.name_ = fileName;
545 result.baseStream_ = fs;
546 result.isStreamOwner = true;
551 /// Create a new <see cref="ZipFile"/> whose data will be stored on a stream.
553 /// <param name="outStream">The stream providing data storage.</param>
554 /// <returns>Returns the newly created <see cref="ZipFile"/></returns>
555 public static ZipFile Create(Stream outStream)
557 if ( outStream == null ) {
558 throw new ArgumentNullException("outStream");
561 if ( !outStream.CanWrite ) {
562 throw new ArgumentException("Stream is not writeable", "outStream");
565 if ( !outStream.CanSeek ) {
566 throw new ArgumentException("Stream is not seekable", "outStream");
569 ZipFile result = new ZipFile();
570 result.baseStream_ = outStream;
578 /// Get/set a flag indicating if the underlying stream is owned by the ZipFile instance.
579 /// If the flag is true then the stream will be closed when <see cref="Close">Close</see> is called.
582 /// The default value is true in all cases.
584 public bool IsStreamOwner
586 get { return isStreamOwner; }
587 set { isStreamOwner = value; }
591 /// Get a value indicating wether
592 /// this archive is embedded in another file or not.
594 public bool IsEmbeddedArchive
596 // Not strictly correct in all circumstances currently
597 get { return offsetOfFirstEntry > 0; }
601 /// Get a value indicating that this archive is a new one.
603 public bool IsNewArchive
605 get { return isNewArchive_; }
609 /// Gets the comment for the zip file.
611 public string ZipFileComment
613 get { return comment_; }
617 /// Gets the name of this zip file.
621 get { return name_; }
625 /// Gets the number of entries in this zip file.
627 /// <exception cref="InvalidOperationException">
628 /// The Zip file has been closed.
630 [Obsolete("Use the Count property instead")]
635 if (entries_ != null) {
636 return entries_.Length;
638 throw new InvalidOperationException("ZipFile is closed");
644 /// Get the number of entries contained in this <see cref="ZipFile"/>.
650 if (entries_ != null) {
651 return entries_.Length;
653 throw new InvalidOperationException("ZipFile is closed");
659 /// Indexer property for ZipEntries
661 [System.Runtime.CompilerServices.IndexerNameAttribute("EntryByIndex")]
662 public ZipEntry this[int index]
665 return (ZipEntry) entries_[index].Clone();
671 #region Input Handling
673 /// Gets an enumerator for the Zip entries in this Zip file.
675 /// <returns>Returns an <see cref="IEnumerator"/> for this archive.</returns>
676 /// <exception cref="InvalidOperationException">
677 /// The Zip file has been closed.
679 public IEnumerator GetEnumerator()
681 if (entries_ == null) {
682 throw new InvalidOperationException("ZipFile has closed");
685 return new ZipEntryEnumerator(entries_);
689 /// Return the index of the entry with a matching name
691 /// <param name="name">Entry name to find</param>
692 /// <param name="ignoreCase">If true the comparison is case insensitive</param>
693 /// <returns>The index position of the matching entry or -1 if not found</returns>
694 /// <exception cref="InvalidOperationException">
695 /// The Zip file has been closed.
697 public int FindEntry(string name, bool ignoreCase)
699 if (entries_ == null) {
700 throw new InvalidOperationException("ZipFile has been closed");
703 // TODO: This will be slow as the next ice age for huge archives!
704 for (int i = 0; i < entries_.Length; i++) {
705 // BUG: Throws MissingMethodException!
706 // CompareOptions options = (ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
707 // int comparison = string.Compare(name, entries_[i].Name, CultureInfo.InvariantCulture, options);
708 if (name.Compare(entries_[i].Name, ignoreCase, CultureInfo.InvariantCulture) == 0)
717 /// Searches for a zip entry in this archive with the given name.
718 /// String comparisons are case insensitive
720 /// <param name="name">
721 /// The name to find. May contain directory components separated by slashes ('/').
724 /// A clone of the zip entry, or null if no entry with that name exists.
726 /// <exception cref="InvalidOperationException">
727 /// The Zip file has been closed.
729 public ZipEntry GetEntry(string name)
731 if (entries_ == null) {
732 throw new InvalidOperationException("ZipFile has been closed");
735 int index = FindEntry(name, true);
736 return (index >= 0) ? (ZipEntry) entries_[index].Clone() : null;
740 /// Gets an input stream for reading the given zip entry data in an uncompressed form.
741 /// Normally the <see cref="ZipEntry"/> should be an entry returned by GetEntry().
743 /// <param name="entry">The <see cref="ZipEntry"/> to obtain a data <see cref="Stream"/> for</param>
744 /// <returns>An input <see cref="Stream"/> containing data for this <see cref="ZipEntry"/></returns>
745 /// <exception cref="InvalidOperationException">
746 /// The ZipFile has already been closed
748 /// <exception cref="ZipException">
749 /// The compression method for the entry is unknown
751 /// <exception cref="IndexOutOfRangeException">
752 /// The entry is not found in the ZipFile
754 public Stream GetInputStream(ZipEntry entry)
756 if ( entry == null ) {
757 throw new ArgumentNullException("entry");
760 if ( entries_ == null ) {
761 throw new InvalidOperationException("ZipFile has closed");
764 long index = entry.ZipFileIndex;
765 if ( (index < 0) || (index >= entries_.Length) || (entries_[index].Name != entry.Name) ) {
766 index = FindEntry(entry.Name, true);
768 throw new ZipException("Entry cannot be found");
771 return GetInputStream(index);
775 /// Creates an input stream reading a zip entry
777 /// <param name="entryIndex">The index of the entry to obtain an input stream for.</param>
779 /// An input <see cref="Stream"/> containing data for this <paramref name="entryIndex"/>
781 /// <exception cref="InvalidOperationException">
782 /// The ZipFile has already been closed
784 /// <exception cref="ZipException">
785 /// The compression method for the entry is unknown
787 /// <exception cref="IndexOutOfRangeException">
788 /// The entry is not found in the ZipFile
790 public Stream GetInputStream(long entryIndex)
792 if ( entries_ == null ) {
793 throw new InvalidOperationException("ZipFile is not open");
796 long start = LocateEntry(entries_[entryIndex]);
797 CompressionMethod method = entries_[entryIndex].CompressionMethod;
798 Stream result = new PartialInputStream(baseStream_, start, entries_[entryIndex].CompressedSize);
800 if (entries_[entryIndex].IsCrypted == true) {
802 throw new ZipException("decryption not supported for Compact Framework 1.0");
804 result = CreateAndInitDecryptionStream(result, entries_[entryIndex]);
805 if (result == null) {
806 throw new ZipException("Unable to decrypt this entry");
812 case CompressionMethod.Stored:
816 case CompressionMethod.Deflated:
817 // No need to worry about ownership and closing as underlying stream close does nothing.
818 result = new InflaterInputStream(result, new Inflater(true));
822 throw new ZipException("Unsupported compression method " + method);
830 #region Archive Testing
832 /// Test an archive for integrity/validity
834 /// <param name="testData">Perform low level data Crc check</param>
835 /// <returns>true if all tests pass, false otherwise</returns>
836 /// <remarks>Testing will terminate on the first error found.</remarks>
837 public bool TestArchive(bool testData)
839 return TestArchive(testData, TestStrategy.FindFirstError, null);
843 /// Test an archive for integrity/validity
845 /// <param name="testData">Perform low level data Crc check</param>
846 /// <param name="strategy">The <see cref="TestStrategy"></see> to apply.</param>
847 /// <param name="resultHandler">The <see cref="ZipTestResultHandler"></see> handler to call during testing.</param>
848 /// <returns>true if all tests pass, false otherwise</returns>
849 public bool TestArchive(bool testData, TestStrategy strategy, ZipTestResultHandler resultHandler)
851 TestStatus status = new TestStatus(this);
853 if ( resultHandler != null ) {
854 resultHandler(status, null);
857 HeaderTest test = testData ? (HeaderTest.Header | HeaderTest.Extract) : HeaderTest.Header;
864 while ( testing && (entryIndex < Count) ) {
865 if ( resultHandler != null ) {
866 status.SetEntry(this[entryIndex]);
867 status.SetOperation(TestOperation.EntryHeader);
868 resultHandler(status, null);
872 TestLocalHeader(this[entryIndex], test);
874 catch(ZipException ex) {
877 if ( resultHandler != null ) {
878 resultHandler(status,
879 string.Format("Exception during test - '{0}'", ex.Message));
882 if ( strategy == TestStrategy.FindFirstError ) {
887 if ( testing && testData && this[entryIndex].IsFile ) {
888 if ( resultHandler != null ) {
889 status.SetOperation(TestOperation.EntryData);
890 resultHandler(status, null);
893 Stream entryStream = this.GetInputStream(this[entryIndex]);
895 Crc32 crc = new Crc32();
896 byte[] buffer = new byte[4096];
899 while ((bytesRead = entryStream.Read(buffer, 0, buffer.Length)) > 0) {
900 crc.Update(buffer, 0, bytesRead);
902 if ( resultHandler != null ) {
903 totalBytes += bytesRead;
904 status.SetBytesTested(totalBytes);
905 resultHandler(status, null);
909 if (this[entryIndex].Crc != crc.Value) {
912 if ( resultHandler != null ) {
913 resultHandler(status, "CRC mismatch");
916 if ( strategy == TestStrategy.FindFirstError ) {
921 if (( this[entryIndex].Flags & (int)GeneralBitFlags.Descriptor) != 0 ) {
922 ZipHelperStream helper = new ZipHelperStream(baseStream_);
923 DescriptorData data = new DescriptorData();
924 helper.ReadDataDescriptor(this[entryIndex].LocalHeaderRequiresZip64, data);
925 if (this[entryIndex].Crc != data.Crc) {
929 if (this[entryIndex].CompressedSize != data.CompressedSize) {
933 if (this[entryIndex].Size != data.Size) {
939 if ( resultHandler != null ) {
940 status.SetOperation(TestOperation.EntryComplete);
941 resultHandler(status, null);
947 if ( resultHandler != null ) {
948 status.SetOperation(TestOperation.MiscellaneousTests);
949 resultHandler(status, null);
952 // TODO: the 'Corrina Johns' test where local headers are missing from
953 // the central directory. They are therefore invisible to many archivers.
955 catch (Exception ex) {
958 if ( resultHandler != null ) {
959 resultHandler(status, string.Format("Exception during test - '{0}'", ex.Message));
963 if ( resultHandler != null ) {
964 status.SetOperation(TestOperation.Complete);
965 status.SetEntry(null);
966 resultHandler(status, null);
969 return (status.ErrorCount == 0);
975 Extract = 0x01, // Check that this header represents an entry whose data can be extracted
976 Header = 0x02, // Check that this header contents are valid
980 /// Test a local header against that provided from the central directory
982 /// <param name="entry">
983 /// The entry to test against
985 /// <param name="tests">The type of <see cref="HeaderTest">tests</see> to carry out.</param>
986 /// <returns>The offset of the entries data in the file</returns>
987 long TestLocalHeader(ZipEntry entry, HeaderTest tests)
991 bool testHeader = (tests & HeaderTest.Header) != 0;
992 bool testData = (tests & HeaderTest.Extract) != 0;
994 baseStream_.Seek(offsetOfFirstEntry + entry.Offset, SeekOrigin.Begin);
995 if ((int)ReadLEUint() != ZipConstants.LocalHeaderSignature) {
996 throw new ZipException(string.Format("Wrong local header signature @{0:X}", offsetOfFirstEntry + entry.Offset));
999 short extractVersion = ( short )ReadLEUshort();
1000 short localFlags = ( short )ReadLEUshort();
1001 short compressionMethod = ( short )ReadLEUshort();
1002 short fileTime = ( short )ReadLEUshort();
1003 short fileDate = ( short )ReadLEUshort();
1004 uint crcValue = ReadLEUint();
1005 long compressedSize = ReadLEUint();
1006 long size = ReadLEUint();
1007 int storedNameLength = ReadLEUshort();
1008 int extraDataLength = ReadLEUshort();
1010 byte[] nameData = new byte[storedNameLength];
1011 StreamUtils.ReadFully(baseStream_, nameData);
1013 byte[] extraData = new byte[extraDataLength];
1014 StreamUtils.ReadFully(baseStream_, extraData);
1016 ZipExtraData ed = new ZipExtraData(extraData);
1018 // Extra data / zip64 checks
1021 // TODO Check for tag values being distinct.. Multiple zip64 tags means what?
1023 // Zip64 extra data but 'extract version' is too low
1024 if (extractVersion < ZipConstants.VersionZip64)
1026 throw new ZipException(
1027 string.Format("Extra data contains Zip64 information but version {0}.{1} is not high enough",
1028 extractVersion / 10, extractVersion % 10));
1031 // Zip64 extra data but size fields dont indicate its required.
1032 if (((uint)size != uint.MaxValue) && ((uint)compressedSize != uint.MaxValue))
1034 throw new ZipException("Entry sizes not correct for Zip64");
1037 size = ed.ReadLong();
1038 compressedSize = ed.ReadLong();
1042 // No zip64 extra data but entry requires it.
1043 if ((extractVersion >= ZipConstants.VersionZip64) &&
1044 (((uint)size == uint.MaxValue) || ((uint)compressedSize == uint.MaxValue)))
1046 throw new ZipException("Required Zip64 extended information missing");
1051 if ( entry.IsFile ) {
1052 if ( !entry.IsCompressionMethodSupported() ) {
1053 throw new ZipException("Compression method not supported");
1056 if ( (extractVersion > ZipConstants.VersionMadeBy)
1057 || ((extractVersion > 20) && (extractVersion < ZipConstants.VersionZip64)) ) {
1058 throw new ZipException(string.Format("Version required to extract this entry not supported ({0})", extractVersion));
1061 if ( (localFlags & ( int )(GeneralBitFlags.Patched | GeneralBitFlags.StrongEncryption | GeneralBitFlags.EnhancedCompress | GeneralBitFlags.HeaderMasked)) != 0 ) {
1062 throw new ZipException("The library does not support the zip version required to extract this entry");
1068 if ((extractVersion <= 63) && // Ignore later versions as we dont know about them..
1069 (extractVersion != 10) &&
1070 (extractVersion != 11) &&
1071 (extractVersion != 20) &&
1072 (extractVersion != 21) &&
1073 (extractVersion != 25) &&
1074 (extractVersion != 27) &&
1075 (extractVersion != 45) &&
1076 (extractVersion != 46) &&
1077 (extractVersion != 50) &&
1078 (extractVersion != 51) &&
1079 (extractVersion != 52) &&
1080 (extractVersion != 61) &&
1081 (extractVersion != 62) &&
1082 (extractVersion != 63)
1084 throw new ZipException(string.Format("Version required to extract this entry is invalid ({0})", extractVersion));
1087 // Local entry flags dont have reserved bit set on.
1088 if ( (localFlags & ( int )(GeneralBitFlags.ReservedPKware4 | GeneralBitFlags.ReservedPkware14 | GeneralBitFlags.ReservedPkware15)) != 0 ) {
1089 throw new ZipException("Reserved bit flags cannot be set.");
1092 // Encryption requires extract version >= 20
1093 if ( ((localFlags & ( int )GeneralBitFlags.Encrypted) != 0) && (extractVersion < 20) ) {
1094 throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
1097 // Strong encryption requires encryption flag to be set and extract version >= 50.
1098 if ( (localFlags & (int)GeneralBitFlags.StrongEncryption) != 0 ) {
1099 if ( (localFlags & (int)GeneralBitFlags.Encrypted) == 0 ) {
1100 throw new ZipException("Strong encryption flag set but encryption flag is not set");
1103 if ( extractVersion < 50 ) {
1104 throw new ZipException(string.Format("Version required to extract this entry is too low for encryption ({0})", extractVersion));
1108 // Patched entries require extract version >= 27
1109 if ( ((localFlags & ( int )GeneralBitFlags.Patched) != 0) && (extractVersion < 27) ) {
1110 throw new ZipException(string.Format("Patched data requires higher version than ({0})", extractVersion));
1113 // Central header flags match local entry flags.
1114 if ( localFlags != entry.Flags ) {
1115 throw new ZipException("Central header/local header flags mismatch");
1118 // Central header compression method matches local entry
1119 if ( entry.CompressionMethod != ( CompressionMethod )compressionMethod ) {
1120 throw new ZipException("Central header/local header compression method mismatch");
1123 // Strong encryption and extract version match
1124 if ( (localFlags & ( int )GeneralBitFlags.StrongEncryption) != 0 ) {
1125 if ( extractVersion < 62 ) {
1126 throw new ZipException("Strong encryption flag set but version not high enough");
1130 if ( (localFlags & ( int )GeneralBitFlags.HeaderMasked) != 0 ) {
1131 if ( (fileTime != 0) || (fileDate != 0) ) {
1132 throw new ZipException("Header masked set but date/time values non-zero");
1136 if ( (localFlags & ( int )GeneralBitFlags.Descriptor) == 0 ) {
1137 if ( crcValue != (uint)entry.Crc ) {
1138 throw new ZipException("Central header/local header crc mismatch");
1142 // Crc valid for empty entry.
1143 // This will also apply to streamed entries where size isnt known and the header cant be patched
1144 if ( (size == 0) && (compressedSize == 0) ) {
1145 if ( crcValue != 0 ) {
1146 throw new ZipException("Invalid CRC for empty entry");
1150 // TODO: make test more correct... can't compare lengths as was done originally as this can fail for MBCS strings
1151 // Assuming a code page at this point is not valid? Best is to store the name length in the ZipEntry probably
1152 if ( entry.Name.Length > storedNameLength ) {
1153 throw new ZipException("File name length mismatch");
1156 // Name data has already been read convert it and compare.
1157 string localName = ZipConstants.ConvertToStringExt(localFlags, nameData);
1159 // Central directory and local entry name match
1160 if ( localName != entry.Name ) {
1161 throw new ZipException("Central header and local header file name mismatch");
1164 // Directories have zero size.
1165 if ( entry.IsDirectory ) {
1166 if ( (compressedSize != 0) || (size != 0) ) {
1167 throw new ZipException("Directory cannot have size");
1171 if ( !ZipNameTransform.IsValidName(localName, true) ) {
1172 throw new ZipException("Name is invalid");
1177 // Tests that apply to both data and header.
1179 // Size can be verified only if it is known in the local header.
1180 // it will always be known in the central header.
1181 if ((localFlags & (int)GeneralBitFlags.Descriptor) == 0 ||
1182 (size != 0 || compressedSize != 0)) {
1184 if (size != entry.Size) {
1185 throw new ZipException(
1186 string.Format("Size mismatch between central header({0}) and local header({1})",
1190 if (compressedSize != entry.CompressedSize) {
1191 throw new ZipException(
1192 string.Format("Compressed size mismatch between central header({0}) and local header({1})",
1193 entry.CompressedSize, compressedSize));
1197 int extraLength = storedNameLength + extraDataLength;
1198 return offsetOfFirstEntry + entry.Offset + ZipConstants.LocalHeaderBaseSize + extraLength;
1206 const int DefaultBufferSize = 4096;
1209 /// The kind of update to apply.
1213 Copy, // Copy original file contents.
1214 Modify, // Change encryption, compression, attributes, name, time etc, of an existing file.
1215 Add, // Add a new file to the archive.
1220 /// Get / set the <see cref="INameTransform"/> to apply to names when updating.
1222 public INameTransform NameTransform
1225 return updateEntryFactory_.NameTransform;
1229 updateEntryFactory_.NameTransform = value;
1234 /// Get/set the <see cref="IEntryFactory"/> used to generate <see cref="ZipEntry"/> values
1237 public IEntryFactory EntryFactory
1240 return updateEntryFactory_;
1244 if (value == null) {
1245 updateEntryFactory_ = new ZipEntryFactory();
1248 updateEntryFactory_ = value;
1254 /// Get /set the buffer size to be used when updating this zip file.
1256 public int BufferSize
1258 get { return bufferSize_; }
1260 if ( value < 1024 ) {
1262 throw new ArgumentOutOfRangeException("value");
1264 throw new ArgumentOutOfRangeException("value", "cannot be below 1024");
1268 if ( bufferSize_ != value ) {
1269 bufferSize_ = value;
1276 /// Get a value indicating an update has <see cref="BeginUpdate()">been started</see>.
1278 public bool IsUpdating
1280 get { return updates_ != null; }
1284 /// Get / set a value indicating how Zip64 Extension usage is determined when adding entries.
1286 public UseZip64 UseZip64
1288 get { return useZip64_; }
1289 set { useZip64_ = value; }
1294 #region Immediate updating
1295 // TBD: Direct form of updating
1297 // public void Update(IEntryMatcher deleteMatcher)
1301 // public void Update(IScanner addScanner)
1306 #region Deferred Updating
1308 /// Begin updating this <see cref="ZipFile"/> archive.
1310 /// <param name="archiveStorage">The <see cref="IArchiveStorage">archive storage</see> for use during the update.</param>
1311 /// <param name="dataSource">The <see cref="IDynamicDataSource">data source</see> to utilise during updating.</param>
1312 public void BeginUpdate(IArchiveStorage archiveStorage, IDynamicDataSource dataSource)
1314 if ( IsEmbeddedArchive ) {
1315 throw new ZipException ("Cannot update embedded/SFX archives");
1318 if ( archiveStorage == null ) {
1319 throw new ArgumentNullException("archiveStorage");
1322 if ( dataSource == null ) {
1323 throw new ArgumentNullException("dataSource");
1326 archiveStorage_ = archiveStorage;
1327 updateDataSource_ = dataSource;
1329 // NOTE: the baseStream_ may not currently support writing or seeking.
1331 updateIndex_ = new Dictionary<string, int>();
1333 if ( entries_ != null ) {
1334 updates_ = new List<ZipUpdate>(entries_.Length);
1335 foreach(ZipEntry entry in entries_) {
1336 ZipUpdate update = new ZipUpdate(entry);
1337 updates_.Add(update);
1338 int index = updates_.IndexOf(update);
1339 updateIndex_.Add(entry.Name, index);
1343 updates_ = new List<ZipUpdate>();
1346 updateCount_ = updates_.Count;
1348 contentsEdited_ = false;
1349 commentEdited_ = false;
1354 /// Begin updating to this <see cref="ZipFile"/> archive.
1356 /// <param name="archiveStorage">The storage to use during the update.</param>
1357 public void BeginUpdate(IArchiveStorage archiveStorage)
1359 BeginUpdate(archiveStorage, new DynamicDiskDataSource());
1363 /// Begin updating this <see cref="ZipFile"/> archive.
1365 /// <seealso cref="BeginUpdate(IArchiveStorage)"/>
1366 /// <seealso cref="CommitUpdate"></seealso>
1367 /// <seealso cref="AbortUpdate"></seealso>
1368 public void BeginUpdate()
1370 if ( Name == null ) {
1371 BeginUpdate(new MemoryArchiveStorage(), new DynamicDiskDataSource());
1374 BeginUpdate(new DiskArchiveStorage(this), new DynamicDiskDataSource());
1379 /// Commit current updates, updating this archive.
1381 /// <seealso cref="BeginUpdate()"></seealso>
1382 /// <seealso cref="AbortUpdate"></seealso>
1383 public void CommitUpdate()
1389 updateIndex_.Clear();
1392 if( contentsEdited_ ) {
1395 else if( commentEdited_ ) {
1396 UpdateCommentOnly();
1399 // Create an empty archive if none existed originally.
1400 if( (entries_!=null)&&(entries_.Length==0) ) {
1401 byte[] theComment=(newComment_!=null)?newComment_.RawComment:ZipConstants.ConvertToArray(comment_);
1402 using( ZipHelperStream zhs=new ZipHelperStream(baseStream_) ) {
1403 zhs.WriteEndOfCentralDirectory(0, 0, 0, theComment);
1410 PostUpdateCleanup();
1415 /// Abort updating leaving the archive unchanged.
1417 /// <seealso cref="BeginUpdate()"></seealso>
1418 /// <seealso cref="CommitUpdate"></seealso>
1419 public void AbortUpdate()
1421 PostUpdateCleanup();
1425 /// Set the file comment to be recorded when the current update is <see cref="CommitUpdate">commited</see>.
1427 /// <param name="comment">The comment to record.</param>
1428 public void SetComment(string comment)
1432 newComment_ = new ZipString(comment);
1434 if ( newComment_.RawLength > 0xffff ) {
1436 throw new ZipException("Comment length exceeds maximum - 65535");
1439 // We dont take account of the original and current comment appearing to be the same
1440 // as encoding may be different.
1441 commentEdited_ = true;
1446 #region Adding Entries
1448 void AddUpdate(ZipUpdate update)
1450 contentsEdited_ = true;
1452 int index = FindExistingUpdate(update.Entry.Name);
1455 if ( updates_[index] == null ) {
1459 // Direct replacement is faster than delete and add.
1460 updates_[index] = update;
1463 updates_.Add(update);
1464 index = updates_.IndexOf(update);
1466 updateIndex_.Add(update.Entry.Name, index);
1471 /// Add a new entry to the archive.
1473 /// <param name="fileName">The name of the file to add.</param>
1474 /// <param name="compressionMethod">The compression method to use.</param>
1475 /// <param name="useUnicodeText">Ensure Unicode text is used for name and comment for this entry.</param>
1476 public void Add(string fileName, CompressionMethod compressionMethod, bool useUnicodeText )
1478 if (fileName == null) {
1479 throw new ArgumentNullException("fileName");
1482 if (!ZipEntry.IsCompressionMethodSupported(compressionMethod)) {
1483 throw new ZipException("Compression method not supported");
1487 contentsEdited_ = true;
1489 ZipEntry entry = EntryFactory.MakeFileEntry(fileName);
1490 entry.IsUnicodeText = useUnicodeText;
1491 entry.CompressionMethod = compressionMethod;
1493 AddUpdate(new ZipUpdate(fileName, entry));
1497 /// Add a new entry to the archive.
1499 /// <param name="fileName">The name of the file to add.</param>
1500 /// <param name="compressionMethod">The compression method to use.</param>
1501 public void Add(string fileName, CompressionMethod compressionMethod)
1503 if ( fileName == null ) {
1504 throw new ArgumentNullException("fileName");
1507 if ( !ZipEntry.IsCompressionMethodSupported(compressionMethod) ) {
1508 throw new ZipException("Compression method not supported");
1512 contentsEdited_ = true;
1514 ZipEntry entry = EntryFactory.MakeFileEntry(fileName);
1515 entry.CompressionMethod = compressionMethod;
1516 AddUpdate(new ZipUpdate(fileName, entry));
1520 /// Add a file to the archive.
1522 /// <param name="fileName">The name of the file to add.</param>
1523 public void Add(string fileName)
1525 if ( fileName == null ) {
1526 throw new ArgumentNullException("fileName");
1530 AddUpdate(new ZipUpdate(fileName, EntryFactory.MakeFileEntry(fileName)));
1534 /// Add a file entry with data.
1536 /// <param name="dataSource">The source of the data for this entry.</param>
1537 /// <param name="entryName">The name to give to the entry.</param>
1538 public void Add(IStaticDataSource dataSource, string entryName)
1540 if ( dataSource == null ) {
1541 throw new ArgumentNullException("dataSource");
1545 AddUpdate(new ZipUpdate(dataSource, EntryFactory.MakeFileEntry(entryName)));
1549 /// Add a file entry with data.
1551 /// <param name="dataSource">The source of the data for this entry.</param>
1552 /// <param name="entryName">The name to give to the entry.</param>
1553 /// <param name="compressionMethod">The compression method to use.</param>
1554 public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod)
1556 if ( dataSource == null ) {
1557 throw new ArgumentNullException("dataSource");
1562 ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false);
1563 entry.CompressionMethod = compressionMethod;
1565 AddUpdate(new ZipUpdate(dataSource, entry));
1569 /// Add a file entry with data.
1571 /// <param name="dataSource">The source of the data for this entry.</param>
1572 /// <param name="entryName">The name to give to the entry.</param>
1573 /// <param name="compressionMethod">The compression method to use.</param>
1574 /// <param name="useUnicodeText">Ensure Unicode text is used for name and comments for this entry.</param>
1575 public void Add(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod, bool useUnicodeText)
1577 if (dataSource == null) {
1578 throw new ArgumentNullException("dataSource");
1583 ZipEntry entry = EntryFactory.MakeFileEntry(entryName, false);
1584 entry.IsUnicodeText = useUnicodeText;
1585 entry.CompressionMethod = compressionMethod;
1587 AddUpdate(new ZipUpdate(dataSource, entry));
1591 /// Add a <see cref="ZipEntry"/> that contains no data.
1593 /// <param name="entry">The entry to add.</param>
1594 /// <remarks>This can be used to add directories, volume labels, or empty file entries.</remarks>
1595 public void Add(ZipEntry entry)
1597 if ( entry == null ) {
1598 throw new ArgumentNullException("entry");
1603 if ( (entry.Size != 0) || (entry.CompressedSize != 0) ) {
1604 throw new ZipException("Entry cannot have any data");
1607 AddUpdate(new ZipUpdate(UpdateCommand.Add, entry));
1611 /// Add a directory entry to the archive.
1613 /// <param name="directoryName">The directory to add.</param>
1614 public void AddDirectory(string directoryName)
1616 if ( directoryName == null ) {
1617 throw new ArgumentNullException("directoryName");
1622 ZipEntry dirEntry = EntryFactory.MakeDirectoryEntry(directoryName);
1623 AddUpdate(new ZipUpdate(UpdateCommand.Add, dirEntry));
1628 #region Modifying Entries
1629 /* Modify not yet ready for public consumption.
1630 Direct modification of an entry should not overwrite original data before its read.
1631 Safe mode is trivial in this sense.
1632 public void Modify(ZipEntry original, ZipEntry updated)
1634 if ( original == null ) {
1635 throw new ArgumentNullException("original");
1638 if ( updated == null ) {
1639 throw new ArgumentNullException("updated");
1643 contentsEdited_ = true;
1644 updates_.Add(new ZipUpdate(original, updated));
1649 #region Deleting Entries
1651 /// Delete an entry by name
1653 /// <param name="fileName">The filename to delete</param>
1654 /// <returns>True if the entry was found and deleted; false otherwise.</returns>
1655 public bool Delete(string fileName)
1659 bool result = false;
1660 int index = FindExistingUpdate(fileName);
1661 if ( (index >= 0) && (updates_[index] != null) ) {
1663 contentsEdited_ = true;
1664 updates_[index] = null;
1668 throw new ZipException("Cannot find entry to delete");
1674 /// Delete a <see cref="ZipEntry"/> from the archive.
1676 /// <param name="entry">The entry to delete.</param>
1677 public void Delete(ZipEntry entry)
1681 int index = FindExistingUpdate(entry);
1683 contentsEdited_ = true;
1684 updates_[index] = null;
1688 throw new ZipException("Cannot find entry to delete");
1694 #region Update Support
1695 #region Writing Values/Headers
1696 void WriteLEShort(int value)
1698 baseStream_.WriteByte(( byte )(value & 0xff));
1699 baseStream_.WriteByte(( byte )((value >> 8) & 0xff));
1703 /// Write an unsigned short in little endian byte order.
1705 void WriteLEUshort(ushort value)
1707 baseStream_.WriteByte(( byte )(value & 0xff));
1708 baseStream_.WriteByte(( byte )(value >> 8));
1712 /// Write an int in little endian byte order.
1714 void WriteLEInt(int value)
1716 WriteLEShort(value & 0xffff);
1717 WriteLEShort(value >> 16);
1721 /// Write an unsigned int in little endian byte order.
1723 void WriteLEUint(uint value)
1725 WriteLEUshort((ushort)(value & 0xffff));
1726 WriteLEUshort((ushort)(value >> 16));
1730 /// Write a long in little endian byte order.
1732 void WriteLeLong(long value)
1734 WriteLEInt(( int )(value & 0xffffffff));
1735 WriteLEInt(( int )(value >> 32));
1738 void WriteLEUlong(ulong value)
1740 WriteLEUint(( uint )(value & 0xffffffff));
1741 WriteLEUint(( uint )(value >> 32));
1744 void WriteLocalEntryHeader(ZipUpdate update)
1746 ZipEntry entry = update.OutEntry;
1748 // TODO: Local offset will require adjusting for multi-disk zip files.
1749 entry.Offset = baseStream_.Position;
1751 // TODO: Need to clear any entry flags that dont make sense or throw an exception here.
1752 if (update.Command != UpdateCommand.Copy) {
1753 if (entry.CompressionMethod == CompressionMethod.Deflated) {
1754 if (entry.Size == 0) {
1755 // No need to compress - no data.
1756 entry.CompressedSize = entry.Size;
1758 entry.CompressionMethod = CompressionMethod.Stored;
1761 else if (entry.CompressionMethod == CompressionMethod.Stored) {
1762 entry.Flags &= ~(int)GeneralBitFlags.Descriptor;
1766 entry.IsCrypted = true;
1767 if (entry.Crc < 0) {
1768 entry.Flags |= (int)GeneralBitFlags.Descriptor;
1772 entry.IsCrypted = false;
1775 switch (useZip64_) {
1776 case UseZip64.Dynamic:
1777 if (entry.Size < 0) {
1787 // Do nothing. The entry itself may be using Zip64 independantly.
1792 // Write the local file header
1793 WriteLEInt(ZipConstants.LocalHeaderSignature);
1795 WriteLEShort(entry.Version);
1796 WriteLEShort(entry.Flags);
1798 WriteLEShort((byte)entry.CompressionMethod);
1799 WriteLEInt(( int )entry.DosTime);
1801 if ( !entry.HasCrc ) {
1802 // Note patch address for updating CRC later.
1803 update.CrcPatchOffset = baseStream_.Position;
1804 WriteLEInt(( int )0);
1807 WriteLEInt(unchecked(( int )entry.Crc));
1810 if (entry.LocalHeaderRequiresZip64) {
1815 if ( (entry.CompressedSize < 0) || (entry.Size < 0) ) {
1816 update.SizePatchOffset = baseStream_.Position;
1819 WriteLEInt(( int )entry.CompressedSize);
1820 WriteLEInt(( int )entry.Size);
1823 byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
1825 if ( name.Length > 0xFFFF ) {
1826 throw new ZipException("Entry name too long.");
1829 ZipExtraData ed = new ZipExtraData(entry.ExtraData);
1831 if ( entry.LocalHeaderRequiresZip64 ) {
1834 // Local entry header always includes size and compressed size.
1835 // NOTE the order of these fields is reversed when compared to the normal headers!
1836 ed.AddLeLong(entry.Size);
1837 ed.AddLeLong(entry.CompressedSize);
1844 entry.ExtraData = ed.GetEntryData();
1846 WriteLEShort(name.Length);
1847 WriteLEShort(entry.ExtraData.Length);
1849 if ( name.Length > 0 ) {
1850 baseStream_.Write(name, 0, name.Length);
1853 if ( entry.LocalHeaderRequiresZip64 ) {
1854 if ( !ed.Find(1) ) {
1855 throw new ZipException("Internal error cannot find extra data");
1858 update.SizePatchOffset = baseStream_.Position + ed.CurrentReadIndex;
1861 if ( entry.ExtraData.Length > 0 ) {
1862 baseStream_.Write(entry.ExtraData, 0, entry.ExtraData.Length);
1866 int WriteCentralDirectoryHeader(ZipEntry entry)
1868 if ( entry.CompressedSize < 0 ) {
1869 throw new ZipException("Attempt to write central directory entry with unknown csize");
1872 if ( entry.Size < 0 ) {
1873 throw new ZipException("Attempt to write central directory entry with unknown size");
1876 if ( entry.Crc < 0 ) {
1877 throw new ZipException("Attempt to write central directory entry with unknown crc");
1880 // Write the central file header
1881 WriteLEInt(ZipConstants.CentralHeaderSignature);
1884 WriteLEShort(ZipConstants.VersionMadeBy);
1886 // Version required to extract
1887 WriteLEShort(entry.Version);
1889 WriteLEShort(entry.Flags);
1892 WriteLEShort((byte)entry.CompressionMethod);
1893 WriteLEInt((int)entry.DosTime);
1894 WriteLEInt((int)entry.Crc);
1897 if ( (entry.IsZip64Forced()) || (entry.CompressedSize >= 0xffffffff) ) {
1901 WriteLEInt((int)(entry.CompressedSize & 0xffffffff));
1904 if ( (entry.IsZip64Forced()) || (entry.Size >= 0xffffffff) ) {
1908 WriteLEInt((int)entry.Size);
1911 byte[] name = ZipConstants.ConvertToArray(entry.Flags, entry.Name);
1913 if ( name.Length > 0xFFFF ) {
1914 throw new ZipException("Entry name is too long.");
1917 WriteLEShort(name.Length);
1919 // Central header extra data is different to local header version so regenerate.
1920 ZipExtraData ed = new ZipExtraData(entry.ExtraData);
1922 if ( entry.CentralHeaderRequiresZip64 ) {
1925 if ( (entry.Size >= 0xffffffff) || (useZip64_ == UseZip64.On) )
1927 ed.AddLeLong(entry.Size);
1930 if ( (entry.CompressedSize >= 0xffffffff) || (useZip64_ == UseZip64.On) )
1932 ed.AddLeLong(entry.CompressedSize);
1935 if ( entry.Offset >= 0xffffffff ) {
1936 ed.AddLeLong(entry.Offset);
1939 // Number of disk on which this file starts isnt supported and is never written here.
1943 // Should have already be done when local header was added.
1947 byte[] centralExtraData = ed.GetEntryData();
1949 WriteLEShort(centralExtraData.Length);
1950 WriteLEShort(entry.Comment != null ? entry.Comment.Length : 0);
1952 WriteLEShort(0); // disk number
1953 WriteLEShort(0); // internal file attributes
1955 // External file attributes...
1956 if ( entry.ExternalFileAttributes != -1 ) {
1957 WriteLEInt(entry.ExternalFileAttributes);
1960 if ( entry.IsDirectory ) {
1968 if ( entry.Offset >= 0xffffffff ) {
1969 WriteLEUint(0xffffffff);
1972 WriteLEUint((uint)(int)entry.Offset);
1975 if ( name.Length > 0 ) {
1976 baseStream_.Write(name, 0, name.Length);
1979 if ( centralExtraData.Length > 0 ) {
1980 baseStream_.Write(centralExtraData, 0, centralExtraData.Length);
1983 byte[] rawComment = (entry.Comment != null) ? Encoding.UTF8.GetBytes(entry.Comment) : new byte[0];
1985 if ( rawComment.Length > 0 ) {
1986 baseStream_.Write(rawComment, 0, rawComment.Length);
1989 return ZipConstants.CentralHeaderBaseSize + name.Length + centralExtraData.Length + rawComment.Length;
1992 void PostUpdateCleanup()
1994 if( archiveStorage_!=null ) {
1995 archiveStorage_.Dispose();
1996 archiveStorage_=null;
1999 updateDataSource_=null;
2001 updateIndex_ = null;
2004 string GetTransformedFileName(string name)
2006 return (NameTransform != null) ?
2007 NameTransform.TransformFile(name) :
2011 string GetTransformedDirectoryName(string name)
2013 return (NameTransform != null) ?
2014 NameTransform.TransformDirectory(name) :
2019 /// Get a raw memory buffer.
2021 /// <returns>Returns a raw memory buffer.</returns>
2024 if ( copyBuffer_ == null ) {
2025 copyBuffer_ = new byte[bufferSize_];
2030 void CopyDescriptorBytes(ZipUpdate update, Stream dest, Stream source)
2032 int bytesToCopy = GetDescriptorSize(update);
2034 if ( bytesToCopy > 0 ) {
2035 byte[] buffer = GetBuffer();
2037 while ( bytesToCopy > 0 ) {
2038 int readSize = Math.Min(buffer.Length, bytesToCopy);
2040 int bytesRead = source.Read(buffer, 0, readSize);
2041 if ( bytesRead > 0 ) {
2042 dest.Write(buffer, 0, bytesRead);
2043 bytesToCopy -= bytesRead;
2046 throw new ZipException("Unxpected end of stream");
2052 void CopyBytes(ZipUpdate update, Stream destination, Stream source,
2053 long bytesToCopy, bool updateCrc)
2055 if ( destination == source ) {
2056 throw new InvalidOperationException("Destination and source are the same");
2059 // NOTE: Compressed size is updated elsewhere.
2060 Crc32 crc = new Crc32();
2061 byte[] buffer = GetBuffer();
2063 long targetBytes = bytesToCopy;
2064 long totalBytesRead = 0;
2068 int readSize = buffer.Length;
2070 if ( bytesToCopy < readSize ) {
2071 readSize = (int)bytesToCopy;
2074 bytesRead = source.Read(buffer, 0, readSize);
2075 if ( bytesRead > 0 ) {
2077 crc.Update(buffer, 0, bytesRead);
2079 destination.Write(buffer, 0, bytesRead);
2080 bytesToCopy -= bytesRead;
2081 totalBytesRead += bytesRead;
2084 while ( (bytesRead > 0) && (bytesToCopy > 0) );
2086 if ( totalBytesRead != targetBytes ) {
2087 throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead));
2091 update.OutEntry.Crc = crc.Value;
2096 /// Get the size of the source descriptor for a <see cref="ZipUpdate"/>.
2098 /// <param name="update">The update to get the size for.</param>
2099 /// <returns>The descriptor size, zero if there isnt one.</returns>
2100 int GetDescriptorSize(ZipUpdate update)
2103 if ( (update.Entry.Flags & (int)GeneralBitFlags.Descriptor) != 0) {
2104 result = ZipConstants.DataDescriptorSize - 4;
2105 if ( update.Entry.LocalHeaderRequiresZip64 ) {
2106 result = ZipConstants.Zip64DataDescriptorSize - 4;
2112 void CopyDescriptorBytesDirect(ZipUpdate update, Stream stream, ref long destinationPosition, long sourcePosition)
2114 int bytesToCopy = GetDescriptorSize(update);
2116 while ( bytesToCopy > 0 ) {
2117 int readSize = (int)bytesToCopy;
2118 byte[] buffer = GetBuffer();
2120 stream.Position = sourcePosition;
2121 int bytesRead = stream.Read(buffer, 0, readSize);
2122 if ( bytesRead > 0 ) {
2123 stream.Position = destinationPosition;
2124 stream.Write(buffer, 0, bytesRead);
2125 bytesToCopy -= bytesRead;
2126 destinationPosition += bytesRead;
2127 sourcePosition += bytesRead;
2130 throw new ZipException("Unxpected end of stream");
2135 void CopyEntryDataDirect(ZipUpdate update, Stream stream, bool updateCrc, ref long destinationPosition, ref long sourcePosition)
2137 long bytesToCopy = update.Entry.CompressedSize;
2139 // NOTE: Compressed size is updated elsewhere.
2140 Crc32 crc = new Crc32();
2141 byte[] buffer = GetBuffer();
2143 long targetBytes = bytesToCopy;
2144 long totalBytesRead = 0;
2149 int readSize = buffer.Length;
2151 if ( bytesToCopy < readSize ) {
2152 readSize = (int)bytesToCopy;
2155 stream.Position = sourcePosition;
2156 bytesRead = stream.Read(buffer, 0, readSize);
2157 if ( bytesRead > 0 ) {
2159 crc.Update(buffer, 0, bytesRead);
2161 stream.Position = destinationPosition;
2162 stream.Write(buffer, 0, bytesRead);
2164 destinationPosition += bytesRead;
2165 sourcePosition += bytesRead;
2166 bytesToCopy -= bytesRead;
2167 totalBytesRead += bytesRead;
2170 while ( (bytesRead > 0) && (bytesToCopy > 0) );
2172 if ( totalBytesRead != targetBytes ) {
2173 throw new ZipException(string.Format("Failed to copy bytes expected {0} read {1}", targetBytes, totalBytesRead));
2177 update.OutEntry.Crc = crc.Value;
2181 int FindExistingUpdate(ZipEntry entry)
2184 string convertedName = GetTransformedFileName(entry.Name);
2186 if (updateIndex_.ContainsKey(convertedName)) {
2187 result = (int)updateIndex_[convertedName];
2190 // This is slow like the coming of the next ice age but takes less storage and may be useful
2192 for (int index = 0; index < updates_.Count; ++index)
2194 ZipUpdate zu = ( ZipUpdate )updates_[index];
2195 if ( (zu.Entry.ZipFileIndex == entry.ZipFileIndex) &&
2196 (string.Compare(convertedName, zu.Entry.Name, true, CultureInfo.InvariantCulture) == 0) ) {
2205 int FindExistingUpdate(string fileName)
2209 string convertedName = GetTransformedFileName(fileName);
2211 if (updateIndex_.ContainsKey(convertedName)) {
2212 result = (int)updateIndex_[convertedName];
2216 // This is slow like the coming of the next ice age but takes less storage and may be useful
2218 for ( int index = 0; index < updates_.Count; ++index ) {
2219 if ( string.Compare(convertedName, (( ZipUpdate )updates_[index]).Entry.Name,
2220 true, CultureInfo.InvariantCulture) == 0 ) {
2231 /// Get an output stream for the specified <see cref="ZipEntry"/>
2233 /// <param name="entry">The entry to get an output stream for.</param>
2234 /// <returns>The output stream obtained for the entry.</returns>
2235 Stream GetOutputStream(ZipEntry entry)
2237 Stream result = baseStream_;
2239 if ( entry.IsCrypted == true ) {
2241 throw new ZipException("Encryption not supported for Compact Framework 1.0");
2243 result = CreateAndInitEncryptionStream(result, entry);
2247 switch ( entry.CompressionMethod ) {
2248 case CompressionMethod.Stored:
2249 result = new UncompressedStream(result);
2252 case CompressionMethod.Deflated:
2253 DeflaterOutputStream dos = new DeflaterOutputStream(result, new Deflater(9, true));
2254 dos.IsStreamOwner = false;
2259 throw new ZipException("Unknown compression method " + entry.CompressionMethod);
2264 void AddEntry(ZipFile workFile, ZipUpdate update)
2266 Stream source = null;
2268 if ( update.Entry.IsFile ) {
2269 source = update.GetSource();
2271 if ( source == null ) {
2272 source = updateDataSource_.GetSource(update.Entry, update.Filename);
2276 if ( source != null ) {
2278 long sourceStreamLength = source.Length;
2279 if ( update.OutEntry.Size < 0 ) {
2280 update.OutEntry.Size = sourceStreamLength;
2283 // Check for errant entries.
2284 if ( update.OutEntry.Size != sourceStreamLength ) {
2285 throw new ZipException("Entry size/stream size mismatch");
2289 workFile.WriteLocalEntryHeader(update);
2291 long dataStart = workFile.baseStream_.Position;
2293 using ( Stream output = workFile.GetOutputStream(update.OutEntry) ) {
2294 CopyBytes(update, output, source, sourceStreamLength, true);
2297 long dataEnd = workFile.baseStream_.Position;
2298 update.OutEntry.CompressedSize = dataEnd - dataStart;
2300 if ((update.OutEntry.Flags & (int)GeneralBitFlags.Descriptor) == (int)GeneralBitFlags.Descriptor)
2302 ZipHelperStream helper = new ZipHelperStream(workFile.baseStream_);
2303 helper.WriteDataDescriptor(update.OutEntry);
2308 workFile.WriteLocalEntryHeader(update);
2309 update.OutEntry.CompressedSize = 0;
2314 void ModifyEntry(ZipFile workFile, ZipUpdate update)
2316 workFile.WriteLocalEntryHeader(update);
2317 long dataStart = workFile.baseStream_.Position;
2319 // TODO: This is slow if the changes don't effect the data!!
2320 if ( update.Entry.IsFile && (update.Filename != null) ) {
2321 using ( Stream output = workFile.GetOutputStream(update.OutEntry) ) {
2322 using ( Stream source = this.GetInputStream(update.Entry) ) {
2323 CopyBytes(update, output, source, source.Length, true);
2328 long dataEnd = workFile.baseStream_.Position;
2329 update.Entry.CompressedSize = dataEnd - dataStart;
2332 void CopyEntryDirect(ZipFile workFile, ZipUpdate update, ref long destinationPosition)
2334 bool skipOver = false;
2335 if ( update.Entry.Offset == destinationPosition ) {
2340 baseStream_.Position = destinationPosition;
2341 workFile.WriteLocalEntryHeader(update);
2342 destinationPosition = baseStream_.Position;
2345 long sourcePosition = 0;
2347 const int NameLengthOffset = 26;
2349 // TODO: Add base for SFX friendly handling
2350 long entryDataOffset = update.Entry.Offset + NameLengthOffset;
2352 baseStream_.Seek(entryDataOffset, SeekOrigin.Begin);
2354 // Clumsy way of handling retrieving the original name and extra data length for now.
2355 // TODO: Stop re-reading name and data length in CopyEntryDirect.
2356 uint nameLength = ReadLEUshort();
2357 uint extraLength = ReadLEUshort();
2359 sourcePosition = baseStream_.Position + nameLength + extraLength;
2362 destinationPosition +=
2363 (sourcePosition - entryDataOffset) + NameLengthOffset + // Header size
2364 update.Entry.CompressedSize + GetDescriptorSize(update);
2367 if ( update.Entry.CompressedSize > 0 ) {
2368 CopyEntryDataDirect(update, baseStream_, false, ref destinationPosition, ref sourcePosition );
2370 CopyDescriptorBytesDirect(update, baseStream_, ref destinationPosition, sourcePosition);
2374 void CopyEntry(ZipFile workFile, ZipUpdate update)
2376 workFile.WriteLocalEntryHeader(update);
2378 if ( update.Entry.CompressedSize > 0 ) {
2379 const int NameLengthOffset = 26;
2381 long entryDataOffset = update.Entry.Offset + NameLengthOffset;
2383 // TODO: This wont work for SFX files!
2384 baseStream_.Seek(entryDataOffset, SeekOrigin.Begin);
2386 uint nameLength = ReadLEUshort();
2387 uint extraLength = ReadLEUshort();
2389 baseStream_.Seek(nameLength + extraLength, SeekOrigin.Current);
2391 CopyBytes(update, workFile.baseStream_, baseStream_, update.Entry.CompressedSize, false);
2393 CopyDescriptorBytes(update, workFile.baseStream_, baseStream_);
2396 void Reopen(Stream source)
2398 if ( source == null ) {
2399 throw new ZipException("Failed to reopen archive - no source");
2402 isNewArchive_ = false;
2403 baseStream_ = source;
2410 throw new InvalidOperationException("Name is not known cannot Reopen");
2413 Reopen(File.OpenRead(Name));
2416 void UpdateCommentOnly()
2418 long baseLength = baseStream_.Length;
2420 ZipHelperStream updateFile = null;
2422 if ( archiveStorage_.UpdateMode == FileUpdateMode.Safe ) {
2423 Stream copyStream = archiveStorage_.MakeTemporaryCopy(baseStream_);
2424 updateFile = new ZipHelperStream(copyStream);
2425 updateFile.IsStreamOwner = true;
2427 baseStream_.Close();
2431 if (archiveStorage_.UpdateMode == FileUpdateMode.Direct) {
2432 // TODO: archiveStorage wasnt originally intended for this use.
2433 // Need to revisit this to tidy up handling as archive storage currently doesnt
2434 // handle the original stream well.
2435 // The problem is when using an existing zip archive with an in memory archive storage.
2436 // The open stream wont support writing but the memory storage should open the same file not an in memory one.
2438 // Need to tidy up the archive storage interface and contract basically.
2439 baseStream_ = archiveStorage_.OpenForDirectUpdate(baseStream_);
2440 updateFile = new ZipHelperStream(baseStream_);
2443 baseStream_.Close();
2445 updateFile = new ZipHelperStream(Name);
2449 using ( updateFile ) {
2450 long locatedCentralDirOffset =
2451 updateFile.LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
2452 baseLength, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
2453 if ( locatedCentralDirOffset < 0 ) {
2454 throw new ZipException("Cannot find central directory");
2457 const int CentralHeaderCommentSizeOffset = 16;
2458 updateFile.Position += CentralHeaderCommentSizeOffset;
2460 byte[] rawComment = newComment_.RawComment;
2462 updateFile.WriteLEShort(rawComment.Length);
2463 updateFile.Write(rawComment, 0, rawComment.Length);
2464 updateFile.SetLength(updateFile.Position);
2467 if ( archiveStorage_.UpdateMode == FileUpdateMode.Safe ) {
2468 Reopen(archiveStorage_.ConvertTemporaryToFinal());
2476 /// Class used to sort updates.
2478 class UpdateComparer : IComparer<ZipUpdate>
2481 /// Compares two objects and returns a value indicating whether one is
2482 /// less than, equal to or greater than the other.
2484 /// <param name="x">First object to compare</param>
2485 /// <param name="y">Second object to compare.</param>
2486 /// <returns>Compare result.</returns>
2491 ZipUpdate zx = x as ZipUpdate;
2492 ZipUpdate zy = y as ZipUpdate;
2504 else if (zy == null) {
2508 int xCmdValue = ((zx.Command == UpdateCommand.Copy) || (zx.Command == UpdateCommand.Modify)) ? 0 : 1;
2509 int yCmdValue = ((zy.Command == UpdateCommand.Copy) || (zy.Command == UpdateCommand.Modify)) ? 0 : 1;
2511 result = xCmdValue - yCmdValue;
2513 long offsetDiff = zx.Entry.Offset - zy.Entry.Offset;
2514 if (offsetDiff < 0) {
2517 else if (offsetDiff == 0) {
2531 long sizeEntries = 0;
2532 long endOfStream = 0;
2534 bool directUpdate = false;
2535 long destinationPosition = 0; // NOT SFX friendly
2539 if ( IsNewArchive ) {
2541 workFile.baseStream_.Position = 0;
2542 directUpdate = true;
2544 else if ( archiveStorage_.UpdateMode == FileUpdateMode.Direct ) {
2546 workFile.baseStream_.Position = 0;
2547 directUpdate = true;
2549 // Sort the updates by offset within copies/modifies, then adds.
2550 // This ensures that data required by copies will not be overwritten.
2551 updates_.Sort(new UpdateComparer());
2554 workFile = ZipFile.Create(archiveStorage_.GetTemporaryOutput());
2555 workFile.UseZip64 = UseZip64;
2558 workFile.key = (byte[])key.Clone();
2563 foreach ( ZipUpdate update in updates_ ) {
2564 if (update != null) {
2565 switch (update.Command) {
2566 case UpdateCommand.Copy:
2568 CopyEntryDirect(workFile, update, ref destinationPosition);
2571 CopyEntry(workFile, update);
2575 case UpdateCommand.Modify:
2576 // TODO: Direct modifying of an entry will take some legwork.
2577 ModifyEntry(workFile, update);
2580 case UpdateCommand.Add:
2581 if (!IsNewArchive && directUpdate) {
2582 workFile.baseStream_.Position = destinationPosition;
2585 AddEntry(workFile, update);
2588 destinationPosition = workFile.baseStream_.Position;
2595 if ( !IsNewArchive && directUpdate ) {
2596 workFile.baseStream_.Position = destinationPosition;
2599 long centralDirOffset = workFile.baseStream_.Position;
2601 foreach ( ZipUpdate update in updates_ ) {
2602 if (update != null) {
2603 sizeEntries += workFile.WriteCentralDirectoryHeader(update.OutEntry);
2607 byte[] theComment = (newComment_ != null) ? newComment_.RawComment : ZipConstants.ConvertToArray(comment_);
2608 using ( ZipHelperStream zhs = new ZipHelperStream(workFile.baseStream_) ) {
2609 zhs.WriteEndOfCentralDirectory(updateCount_, sizeEntries, centralDirOffset, theComment);
2612 endOfStream = workFile.baseStream_.Position;
2614 // And now patch entries...
2615 foreach ( ZipUpdate update in updates_ ) {
2619 // If the size of the entry is zero leave the crc as 0 as well.
2620 // The calculated crc will be all bits on...
2621 if ((update.CrcPatchOffset > 0) && (update.OutEntry.CompressedSize > 0)) {
2622 workFile.baseStream_.Position = update.CrcPatchOffset;
2623 workFile.WriteLEInt((int)update.OutEntry.Crc);
2626 if (update.SizePatchOffset > 0) {
2627 workFile.baseStream_.Position = update.SizePatchOffset;
2628 if (update.OutEntry.LocalHeaderRequiresZip64) {
2629 workFile.WriteLeLong(update.OutEntry.Size);
2630 workFile.WriteLeLong(update.OutEntry.CompressedSize);
2633 workFile.WriteLEInt((int)update.OutEntry.CompressedSize);
2634 workFile.WriteLEInt((int)update.OutEntry.Size);
2644 if ( directUpdate ) {
2646 workFile.baseStream_.Flush();
2647 workFile.baseStream_.SetLength(endOfStream);
2656 if ( directUpdate ) {
2657 isNewArchive_ = false;
2658 workFile.baseStream_.Flush();
2662 baseStream_.Close();
2663 Reopen(archiveStorage_.ConvertTemporaryToFinal());
2668 if ( !directUpdate && (workFile.Name != null) ) {
2669 File.Delete(workFile.Name);
2674 void CheckUpdating()
2676 if ( updates_ == null ) {
2677 throw new ZipException("Cannot update until BeginUpdate has been called");
2683 #region ZipUpdate class
2685 /// Represents a pending update to a Zip file.
2689 #region Constructors
2690 public ZipUpdate(string fileName, ZipEntry entry)
2692 command_ = UpdateCommand.Add;
2694 filename_ = fileName;
2698 public ZipUpdate(string fileName, string entryName, CompressionMethod compressionMethod)
2700 command_ = UpdateCommand.Add;
2701 entry_ = new ZipEntry(entryName);
2702 entry_.CompressionMethod = compressionMethod;
2703 filename_ = fileName;
2707 public ZipUpdate(string fileName, string entryName)
2708 : this(fileName, entryName, CompressionMethod.Deflated)
2714 public ZipUpdate(IStaticDataSource dataSource, string entryName, CompressionMethod compressionMethod)
2716 command_ = UpdateCommand.Add;
2717 entry_ = new ZipEntry(entryName);
2718 entry_.CompressionMethod = compressionMethod;
2719 dataSource_ = dataSource;
2722 public ZipUpdate(IStaticDataSource dataSource, ZipEntry entry)
2724 command_ = UpdateCommand.Add;
2726 dataSource_ = dataSource;
2729 public ZipUpdate(ZipEntry original, ZipEntry updated)
2731 throw new ZipException("Modify not currently supported");
2733 command_ = UpdateCommand.Modify;
2734 entry_ = ( ZipEntry )original.Clone();
2735 outEntry_ = ( ZipEntry )updated.Clone();
2739 public ZipUpdate(UpdateCommand command, ZipEntry entry)
2742 entry_ = ( ZipEntry )entry.Clone();
2747 /// Copy an existing entry.
2749 /// <param name="entry">The existing entry to copy.</param>
2750 public ZipUpdate(ZipEntry entry)
2751 : this(UpdateCommand.Copy, entry)
2758 /// Get the <see cref="ZipEntry"/> for this update.
2760 /// <remarks>This is the source or original entry.</remarks>
2761 public ZipEntry Entry
2763 get { return entry_; }
2767 /// Get the <see cref="ZipEntry"/> that will be written to the updated/new file.
2769 public ZipEntry OutEntry
2772 if ( outEntry_ == null ) {
2773 outEntry_ = (ZipEntry)entry_.Clone();
2781 /// Get the command for this update.
2783 public UpdateCommand Command
2785 get { return command_; }
2789 /// Get the filename if any for this update. Null if none exists.
2791 public string Filename
2793 get { return filename_; }
2797 /// Get/set the location of the size patch for this update.
2799 public long SizePatchOffset
2801 get { return sizePatchOffset_; }
2802 set { sizePatchOffset_ = value; }
2806 /// Get /set the location of the crc patch for this update.
2808 public long CrcPatchOffset
2810 get { return crcPatchOffset_; }
2811 set { crcPatchOffset_ = value; }
2814 public Stream GetSource()
2816 Stream result = null;
2817 if ( dataSource_ != null ) {
2818 result = dataSource_.GetSource();
2824 #region Instance Fields
2827 UpdateCommand command_;
2828 IStaticDataSource dataSource_;
2830 long sizePatchOffset_ = -1;
2831 long crcPatchOffset_ = -1;
2840 #region IDisposable Members
2841 void IDisposable.Dispose()
2847 void DisposeInternal(bool disposing)
2849 if ( !isDisposed_ ) {
2852 if ( IsStreamOwner && (baseStream_ != null) ) {
2854 baseStream_.Close();
2861 /// Releases the unmanaged resources used by the this instance and optionally releases the managed resources.
2863 /// <param name="disposing">true to release both managed and unmanaged resources;
2864 /// false to release only unmanaged resources.</param>
2865 protected virtual void Dispose(bool disposing)
2867 DisposeInternal(disposing);
2872 #region Internal routines
2875 /// Read an unsigned short in little endian byte order.
2877 /// <returns>Returns the value read.</returns>
2878 /// <exception cref="EndOfStreamException">
2879 /// The stream ends prematurely
2881 ushort ReadLEUshort()
2883 int data1 = baseStream_.ReadByte();
2886 throw new EndOfStreamException("End of stream");
2889 int data2 = baseStream_.ReadByte();
2892 throw new EndOfStreamException("End of stream");
2896 return unchecked((ushort)((ushort)data1 | (ushort)(data2 << 8)));
2900 /// Read a uint in little endian byte order.
2902 /// <returns>Returns the value read.</returns>
2903 /// <exception cref="IOException">
2904 /// An i/o error occurs.
2906 /// <exception cref="System.IO.EndOfStreamException">
2907 /// The file ends prematurely
2911 return (uint)(ReadLEUshort() | (ReadLEUshort() << 16));
2916 return ReadLEUint() | ((ulong)ReadLEUint() << 32);
2920 // NOTE this returns the offset of the first byte after the signature.
2921 long LocateBlockWithSignature(int signature, long endLocation, int minimumBlockSize, int maximumVariableData)
2923 using ( ZipHelperStream les = new ZipHelperStream(baseStream_) ) {
2924 return les.LocateBlockWithSignature(signature, endLocation, minimumBlockSize, maximumVariableData);
2929 /// Search for and read the central directory of a zip file filling the entries array.
2931 /// <exception cref="System.IO.IOException">
2932 /// An i/o error occurs.
2934 /// <exception cref="ZipException">
2935 /// The central directory is malformed or cannot be found
2939 // Search for the End Of Central Directory. When a zip comment is
2940 // present the directory will start earlier
2942 // The search is limited to 64K which is the maximum size of a trailing comment field to aid speed.
2943 // This should be compatible with both SFX and ZIP files but has only been tested for Zip files
2944 // If a SFX file has the Zip data attached as a resource and there are other resources occuring later then
2945 // this could be invalid.
2946 // Could also speed this up by reading memory in larger blocks.
2948 if (baseStream_.CanSeek == false) {
2949 throw new ZipException("ZipFile stream must be seekable");
2952 long locatedEndOfCentralDir = LocateBlockWithSignature(ZipConstants.EndOfCentralDirectorySignature,
2953 baseStream_.Length, ZipConstants.EndOfCentralRecordBaseSize, 0xffff);
2955 if (locatedEndOfCentralDir < 0) {
2956 throw new ZipException("Cannot find central directory");
2959 // Read end of central directory record
2960 ushort thisDiskNumber = ReadLEUshort();
2961 ushort startCentralDirDisk = ReadLEUshort();
2962 ulong entriesForThisDisk = ReadLEUshort();
2963 ulong entriesForWholeCentralDir = ReadLEUshort();
2964 ulong centralDirSize = ReadLEUint();
2965 long offsetOfCentralDir = ReadLEUint();
2966 uint commentSize = ReadLEUshort();
2968 if ( commentSize > 0 ) {
2969 byte[] comment = new byte[commentSize];
2971 StreamUtils.ReadFully(baseStream_, comment);
2972 comment_ = ZipConstants.ConvertToString(comment);
2975 comment_ = string.Empty;
2978 bool isZip64 = false;
2980 // Check if zip64 header information is required.
2981 if ( (thisDiskNumber == 0xffff) ||
2982 (startCentralDirDisk == 0xffff) ||
2983 (entriesForThisDisk == 0xffff) ||
2984 (entriesForWholeCentralDir == 0xffff) ||
2985 (centralDirSize == 0xffffffff) ||
2986 (offsetOfCentralDir == 0xffffffff) ) {
2989 long offset = LocateBlockWithSignature(ZipConstants.Zip64CentralDirLocatorSignature, locatedEndOfCentralDir, 0, 0x1000);
2991 throw new ZipException("Cannot find Zip64 locator");
2994 // number of the disk with the start of the zip64 end of central directory 4 bytes
2995 // relative offset of the zip64 end of central directory record 8 bytes
2996 // total number of disks 4 bytes
2997 ReadLEUint(); // startDisk64 is not currently used
2998 ulong offset64 = ReadLEUlong();
2999 uint totalDisks = ReadLEUint();
3001 baseStream_.Position = (long)offset64;
3002 long sig64 = ReadLEUint();
3004 if ( sig64 != ZipConstants.Zip64CentralFileHeaderSignature ) {
3005 throw new ZipException(string.Format("Invalid Zip64 Central directory signature at {0:X}", offset64));
3008 // NOTE: Record size = SizeOfFixedFields + SizeOfVariableData - 12.
3009 ulong recordSize = ( ulong )ReadLEUlong();
3010 int versionMadeBy = ReadLEUshort();
3011 int versionToExtract = ReadLEUshort();
3012 uint thisDisk = ReadLEUint();
3013 uint centralDirDisk = ReadLEUint();
3014 entriesForThisDisk = ReadLEUlong();
3015 entriesForWholeCentralDir = ReadLEUlong();
3016 centralDirSize = ReadLEUlong();
3017 offsetOfCentralDir = (long)ReadLEUlong();
3019 // NOTE: zip64 extensible data sector (variable size) is ignored.
3022 entries_ = new ZipEntry[entriesForThisDisk];
3024 // SFX/embedded support, find the offset of the first entry vis the start of the stream
3025 // This applies to Zip files that are appended to the end of an SFX stub.
3026 // Or are appended as a resource to an executable.
3027 // Zip files created by some archivers have the offsets altered to reflect the true offsets
3028 // and so dont require any adjustment here...
3029 // TODO: Difficulty with Zip64 and SFX offset handling needs resolution - maths?
3030 if ( !isZip64 && (offsetOfCentralDir < locatedEndOfCentralDir - (4 + (long)centralDirSize)) ) {
3031 offsetOfFirstEntry = locatedEndOfCentralDir - (4 + (long)centralDirSize + offsetOfCentralDir);
3032 if (offsetOfFirstEntry <= 0) {
3033 throw new ZipException("Invalid embedded zip archive");
3037 baseStream_.Seek(offsetOfFirstEntry + offsetOfCentralDir, SeekOrigin.Begin);
3039 for (ulong i = 0; i < entriesForThisDisk; i++) {
3040 if (ReadLEUint() != ZipConstants.CentralHeaderSignature) {
3041 throw new ZipException("Wrong Central Directory signature");
3044 int versionMadeBy = ReadLEUshort();
3045 int versionToExtract = ReadLEUshort();
3046 int bitFlags = ReadLEUshort();
3047 int method = ReadLEUshort();
3048 uint dostime = ReadLEUint();
3049 uint crc = ReadLEUint();
3050 long csize = (long)ReadLEUint();
3051 long size = (long)ReadLEUint();
3052 int nameLen = ReadLEUshort();
3053 int extraLen = ReadLEUshort();
3054 int commentLen = ReadLEUshort();
3056 int diskStartNo = ReadLEUshort(); // Not currently used
3057 int internalAttributes = ReadLEUshort(); // Not currently used
3059 uint externalAttributes = ReadLEUint();
3060 long offset = ReadLEUint();
3062 byte[] buffer = new byte[Math.Max(nameLen, commentLen)];
3064 StreamUtils.ReadFully(baseStream_, buffer, 0, nameLen);
3065 string name = ZipConstants.ConvertToStringExt(bitFlags, buffer, nameLen);
3067 ZipEntry entry = new ZipEntry(name, versionToExtract, versionMadeBy, (CompressionMethod)method);
3068 entry.Crc = crc & 0xffffffffL;
3069 entry.Size = size & 0xffffffffL;
3070 entry.CompressedSize = csize & 0xffffffffL;
3071 entry.Flags = bitFlags;
3072 entry.DosTime = (uint)dostime;
3073 entry.ZipFileIndex = (long)i;
3074 entry.Offset = offset;
3075 entry.ExternalFileAttributes = (int)externalAttributes;
3077 if ((bitFlags & 8) == 0) {
3078 entry.CryptoCheckValue = (byte)(crc >> 24);
3081 entry.CryptoCheckValue = (byte)((dostime >> 8) & 0xff);
3085 byte[] extra = new byte[extraLen];
3086 StreamUtils.ReadFully(baseStream_, extra);
3087 entry.ExtraData = extra;
3090 entry.ProcessExtraData(false);
3092 if (commentLen > 0) {
3093 StreamUtils.ReadFully(baseStream_, buffer, 0, commentLen);
3094 entry.Comment = ZipConstants.ConvertToStringExt(bitFlags, buffer, commentLen);
3097 entries_[i] = entry;
3102 /// Locate the data for a given entry.
3105 /// The start offset of the data.
3107 /// <exception cref="System.IO.EndOfStreamException">
3108 /// The stream ends prematurely
3110 /// <exception cref="ZipException">
3111 /// The local header signature is invalid, the entry and central header file name lengths are different
3112 /// or the local and entry compression methods dont match
3114 long LocateEntry(ZipEntry entry)
3116 return TestLocalHeader(entry, HeaderTest.Extract);
3120 Stream CreateAndInitDecryptionStream(Stream baseStream, ZipEntry entry)
3122 CryptoStream result = null;
3124 if ( (entry.Version < ZipConstants.VersionStrongEncryption)
3125 || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) {
3126 PkzipClassicManaged classicManaged = new PkzipClassicManaged();
3128 OnKeysRequired(entry.Name);
3129 if (HaveKeys == false) {
3130 throw new ZipException("No password available for encrypted stream");
3133 result = new CryptoStream(baseStream, classicManaged.CreateDecryptor(key, null), CryptoStreamMode.Read);
3134 CheckClassicPassword(result, entry);
3137 throw new ZipException("Decryption method not supported");
3143 Stream CreateAndInitEncryptionStream(Stream baseStream, ZipEntry entry)
3145 CryptoStream result = null;
3146 if ( (entry.Version < ZipConstants.VersionStrongEncryption)
3147 || (entry.Flags & (int)GeneralBitFlags.StrongEncryption) == 0) {
3148 PkzipClassicManaged classicManaged = new PkzipClassicManaged();
3150 OnKeysRequired(entry.Name);
3151 if (HaveKeys == false) {
3152 throw new ZipException("No password available for encrypted stream");
3155 // Closing a CryptoStream will close the base stream as well so wrap it in an UncompressedStream
3156 // which doesnt do this.
3157 result = new CryptoStream(new UncompressedStream(baseStream),
3158 classicManaged.CreateEncryptor(key, null), CryptoStreamMode.Write);
3160 if ( (entry.Crc < 0) || (entry.Flags & 8) != 0) {
3161 WriteEncryptionHeader(result, entry.DosTime << 16);
3164 WriteEncryptionHeader(result, entry.Crc);
3170 static void CheckClassicPassword(CryptoStream classicCryptoStream, ZipEntry entry)
3172 byte[] cryptbuffer = new byte[ZipConstants.CryptoHeaderSize];
3173 StreamUtils.ReadFully(classicCryptoStream, cryptbuffer);
3174 if (cryptbuffer[ZipConstants.CryptoHeaderSize - 1] != entry.CryptoCheckValue) {
3175 throw new ZipException("Invalid password");
3180 static void WriteEncryptionHeader(Stream stream, long crcValue)
3182 byte[] cryptBuffer = new byte[ZipConstants.CryptoHeaderSize];
3183 Random rnd = new Random();
3184 rnd.NextBytes(cryptBuffer);
3185 cryptBuffer[11] = (byte)(crcValue >> 24);
3186 stream.Write(cryptBuffer, 0, cryptBuffer.Length);
3191 #region Instance Fields
3197 long offsetOfFirstEntry;
3198 ZipEntry[] entries_;
3202 // Default is dynamic which is not backwards compatible and can cause problems
3203 // with XP's built in compression which cant read Zip64 archives.
3204 // However it does avoid the situation were a large file is added and cannot be completed correctly.
3205 UseZip64 useZip64_ = UseZip64.Dynamic ;
3207 #region Zip Update Instance Fields
3208 List<ZipUpdate> updates_;
3209 long updateCount_; // Count is managed manually as updates_ can contain nulls!
3210 Dictionary<string, int> updateIndex_;
3211 IArchiveStorage archiveStorage_;
3212 IDynamicDataSource updateDataSource_;
3213 bool contentsEdited_;
3214 int bufferSize_ = DefaultBufferSize;
3216 ZipString newComment_;
3217 bool commentEdited_;
3218 IEntryFactory updateEntryFactory_ = new ZipEntryFactory();
3219 string tempDirectory_ = string.Empty;
3223 #region Support Classes
3225 /// Represents a string from a <see cref="ZipFile"/> which is stored as an array of bytes.
3229 #region Constructors
3231 /// Initialise a <see cref="ZipString"/> with a string.
3233 /// <param name="comment">The textual string form.</param>
3234 public ZipString(string comment)
3237 isSourceString_ = true;
3241 /// Initialise a <see cref="ZipString"/> using a string in its binary 'raw' form.
3243 /// <param name="rawString"></param>
3244 public ZipString(byte[] rawString)
3246 rawComment_ = rawString;
3251 /// Get a value indicating the original source of data for this instance.
3252 /// True if the source was a string; false if the source was binary data.
3254 public bool IsSourceString
3256 get { return isSourceString_; }
3260 /// Get the length of the comment when represented as raw bytes.
3262 public int RawLength
3265 MakeBytesAvailable();
3266 return rawComment_.Length;
3271 /// Get the comment in its 'raw' form as plain bytes.
3273 public byte[] RawComment
3276 MakeBytesAvailable();
3277 return (byte[])rawComment_.Clone();
3282 /// Reset the comment to its initial state.
3286 if ( isSourceString_ ) {
3294 void MakeTextAvailable()
3296 if ( comment_ == null ) {
3297 comment_ = ZipConstants.ConvertToString(rawComment_);
3301 void MakeBytesAvailable()
3303 if ( rawComment_ == null ) {
3304 rawComment_ = ZipConstants.ConvertToArray(comment_);
3309 /// Implicit conversion of comment to a string.
3311 /// <param name="zipString">The <see cref="ZipString"/> to convert to a string.</param>
3312 /// <returns>The textual equivalent for the input value.</returns>
3313 static public implicit operator string(ZipString zipString)
3315 zipString.MakeTextAvailable();
3316 return zipString.comment_;
3319 #region Instance Fields
3322 bool isSourceString_;
3327 /// An <see cref="IEnumerator">enumerator</see> for <see cref="ZipEntry">Zip entries</see>
3329 class ZipEntryEnumerator : IEnumerator
3331 #region Constructors
3332 public ZipEntryEnumerator(ZipEntry[] entries)
3338 #region IEnumerator Members
3339 public object Current
3342 return array[index];
3351 public bool MoveNext()
3353 return (++index < array.Length);
3356 #region Instance Fields
3363 /// An <see cref="UncompressedStream"/> is a stream that you can write uncompressed data
3364 /// to and flush, but cannot read, seek or do anything else to.
3366 class UncompressedStream : Stream
3368 #region Constructors
3369 public UncompressedStream(Stream baseStream)
3371 baseStream_ = baseStream;
3377 /// Close this stream instance.
3379 public override void Close()
3385 /// Gets a value indicating whether the current stream supports reading.
3387 public override bool CanRead
3395 /// Write any buffered data to underlying storage.
3397 public override void Flush()
3399 baseStream_.Flush();
3403 /// Gets a value indicating whether the current stream supports writing.
3405 public override bool CanWrite
3408 return baseStream_.CanWrite;
3413 /// Gets a value indicating whether the current stream supports seeking.
3415 public override bool CanSeek
3423 /// Get the length in bytes of the stream.
3425 public override long Length
3433 /// Gets or sets the position within the current stream.
3435 public override long Position
3438 return baseStream_.Position;
3446 public override int Read(byte[] buffer, int offset, int count)
3451 public override long Seek(long offset, SeekOrigin origin)
3456 public override void SetLength(long value)
3460 public override void Write(byte[] buffer, int offset, int count)
3462 baseStream_.Write(buffer, offset, count);
3465 #region Instance Fields
3471 /// A <see cref="PartialInputStream"/> is an <see cref="InflaterInputStream"/>
3472 /// whose data is only a part or subsection of a file.
3474 class PartialInputStream : InflaterInputStream
3476 #region Constructors
3478 /// Initialise a new instance of the <see cref="PartialInputStream"/> class.
3480 /// <param name="baseStream">The underlying stream to use for IO.</param>
3481 /// <param name="start">The start of the partial data.</param>
3482 /// <param name="length">The length of the partial data.</param>
3483 public PartialInputStream(Stream baseStream, long start, long length)
3486 baseStream_ = baseStream;
3488 end_ = start + length;
3494 /// Skip the specified number of input bytes.
3496 /// <param name="count">The maximum number of input bytes to skip.</param>
3497 /// <returns>The actuial number of input bytes skipped.</returns>
3498 public long SkipBytes(long count)
3502 throw new ArgumentOutOfRangeException("count");
3504 throw new ArgumentOutOfRangeException("count", "is less than zero");
3508 if (count > end_ - filepos_) {
3509 count = end_ - filepos_;
3516 public override int Available
3519 long amount = end_ - filepos_;
3520 if (amount > int.MaxValue) {
3521 return int.MaxValue;
3524 return (int) amount;
3529 /// Read a byte from this stream.
3531 /// <returns>Returns the byte read or -1 on end of stream.</returns>
3532 public override int ReadByte()
3534 if (filepos_ == end_) {
3535 // -1 is the correct value at end of stream.
3539 lock( baseStream_ ) {
3540 baseStream_.Seek(filepos_++, SeekOrigin.Begin);
3541 return baseStream_.ReadByte();
3546 /// Close this <see cref="PartialInputStream">partial input stream</see>.
3549 /// The underlying stream is not closed. Close the parent ZipFile class to do that.
3551 public override void Close()
3553 // Do nothing at all!
3556 public override int Read(byte[] buffer, int offset, int count)
3558 if (count > end_ - filepos_) {
3559 count = (int) (end_ - filepos_);
3566 baseStream_.Seek(filepos_, SeekOrigin.Begin);
3567 int readCount = baseStream_.Read(buffer, offset, count);
3568 if (readCount > 0) {
3569 filepos_ += readCount;
3575 #region Instance Fields
3585 /// Provides a static way to obtain a source of data for an entry.
3587 public interface IStaticDataSource
3590 /// Get a source of data by creating a new stream.
3592 /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns>
3593 /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks>
3598 /// Represents a source of data that can dynamically provide
3599 /// multiple <see cref="Stream">data sources</see> based on the parameters passed.
3601 public interface IDynamicDataSource
3604 /// Get a data source.
3606 /// <param name="entry">The <see cref="ZipEntry"/> to get a source for.</param>
3607 /// <param name="name">The name for data if known.</param>
3608 /// <returns>Returns a <see cref="Stream"/> to use for compression input.</returns>
3609 /// <remarks>Ideally a new stream is created and opened to achieve this, to avoid locking problems.</remarks>
3610 Stream GetSource(ZipEntry entry, string name);
3614 /// Default implementation of a <see cref="IStaticDataSource"/> for use with files stored on disk.
3616 class StaticDiskDataSource : IStaticDataSource
3619 /// Initialise a new instnace of <see cref="StaticDiskDataSource"/>
3621 /// <param name="fileName">The name of the file to obtain data from.</param>
3622 public StaticDiskDataSource(string fileName)
3624 fileName_ = fileName;
3627 #region IDataSource Members
3630 /// Get a <see cref="Stream"/> providing data.
3632 /// <returns>Returns a <see cref="Stream"/> provising data.</returns>
3633 public Stream GetSource()
3635 return File.OpenRead(fileName_);
3639 #region Instance Fields
3645 /// Default implementation of <see cref="IDynamicDataSource"/> for files stored on disk.
3647 class DynamicDiskDataSource : IDynamicDataSource
3650 /// Initialise a default instance of <see cref="DynamicDiskDataSource"/>.
3652 public DynamicDiskDataSource()
3656 #region IDataSource Members
3658 /// Get a <see cref="Stream"/> providing data for an entry.
3660 /// <param name="entry">The entry to provide data for.</param>
3661 /// <param name="name">The file name for data if known.</param>
3662 /// <returns>Returns a stream providing data; or null if not available</returns>
3663 public Stream GetSource(ZipEntry entry, string name)
3665 Stream result = null;
3667 if ( name != null ) {
3668 result = File.OpenRead(name);
3678 /// Defines facilities for data storage when updating Zip Archives.
3680 public interface IArchiveStorage
3683 /// Get the <see cref="FileUpdateMode"/> to apply during updates.
3685 FileUpdateMode UpdateMode { get; }
3688 /// Get an empty <see cref="Stream"/> that can be used for temporary output.
3690 /// <returns>Returns a temporary output <see cref="Stream"/></returns>
3691 /// <seealso cref="ConvertTemporaryToFinal"></seealso>
3692 Stream GetTemporaryOutput();
3695 /// Convert a temporary output stream to a final stream.
3697 /// <returns>The resulting final <see cref="Stream"/></returns>
3698 /// <seealso cref="GetTemporaryOutput"/>
3699 Stream ConvertTemporaryToFinal();
3702 /// Make a temporary copy of the original stream.
3704 /// <param name="stream">The <see cref="Stream"/> to copy.</param>
3705 /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
3706 Stream MakeTemporaryCopy(Stream stream);
3709 /// Return a stream suitable for performing direct updates on the original source.
3711 /// <param name="stream">The current stream.</param>
3712 /// <returns>Returns a stream suitable for direct updating.</returns>
3713 /// <remarks>This may be the current stream passed.</remarks>
3714 Stream OpenForDirectUpdate(Stream stream);
3717 /// Dispose of this instance.
3723 /// An abstract <see cref="IArchiveStorage"/> suitable for extension by inheritance.
3725 abstract public class BaseArchiveStorage : IArchiveStorage
3727 #region Constructors
3729 /// Initializes a new instance of the <see cref="BaseArchiveStorage"/> class.
3731 /// <param name="updateMode">The update mode.</param>
3732 public BaseArchiveStorage(FileUpdateMode updateMode)
3734 updateMode_ = updateMode;
3738 #region IArchiveStorage Members
3741 /// Gets a temporary output <see cref="Stream"/>
3743 /// <returns>Returns the temporary output stream.</returns>
3744 /// <seealso cref="ConvertTemporaryToFinal"></seealso>
3745 public abstract Stream GetTemporaryOutput();
3748 /// Converts the temporary <see cref="Stream"/> to its final form.
3750 /// <returns>Returns a <see cref="Stream"/> that can be used to read
3751 /// the final storage for the archive.</returns>
3752 /// <seealso cref="GetTemporaryOutput"/>
3753 public abstract Stream ConvertTemporaryToFinal();
3756 /// Make a temporary copy of a <see cref="Stream"/>.
3758 /// <param name="stream">The <see cref="Stream"/> to make a copy of.</param>
3759 /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
3760 public abstract Stream MakeTemporaryCopy(Stream stream);
3763 /// Return a stream suitable for performing direct updates on the original source.
3765 /// <param name="stream">The <see cref="Stream"/> to open for direct update.</param>
3766 /// <returns>Returns a stream suitable for direct updating.</returns>
3767 public abstract Stream OpenForDirectUpdate(Stream stream);
3770 /// Disposes this instance.
3772 public abstract void Dispose();
3775 /// Gets the update mode applicable.
3777 /// <value>The update mode.</value>
3778 public FileUpdateMode UpdateMode
3787 #region Instance Fields
3788 FileUpdateMode updateMode_;
3793 /// An <see cref="IArchiveStorage"/> implementation suitable for hard disks.
3795 public class DiskArchiveStorage : BaseArchiveStorage
3797 #region Constructors
3799 /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class.
3801 /// <param name="file">The file.</param>
3802 /// <param name="updateMode">The update mode.</param>
3803 public DiskArchiveStorage(ZipFile file, FileUpdateMode updateMode)
3806 if ( file.Name == null ) {
3807 throw new ZipException("Cant handle non file archives");
3810 fileName_ = file.Name;
3814 /// Initializes a new instance of the <see cref="DiskArchiveStorage"/> class.
3816 /// <param name="file">The file.</param>
3817 public DiskArchiveStorage(ZipFile file)
3818 : this(file, FileUpdateMode.Safe)
3823 #region IArchiveStorage Members
3826 /// Gets a temporary output <see cref="Stream"/> for performing updates on.
3828 /// <returns>Returns the temporary output stream.</returns>
3829 public override Stream GetTemporaryOutput()
3831 if ( temporaryName_ != null ) {
3832 temporaryName_ = GetTempFileName(temporaryName_, true);
3833 temporaryStream_ = File.OpenWrite(temporaryName_);
3836 // Determine where to place files based on internal strategy.
3837 // Currently this is always done in system temp directory.
3838 temporaryName_ = Path.GetTempFileName();
3839 temporaryStream_ = File.OpenWrite(temporaryName_);
3842 return temporaryStream_;
3846 /// Converts a temporary <see cref="Stream"/> to its final form.
3848 /// <returns>Returns a <see cref="Stream"/> that can be used to read
3849 /// the final storage for the archive.</returns>
3850 public override Stream ConvertTemporaryToFinal()
3852 if ( temporaryStream_ == null ) {
3853 throw new ZipException("No temporary stream has been created");
3856 Stream result = null;
3858 string moveTempName = GetTempFileName(fileName_, false);
3859 bool newFileCreated = false;
3862 temporaryStream_.Close();
3863 File.Move(fileName_, moveTempName);
3864 File.Move(temporaryName_, fileName_);
3865 newFileCreated = true;
3866 File.Delete(moveTempName);
3868 result = File.OpenRead(fileName_);
3873 // Try to roll back changes...
3874 if ( !newFileCreated ) {
3875 File.Move(moveTempName, fileName_);
3876 File.Delete(temporaryName_);
3886 /// Make a temporary copy of a stream.
3888 /// <param name="stream">The <see cref="Stream"/> to copy.</param>
3889 /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
3890 public override Stream MakeTemporaryCopy(Stream stream)
3894 temporaryName_ = GetTempFileName(fileName_, true);
3895 File.Copy(fileName_, temporaryName_, true);
3897 temporaryStream_ = new FileStream(temporaryName_,
3899 FileAccess.ReadWrite);
3900 return temporaryStream_;
3904 /// Return a stream suitable for performing direct updates on the original source.
3906 /// <param name="current">The current stream.</param>
3907 /// <returns>Returns a stream suitable for direct updating.</returns>
3908 /// <remarks>If the <paramref name="current"/> stream is not null this is used as is.</remarks>
3909 public override Stream OpenForDirectUpdate(Stream current)
3912 if ((current == null) || !current.CanWrite)
3914 if (current != null) {
3918 result = new FileStream(fileName_,
3920 FileAccess.ReadWrite);
3931 /// Disposes this instance.
3933 public override void Dispose()
3935 if ( temporaryStream_ != null ) {
3936 temporaryStream_.Close();
3942 #region Internal routines
3943 string GetTempFileName(string original, bool makeTempFile)
3945 string result = null;
3947 if ( original == null ) {
3948 result = Path.GetTempFileName();
3952 int suffixSeed = DateTime.Now.Second;
3954 while ( result == null ) {
3956 string newName = string.Format("{0}.{1}{2}.tmp", original, suffixSeed, counter);
3957 if ( !File.Exists(newName) ) {
3958 if ( makeTempFile) {
3960 // Try and create the file.
3961 using ( FileStream stream = File.Create(newName) ) {
3966 suffixSeed = DateTime.Now.Second;
3979 #region Instance Fields
3980 Stream temporaryStream_;
3982 string temporaryName_;
3987 /// An <see cref="IArchiveStorage"/> implementation suitable for in memory streams.
3989 public class MemoryArchiveStorage : BaseArchiveStorage
3991 #region Constructors
3993 /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class.
3995 public MemoryArchiveStorage()
3996 : base(FileUpdateMode.Direct)
4001 /// Initializes a new instance of the <see cref="MemoryArchiveStorage"/> class.
4003 /// <param name="updateMode">The <see cref="FileUpdateMode"/> to use</param>
4004 /// <remarks>This constructor is for testing as memory streams dont really require safe mode.</remarks>
4005 public MemoryArchiveStorage(FileUpdateMode updateMode)
4014 /// Get the stream returned by <see cref="ConvertTemporaryToFinal"/> if this was in fact called.
4016 public MemoryStream FinalStream
4018 get { return finalStream_; }
4023 #region IArchiveStorage Members
4026 /// Gets the temporary output <see cref="Stream"/>
4028 /// <returns>Returns the temporary output stream.</returns>
4029 public override Stream GetTemporaryOutput()
4031 temporaryStream_ = new MemoryStream();
4032 return temporaryStream_;
4036 /// Converts the temporary <see cref="Stream"/> to its final form.
4038 /// <returns>Returns a <see cref="Stream"/> that can be used to read
4039 /// the final storage for the archive.</returns>
4040 public override Stream ConvertTemporaryToFinal()
4042 if ( temporaryStream_ == null ) {
4043 throw new ZipException("No temporary stream has been created");
4046 finalStream_ = new MemoryStream(temporaryStream_.ToArray());
4047 return finalStream_;
4051 /// Make a temporary copy of the original stream.
4053 /// <param name="stream">The <see cref="Stream"/> to copy.</param>
4054 /// <returns>Returns a temporary output <see cref="Stream"/> that is a copy of the input.</returns>
4055 public override Stream MakeTemporaryCopy(Stream stream)
4057 temporaryStream_ = new MemoryStream();
4058 stream.Position = 0;
4059 StreamUtils.Copy(stream, temporaryStream_, new byte[4096]);
4060 return temporaryStream_;
4064 /// Return a stream suitable for performing direct updates on the original source.
4066 /// <param name="stream">The original source stream</param>
4067 /// <returns>Returns a stream suitable for direct updating.</returns>
4068 /// <remarks>If the <paramref name="stream"/> passed is not null this is used;
4069 /// otherwise a new <see cref="MemoryStream"/> is returned.</remarks>
4070 public override Stream OpenForDirectUpdate(Stream stream)
4073 if ((stream == null) || !stream.CanWrite) {
4075 result = new MemoryStream();
4077 if (stream != null) {
4078 stream.Position = 0;
4079 StreamUtils.Copy(stream, result, new byte[4096]);
4092 /// Disposes this instance.
4094 public override void Dispose()
4096 if ( temporaryStream_ != null ) {
4097 temporaryStream_.Close();
4103 #region Instance Fields
4104 MemoryStream temporaryStream_;
4105 MemoryStream finalStream_;