Include Non-NuGet Resources

This commit is contained in:
Gary Sharp
2013-02-01 12:52:29 +11:00
parent 0a93429800
commit 442cb1bdb7
135 changed files with 224150 additions and 0 deletions
@@ -0,0 +1,116 @@
// ComHelper.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-June-13 17:04:06>
//
// ------------------------------------------------------------------
//
// This module defines a COM Helper class.
//
// Created: Tue, 08 Sep 2009 22:03
//
using Interop=System.Runtime.InteropServices;
namespace Ionic.Zip
{
/// <summary>
/// This class exposes a set of COM-accessible wrappers for static
/// methods available on the ZipFile class. You don't need this
/// class unless you are using DotNetZip from a COM environment.
/// </summary>
[System.Runtime.InteropServices.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000F")]
[System.Runtime.InteropServices.ComVisible(true)]
#if !NETCF
[System.Runtime.InteropServices.ClassInterface(System.Runtime.InteropServices.ClassInterfaceType.AutoDispatch)]
#endif
public class ComHelper
{
/// <summary>
/// A wrapper for <see cref="ZipFile.IsZipFile(string)">ZipFile.IsZipFile(string)</see>
/// </summary>
/// <param name="filename">The filename to of the zip file to check.</param>
/// <returns>true if the file contains a valid zip file.</returns>
public bool IsZipFile(string filename)
{
return ZipFile.IsZipFile(filename);
}
/// <summary>
/// A wrapper for <see cref="ZipFile.IsZipFile(string, bool)">ZipFile.IsZipFile(string, bool)</see>
/// </summary>
/// <remarks>
/// We cannot use "overloaded" Method names in COM interop.
/// So, here, we use a unique name.
/// </remarks>
/// <param name="filename">The filename to of the zip file to check.</param>
/// <returns>true if the file contains a valid zip file.</returns>
public bool IsZipFileWithExtract(string filename)
{
return ZipFile.IsZipFile(filename, true);
}
#if !NETCF
/// <summary>
/// A wrapper for <see cref="ZipFile.CheckZip(string)">ZipFile.CheckZip(string)</see>
/// </summary>
/// <param name="filename">The filename to of the zip file to check.</param>
///
/// <returns>true if the named zip file checks OK. Otherwise, false. </returns>
public bool CheckZip(string filename)
{
return ZipFile.CheckZip(filename);
}
/// <summary>
/// A COM-friendly wrapper for the static method <see cref="ZipFile.CheckZipPassword(string,string)"/>.
/// </summary>
///
/// <param name="filename">The filename to of the zip file to check.</param>
///
/// <param name="password">The password to check.</param>
///
/// <returns>true if the named zip file checks OK. Otherwise, false. </returns>
public bool CheckZipPassword(string filename, string password)
{
return ZipFile.CheckZipPassword(filename, password);
}
/// <summary>
/// A wrapper for <see cref="ZipFile.FixZipDirectory(string)">ZipFile.FixZipDirectory(string)</see>
/// </summary>
/// <param name="filename">The filename to of the zip file to fix.</param>
public void FixZipDirectory(string filename)
{
ZipFile.FixZipDirectory(filename);
}
#endif
/// <summary>
/// A wrapper for <see cref="ZipFile.LibraryVersion">ZipFile.LibraryVersion</see>
/// </summary>
/// <returns>
/// the version number on the DotNetZip assembly, formatted as a string.
/// </returns>
public string GetZipLibraryVersion()
{
return ZipFile.LibraryVersion.ToString();
}
}
}
@@ -0,0 +1,135 @@
// EncryptionAlgorithm.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-October-21 17:24:45>
//
// ------------------------------------------------------------------
//
// This module defines the EncryptionAgorithm enum
//
//
// ------------------------------------------------------------------
namespace Ionic.Zip
{
/// <summary>
/// An enum that provides the various encryption algorithms supported by this
/// library.
/// </summary>
///
/// <remarks>
///
/// <para>
/// <c>PkzipWeak</c> implies the use of Zip 2.0 encryption, which is known to be
/// weak and subvertible.
/// </para>
///
/// <para>
/// A note on interoperability: Values of <c>PkzipWeak</c> and <c>None</c> are
/// specified in <see
/// href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">PKWARE's zip
/// specification</see>, and are considered to be "standard". Zip archives
/// produced using these options will be interoperable with many other zip tools
/// and libraries, including Windows Explorer.
/// </para>
///
/// <para>
/// Values of <c>WinZipAes128</c> and <c>WinZipAes256</c> are not part of the Zip
/// specification, but rather imply the use of a vendor-specific extension from
/// WinZip. If you want to produce interoperable Zip archives, do not use these
/// values. For example, if you produce a zip archive using WinZipAes256, you
/// will be able to open it in Windows Explorer on Windows XP and Vista, but you
/// will not be able to extract entries; trying this will lead to an "unspecified
/// error". For this reason, some people have said that a zip archive that uses
/// WinZip's AES encryption is not actually a zip archive at all. A zip archive
/// produced this way will be readable with the WinZip tool (Version 11 and
/// beyond).
/// </para>
///
/// <para>
/// There are other third-party tools and libraries, both commercial and
/// otherwise, that support WinZip's AES encryption. These will be able to read
/// AES-encrypted zip archives produced by DotNetZip, and conversely applications
/// that use DotNetZip to read zip archives will be able to read AES-encrypted
/// archives produced by those tools or libraries. Consult the documentation for
/// those other tools and libraries to find out if WinZip's AES encryption is
/// supported.
/// </para>
///
/// <para>
/// In case you care: According to <see
/// href="http://www.winzip.com/aes_info.htm">the WinZip specification</see>, the
/// actual AES key used is derived from the <see cref="ZipEntry.Password"/> via an
/// algorithm that complies with <see
/// href="http://www.ietf.org/rfc/rfc2898.txt">RFC 2898</see>, using an iteration
/// count of 1000. The algorithm is sometimes referred to as PBKDF2, which stands
/// for "Password Based Key Derivation Function #2".
/// </para>
///
/// <para>
/// A word about password strength and length: The AES encryption technology is
/// very good, but any system is only as secure as the weakest link. If you want
/// to secure your data, be sure to use a password that is hard to guess. To make
/// it harder to guess (increase its "entropy"), you should make it longer. If
/// you use normal characters from an ASCII keyboard, a password of length 20 will
/// be strong enough that it will be impossible to guess. For more information on
/// that, I'd encourage you to read <see
/// href="http://www.redkestrel.co.uk/Articles/RandomPasswordStrength.html">this
/// article.</see>
/// </para>
///
/// <para>
/// The WinZip AES algorithms are not supported with the version of DotNetZip that
/// runs on the .NET Compact Framework. This is because .NET CF lacks the
/// HMACSHA1 class that is required for producing the archive.
/// </para>
/// </remarks>
public enum EncryptionAlgorithm
{
/// <summary>
/// No encryption at all.
/// </summary>
None = 0,
/// <summary>
/// Traditional or Classic pkzip encryption.
/// </summary>
PkzipWeak,
#if AESCRYPTO
/// <summary>
/// WinZip AES encryption (128 key bits).
/// </summary>
WinZipAes128,
/// <summary>
/// WinZip AES encryption (256 key bits).
/// </summary>
WinZipAes256,
#endif
/// <summary>
/// An encryption algorithm that is not supported by DotNetZip.
/// </summary>
Unsupported = 4,
// others... not implemented (yet?)
}
}
@@ -0,0 +1,684 @@
// Events.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2006, 2007, 2008, 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-August-06 12:26:24>
//
// ------------------------------------------------------------------
//
// This module defines events used by the ZipFile class.
//
//
using System;
using System.Collections.Generic;
using System.Text;
namespace Ionic.Zip
{
/// <summary>
/// Delegate in which the application writes the <c>ZipEntry</c> content for the named entry.
/// </summary>
///
/// <param name="entryName">The name of the entry that must be written.</param>
/// <param name="stream">The stream to which the entry data should be written.</param>
///
/// <remarks>
/// When you add an entry and specify a <c>WriteDelegate</c>, via <see
/// cref="Ionic.Zip.ZipFile.AddEntry(string, WriteDelegate)"/>, the application
/// code provides the logic that writes the entry data directly into the zip file.
/// </remarks>
///
/// <example>
///
/// This example shows how to define a WriteDelegate that obtains a DataSet, and then
/// writes the XML for the DataSet into the zip archive. There's no need to
/// save the XML to a disk file first.
///
/// <code lang="C#">
/// private void WriteEntry (String filename, Stream output)
/// {
/// DataSet ds1 = ObtainDataSet();
/// ds1.WriteXml(output);
/// }
///
/// private void Run()
/// {
/// using (var zip = new ZipFile())
/// {
/// zip.AddEntry(zipEntryName, WriteEntry);
/// zip.Save(zipFileName);
/// }
/// }
/// </code>
///
/// <code lang="vb">
/// Private Sub WriteEntry (ByVal filename As String, ByVal output As Stream)
/// DataSet ds1 = ObtainDataSet()
/// ds1.WriteXml(stream)
/// End Sub
///
/// Public Sub Run()
/// Using zip = New ZipFile
/// zip.AddEntry(zipEntryName, New WriteDelegate(AddressOf WriteEntry))
/// zip.Save(zipFileName)
/// End Using
/// End Sub
/// </code>
/// </example>
/// <seealso cref="Ionic.Zip.ZipFile.AddEntry(string, WriteDelegate)"/>
public delegate void WriteDelegate(string entryName, System.IO.Stream stream);
/// <summary>
/// Delegate in which the application opens the stream, just-in-time, for the named entry.
/// </summary>
///
/// <param name="entryName">
/// The name of the ZipEntry that the application should open the stream for.
/// </param>
///
/// <remarks>
/// When you add an entry via <see cref="Ionic.Zip.ZipFile.AddEntry(string,
/// OpenDelegate, CloseDelegate)"/>, the application code provides the logic that
/// opens and closes the stream for the given ZipEntry.
/// </remarks>
///
/// <seealso cref="Ionic.Zip.ZipFile.AddEntry(string, OpenDelegate, CloseDelegate)"/>
public delegate System.IO.Stream OpenDelegate(string entryName);
/// <summary>
/// Delegate in which the application closes the stream, just-in-time, for the named entry.
/// </summary>
///
/// <param name="entryName">
/// The name of the ZipEntry that the application should close the stream for.
/// </param>
///
/// <param name="stream">The stream to be closed.</param>
///
/// <remarks>
/// When you add an entry via <see cref="Ionic.Zip.ZipFile.AddEntry(string,
/// OpenDelegate, CloseDelegate)"/>, the application code provides the logic that
/// opens and closes the stream for the given ZipEntry.
/// </remarks>
///
/// <seealso cref="Ionic.Zip.ZipFile.AddEntry(string, OpenDelegate, CloseDelegate)"/>
public delegate void CloseDelegate(string entryName, System.IO.Stream stream);
/// <summary>
/// Delegate for the callback by which the application tells the
/// library the CompressionLevel to use for a file.
/// </summary>
///
/// <remarks>
/// <para>
/// Using this callback, the application can, for example, specify that
/// previously-compressed files (.mp3, .png, .docx, etc) should use a
/// <c>CompressionLevel</c> of <c>None</c>, or can set the compression level based
/// on any other factor.
/// </para>
/// </remarks>
/// <seealso cref="Ionic.Zip.ZipFile.SetCompression"/>
public delegate Ionic.Zlib.CompressionLevel SetCompressionCallback(string localFileName, string fileNameInArchive);
/// <summary>
/// In an EventArgs type, indicates which sort of progress event is being
/// reported.
/// </summary>
/// <remarks>
/// There are events for reading, events for saving, and events for
/// extracting. This enumeration allows a single EventArgs type to be sued to
/// describe one of multiple subevents. For example, a SaveProgress event is
/// invoked before, after, and during the saving of a single entry. The value
/// of an enum with this type, specifies which event is being triggered. The
/// same applies to Extraction, Reading and Adding events.
/// </remarks>
public enum ZipProgressEventType
{
/// <summary>
/// Indicates that a Add() operation has started.
/// </summary>
Adding_Started,
/// <summary>
/// Indicates that an individual entry in the archive has been added.
/// </summary>
Adding_AfterAddEntry,
/// <summary>
/// Indicates that a Add() operation has completed.
/// </summary>
Adding_Completed,
/// <summary>
/// Indicates that a Read() operation has started.
/// </summary>
Reading_Started,
/// <summary>
/// Indicates that an individual entry in the archive is about to be read.
/// </summary>
Reading_BeforeReadEntry,
/// <summary>
/// Indicates that an individual entry in the archive has just been read.
/// </summary>
Reading_AfterReadEntry,
/// <summary>
/// Indicates that a Read() operation has completed.
/// </summary>
Reading_Completed,
/// <summary>
/// The given event reports the number of bytes read so far
/// during a Read() operation.
/// </summary>
Reading_ArchiveBytesRead,
/// <summary>
/// Indicates that a Save() operation has started.
/// </summary>
Saving_Started,
/// <summary>
/// Indicates that an individual entry in the archive is about to be written.
/// </summary>
Saving_BeforeWriteEntry,
/// <summary>
/// Indicates that an individual entry in the archive has just been saved.
/// </summary>
Saving_AfterWriteEntry,
/// <summary>
/// Indicates that a Save() operation has completed.
/// </summary>
Saving_Completed,
/// <summary>
/// Indicates that the zip archive has been created in a
/// temporary location during a Save() operation.
/// </summary>
Saving_AfterSaveTempArchive,
/// <summary>
/// Indicates that the temporary file is about to be renamed to the final archive
/// name during a Save() operation.
/// </summary>
Saving_BeforeRenameTempArchive,
/// <summary>
/// Indicates that the temporary file is has just been renamed to the final archive
/// name during a Save() operation.
/// </summary>
Saving_AfterRenameTempArchive,
/// <summary>
/// Indicates that the self-extracting archive has been compiled
/// during a Save() operation.
/// </summary>
Saving_AfterCompileSelfExtractor,
/// <summary>
/// The given event is reporting the number of source bytes that have run through the compressor so far
/// during a Save() operation.
/// </summary>
Saving_EntryBytesRead,
/// <summary>
/// Indicates that an entry is about to be extracted.
/// </summary>
Extracting_BeforeExtractEntry,
/// <summary>
/// Indicates that an entry has just been extracted.
/// </summary>
Extracting_AfterExtractEntry,
/// <summary>
/// Indicates that extraction of an entry would overwrite an existing
/// filesystem file. You must use
/// <see cref="ExtractExistingFileAction.InvokeExtractProgressEvent">
/// ExtractExistingFileAction.InvokeExtractProgressEvent</see> in the call
/// to <c>ZipEntry.Extract()</c> in order to receive this event.
/// </summary>
Extracting_ExtractEntryWouldOverwrite,
/// <summary>
/// The given event is reporting the number of bytes written so far for
/// the current entry during an Extract() operation.
/// </summary>
Extracting_EntryBytesWritten,
/// <summary>
/// Indicates that an ExtractAll operation is about to begin.
/// </summary>
Extracting_BeforeExtractAll,
/// <summary>
/// Indicates that an ExtractAll operation has completed.
/// </summary>
Extracting_AfterExtractAll,
/// <summary>
/// Indicates that an error has occurred while saving a zip file.
/// This generally means the file cannot be opened, because it has been
/// removed, or because it is locked by another process. It can also
/// mean that the file cannot be Read, because of a range lock conflict.
/// </summary>
Error_Saving,
}
/// <summary>
/// Provides information about the progress of a save, read, or extract operation.
/// This is a base class; you will probably use one of the classes derived from this one.
/// </summary>
public class ZipProgressEventArgs : EventArgs
{
private int _entriesTotal;
private bool _cancel;
private ZipEntry _latestEntry;
private ZipProgressEventType _flavor;
private String _archiveName;
private Int64 _bytesTransferred;
private Int64 _totalBytesToTransfer;
internal ZipProgressEventArgs() { }
internal ZipProgressEventArgs(string archiveName, ZipProgressEventType flavor)
{
this._archiveName = archiveName;
this._flavor = flavor;
}
/// <summary>
/// The total number of entries to be saved or extracted.
/// </summary>
public int EntriesTotal
{
get { return _entriesTotal; }
set { _entriesTotal = value; }
}
/// <summary>
/// The name of the last entry saved or extracted.
/// </summary>
public ZipEntry CurrentEntry
{
get { return _latestEntry; }
set { _latestEntry = value; }
}
/// <summary>
/// In an event handler, set this to cancel the save or extract
/// operation that is in progress.
/// </summary>
public bool Cancel
{
get { return _cancel; }
set { _cancel = _cancel || value; }
}
/// <summary>
/// The type of event being reported.
/// </summary>
public ZipProgressEventType EventType
{
get { return _flavor; }
set { _flavor = value; }
}
/// <summary>
/// Returns the archive name associated to this event.
/// </summary>
public String ArchiveName
{
get { return _archiveName; }
set { _archiveName = value; }
}
/// <summary>
/// The number of bytes read or written so far for this entry.
/// </summary>
public Int64 BytesTransferred
{
get { return _bytesTransferred; }
set { _bytesTransferred = value; }
}
/// <summary>
/// Total number of bytes that will be read or written for this entry.
/// This number will be -1 if the value cannot be determined.
/// </summary>
public Int64 TotalBytesToTransfer
{
get { return _totalBytesToTransfer; }
set { _totalBytesToTransfer = value; }
}
}
/// <summary>
/// Provides information about the progress of a Read operation.
/// </summary>
public class ReadProgressEventArgs : ZipProgressEventArgs
{
internal ReadProgressEventArgs() { }
private ReadProgressEventArgs(string archiveName, ZipProgressEventType flavor)
: base(archiveName, flavor)
{ }
internal static ReadProgressEventArgs Before(string archiveName, int entriesTotal)
{
var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_BeforeReadEntry);
x.EntriesTotal = entriesTotal;
return x;
}
internal static ReadProgressEventArgs After(string archiveName, ZipEntry entry, int entriesTotal)
{
var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_AfterReadEntry);
x.EntriesTotal = entriesTotal;
x.CurrentEntry = entry;
return x;
}
internal static ReadProgressEventArgs Started(string archiveName)
{
var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_Started);
return x;
}
internal static ReadProgressEventArgs ByteUpdate(string archiveName, ZipEntry entry, Int64 bytesXferred, Int64 totalBytes)
{
var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_ArchiveBytesRead);
x.CurrentEntry = entry;
x.BytesTransferred = bytesXferred;
x.TotalBytesToTransfer = totalBytes;
return x;
}
internal static ReadProgressEventArgs Completed(string archiveName)
{
var x = new ReadProgressEventArgs(archiveName, ZipProgressEventType.Reading_Completed);
return x;
}
}
/// <summary>
/// Provides information about the progress of a Add operation.
/// </summary>
public class AddProgressEventArgs : ZipProgressEventArgs
{
internal AddProgressEventArgs() { }
private AddProgressEventArgs(string archiveName, ZipProgressEventType flavor)
: base(archiveName, flavor)
{ }
internal static AddProgressEventArgs AfterEntry(string archiveName, ZipEntry entry, int entriesTotal)
{
var x = new AddProgressEventArgs(archiveName, ZipProgressEventType.Adding_AfterAddEntry);
x.EntriesTotal = entriesTotal;
x.CurrentEntry = entry;
return x;
}
internal static AddProgressEventArgs Started(string archiveName)
{
var x = new AddProgressEventArgs(archiveName, ZipProgressEventType.Adding_Started);
return x;
}
internal static AddProgressEventArgs Completed(string archiveName)
{
var x = new AddProgressEventArgs(archiveName, ZipProgressEventType.Adding_Completed);
return x;
}
}
/// <summary>
/// Provides information about the progress of a save operation.
/// </summary>
public class SaveProgressEventArgs : ZipProgressEventArgs
{
private int _entriesSaved;
/// <summary>
/// Constructor for the SaveProgressEventArgs.
/// </summary>
/// <param name="archiveName">the name of the zip archive.</param>
/// <param name="before">whether this is before saving the entry, or after</param>
/// <param name="entriesTotal">The total number of entries in the zip archive.</param>
/// <param name="entriesSaved">Number of entries that have been saved.</param>
/// <param name="entry">The entry involved in the event.</param>
internal SaveProgressEventArgs(string archiveName, bool before, int entriesTotal, int entriesSaved, ZipEntry entry)
: base(archiveName, (before) ? ZipProgressEventType.Saving_BeforeWriteEntry : ZipProgressEventType.Saving_AfterWriteEntry)
{
this.EntriesTotal = entriesTotal;
this.CurrentEntry = entry;
this._entriesSaved = entriesSaved;
}
internal SaveProgressEventArgs() { }
internal SaveProgressEventArgs(string archiveName, ZipProgressEventType flavor)
: base(archiveName, flavor)
{ }
internal static SaveProgressEventArgs ByteUpdate(string archiveName, ZipEntry entry, Int64 bytesXferred, Int64 totalBytes)
{
var x = new SaveProgressEventArgs(archiveName, ZipProgressEventType.Saving_EntryBytesRead);
x.ArchiveName = archiveName;
x.CurrentEntry = entry;
x.BytesTransferred = bytesXferred;
x.TotalBytesToTransfer = totalBytes;
return x;
}
internal static SaveProgressEventArgs Started(string archiveName)
{
var x = new SaveProgressEventArgs(archiveName, ZipProgressEventType.Saving_Started);
return x;
}
internal static SaveProgressEventArgs Completed(string archiveName)
{
var x = new SaveProgressEventArgs(archiveName, ZipProgressEventType.Saving_Completed);
return x;
}
/// <summary>
/// Number of entries saved so far.
/// </summary>
public int EntriesSaved
{
get { return _entriesSaved; }
}
}
/// <summary>
/// Provides information about the progress of the extract operation.
/// </summary>
public class ExtractProgressEventArgs : ZipProgressEventArgs
{
private int _entriesExtracted;
private string _target;
/// <summary>
/// Constructor for the ExtractProgressEventArgs.
/// </summary>
/// <param name="archiveName">the name of the zip archive.</param>
/// <param name="before">whether this is before saving the entry, or after</param>
/// <param name="entriesTotal">The total number of entries in the zip archive.</param>
/// <param name="entriesExtracted">Number of entries that have been extracted.</param>
/// <param name="entry">The entry involved in the event.</param>
/// <param name="extractLocation">The location to which entries are extracted.</param>
internal ExtractProgressEventArgs(string archiveName, bool before, int entriesTotal, int entriesExtracted, ZipEntry entry, string extractLocation)
: base(archiveName, (before) ? ZipProgressEventType.Extracting_BeforeExtractEntry : ZipProgressEventType.Extracting_AfterExtractEntry)
{
this.EntriesTotal = entriesTotal;
this.CurrentEntry = entry;
this._entriesExtracted = entriesExtracted;
this._target = extractLocation;
}
internal ExtractProgressEventArgs(string archiveName, ZipProgressEventType flavor)
: base(archiveName, flavor)
{ }
internal ExtractProgressEventArgs()
{ }
internal static ExtractProgressEventArgs BeforeExtractEntry(string archiveName, ZipEntry entry, string extractLocation)
{
var x = new ExtractProgressEventArgs
{
ArchiveName = archiveName,
EventType = ZipProgressEventType.Extracting_BeforeExtractEntry,
CurrentEntry = entry,
_target = extractLocation,
};
return x;
}
internal static ExtractProgressEventArgs ExtractExisting(string archiveName, ZipEntry entry, string extractLocation)
{
var x = new ExtractProgressEventArgs
{
ArchiveName = archiveName,
EventType = ZipProgressEventType.Extracting_ExtractEntryWouldOverwrite,
CurrentEntry = entry,
_target = extractLocation,
};
return x;
}
internal static ExtractProgressEventArgs AfterExtractEntry(string archiveName, ZipEntry entry, string extractLocation)
{
var x = new ExtractProgressEventArgs
{
ArchiveName = archiveName,
EventType = ZipProgressEventType.Extracting_AfterExtractEntry,
CurrentEntry = entry,
_target = extractLocation,
};
return x;
}
internal static ExtractProgressEventArgs ExtractAllStarted(string archiveName, string extractLocation)
{
var x = new ExtractProgressEventArgs(archiveName, ZipProgressEventType.Extracting_BeforeExtractAll);
x._target = extractLocation;
return x;
}
internal static ExtractProgressEventArgs ExtractAllCompleted(string archiveName, string extractLocation)
{
var x = new ExtractProgressEventArgs(archiveName, ZipProgressEventType.Extracting_AfterExtractAll);
x._target = extractLocation;
return x;
}
internal static ExtractProgressEventArgs ByteUpdate(string archiveName, ZipEntry entry, Int64 bytesWritten, Int64 totalBytes)
{
var x = new ExtractProgressEventArgs(archiveName, ZipProgressEventType.Extracting_EntryBytesWritten);
x.ArchiveName = archiveName;
x.CurrentEntry = entry;
x.BytesTransferred = bytesWritten;
x.TotalBytesToTransfer = totalBytes;
return x;
}
/// <summary>
/// Number of entries extracted so far. This is set only if the
/// EventType is Extracting_BeforeExtractEntry or Extracting_AfterExtractEntry, and
/// the Extract() is occurring witin the scope of a call to ExtractAll().
/// </summary>
public int EntriesExtracted
{
get { return _entriesExtracted; }
}
/// <summary>
/// Returns the extraction target location, a filesystem path.
/// </summary>
public String ExtractLocation
{
get { return _target; }
}
}
/// <summary>
/// Provides information about the an error that occurred while zipping.
/// </summary>
public class ZipErrorEventArgs : ZipProgressEventArgs
{
private Exception _exc;
private ZipErrorEventArgs() { }
internal static ZipErrorEventArgs Saving(string archiveName, ZipEntry entry, Exception exception)
{
var x = new ZipErrorEventArgs
{
EventType = ZipProgressEventType.Error_Saving,
ArchiveName = archiveName,
CurrentEntry = entry,
_exc = exception
};
return x;
}
/// <summary>
/// Returns the exception that occurred, if any.
/// </summary>
public Exception @Exception
{
get { return _exc; }
}
/// <summary>
/// Returns the name of the file that caused the exception, if any.
/// </summary>
public String FileName
{
get { return CurrentEntry.LocalFileName; }
}
}
}
@@ -0,0 +1,300 @@
// Exceptions.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2008, 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-12 12:19:10>
//
// ------------------------------------------------------------------
//
// This module defines exceptions used in the class library.
//
using System;
using System.Collections.Generic;
using System.Text;
#if !NETCF
using System.Runtime.Serialization;
#endif
namespace Ionic.Zip
{
///// <summary>
///// Base exception type for all custom exceptions in the Zip library. It acts as a marker class.
///// </summary>
//[AttributeUsage(AttributeTargets.Class)]
//public class ZipExceptionAttribute : Attribute { }
/// <summary>
/// Issued when an <c>ZipEntry.ExtractWithPassword()</c> method is invoked
/// with an incorrect password.
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
[System.Runtime.InteropServices.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000B")]
public class BadPasswordException : ZipException
{
/// <summary>
/// Default ctor.
/// </summary>
public BadPasswordException() { }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
public BadPasswordException(String message)
: base(message)
{ }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
/// <param name="innerException">The innerException for this exception.</param>
public BadPasswordException(String message, Exception innerException)
: base(message, innerException)
{
}
#if ! (NETCF || SILVERLIGHT)
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="info">The serialization info for the exception.</param>
/// <param name="context">The streaming context from which to deserialize.</param>
protected BadPasswordException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
#endif
}
/// <summary>
/// Indicates that a read was attempted on a stream, and bad or incomplete data was
/// received.
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
[System.Runtime.InteropServices.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d0000A")]
public class BadReadException : ZipException
{
/// <summary>
/// Default ctor.
/// </summary>
public BadReadException() { }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
public BadReadException(String message)
: base(message)
{ }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
/// <param name="innerException">The innerException for this exception.</param>
public BadReadException(String message, Exception innerException)
: base(message, innerException)
{
}
#if ! (NETCF || SILVERLIGHT)
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="info">The serialization info for the exception.</param>
/// <param name="context">The streaming context from which to deserialize.</param>
protected BadReadException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
#endif
}
/// <summary>
/// Issued when an CRC check fails upon extracting an entry from a zip archive.
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
[System.Runtime.InteropServices.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d00009")]
public class BadCrcException : ZipException
{
/// <summary>
/// Default ctor.
/// </summary>
public BadCrcException() { }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
public BadCrcException(String message)
: base(message)
{ }
#if ! (NETCF || SILVERLIGHT)
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="info">The serialization info for the exception.</param>
/// <param name="context">The streaming context from which to deserialize.</param>
protected BadCrcException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
#endif
}
/// <summary>
/// Issued when errors occur saving a self-extracting archive.
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
[System.Runtime.InteropServices.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d00008")]
public class SfxGenerationException : ZipException
{
/// <summary>
/// Default ctor.
/// </summary>
public SfxGenerationException() { }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
public SfxGenerationException(String message)
: base(message)
{ }
#if ! (NETCF || SILVERLIGHT)
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="info">The serialization info for the exception.</param>
/// <param name="context">The streaming context from which to deserialize.</param>
protected SfxGenerationException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
#endif
}
/// <summary>
/// Indicates that an operation was attempted on a ZipFile which was not possible
/// given the state of the instance. For example, if you call <c>Save()</c> on a ZipFile
/// which has no filename set, you can get this exception.
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
[System.Runtime.InteropServices.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d00007")]
public class BadStateException : ZipException
{
/// <summary>
/// Default ctor.
/// </summary>
public BadStateException() { }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
public BadStateException(String message)
: base(message)
{ }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
/// <param name="innerException">The innerException for this exception.</param>
public BadStateException(String message, Exception innerException)
: base(message, innerException)
{}
#if ! (NETCF || SILVERLIGHT)
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="info">The serialization info for the exception.</param>
/// <param name="context">The streaming context from which to deserialize.</param>
protected BadStateException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
#endif
}
/// <summary>
/// Base class for all exceptions defined by and throw by the Zip library.
/// </summary>
#if !SILVERLIGHT
[Serializable]
#endif
[System.Runtime.InteropServices.GuidAttribute("ebc25cf6-9120-4283-b972-0e5520d00006")]
public class ZipException : Exception
{
/// <summary>
/// Default ctor.
/// </summary>
public ZipException() { }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
public ZipException(String message) : base(message) { }
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="message">The message in the exception.</param>
/// <param name="innerException">The innerException for this exception.</param>
public ZipException(String message, Exception innerException)
: base(message, innerException)
{ }
#if ! (NETCF || SILVERLIGHT)
/// <summary>
/// Come on, you know how exceptions work. Why are you looking at this documentation?
/// </summary>
/// <param name="info">The serialization info for the exception.</param>
/// <param name="context">The streaming context from which to deserialize.</param>
protected ZipException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
#endif
}
}
@@ -0,0 +1,85 @@
// ExtractExistingFileAction.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-August-25 08:44:37>
//
// ------------------------------------------------------------------
//
// This module defines the ExtractExistingFileAction enum
//
//
// ------------------------------------------------------------------
namespace Ionic.Zip
{
/// <summary>
/// An enum for the options when extracting an entry would overwrite an existing file.
/// </summary>
///
/// <remarks>
/// <para>
/// This enum describes the actions that the library can take when an
/// <c>Extract()</c> or <c>ExtractWithPassword()</c> method is called to extract an
/// entry to a filesystem, and the extraction would overwrite an existing filesystem
/// file.
/// </para>
/// </remarks>
///
public enum ExtractExistingFileAction
{
/// <summary>
/// Throw an exception when extraction would overwrite an existing file. (For
/// COM clients, this is a 0 (zero).)
/// </summary>
Throw,
/// <summary>
/// When extraction would overwrite an existing file, overwrite the file silently.
/// The overwrite will happen even if the target file is marked as read-only.
/// (For COM clients, this is a 1.)
/// </summary>
OverwriteSilently,
/// <summary>
/// When extraction would overwrite an existing file, don't overwrite the file, silently.
/// (For COM clients, this is a 2.)
/// </summary>
DoNotOverwrite,
/// <summary>
/// When extraction would overwrite an existing file, invoke the ExtractProgress
/// event, using an event type of <see
/// cref="ZipProgressEventType.Extracting_ExtractEntryWouldOverwrite"/>. In
/// this way, the application can decide, just-in-time, whether to overwrite the
/// file. For example, a GUI application may wish to pop up a dialog to allow
/// the user to choose. You may want to examine the <see
/// cref="ExtractProgressEventArgs.ExtractLocation"/> property before making
/// the decision. If, after your processing in the Extract progress event, you
/// want to NOT extract the file, set <see cref="ZipEntry.ExtractExistingFile"/>
/// on the <c>ZipProgressEventArgs.CurrentEntry</c> to <c>DoNotOverwrite</c>.
/// If you do want to extract the file, set <c>ZipEntry.ExtractExistingFile</c>
/// to <c>OverwriteSilently</c>. If you want to cancel the Extraction, set
/// <c>ZipProgressEventArgs.Cancel</c> to true. Cancelling differs from using
/// DoNotOverwrite in that a cancel will not extract any further entries, if
/// there are any. (For COM clients, the value of this enum is a 3.)
/// </summary>
InvokeExtractProgressEvent,
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,114 @@
// OffsetStream.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-August-27 12:50:35>
//
// ------------------------------------------------------------------
//
// This module defines logic for handling reading of zip archives embedded
// into larger streams. The initial position of the stream serves as
// the base offset for all future Seek() operations.
//
// ------------------------------------------------------------------
using System;
using System.IO;
namespace Ionic.Zip
{
internal class OffsetStream : System.IO.Stream, System.IDisposable
{
private Int64 _originalPosition;
private Stream _innerStream;
public OffsetStream(Stream s)
: base()
{
_originalPosition = s.Position;
_innerStream = s;
}
public override int Read(byte[] buffer, int offset, int count)
{
return _innerStream.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override bool CanRead
{
get { return _innerStream.CanRead; }
}
public override bool CanSeek
{
get { return _innerStream.CanSeek; }
}
public override bool CanWrite
{
get { return false; }
}
public override void Flush()
{
_innerStream.Flush();
}
public override long Length
{
get
{
return _innerStream.Length;
}
}
public override long Position
{
get { return _innerStream.Position - _originalPosition; }
set { _innerStream.Position = _originalPosition + value; }
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
return _innerStream.Seek(_originalPosition + offset, origin) - _originalPosition;
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
void IDisposable.Dispose()
{
Close();
}
public override void Close()
{
base.Close();
}
}
}
@@ -0,0 +1,901 @@
// Shared.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2006-2011 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// Last Saved: <2011-August-02 19:41:01>
//
// ------------------------------------------------------------------
//
// This module defines some shared utility classes and methods.
//
// Created: Tue, 27 Mar 2007 15:30
//
using System;
using System.IO;
using System.Security.Permissions;
namespace Ionic.Zip
{
/// <summary>
/// Collects general purpose utility methods.
/// </summary>
internal static class SharedUtilities
{
/// private null constructor
//private SharedUtilities() { }
// workitem 8423
public static Int64 GetFileLength(string fileName)
{
if (!File.Exists(fileName))
throw new System.IO.FileNotFoundException(fileName);
long fileLength = 0L;
FileShare fs = FileShare.ReadWrite;
#if !NETCF
// FileShare.Delete is not defined for the Compact Framework
fs |= FileShare.Delete;
#endif
using (var s = File.Open(fileName, FileMode.Open, FileAccess.Read, fs))
{
fileLength = s.Length;
}
return fileLength;
}
[System.Diagnostics.Conditional("NETCF")]
public static void Workaround_Ladybug318918(Stream s)
{
// This is a workaround for this issue:
// https://connect.microsoft.com/VisualStudio/feedback/details/318918
// It's required only on NETCF.
s.Flush();
}
#if LEGACY
/// <summary>
/// Round the given DateTime value to an even second value.
/// </summary>
///
/// <remarks>
/// <para>
/// Round up in the case of an odd second value. The rounding does not consider
/// fractional seconds.
/// </para>
/// <para>
/// This is useful because the Zip spec allows storage of time only to the nearest
/// even second. So if you want to compare the time of an entry in the archive with
/// it's actual time in the filesystem, you need to round the actual filesystem
/// time, or use a 2-second threshold for the comparison.
/// </para>
/// <para>
/// This is most nautrally an extension method for the DateTime class but this
/// library is built for .NET 2.0, not for .NET 3.5; This means extension methods
/// are a no-no.
/// </para>
/// </remarks>
/// <param name="source">The DateTime value to round</param>
/// <returns>The ruonded DateTime value</returns>
public static DateTime RoundToEvenSecond(DateTime source)
{
// round to nearest second:
if ((source.Second % 2) == 1)
source += new TimeSpan(0, 0, 1);
DateTime dtRounded = new DateTime(source.Year, source.Month, source.Day, source.Hour, source.Minute, source.Second);
//if (source.Millisecond >= 500) dtRounded = dtRounded.AddSeconds(1);
return dtRounded;
}
#endif
#if YOU_LIKE_REDUNDANT_CODE
internal static string NormalizePath(string path)
{
// remove leading single dot slash
if (path.StartsWith(".\\")) path = path.Substring(2);
// remove intervening dot-slash
path = path.Replace("\\.\\", "\\");
// remove double dot when preceded by a directory name
var re = new System.Text.RegularExpressions.Regex(@"^(.*\\)?([^\\\.]+\\\.\.\\)(.+)$");
path = re.Replace(path, "$1$3");
return path;
}
#endif
private static System.Text.RegularExpressions.Regex doubleDotRegex1 =
new System.Text.RegularExpressions.Regex(@"^(.*/)?([^/\\.]+/\\.\\./)(.+)$");
private static string SimplifyFwdSlashPath(string path)
{
if (path.StartsWith("./")) path = path.Substring(2);
path = path.Replace("/./", "/");
// Replace foo/anything/../bar with foo/bar
path = doubleDotRegex1.Replace(path, "$1$3");
return path;
}
/// <summary>
/// Utility routine for transforming path names from filesystem format (on Windows that means backslashes) to
/// a format suitable for use within zipfiles. This means trimming the volume letter and colon (if any) And
/// swapping backslashes for forward slashes.
/// </summary>
/// <param name="pathName">source path.</param>
/// <returns>transformed path</returns>
public static string NormalizePathForUseInZipFile(string pathName)
{
// boundary case
if (String.IsNullOrEmpty(pathName)) return pathName;
// trim volume if necessary
if ((pathName.Length >= 2) && ((pathName[1] == ':') && (pathName[2] == '\\')))
pathName = pathName.Substring(3);
// swap slashes
pathName = pathName.Replace('\\', '/');
// trim all leading slashes
while (pathName.StartsWith("/")) pathName = pathName.Substring(1);
return SimplifyFwdSlashPath(pathName);
}
static System.Text.Encoding ibm437 = System.Text.Encoding.GetEncoding("IBM437");
static System.Text.Encoding utf8 = System.Text.Encoding.GetEncoding("UTF-8");
internal static byte[] StringToByteArray(string value, System.Text.Encoding encoding)
{
byte[] a = encoding.GetBytes(value);
return a;
}
internal static byte[] StringToByteArray(string value)
{
return StringToByteArray(value, ibm437);
}
//internal static byte[] Utf8StringToByteArray(string value)
//{
// return StringToByteArray(value, utf8);
//}
//internal static string StringFromBuffer(byte[] buf, int maxlength)
//{
// return StringFromBuffer(buf, maxlength, ibm437);
//}
internal static string Utf8StringFromBuffer(byte[] buf)
{
return StringFromBuffer(buf, utf8);
}
internal static string StringFromBuffer(byte[] buf, System.Text.Encoding encoding)
{
// this form of the GetString() method is required for .NET CF compatibility
string s = encoding.GetString(buf, 0, buf.Length);
return s;
}
internal static int ReadSignature(System.IO.Stream s)
{
int x = 0;
try { x = _ReadFourBytes(s, "n/a"); }
catch (BadReadException) { }
return x;
}
internal static int ReadEntrySignature(System.IO.Stream s)
{
// handle the case of ill-formatted zip archives - includes a data descriptor
// when none is expected.
int x = 0;
try
{
x = _ReadFourBytes(s, "n/a");
if (x == ZipConstants.ZipEntryDataDescriptorSignature)
{
// advance past data descriptor - 12 bytes if not zip64
s.Seek(12, SeekOrigin.Current);
// workitem 10178
Workaround_Ladybug318918(s);
x = _ReadFourBytes(s, "n/a");
if (x != ZipConstants.ZipEntrySignature)
{
// Maybe zip64 was in use for the prior entry.
// Therefore, skip another 8 bytes.
s.Seek(8, SeekOrigin.Current);
// workitem 10178
Workaround_Ladybug318918(s);
x = _ReadFourBytes(s, "n/a");
if (x != ZipConstants.ZipEntrySignature)
{
// seek back to the first spot
s.Seek(-24, SeekOrigin.Current);
// workitem 10178
Workaround_Ladybug318918(s);
x = _ReadFourBytes(s, "n/a");
}
}
}
}
catch (BadReadException) { }
return x;
}
internal static int ReadInt(System.IO.Stream s)
{
return _ReadFourBytes(s, "Could not read block - no data! (position 0x{0:X8})");
}
private static int _ReadFourBytes(System.IO.Stream s, string message)
{
int n = 0;
byte[] block = new byte[4];
#if NETCF
// workitem 9181
// Reading here in NETCF sometimes reads "backwards". Seems to happen for
// larger files. Not sure why. Maybe an error in caching. If the data is:
//
// 00100210: 9efa 0f00 7072 6f6a 6563 742e 6963 7750 ....project.icwP
// 00100220: 4b05 0600 0000 0006 0006 0091 0100 008e K...............
// 00100230: 0010 0000 00 .....
//
// ...and the stream Position is 10021F, then a Read of 4 bytes is returning
// 50776369, instead of 06054b50. This seems to happen the 2nd time Read()
// is called from that Position..
//
// submitted to connect.microsoft.com
// https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=318918#tabs
//
for (int i = 0; i < block.Length; i++)
{
n+= s.Read(block, i, 1);
}
#else
n = s.Read(block, 0, block.Length);
#endif
if (n != block.Length) throw new BadReadException(String.Format(message, s.Position));
int data = unchecked((((block[3] * 256 + block[2]) * 256) + block[1]) * 256 + block[0]);
return data;
}
/// <summary>
/// Finds a signature in the zip stream. This is useful for finding
/// the end of a zip entry, for example, or the beginning of the next ZipEntry.
/// </summary>
///
/// <remarks>
/// <para>
/// Scans through 64k at a time.
/// </para>
///
/// <para>
/// If the method fails to find the requested signature, the stream Position
/// after completion of this method is unchanged. If the method succeeds in
/// finding the requested signature, the stream position after completion is
/// direct AFTER the signature found in the stream.
/// </para>
/// </remarks>
///
/// <param name="stream">The stream to search</param>
/// <param name="SignatureToFind">The 4-byte signature to find</param>
/// <returns>The number of bytes read</returns>
internal static long FindSignature(System.IO.Stream stream, int SignatureToFind)
{
long startingPosition = stream.Position;
int BATCH_SIZE = 65536; // 8192;
byte[] targetBytes = new byte[4];
targetBytes[0] = (byte)(SignatureToFind >> 24);
targetBytes[1] = (byte)((SignatureToFind & 0x00FF0000) >> 16);
targetBytes[2] = (byte)((SignatureToFind & 0x0000FF00) >> 8);
targetBytes[3] = (byte)(SignatureToFind & 0x000000FF);
byte[] batch = new byte[BATCH_SIZE];
int n = 0;
bool success = false;
do
{
n = stream.Read(batch, 0, batch.Length);
if (n != 0)
{
for (int i = 0; i < n; i++)
{
if (batch[i] == targetBytes[3])
{
long curPosition = stream.Position;
stream.Seek(i - n, System.IO.SeekOrigin.Current);
// workitem 10178
Workaround_Ladybug318918(stream);
// workitem 7711
int sig = ReadSignature(stream);
success = (sig == SignatureToFind);
if (!success)
{
stream.Seek(curPosition, System.IO.SeekOrigin.Begin);
// workitem 10178
Workaround_Ladybug318918(stream);
}
else
break; // out of for loop
}
}
}
else break;
if (success) break;
} while (true);
if (!success)
{
stream.Seek(startingPosition, System.IO.SeekOrigin.Begin);
// workitem 10178
Workaround_Ladybug318918(stream);
return -1; // or throw?
}
// subtract 4 for the signature.
long bytesRead = (stream.Position - startingPosition) - 4;
return bytesRead;
}
// If I have a time in the .NET environment, and I want to use it for
// SetWastWriteTime() etc, then I need to adjust it for Win32.
internal static DateTime AdjustTime_Reverse(DateTime time)
{
if (time.Kind == DateTimeKind.Utc) return time;
DateTime adjusted = time;
if (DateTime.Now.IsDaylightSavingTime() && !time.IsDaylightSavingTime())
adjusted = time - new System.TimeSpan(1, 0, 0);
else if (!DateTime.Now.IsDaylightSavingTime() && time.IsDaylightSavingTime())
adjusted = time + new System.TimeSpan(1, 0, 0);
return adjusted;
}
#if NECESSARY
// If I read a time from a file with GetLastWriteTime() (etc), I need
// to adjust it for display in the .NET environment.
internal static DateTime AdjustTime_Forward(DateTime time)
{
if (time.Kind == DateTimeKind.Utc) return time;
DateTime adjusted = time;
if (DateTime.Now.IsDaylightSavingTime() && !time.IsDaylightSavingTime())
adjusted = time + new System.TimeSpan(1, 0, 0);
else if (!DateTime.Now.IsDaylightSavingTime() && time.IsDaylightSavingTime())
adjusted = time - new System.TimeSpan(1, 0, 0);
return adjusted;
}
#endif
internal static DateTime PackedToDateTime(Int32 packedDateTime)
{
// workitem 7074 & workitem 7170
if (packedDateTime == 0xFFFF || packedDateTime == 0)
return new System.DateTime(1995, 1, 1, 0, 0, 0, 0); // return a fixed date when none is supplied.
Int16 packedTime = unchecked((Int16)(packedDateTime & 0x0000ffff));
Int16 packedDate = unchecked((Int16)((packedDateTime & 0xffff0000) >> 16));
int year = 1980 + ((packedDate & 0xFE00) >> 9);
int month = (packedDate & 0x01E0) >> 5;
int day = packedDate & 0x001F;
int hour = (packedTime & 0xF800) >> 11;
int minute = (packedTime & 0x07E0) >> 5;
//int second = packedTime & 0x001F;
int second = (packedTime & 0x001F) * 2;
// validation and error checking.
// this is not foolproof but will catch most errors.
if (second >= 60) { minute++; second = 0; }
if (minute >= 60) { hour++; minute = 0; }
if (hour >= 24) { day++; hour = 0; }
DateTime d = System.DateTime.Now;
bool success= false;
try
{
d = new System.DateTime(year, month, day, hour, minute, second, 0);
success= true;
}
catch (System.ArgumentOutOfRangeException)
{
if (year == 1980 && (month == 0 || day == 0))
{
try
{
d = new System.DateTime(1980, 1, 1, hour, minute, second, 0);
success= true;
}
catch (System.ArgumentOutOfRangeException)
{
try
{
d = new System.DateTime(1980, 1, 1, 0, 0, 0, 0);
success= true;
}
catch (System.ArgumentOutOfRangeException) { }
}
}
// workitem 8814
// my god, I can't believe how many different ways applications
// can mess up a simple date format.
else
{
try
{
while (year < 1980) year++;
while (year > 2030) year--;
while (month < 1) month++;
while (month > 12) month--;
while (day < 1) day++;
while (day > 28) day--;
while (minute < 0) minute++;
while (minute > 59) minute--;
while (second < 0) second++;
while (second > 59) second--;
d = new System.DateTime(year, month, day, hour, minute, second, 0);
success= true;
}
catch (System.ArgumentOutOfRangeException) { }
}
}
if (!success)
{
string msg = String.Format("y({0}) m({1}) d({2}) h({3}) m({4}) s({5})", year, month, day, hour, minute, second);
throw new ZipException(String.Format("Bad date/time format in the zip file. ({0})", msg));
}
// workitem 6191
//d = AdjustTime_Reverse(d);
d = DateTime.SpecifyKind(d, DateTimeKind.Local);
return d;
}
internal
static Int32 DateTimeToPacked(DateTime time)
{
// The time is passed in here only for purposes of writing LastModified to the
// zip archive. It should always be LocalTime, but we convert anyway. And,
// since the time is being written out, it needs to be adjusted.
time = time.ToLocalTime();
// workitem 7966
//time = AdjustTime_Forward(time);
// see http://www.vsft.com/hal/dostime.htm for the format
UInt16 packedDate = (UInt16)((time.Day & 0x0000001F) | ((time.Month << 5) & 0x000001E0) | (((time.Year - 1980) << 9) & 0x0000FE00));
UInt16 packedTime = (UInt16)((time.Second / 2 & 0x0000001F) | ((time.Minute << 5) & 0x000007E0) | ((time.Hour << 11) & 0x0000F800));
Int32 result = (Int32)(((UInt32)(packedDate << 16)) | packedTime);
return result;
}
/// <summary>
/// Create a pseudo-random filename, suitable for use as a temporary
/// file, and open it.
/// </summary>
/// <remarks>
/// <para>
/// The System.IO.Path.GetRandomFileName() method is not available on
/// the Compact Framework, so this library provides its own substitute
/// on NETCF.
/// </para>
/// <para>
/// This method produces a filename of the form
/// DotNetZip-xxxxxxxx.tmp, where xxxxxxxx is replaced by randomly
/// chosen characters, and creates that file.
/// </para>
/// </remarks>
public static void CreateAndOpenUniqueTempFile(string dir,
out Stream fs,
out string filename)
{
// workitem 9763
// http://dotnet.org.za/markn/archive/2006/04/15/51594.aspx
// try 3 times:
for (int i = 0; i < 3; i++)
{
try
{
filename = Path.Combine(dir, InternalGetTempFileName());
fs = new FileStream(filename, FileMode.CreateNew);
return;
}
catch (IOException)
{
if (i == 2) throw;
}
}
throw new IOException();
}
#if NETCF || SILVERLIGHT
public static string InternalGetTempFileName()
{
return "DotNetZip-" + GenerateRandomStringImpl(8,0) + ".tmp";
}
internal static string GenerateRandomStringImpl(int length, int delta)
{
bool WantMixedCase = (delta == 0);
System.Random rnd = new System.Random();
string result = "";
char[] a = new char[length];
for (int i = 0; i < length; i++)
{
// delta == 65 means uppercase
// delta == 97 means lowercase
if (WantMixedCase)
delta = (rnd.Next(2) == 0) ? 65 : 97;
a[i] = (char)(rnd.Next(26) + delta);
}
result = new System.String(a);
return result;
}
#else
public static string InternalGetTempFileName()
{
return "DotNetZip-" + Path.GetRandomFileName().Substring(0, 8) + ".tmp";
}
#endif
/// <summary>
/// Workitem 7889: handle ERROR_LOCK_VIOLATION during read
/// </summary>
/// <remarks>
/// This could be gracefully handled with an extension attribute, but
/// This assembly is built for .NET 2.0, so I cannot use them.
/// </remarks>
internal static int ReadWithRetry(System.IO.Stream s, byte[] buffer, int offset, int count, string FileName)
{
int n = 0;
bool done = false;
#if !NETCF && !SILVERLIGHT
int retries = 0;
#endif
do
{
try
{
n = s.Read(buffer, offset, count);
done = true;
}
#if NETCF || SILVERLIGHT
catch (System.IO.IOException)
{
throw;
}
#else
catch (System.IO.IOException ioexc1)
{
// Check if we can call GetHRForException,
// which makes unmanaged code calls.
var p = new SecurityPermission(SecurityPermissionFlag.UnmanagedCode);
if (p.IsUnrestricted())
{
uint hresult = _HRForException(ioexc1);
if (hresult != 0x80070021) // ERROR_LOCK_VIOLATION
throw new System.IO.IOException(String.Format("Cannot read file {0}", FileName), ioexc1);
retries++;
if (retries > 10)
throw new System.IO.IOException(String.Format("Cannot read file {0}, at offset 0x{1:X8} after 10 retries", FileName, offset), ioexc1);
// max time waited on last retry = 250 + 10*550 = 5.75s
// aggregate time waited after 10 retries: 250 + 55*550 = 30.5s
System.Threading.Thread.Sleep(250 + retries * 550);
}
else
{
// The permission.Demand() failed. Therefore, we cannot call
// GetHRForException, and cannot do the subtle handling of
// ERROR_LOCK_VIOLATION. Just bail.
throw;
}
}
#endif
}
while (!done);
return n;
}
#if !NETCF
// workitem 8009
//
// This method must remain separate.
//
// Marshal.GetHRForException() is needed to do special exception handling for
// the read. But, that method requires UnmanagedCode permissions, and is marked
// with LinkDemand for UnmanagedCode. In an ASP.NET medium trust environment,
// where UnmanagedCode is restricted, will generate a SecurityException at the
// time of JIT of the method that calls a method that is marked with LinkDemand
// for UnmanagedCode. The SecurityException, if it is restricted, will occur
// when this method is JITed.
//
// The Marshal.GetHRForException() is factored out of ReadWithRetry in order to
// avoid the SecurityException at JIT compile time. Because _HRForException is
// called only when the UnmanagedCode is allowed. This means .NET never
// JIT-compiles this method when UnmanagedCode is disallowed, and thus never
// generates the JIT-compile time exception.
//
#endif
private static uint _HRForException(System.Exception ex1)
{
return unchecked((uint)System.Runtime.InteropServices.Marshal.GetHRForException(ex1));
}
}
/// <summary>
/// A decorator stream. It wraps another stream, and performs bookkeeping
/// to keep track of the stream Position.
/// </summary>
/// <remarks>
/// <para>
/// In some cases, it is not possible to get the Position of a stream, let's
/// say, on a write-only output stream like ASP.NET's
/// <c>Response.OutputStream</c>, or on a different write-only stream
/// provided as the destination for the zip by the application. In this
/// case, programmers can use this counting stream to count the bytes read
/// or written.
/// </para>
/// <para>
/// Consider the scenario of an application that saves a self-extracting
/// archive (SFX), that uses a custom SFX stub.
/// </para>
/// <para>
/// Saving to a filesystem file, the application would open the
/// filesystem file (getting a <c>FileStream</c>), save the custom sfx stub
/// into it, and then call <c>ZipFile.Save()</c>, specifying the same
/// FileStream. <c>ZipFile.Save()</c> does the right thing for the zipentry
/// offsets, by inquiring the Position of the <c>FileStream</c> before writing
/// any data, and then adding that initial offset into any ZipEntry
/// offsets in the zip directory. Everything works fine.
/// </para>
/// <para>
/// Now suppose the application is an ASPNET application and it saves
/// directly to <c>Response.OutputStream</c>. It's not possible for DotNetZip to
/// inquire the <c>Position</c>, so the offsets for the SFX will be wrong.
/// </para>
/// <para>
/// The workaround is for the application to use this class to wrap
/// <c>HttpResponse.OutputStream</c>, then write the SFX stub and the ZipFile
/// into that wrapper stream. Because <c>ZipFile.Save()</c> can inquire the
/// <c>Position</c>, it will then do the right thing with the offsets.
/// </para>
/// </remarks>
public class CountingStream : System.IO.Stream
{
// workitem 12374: this class is now public
private System.IO.Stream _s;
private Int64 _bytesWritten;
private Int64 _bytesRead;
private Int64 _initialOffset;
/// <summary>
/// The constructor.
/// </summary>
/// <param name="stream">The underlying stream</param>
public CountingStream(System.IO.Stream stream)
: base()
{
_s = stream;
try
{
_initialOffset = _s.Position;
}
catch
{
_initialOffset = 0L;
}
}
/// <summary>
/// Gets the wrapped stream.
/// </summary>
public Stream WrappedStream
{
get
{
return _s;
}
}
/// <summary>
/// The count of bytes written out to the stream.
/// </summary>
public Int64 BytesWritten
{
get { return _bytesWritten; }
}
/// <summary>
/// the count of bytes that have been read from the stream.
/// </summary>
public Int64 BytesRead
{
get { return _bytesRead; }
}
/// <summary>
/// Adjust the byte count on the stream.
/// </summary>
///
/// <param name='delta'>
/// the number of bytes to subtract from the count.
/// </param>
///
/// <remarks>
/// <para>
/// Subtract delta from the count of bytes written to the stream.
/// This is necessary when seeking back, and writing additional data,
/// as happens in some cases when saving Zip files.
/// </para>
/// </remarks>
public void Adjust(Int64 delta)
{
_bytesWritten -= delta;
if (_bytesWritten < 0)
throw new InvalidOperationException();
if (_s as CountingStream != null)
((CountingStream)_s).Adjust(delta);
}
/// <summary>
/// The read method.
/// </summary>
/// <param name="buffer">The buffer to hold the data read from the stream.</param>
/// <param name="offset">the offset within the buffer to copy the first byte read.</param>
/// <param name="count">the number of bytes to read.</param>
/// <returns>the number of bytes read, after decryption and decompression.</returns>
public override int Read(byte[] buffer, int offset, int count)
{
int n = _s.Read(buffer, offset, count);
_bytesRead += n;
return n;
}
/// <summary>
/// Write data into the stream.
/// </summary>
/// <param name="buffer">The buffer holding data to write to the stream.</param>
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
/// <param name="count">the number of bytes to write.</param>
public override void Write(byte[] buffer, int offset, int count)
{
if (count == 0) return;
_s.Write(buffer, offset, count);
_bytesWritten += count;
}
/// <summary>
/// Whether the stream can be read.
/// </summary>
public override bool CanRead
{
get { return _s.CanRead; }
}
/// <summary>
/// Whether it is possible to call Seek() on the stream.
/// </summary>
public override bool CanSeek
{
get { return _s.CanSeek; }
}
/// <summary>
/// Whether it is possible to call Write() on the stream.
/// </summary>
public override bool CanWrite
{
get { return _s.CanWrite; }
}
/// <summary>
/// Flushes the underlying stream.
/// </summary>
public override void Flush()
{
_s.Flush();
}
/// <summary>
/// The length of the underlying stream.
/// </summary>
public override long Length
{
get { return _s.Length; } // bytesWritten??
}
/// <summary>
/// Returns the sum of number of bytes written, plus the initial
/// offset before writing.
/// </summary>
public long ComputedPosition
{
get { return _initialOffset + _bytesWritten; }
}
/// <summary>
/// The Position of the stream.
/// </summary>
public override long Position
{
get { return _s.Position; }
set
{
_s.Seek(value, System.IO.SeekOrigin.Begin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_s);
}
}
/// <summary>
/// Seek in the stream.
/// </summary>
/// <param name="offset">the offset point to seek to</param>
/// <param name="origin">the reference point from which to seek</param>
/// <returns>The new position</returns>
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
return _s.Seek(offset, origin);
}
/// <summary>
/// Set the length of the underlying stream. Be careful with this!
/// </summary>
///
/// <param name='value'>the length to set on the underlying stream.</param>
public override void SetLength(long value)
{
_s.SetLength(value);
}
}
}
@@ -0,0 +1,941 @@
//#define Trace
// WinZipAes.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009-2011 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-12 13:42:06>
//
// ------------------------------------------------------------------
//
// This module defines the classes for dealing with WinZip's AES encryption,
// according to the specifications for the format available on WinZip's website.
//
// Created: January 2009
//
// ------------------------------------------------------------------
using System;
using System.IO;
using System.Collections.Generic;
using System.Security.Cryptography;
#if AESCRYPTO
namespace Ionic.Zip
{
/// <summary>
/// This is a helper class supporting WinZip AES encryption.
/// This class is intended for use only by the DotNetZip library.
/// </summary>
///
/// <remarks>
/// Most uses of the DotNetZip library will not involve direct calls into
/// the WinZipAesCrypto class. Instead, the WinZipAesCrypto class is
/// instantiated and used by the ZipEntry() class when WinZip AES
/// encryption or decryption on an entry is employed.
/// </remarks>
internal class WinZipAesCrypto
{
internal byte[] _Salt;
internal byte[] _providedPv;
internal byte[] _generatedPv;
internal int _KeyStrengthInBits;
private byte[] _MacInitializationVector;
private byte[] _StoredMac;
private byte[] _keyBytes;
private Int16 PasswordVerificationStored;
private Int16 PasswordVerificationGenerated;
private int Rfc2898KeygenIterations = 1000;
private string _Password;
private bool _cryptoGenerated ;
private WinZipAesCrypto(string password, int KeyStrengthInBits)
{
_Password = password;
_KeyStrengthInBits = KeyStrengthInBits;
}
public static WinZipAesCrypto Generate(string password, int KeyStrengthInBits)
{
WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits);
int saltSizeInBytes = c._KeyStrengthInBytes / 2;
c._Salt = new byte[saltSizeInBytes];
Random rnd = new Random();
rnd.NextBytes(c._Salt);
return c;
}
public static WinZipAesCrypto ReadFromStream(string password, int KeyStrengthInBits, Stream s)
{
// from http://www.winzip.com/aes_info.htm
//
// Size(bytes) Content
// -----------------------------------
// Variable Salt value
// 2 Password verification value
// Variable Encrypted file data
// 10 Authentication code
//
// ZipEntry.CompressedSize represents the size of all of those elements.
// salt size varies with key length:
// 128 bit key => 8 bytes salt
// 192 bits => 12 bytes salt
// 256 bits => 16 bytes salt
WinZipAesCrypto c = new WinZipAesCrypto(password, KeyStrengthInBits);
int saltSizeInBytes = c._KeyStrengthInBytes / 2;
c._Salt = new byte[saltSizeInBytes];
c._providedPv = new byte[2];
s.Read(c._Salt, 0, c._Salt.Length);
s.Read(c._providedPv, 0, c._providedPv.Length);
c.PasswordVerificationStored = (Int16)(c._providedPv[0] + c._providedPv[1] * 256);
if (password != null)
{
c.PasswordVerificationGenerated = (Int16)(c.GeneratedPV[0] + c.GeneratedPV[1] * 256);
if (c.PasswordVerificationGenerated != c.PasswordVerificationStored)
throw new BadPasswordException("bad password");
}
return c;
}
public byte[] GeneratedPV
{
get
{
if (!_cryptoGenerated) _GenerateCryptoBytes();
return _generatedPv;
}
}
public byte[] Salt
{
get
{
return _Salt;
}
}
private int _KeyStrengthInBytes
{
get
{
return _KeyStrengthInBits / 8;
}
}
public int SizeOfEncryptionMetadata
{
get
{
// 10 bytes after, (n-10) before the compressed data
return _KeyStrengthInBytes / 2 + 10 + 2;
}
}
public string Password
{
set
{
_Password = value;
if (_Password != null)
{
PasswordVerificationGenerated = (Int16)(GeneratedPV[0] + GeneratedPV[1] * 256);
if (PasswordVerificationGenerated != PasswordVerificationStored)
throw new Ionic.Zip.BadPasswordException();
}
}
private get
{
return _Password;
}
}
private void _GenerateCryptoBytes()
{
//Console.WriteLine(" provided password: '{0}'", _Password);
System.Security.Cryptography.Rfc2898DeriveBytes rfc2898 =
new System.Security.Cryptography.Rfc2898DeriveBytes(_Password, Salt, Rfc2898KeygenIterations);
_keyBytes = rfc2898.GetBytes(_KeyStrengthInBytes); // 16 or 24 or 32 ???
_MacInitializationVector = rfc2898.GetBytes(_KeyStrengthInBytes);
_generatedPv = rfc2898.GetBytes(2);
_cryptoGenerated = true;
}
public byte[] KeyBytes
{
get
{
if (!_cryptoGenerated) _GenerateCryptoBytes();
return _keyBytes;
}
}
public byte[] MacIv
{
get
{
if (!_cryptoGenerated) _GenerateCryptoBytes();
return _MacInitializationVector;
}
}
public byte[] CalculatedMac;
public void ReadAndVerifyMac(System.IO.Stream s)
{
bool invalid = false;
// read integrityCheckVector.
// caller must ensure that the file pointer is in the right spot!
_StoredMac = new byte[10]; // aka "authentication code"
s.Read(_StoredMac, 0, _StoredMac.Length);
if (_StoredMac.Length != CalculatedMac.Length)
invalid = true;
if (!invalid)
{
for (int i = 0; i < _StoredMac.Length; i++)
{
if (_StoredMac[i] != CalculatedMac[i])
invalid = true;
}
}
if (invalid)
throw new Ionic.Zip.BadStateException("The MAC does not match.");
}
}
#region DONT_COMPILE_BUT_KEEP_FOR_POTENTIAL_FUTURE_USE
#if NO
internal class Util
{
private static void _Format(System.Text.StringBuilder sb1,
byte[] b,
int offset,
int length)
{
System.Text.StringBuilder sb2 = new System.Text.StringBuilder();
sb1.Append("0000 ");
int i;
for (i = 0; i < length; i++)
{
int x = offset+i;
if (i != 0 && i % 16 == 0)
{
sb1.Append(" ")
.Append(sb2)
.Append("\n")
.Append(String.Format("{0:X4} ", i));
sb2.Remove(0,sb2.Length);
}
sb1.Append(System.String.Format("{0:X2} ", b[x]));
if (b[x] >=32 && b[x] <= 126)
sb2.Append((char)b[x]);
else
sb2.Append(".");
}
if (sb2.Length > 0)
{
sb1.Append(new String(' ', ((16 - i%16) * 3) + 4))
.Append(sb2);
}
}
internal static string FormatByteArray(byte[] b, int limit)
{
System.Text.StringBuilder sb1 = new System.Text.StringBuilder();
if ((limit * 2 > b.Length) || limit == 0)
{
_Format(sb1, b, 0, b.Length);
}
else
{
// first N bytes of the buffer
_Format(sb1, b, 0, limit);
if (b.Length > limit * 2)
sb1.Append(String.Format("\n ...({0} other bytes here)....\n", b.Length - limit * 2));
// last N bytes of the buffer
_Format(sb1, b, b.Length - limit, limit);
}
return sb1.ToString();
}
internal static string FormatByteArray(byte[] b)
{
return FormatByteArray(b, 0);
}
}
#endif
#endregion
/// <summary>
/// A stream that encrypts as it writes, or decrypts as it reads. The
/// Crypto is AES in CTR (counter) mode, which is compatible with the AES
/// encryption employed by WinZip 12.0.
/// </summary>
/// <remarks>
/// <para>
/// The AES/CTR encryption protocol used by WinZip works like this:
///
/// - start with a counter, initialized to zero.
///
/// - to encrypt, take the data by 16-byte blocks. For each block:
/// - apply the transform to the counter
/// - increement the counter
/// - XOR the result of the transform with the plaintext to
/// get the ciphertext.
/// - compute the mac on the encrypted bytes
/// - when finished with all blocks, store the computed MAC.
///
/// - to decrypt, take the data by 16-byte blocks. For each block:
/// - compute the mac on the encrypted bytes,
/// - apply the transform to the counter
/// - increement the counter
/// - XOR the result of the transform with the ciphertext to
/// get the plaintext.
/// - when finished with all blocks, compare the computed MAC against
/// the stored MAC
///
/// </para>
/// </remarks>
//
internal class WinZipAesCipherStream : Stream
{
private WinZipAesCrypto _params;
private System.IO.Stream _s;
private CryptoMode _mode;
private int _nonce;
private bool _finalBlock;
internal HMACSHA1 _mac;
// Use RijndaelManaged from .NET 2.0.
// AesManaged came in .NET 3.5, but we want to limit
// dependency to .NET 2.0. AES is just a restricted form
// of Rijndael (fixed block size of 128, some crypto modes not supported).
internal RijndaelManaged _aesCipher;
internal ICryptoTransform _xform;
private const int BLOCK_SIZE_IN_BYTES = 16;
private byte[] counter = new byte[BLOCK_SIZE_IN_BYTES];
private byte[] counterOut = new byte[BLOCK_SIZE_IN_BYTES];
// I've had a problem when wrapping a WinZipAesCipherStream inside
// a DeflateStream. Calling Read() on the DeflateStream results in
// a Read() on the WinZipAesCipherStream, but the buffer is larger
// than the total size of the encrypted data, and larger than the
// initial Read() on the DeflateStream! When the encrypted
// bytestream is embedded within a larger stream (As in a zip
// archive), the Read() doesn't fail with EOF. This causes bad
// data to be returned, and it messes up the MAC.
// This field is used to provide a hard-stop to the size of
// data that can be read from the stream. In Read(), if the buffer or
// read request goes beyond the stop, we truncate it.
private long _length;
private long _totalBytesXferred;
private byte[] _PendingWriteBlock;
private int _pendingCount;
private byte[] _iobuf;
/// <summary>
/// The constructor.
/// </summary>
/// <param name="s">The underlying stream</param>
/// <param name="mode">To either encrypt or decrypt.</param>
/// <param name="cryptoParams">The pre-initialized WinZipAesCrypto object.</param>
/// <param name="length">The maximum number of bytes to read from the stream.</param>
internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, long length, CryptoMode mode)
: this(s, cryptoParams, mode)
{
// don't read beyond this limit!
_length = length;
//Console.WriteLine("max length of AES stream: {0}", _length);
}
#if WANT_TRACE
Stream untransformed;
String traceFileUntransformed;
Stream transformed;
String traceFileTransformed;
#endif
internal WinZipAesCipherStream(System.IO.Stream s, WinZipAesCrypto cryptoParams, CryptoMode mode)
: base()
{
TraceOutput("-------------------------------------------------------");
TraceOutput("Create {0:X8}", this.GetHashCode());
_params = cryptoParams;
_s = s;
_mode = mode;
_nonce = 1;
if (_params == null)
throw new BadPasswordException("Supply a password to use AES encryption.");
int keySizeInBits = _params.KeyBytes.Length * 8;
if (keySizeInBits != 256 && keySizeInBits != 128 && keySizeInBits != 192)
throw new ArgumentOutOfRangeException("keysize",
"size of key must be 128, 192, or 256");
_mac = new HMACSHA1(_params.MacIv);
_aesCipher = new System.Security.Cryptography.RijndaelManaged();
_aesCipher.BlockSize = 128;
_aesCipher.KeySize = keySizeInBits; // 128, 192, 256
_aesCipher.Mode = CipherMode.ECB;
_aesCipher.Padding = PaddingMode.None;
byte[] iv = new byte[BLOCK_SIZE_IN_BYTES]; // all zeroes
// Create an ENCRYPTOR, regardless whether doing decryption or encryption.
// It is reflexive.
_xform = _aesCipher.CreateEncryptor(_params.KeyBytes, iv);
if (_mode == CryptoMode.Encrypt)
{
_iobuf = new byte[2048];
_PendingWriteBlock = new byte[BLOCK_SIZE_IN_BYTES];
}
#if WANT_TRACE
traceFileUntransformed = "unpack\\WinZipAesCipherStream.trace.untransformed.out";
traceFileTransformed = "unpack\\WinZipAesCipherStream.trace.transformed.out";
untransformed = System.IO.File.Create(traceFileUntransformed);
transformed = System.IO.File.Create(traceFileTransformed);
#endif
}
private void XorInPlace(byte[] buffer, int offset, int count)
{
for (int i = 0; i < count; i++)
{
buffer[offset + i] = (byte)(counterOut[i] ^ buffer[offset + i]);
}
}
private void WriteTransformOneBlock(byte[] buffer, int offset)
{
System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
_xform.TransformBlock(counter,
0,
BLOCK_SIZE_IN_BYTES,
counterOut,
0);
XorInPlace(buffer, offset, BLOCK_SIZE_IN_BYTES);
_mac.TransformBlock(buffer, offset, BLOCK_SIZE_IN_BYTES, null, 0);
}
private void WriteTransformBlocks(byte[] buffer, int offset, int count)
{
int posn = offset;
int last = count + offset;
while (posn < buffer.Length && posn < last)
{
WriteTransformOneBlock (buffer, posn);
posn += BLOCK_SIZE_IN_BYTES;
}
}
private void WriteTransformFinalBlock()
{
if (_pendingCount == 0)
throw new InvalidOperationException("No bytes available.");
if (_finalBlock)
throw new InvalidOperationException("The final block has already been transformed.");
System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
counterOut = _xform.TransformFinalBlock(counter,
0,
BLOCK_SIZE_IN_BYTES);
XorInPlace(_PendingWriteBlock, 0, _pendingCount);
_mac.TransformFinalBlock(_PendingWriteBlock, 0, _pendingCount);
_finalBlock = true;
}
private int ReadTransformOneBlock(byte[] buffer, int offset, int last)
{
if (_finalBlock)
throw new NotSupportedException();
int bytesRemaining = last - offset;
int bytesToRead = (bytesRemaining > BLOCK_SIZE_IN_BYTES)
? BLOCK_SIZE_IN_BYTES
: bytesRemaining;
// update the counter
System.Array.Copy(BitConverter.GetBytes(_nonce++), 0, counter, 0, 4);
// Determine if this is the final block
if ((bytesToRead == bytesRemaining) &&
(_length > 0) &&
(_totalBytesXferred + last == _length))
{
_mac.TransformFinalBlock(buffer, offset, bytesToRead);
counterOut = _xform.TransformFinalBlock(counter,
0,
BLOCK_SIZE_IN_BYTES);
_finalBlock = true;
}
else
{
_mac.TransformBlock(buffer, offset, bytesToRead, null, 0);
_xform.TransformBlock(counter,
0, // offset
BLOCK_SIZE_IN_BYTES,
counterOut,
0); // offset
}
XorInPlace(buffer, offset, bytesToRead);
return bytesToRead;
}
private void ReadTransformBlocks(byte[] buffer, int offset, int count)
{
int posn = offset;
int last = count + offset;
while (posn < buffer.Length && posn < last )
{
int n = ReadTransformOneBlock (buffer, posn, last);
posn += n;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
if (_mode == CryptoMode.Encrypt)
throw new NotSupportedException();
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset",
"Must not be less than zero.");
if (count < 0)
throw new ArgumentOutOfRangeException("count",
"Must not be less than zero.");
if (buffer.Length < offset + count)
throw new ArgumentException("The buffer is too small");
// When I wrap a WinZipAesStream in a DeflateStream, the
// DeflateStream asks its captive to read 4k blocks, even if the
// encrypted bytestream is smaller than that. This is a way to
// limit the number of bytes read.
int bytesToRead = count;
if (_totalBytesXferred >= _length)
{
return 0; // EOF
}
long bytesRemaining = _length - _totalBytesXferred;
if (bytesRemaining < count) bytesToRead = (int)bytesRemaining;
int n = _s.Read(buffer, offset, bytesToRead);
#if WANT_TRACE
untransformed.Write(buffer, offset, bytesToRead);
#endif
ReadTransformBlocks(buffer, offset, bytesToRead);
#if WANT_TRACE
transformed.Write(buffer, offset, bytesToRead);
#endif
_totalBytesXferred += n;
return n;
}
/// <summary>
/// Returns the final HMAC-SHA1-80 for the data that was encrypted.
/// </summary>
public byte[] FinalAuthentication
{
get
{
if (!_finalBlock)
{
// special-case zero-byte files
if ( _totalBytesXferred != 0)
throw new BadStateException("The final hash has not been computed.");
// Must call ComputeHash on an empty byte array when no data
// has run through the MAC.
byte[] b = { };
_mac.ComputeHash(b);
// fall through
}
byte[] macBytes10 = new byte[10];
System.Array.Copy(_mac.Hash, 0, macBytes10, 0, 10);
return macBytes10;
}
}
public override void Write(byte[] buffer, int offset, int count)
{
if (_finalBlock)
throw new InvalidOperationException("The final block has already been transformed.");
if (_mode == CryptoMode.Decrypt)
throw new NotSupportedException();
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset",
"Must not be less than zero.");
if (count < 0)
throw new ArgumentOutOfRangeException("count",
"Must not be less than zero.");
if (buffer.Length < offset + count)
throw new ArgumentException("The offset and count are too large");
if (count == 0)
return;
TraceOutput("Write off({0}) count({1})", offset, count);
#if WANT_TRACE
untransformed.Write(buffer, offset, count);
#endif
// For proper AES encryption, an AES encryptor application calls
// TransformBlock repeatedly for all 16-byte blocks except the
// last. For the last block, it then calls TransformFinalBlock().
//
// This class is a stream that encrypts via Write(). But, it's not
// possible to recognize which are the "last" bytes from within the call
// to Write(). The caller can call Write() several times in succession,
// with varying buffers. This class only "knows" that the last bytes
// have been written when the app calls Close().
//
// Therefore, this class buffers writes: After completion every Write(),
// a 16-byte "pending" block (_PendingWriteBlock) must hold between 1
// and 16 bytes, which will be used in TransformFinalBlock if the app
// calls Close() immediately thereafter. Also, every write must
// transform any pending bytes, before transforming the data passed in
// to the current call.
//
// In operation, after the first call to Write() and before the call to
// Close(), one full or partial block of bytes is always available,
// pending. At time of Close(), this class calls
// WriteTransformFinalBlock() to flush the pending bytes.
//
// This approach works whether the caller writes in odd-sized batches,
// for example 5000 bytes, or in batches that are neat multiples of the
// blocksize (16).
//
// Logicaly, what we do is this:
//
// 1. if there are fewer than 16 bytes (pending + current), then
// just copy them into th pending buffer and return.
//
// 2. there are more than 16 bytes to write. So, take the leading slice
// of bytes from the current buffer, enough to fill the pending
// buffer. Transform the pending block, and write it out.
//
// 3. Take the trailing slice of bytes (a full block or a partial block),
// and copy it to the pending block for next time.
//
// 4. transform and write all the other blocks, the middle slice.
//
// There are 16 or fewer bytes, so just buffer the bytes.
if (count + _pendingCount <= BLOCK_SIZE_IN_BYTES)
{
Buffer.BlockCopy(buffer,
offset,
_PendingWriteBlock,
_pendingCount,
count);
_pendingCount += count;
// At this point, _PendingWriteBlock contains up to
// BLOCK_SIZE_IN_BYTES bytes, and _pendingCount ranges from 0 to
// BLOCK_SIZE_IN_BYTES. We don't want to xform+write them yet,
// because this may have been the last block. The last block gets
// written at Close().
return;
}
// We know there are at least 17 bytes, counting those in the current
// buffer, along with the (possibly empty) pending block.
int bytesRemaining = count;
int curOffset = offset;
// workitem 12815
//
// xform chunkwise ... Cannot transform in place using the original
// buffer because that is user-maintained.
if (_pendingCount != 0)
{
// We have more than one block of data to write, therefore it is safe
// to xform+write.
int fillCount = BLOCK_SIZE_IN_BYTES - _pendingCount;
// fillCount is possibly zero here. That happens when the pending
// buffer held 16 bytes (one complete block) before this call to
// Write.
if (fillCount > 0)
{
Buffer.BlockCopy(buffer,
offset,
_PendingWriteBlock,
_pendingCount,
fillCount);
// adjust counts:
bytesRemaining -= fillCount;
curOffset += fillCount;
}
// xform and write:
WriteTransformOneBlock(_PendingWriteBlock, 0);
_s.Write(_PendingWriteBlock, 0, BLOCK_SIZE_IN_BYTES);
_totalBytesXferred += BLOCK_SIZE_IN_BYTES;
_pendingCount = 0;
}
// At this point _PendingWriteBlock is empty, and bytesRemaining is
// always greater than 0.
// Now, xform N blocks, where N = floor((bytesRemaining-1)/16). If
// writing 32 bytes, then xform 1 block, and stage the remaining 16. If
// writing 10037 bytes, xform 627 blocks of 16 bytes, then stage the
// remaining 5 bytes.
int blocksToXform = (bytesRemaining-1)/BLOCK_SIZE_IN_BYTES;
_pendingCount = bytesRemaining - (blocksToXform * BLOCK_SIZE_IN_BYTES);
// _pendingCount is ALWAYS between 1 and 16.
// Put the last _pendingCount bytes into the pending block.
Buffer.BlockCopy(buffer,
curOffset + bytesRemaining - _pendingCount,
_PendingWriteBlock,
0,
_pendingCount);
bytesRemaining -= _pendingCount;
_totalBytesXferred += bytesRemaining; // will be true after the loop
// now, transform all the full blocks preceding that.
// bytesRemaining is always a multiple of 16 .
if (blocksToXform > 0)
{
do
{
int c = _iobuf.Length;
if (c > bytesRemaining) c = bytesRemaining;
Buffer.BlockCopy(buffer,
curOffset,
_iobuf,
0,
c);
WriteTransformBlocks(_iobuf, 0, c);
_s.Write(_iobuf, 0, c);
bytesRemaining -= c;
curOffset += c;
} while(bytesRemaining > 0);
}
}
/// <summary>
/// Close the stream.
/// </summary>
public override void Close()
{
TraceOutput("Close {0:X8}", this.GetHashCode());
// In the degenerate case, no bytes have been written to the
// stream at all. Need to check here, and NOT emit the
// final block if Write has not been called.
if (_pendingCount > 0)
{
WriteTransformFinalBlock();
_s.Write(_PendingWriteBlock, 0, _pendingCount);
_totalBytesXferred += _pendingCount;
_pendingCount = 0;
}
_s.Close();
#if WANT_TRACE
untransformed.Close();
transformed.Close();
Console.WriteLine("\nuntransformed bytestream is in {0}", traceFileUntransformed);
Console.WriteLine("\ntransformed bytestream is in {0}", traceFileTransformed);
#endif
TraceOutput("-------------------------------------------------------");
}
/// <summary>
/// Returns true if the stream can be read.
/// </summary>
public override bool CanRead
{
get
{
if (_mode != CryptoMode.Decrypt) return false;
return true;
}
}
/// <summary>
/// Always returns false.
/// </summary>
public override bool CanSeek
{
get { return false; }
}
/// <summary>
/// Returns true if the CryptoMode is Encrypt.
/// </summary>
public override bool CanWrite
{
get { return (_mode == CryptoMode.Encrypt); }
}
/// <summary>
/// Flush the content in the stream.
/// </summary>
public override void Flush()
{
_s.Flush();
}
/// <summary>
/// Getting this property throws a NotImplementedException.
/// </summary>
public override long Length
{
get { throw new NotImplementedException(); }
}
/// <summary>
/// Getting or Setting this property throws a NotImplementedException.
/// </summary>
public override long Position
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
/// <summary>
/// This method throws a NotImplementedException.
/// </summary>
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotImplementedException();
}
/// <summary>
/// This method throws a NotImplementedException.
/// </summary>
public override void SetLength(long value)
{
throw new NotImplementedException();
}
[System.Diagnostics.ConditionalAttribute("Trace")]
private void TraceOutput(string format, params object[] varParams)
{
lock(_outputLock)
{
int tid = System.Threading.Thread.CurrentThread.GetHashCode();
Console.ForegroundColor = (ConsoleColor) (tid % 8 + 8);
Console.Write("{0:000} WZACS ", tid);
Console.WriteLine(format, varParams);
Console.ResetColor();
}
}
private object _outputLock = new Object();
}
}
#endif
@@ -0,0 +1,51 @@
// ZipConstants.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2006, 2007, 2008, 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-August-27 23:22:32>
//
// ------------------------------------------------------------------
//
// This module defines a few constants that are used in the project.
//
// ------------------------------------------------------------------
using System;
namespace Ionic.Zip
{
static class ZipConstants
{
public const UInt32 PackedToRemovableMedia = 0x30304b50;
public const UInt32 Zip64EndOfCentralDirectoryRecordSignature = 0x06064b50;
public const UInt32 Zip64EndOfCentralDirectoryLocatorSignature = 0x07064b50;
public const UInt32 EndOfCentralDirectorySignature = 0x06054b50;
public const int ZipEntrySignature = 0x04034b50;
public const int ZipEntryDataDescriptorSignature = 0x08074b50;
public const int SplitArchiveSignature = 0x08074b50;
public const int ZipDirEntrySignature = 0x02014b50;
// These are dictated by the Zip Spec.See APPNOTE.txt
public const int AesKeySize = 192; // 128, 192, 256
public const int AesBlockSize = 128; // ???
public const UInt16 AesAlgId128 = 0x660E;
public const UInt16 AesAlgId192 = 0x660F;
public const UInt16 AesAlgId256 = 0x6610;
}
}
@@ -0,0 +1,455 @@
// ZipCrypto.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2008, 2009, 2011 Dino Chiesa
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-28 06:30:59>
//
// ------------------------------------------------------------------
//
// This module provides the implementation for "traditional" Zip encryption.
//
// Created Tue Apr 15 17:39:56 2008
//
// ------------------------------------------------------------------
using System;
namespace Ionic.Zip
{
/// <summary>
/// This class implements the "traditional" or "classic" PKZip encryption,
/// which today is considered to be weak. On the other hand it is
/// ubiquitous. This class is intended for use only by the DotNetZip
/// library.
/// </summary>
///
/// <remarks>
/// Most uses of the DotNetZip library will not involve direct calls into
/// the ZipCrypto class. Instead, the ZipCrypto class is instantiated and
/// used by the ZipEntry() class when encryption or decryption on an entry
/// is employed. If for some reason you really wanted to use a weak
/// encryption algorithm in some other application, you might use this
/// library. But you would be much better off using one of the built-in
/// strong encryption libraries in the .NET Framework, like the AES
/// algorithm or SHA.
/// </remarks>
internal class ZipCrypto
{
/// <summary>
/// The default constructor for ZipCrypto.
/// </summary>
///
/// <remarks>
/// This class is intended for internal use by the library only. It's
/// probably not useful to you. Seriously. Stop reading this
/// documentation. It's a waste of your time. Go do something else.
/// Check the football scores. Go get an ice cream with a friend.
/// Seriously.
/// </remarks>
///
private ZipCrypto() { }
public static ZipCrypto ForWrite(string password)
{
ZipCrypto z = new ZipCrypto();
if (password == null)
throw new BadPasswordException("This entry requires a password.");
z.InitCipher(password);
return z;
}
public static ZipCrypto ForRead(string password, ZipEntry e)
{
System.IO.Stream s = e._archiveStream;
e._WeakEncryptionHeader = new byte[12];
byte[] eh = e._WeakEncryptionHeader;
ZipCrypto z = new ZipCrypto();
if (password == null)
throw new BadPasswordException("This entry requires a password.");
z.InitCipher(password);
ZipEntry.ReadWeakEncryptionHeader(s, eh);
// Decrypt the header. This has a side effect of "further initializing the
// encryption keys" in the traditional zip encryption.
byte[] DecryptedHeader = z.DecryptMessage(eh, eh.Length);
// CRC check
// According to the pkzip spec, the final byte in the decrypted header
// is the highest-order byte in the CRC. We check it here.
if (DecryptedHeader[11] != (byte)((e._Crc32 >> 24) & 0xff))
{
// In the case that bit 3 of the general purpose bit flag is set to
// indicate the presence of an 'Extended File Header' or a 'data
// descriptor' (signature 0x08074b50), the last byte of the decrypted
// header is sometimes compared with the high-order byte of the
// lastmodified time, rather than the high-order byte of the CRC, to
// verify the password.
//
// This is not documented in the PKWare Appnote.txt. It was
// discovered this by analysis of the Crypt.c source file in the
// InfoZip library http://www.info-zip.org/pub/infozip/
//
// The reason for this is that the CRC for a file cannot be known
// until the entire contents of the file have been streamed. This
// means a tool would have to read the file content TWICE in its
// entirety in order to perform PKZIP encryption - once to compute
// the CRC, and again to actually encrypt.
//
// This is so important for performance that using the timeblob as
// the verification should be the standard practice for DotNetZip
// when using PKZIP encryption. This implies that bit 3 must be
// set. The downside is that some tools still cannot cope with ZIP
// files that use bit 3. Therefore, DotNetZip DOES NOT force bit 3
// when PKZIP encryption is in use, and instead, reads the stream
// twice.
//
if ((e._BitField & 0x0008) != 0x0008)
{
throw new BadPasswordException("The password did not match.");
}
else if (DecryptedHeader[11] != (byte)((e._TimeBlob >> 8) & 0xff))
{
throw new BadPasswordException("The password did not match.");
}
// We have a good password.
}
else
{
// A-OK
}
return z;
}
/// <summary>
/// From AppNote.txt:
/// unsigned char decrypt_byte()
/// local unsigned short temp
/// temp :=- Key(2) | 2
/// decrypt_byte := (temp * (temp ^ 1)) bitshift-right 8
/// end decrypt_byte
/// </summary>
private byte MagicByte
{
get
{
UInt16 t = (UInt16)((UInt16)(_Keys[2] & 0xFFFF) | 2);
return (byte)((t * (t ^ 1)) >> 8);
}
}
// Decrypting:
// From AppNote.txt:
// loop for i from 0 to 11
// C := buffer(i) ^ decrypt_byte()
// update_keys(C)
// buffer(i) := C
// end loop
/// <summary>
/// Call this method on a cipher text to render the plaintext. You must
/// first initialize the cipher with a call to InitCipher.
/// </summary>
///
/// <example>
/// <code>
/// var cipher = new ZipCrypto();
/// cipher.InitCipher(Password);
/// // Decrypt the header. This has a side effect of "further initializing the
/// // encryption keys" in the traditional zip encryption.
/// byte[] DecryptedMessage = cipher.DecryptMessage(EncryptedMessage);
/// </code>
/// </example>
///
/// <param name="cipherText">The encrypted buffer.</param>
/// <param name="length">
/// The number of bytes to encrypt.
/// Should be less than or equal to CipherText.Length.
/// </param>
///
/// <returns>The plaintext.</returns>
public byte[] DecryptMessage(byte[] cipherText, int length)
{
if (cipherText == null)
throw new ArgumentNullException("cipherText");
if (length > cipherText.Length)
throw new ArgumentOutOfRangeException("length",
"Bad length during Decryption: the length parameter must be smaller than or equal to the size of the destination array.");
byte[] plainText = new byte[length];
for (int i = 0; i < length; i++)
{
byte C = (byte)(cipherText[i] ^ MagicByte);
UpdateKeys(C);
plainText[i] = C;
}
return plainText;
}
/// <summary>
/// This is the converse of DecryptMessage. It encrypts the plaintext
/// and produces a ciphertext.
/// </summary>
///
/// <param name="plainText">The plain text buffer.</param>
///
/// <param name="length">
/// The number of bytes to encrypt.
/// Should be less than or equal to plainText.Length.
/// </param>
///
/// <returns>The ciphertext.</returns>
public byte[] EncryptMessage(byte[] plainText, int length)
{
if (plainText == null)
throw new ArgumentNullException("plaintext");
if (length > plainText.Length)
throw new ArgumentOutOfRangeException("length",
"Bad length during Encryption: The length parameter must be smaller than or equal to the size of the destination array.");
byte[] cipherText = new byte[length];
for (int i = 0; i < length; i++)
{
byte C = plainText[i];
cipherText[i] = (byte)(plainText[i] ^ MagicByte);
UpdateKeys(C);
}
return cipherText;
}
/// <summary>
/// This initializes the cipher with the given password.
/// See AppNote.txt for details.
/// </summary>
///
/// <param name="passphrase">
/// The passphrase for encrypting or decrypting with this cipher.
/// </param>
///
/// <remarks>
/// <code>
/// Step 1 - Initializing the encryption keys
/// -----------------------------------------
/// Start with these keys:
/// Key(0) := 305419896 (0x12345678)
/// Key(1) := 591751049 (0x23456789)
/// Key(2) := 878082192 (0x34567890)
///
/// Then, initialize the keys with a password:
///
/// loop for i from 0 to length(password)-1
/// update_keys(password(i))
/// end loop
///
/// Where update_keys() is defined as:
///
/// update_keys(char):
/// Key(0) := crc32(key(0),char)
/// Key(1) := Key(1) + (Key(0) bitwiseAND 000000ffH)
/// Key(1) := Key(1) * 134775813 + 1
/// Key(2) := crc32(key(2),key(1) rightshift 24)
/// end update_keys
///
/// Where crc32(old_crc,char) is a routine that given a CRC value and a
/// character, returns an updated CRC value after applying the CRC-32
/// algorithm described elsewhere in this document.
///
/// </code>
///
/// <para>
/// After the keys are initialized, then you can use the cipher to
/// encrypt the plaintext.
/// </para>
///
/// <para>
/// Essentially we encrypt the password with the keys, then discard the
/// ciphertext for the password. This initializes the keys for later use.
/// </para>
///
/// </remarks>
public void InitCipher(string passphrase)
{
byte[] p = SharedUtilities.StringToByteArray(passphrase);
for (int i = 0; i < passphrase.Length; i++)
UpdateKeys(p[i]);
}
private void UpdateKeys(byte byteValue)
{
_Keys[0] = (UInt32)crc32.ComputeCrc32((int)_Keys[0], byteValue);
_Keys[1] = _Keys[1] + (byte)_Keys[0];
_Keys[1] = _Keys[1] * 0x08088405 + 1;
_Keys[2] = (UInt32)crc32.ComputeCrc32((int)_Keys[2], (byte)(_Keys[1] >> 24));
}
///// <summary>
///// The byte array representing the seed keys used.
///// Get this after calling InitCipher. The 12 bytes represents
///// what the zip spec calls the "EncryptionHeader".
///// </summary>
//public byte[] KeyHeader
//{
// get
// {
// byte[] result = new byte[12];
// result[0] = (byte)(_Keys[0] & 0xff);
// result[1] = (byte)((_Keys[0] >> 8) & 0xff);
// result[2] = (byte)((_Keys[0] >> 16) & 0xff);
// result[3] = (byte)((_Keys[0] >> 24) & 0xff);
// result[4] = (byte)(_Keys[1] & 0xff);
// result[5] = (byte)((_Keys[1] >> 8) & 0xff);
// result[6] = (byte)((_Keys[1] >> 16) & 0xff);
// result[7] = (byte)((_Keys[1] >> 24) & 0xff);
// result[8] = (byte)(_Keys[2] & 0xff);
// result[9] = (byte)((_Keys[2] >> 8) & 0xff);
// result[10] = (byte)((_Keys[2] >> 16) & 0xff);
// result[11] = (byte)((_Keys[2] >> 24) & 0xff);
// return result;
// }
//}
// private fields for the crypto stuff:
private UInt32[] _Keys = { 0x12345678, 0x23456789, 0x34567890 };
private Ionic.Crc.CRC32 crc32 = new Ionic.Crc.CRC32();
}
internal enum CryptoMode
{
Encrypt,
Decrypt
}
/// <summary>
/// A Stream for reading and concurrently decrypting data from a zip file,
/// or for writing and concurrently encrypting data to a zip file.
/// </summary>
internal class ZipCipherStream : System.IO.Stream
{
private ZipCrypto _cipher;
private System.IO.Stream _s;
private CryptoMode _mode;
/// <summary> The constructor. </summary>
/// <param name="s">The underlying stream</param>
/// <param name="mode">To either encrypt or decrypt.</param>
/// <param name="cipher">The pre-initialized ZipCrypto object.</param>
public ZipCipherStream(System.IO.Stream s, ZipCrypto cipher, CryptoMode mode)
: base()
{
_cipher = cipher;
_s = s;
_mode = mode;
}
public override int Read(byte[] buffer, int offset, int count)
{
if (_mode == CryptoMode.Encrypt)
throw new NotSupportedException("This stream does not encrypt via Read()");
if (buffer == null)
throw new ArgumentNullException("buffer");
byte[] db = new byte[count];
int n = _s.Read(db, 0, count);
byte[] decrypted = _cipher.DecryptMessage(db, n);
for (int i = 0; i < n; i++)
{
buffer[offset + i] = decrypted[i];
}
return n;
}
public override void Write(byte[] buffer, int offset, int count)
{
if (_mode == CryptoMode.Decrypt)
throw new NotSupportedException("This stream does not Decrypt via Write()");
if (buffer == null)
throw new ArgumentNullException("buffer");
// workitem 7696
if (count == 0) return;
byte[] plaintext = null;
if (offset != 0)
{
plaintext = new byte[count];
for (int i = 0; i < count; i++)
{
plaintext[i] = buffer[offset + i];
}
}
else plaintext = buffer;
byte[] encrypted = _cipher.EncryptMessage(plaintext, count);
_s.Write(encrypted, 0, encrypted.Length);
}
public override bool CanRead
{
get { return (_mode == CryptoMode.Decrypt); }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return (_mode == CryptoMode.Encrypt); }
}
public override void Flush()
{
//throw new NotSupportedException();
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
}
}
@@ -0,0 +1,381 @@
// ZipDirEntry.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2006-2011 Dino Chiesa .
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-11 12:03:03>
//
// ------------------------------------------------------------------
//
// This module defines members of the ZipEntry class for reading the
// Zip file central directory.
//
// Created: Tue, 27 Mar 2007 15:30
//
// ------------------------------------------------------------------
using System;
using System.Collections.Generic;
namespace Ionic.Zip
{
partial class ZipEntry
{
/// <summary>
/// True if the referenced entry is a directory.
/// </summary>
internal bool AttributesIndicateDirectory
{
get { return ((_InternalFileAttrs == 0) && ((_ExternalFileAttrs & 0x0010) == 0x0010)); }
}
internal void ResetDirEntry()
{
// __FileDataPosition is the position of the file data for an entry.
// It is _RelativeOffsetOfLocalHeader + size of local header.
// We cannot know the __FileDataPosition until we read the local
// header.
// The local header is not necessarily the same length as the record
// in the central directory.
// Set to -1, to indicate we need to read this later.
this.__FileDataPosition = -1;
// set _LengthOfHeader to 0, to indicate we need to read later.
this._LengthOfHeader = 0;
}
/// <summary>
/// Provides a human-readable string with information about the ZipEntry.
/// </summary>
public string Info
{
get
{
var builder = new System.Text.StringBuilder();
builder
.Append(string.Format(" ZipEntry: {0}\n", this.FileName))
.Append(string.Format(" Version Made By: {0}\n", this._VersionMadeBy))
.Append(string.Format(" Needed to extract: {0}\n", this.VersionNeeded));
if (this._IsDirectory)
builder.Append(" Entry type: directory\n");
else
{
builder.Append(string.Format(" File type: {0}\n", this._IsText? "text":"binary"))
.Append(string.Format(" Compression: {0}\n", this.CompressionMethod))
.Append(string.Format(" Compressed: 0x{0:X}\n", this.CompressedSize))
.Append(string.Format(" Uncompressed: 0x{0:X}\n", this.UncompressedSize))
.Append(string.Format(" CRC32: 0x{0:X8}\n", this._Crc32));
}
builder.Append(string.Format(" Disk Number: {0}\n", this._diskNumber));
if (this._RelativeOffsetOfLocalHeader > 0xFFFFFFFF)
builder
.Append(string.Format(" Relative Offset: 0x{0:X16}\n", this._RelativeOffsetOfLocalHeader));
else
builder
.Append(string.Format(" Relative Offset: 0x{0:X8}\n", this._RelativeOffsetOfLocalHeader));
builder
.Append(string.Format(" Bit Field: 0x{0:X4}\n", this._BitField))
.Append(string.Format(" Encrypted?: {0}\n", this._sourceIsEncrypted))
.Append(string.Format(" Timeblob: 0x{0:X8}\n", this._TimeBlob))
.Append(string.Format(" Time: {0}\n", Ionic.Zip.SharedUtilities.PackedToDateTime(this._TimeBlob)));
builder.Append(string.Format(" Is Zip64?: {0}\n", this._InputUsesZip64));
if (!string.IsNullOrEmpty(this._Comment))
{
builder.Append(string.Format(" Comment: {0}\n", this._Comment));
}
builder.Append("\n");
return builder.ToString();
}
}
// workitem 10330
private class CopyHelper
{
private static System.Text.RegularExpressions.Regex re =
new System.Text.RegularExpressions.Regex(" \\(copy (\\d+)\\)$");
private static int callCount = 0;
internal static string AppendCopyToFileName(string f)
{
callCount++;
if (callCount > 25)
throw new OverflowException("overflow while creating filename");
int n = 1;
int r = f.LastIndexOf(".");
if (r == -1)
{
// there is no extension
System.Text.RegularExpressions.Match m = re.Match(f);
if (m.Success)
{
n = Int32.Parse(m.Groups[1].Value) + 1;
string copy = String.Format(" (copy {0})", n);
f = f.Substring(0, m.Index) + copy;
}
else
{
string copy = String.Format(" (copy {0})", n);
f = f + copy;
}
}
else
{
//System.Console.WriteLine("HasExtension");
System.Text.RegularExpressions.Match m = re.Match(f.Substring(0, r));
if (m.Success)
{
n = Int32.Parse(m.Groups[1].Value) + 1;
string copy = String.Format(" (copy {0})", n);
f = f.Substring(0, m.Index) + copy + f.Substring(r);
}
else
{
string copy = String.Format(" (copy {0})", n);
f = f.Substring(0, r) + copy + f.Substring(r);
}
//System.Console.WriteLine("returning f({0})", f);
}
return f;
}
}
/// <summary>
/// Reads one entry from the zip directory structure in the zip file.
/// </summary>
///
/// <param name="zf">
/// The zipfile for which a directory entry will be read. From this param, the
/// method gets the ReadStream and the expected text encoding
/// (ProvisionalAlternateEncoding) which is used if the entry is not marked
/// UTF-8.
/// </param>
///
/// <param name="previouslySeen">
/// a list of previously seen entry names; used to prevent duplicates.
/// </param>
///
/// <returns>the entry read from the archive.</returns>
internal static ZipEntry ReadDirEntry(ZipFile zf,
Dictionary<String,Object> previouslySeen)
{
System.IO.Stream s = zf.ReadStream;
System.Text.Encoding expectedEncoding = (zf.AlternateEncodingUsage == ZipOption.Always)
? zf.AlternateEncoding
: ZipFile.DefaultEncoding;
int signature = Ionic.Zip.SharedUtilities.ReadSignature(s);
// return null if this is not a local file header signature
if (IsNotValidZipDirEntrySig(signature))
{
s.Seek(-4, System.IO.SeekOrigin.Current);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
// Getting "not a ZipDirEntry signature" here is not always wrong or an
// error. This can happen when walking through a zipfile. After the
// last ZipDirEntry, we expect to read an
// EndOfCentralDirectorySignature. When we get this is how we know
// we've reached the end of the central directory.
if (signature != ZipConstants.EndOfCentralDirectorySignature &&
signature != ZipConstants.Zip64EndOfCentralDirectoryRecordSignature &&
signature != ZipConstants.ZipEntrySignature // workitem 8299
)
{
throw new BadReadException(String.Format(" Bad signature (0x{0:X8}) at position 0x{1:X8}", signature, s.Position));
}
return null;
}
int bytesRead = 42 + 4;
byte[] block = new byte[42];
int n = s.Read(block, 0, block.Length);
if (n != block.Length) return null;
int i = 0;
ZipEntry zde = new ZipEntry();
zde.AlternateEncoding = expectedEncoding;
zde._Source = ZipEntrySource.ZipFile;
zde._container = new ZipContainer(zf);
unchecked
{
zde._VersionMadeBy = (short)(block[i++] + block[i++] * 256);
zde._VersionNeeded = (short)(block[i++] + block[i++] * 256);
zde._BitField = (short)(block[i++] + block[i++] * 256);
zde._CompressionMethod = (Int16)(block[i++] + block[i++] * 256);
zde._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
zde._LastModified = Ionic.Zip.SharedUtilities.PackedToDateTime(zde._TimeBlob);
zde._timestamp |= ZipEntryTimestamp.DOS;
zde._Crc32 = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
zde._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
zde._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
}
// preserve
zde._CompressionMethod_FromZipFile = zde._CompressionMethod;
zde._filenameLength = (short)(block[i++] + block[i++] * 256);
zde._extraFieldLength = (short)(block[i++] + block[i++] * 256);
zde._commentLength = (short)(block[i++] + block[i++] * 256);
zde._diskNumber = (UInt32)(block[i++] + block[i++] * 256);
zde._InternalFileAttrs = (short)(block[i++] + block[i++] * 256);
zde._ExternalFileAttrs = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
zde._RelativeOffsetOfLocalHeader = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
// workitem 7801
zde.IsText = ((zde._InternalFileAttrs & 0x01) == 0x01);
block = new byte[zde._filenameLength];
n = s.Read(block, 0, block.Length);
bytesRead += n;
if ((zde._BitField & 0x0800) == 0x0800)
{
// UTF-8 is in use
zde._FileNameInArchive = Ionic.Zip.SharedUtilities.Utf8StringFromBuffer(block);
}
else
{
zde._FileNameInArchive = Ionic.Zip.SharedUtilities.StringFromBuffer(block, expectedEncoding);
}
// workitem 10330
// insure unique entry names
while (previouslySeen.ContainsKey(zde._FileNameInArchive))
{
zde._FileNameInArchive = CopyHelper.AppendCopyToFileName(zde._FileNameInArchive);
zde._metadataChanged = true;
}
if (zde.AttributesIndicateDirectory)
zde.MarkAsDirectory(); // may append a slash to filename if nec.
// workitem 6898
else if (zde._FileNameInArchive.EndsWith("/")) zde.MarkAsDirectory();
zde._CompressedFileDataSize = zde._CompressedSize;
if ((zde._BitField & 0x01) == 0x01)
{
// this may change after processing the Extra field
zde._Encryption_FromZipFile = zde._Encryption =
EncryptionAlgorithm.PkzipWeak;
zde._sourceIsEncrypted = true;
}
if (zde._extraFieldLength > 0)
{
zde._InputUsesZip64 = (zde._CompressedSize == 0xFFFFFFFF ||
zde._UncompressedSize == 0xFFFFFFFF ||
zde._RelativeOffsetOfLocalHeader == 0xFFFFFFFF);
// Console.WriteLine(" Input uses Z64?: {0}", zde._InputUsesZip64);
bytesRead += zde.ProcessExtraField(s, zde._extraFieldLength);
zde._CompressedFileDataSize = zde._CompressedSize;
}
// we've processed the extra field, so we know the encryption method is set now.
if (zde._Encryption == EncryptionAlgorithm.PkzipWeak)
{
// the "encryption header" of 12 bytes precedes the file data
zde._CompressedFileDataSize -= 12;
}
#if AESCRYPTO
else if (zde.Encryption == EncryptionAlgorithm.WinZipAes128 ||
zde.Encryption == EncryptionAlgorithm.WinZipAes256)
{
zde._CompressedFileDataSize = zde.CompressedSize -
(ZipEntry.GetLengthOfCryptoHeaderBytes(zde.Encryption) + 10);
zde._LengthOfTrailer = 10;
}
#endif
// tally the trailing descriptor
if ((zde._BitField & 0x0008) == 0x0008)
{
// sig, CRC, Comp and Uncomp sizes
if (zde._InputUsesZip64)
zde._LengthOfTrailer += 24;
else
zde._LengthOfTrailer += 16;
}
// workitem 12744
zde.AlternateEncoding = ((zde._BitField & 0x0800) == 0x0800)
? System.Text.Encoding.UTF8
:expectedEncoding;
zde.AlternateEncodingUsage = ZipOption.Always;
if (zde._commentLength > 0)
{
block = new byte[zde._commentLength];
n = s.Read(block, 0, block.Length);
bytesRead += n;
if ((zde._BitField & 0x0800) == 0x0800)
{
// UTF-8 is in use
zde._Comment = Ionic.Zip.SharedUtilities.Utf8StringFromBuffer(block);
}
else
{
zde._Comment = Ionic.Zip.SharedUtilities.StringFromBuffer(block, expectedEncoding);
}
}
//zde._LengthOfDirEntry = bytesRead;
return zde;
}
/// <summary>
/// Returns true if the passed-in value is a valid signature for a ZipDirEntry.
/// </summary>
/// <param name="signature">the candidate 4-byte signature value.</param>
/// <returns>true, if the signature is valid according to the PKWare spec.</returns>
internal static bool IsNotValidZipDirEntrySig(int signature)
{
return (signature != ZipConstants.ZipDirEntrySignature);
}
private Int16 _VersionMadeBy;
private Int16 _InternalFileAttrs;
private Int32 _ExternalFileAttrs;
//private Int32 _LengthOfDirEntry;
private Int16 _filenameLength;
private Int16 _extraFieldLength;
private Int16 _commentLength;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,798 @@
// ZipEntry.Read.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009-2011 Dino Chiesa
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-09 21:31:28>
//
// ------------------------------------------------------------------
//
// This module defines logic for Reading the ZipEntry from a
// zip file.
//
// ------------------------------------------------------------------
using System;
using System.IO;
namespace Ionic.Zip
{
public partial class ZipEntry
{
private int _readExtraDepth;
private void ReadExtraField()
{
_readExtraDepth++;
// workitem 8098: ok (restore)
long posn = this.ArchiveStream.Position;
this.ArchiveStream.Seek(this._RelativeOffsetOfLocalHeader, SeekOrigin.Begin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);
byte[] block = new byte[30];
this.ArchiveStream.Read(block, 0, block.Length);
int i = 26;
Int16 filenameLength = (short)(block[i++] + block[i++] * 256);
Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256);
// workitem 8098: ok (relative)
this.ArchiveStream.Seek(filenameLength, SeekOrigin.Current);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);
ProcessExtraField(this.ArchiveStream, extraFieldLength);
// workitem 8098: ok (restore)
this.ArchiveStream.Seek(posn, SeekOrigin.Begin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(this.ArchiveStream);
_readExtraDepth--;
}
private static bool ReadHeader(ZipEntry ze, System.Text.Encoding defaultEncoding)
{
int bytesRead = 0;
// change for workitem 8098
ze._RelativeOffsetOfLocalHeader = ze.ArchiveStream.Position;
int signature = Ionic.Zip.SharedUtilities.ReadEntrySignature(ze.ArchiveStream);
bytesRead += 4;
// Return false if this is not a local file header signature.
if (ZipEntry.IsNotValidSig(signature))
{
// Getting "not a ZipEntry signature" is not always wrong or an error.
// This will happen after the last entry in a zipfile. In that case, we
// expect to read :
// a ZipDirEntry signature (if a non-empty zip file) or
// a ZipConstants.EndOfCentralDirectorySignature.
//
// Anything else is a surprise.
ze.ArchiveStream.Seek(-4, SeekOrigin.Current); // unread the signature
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream);
if (ZipEntry.IsNotValidZipDirEntrySig(signature) && (signature != ZipConstants.EndOfCentralDirectorySignature))
{
throw new BadReadException(String.Format(" Bad signature (0x{0:X8}) at position 0x{1:X8}", signature, ze.ArchiveStream.Position));
}
return false;
}
byte[] block = new byte[26];
int n = ze.ArchiveStream.Read(block, 0, block.Length);
if (n != block.Length) return false;
bytesRead += n;
int i = 0;
ze._VersionNeeded = (Int16)(block[i++] + block[i++] * 256);
ze._BitField = (Int16)(block[i++] + block[i++] * 256);
ze._CompressionMethod_FromZipFile = ze._CompressionMethod = (Int16)(block[i++] + block[i++] * 256);
ze._TimeBlob = block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256;
// transform the time data into something usable (a DateTime)
ze._LastModified = Ionic.Zip.SharedUtilities.PackedToDateTime(ze._TimeBlob);
ze._timestamp |= ZipEntryTimestamp.DOS;
if ((ze._BitField & 0x01) == 0x01)
{
ze._Encryption_FromZipFile = ze._Encryption = EncryptionAlgorithm.PkzipWeak; // this *may* change after processing the Extra field
ze._sourceIsEncrypted = true;
}
// NB: if ((ze._BitField & 0x0008) != 0x0008), then the Compressed, uncompressed and
// CRC values are not true values; the true values will follow the entry data.
// But, regardless of the status of bit 3 in the bitfield, the slots for
// the three amigos may contain marker values for ZIP64. So we must read them.
{
ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
if ((uint)ze._CompressedSize == 0xFFFFFFFF ||
(uint)ze._UncompressedSize == 0xFFFFFFFF)
ze._InputUsesZip64 = true;
}
Int16 filenameLength = (short)(block[i++] + block[i++] * 256);
Int16 extraFieldLength = (short)(block[i++] + block[i++] * 256);
block = new byte[filenameLength];
n = ze.ArchiveStream.Read(block, 0, block.Length);
bytesRead += n;
// if the UTF8 bit is set for this entry, override the
// encoding the application requested.
if ((ze._BitField & 0x0800) == 0x0800)
{
// workitem 12744
ze.AlternateEncoding = System.Text.Encoding.UTF8;
ze.AlternateEncodingUsage = ZipOption.Always;
}
// need to use this form of GetString() for .NET CF
ze._FileNameInArchive = ze.AlternateEncoding.GetString(block, 0, block.Length);
// workitem 6898
if (ze._FileNameInArchive.EndsWith("/")) ze.MarkAsDirectory();
bytesRead += ze.ProcessExtraField(ze.ArchiveStream, extraFieldLength);
ze._LengthOfTrailer = 0;
// workitem 6607 - don't read for directories
// actually get the compressed size and CRC if necessary
if (!ze._FileNameInArchive.EndsWith("/") && (ze._BitField & 0x0008) == 0x0008)
{
// This descriptor exists only if bit 3 of the general
// purpose bit flag is set (see below). It is byte aligned
// and immediately follows the last byte of compressed data,
// as well as any encryption trailer, as with AES.
// This descriptor is used only when it was not possible to
// seek in the output .ZIP file, e.g., when the output .ZIP file
// was standard output or a non-seekable device. For ZIP64(tm) format
// archives, the compressed and uncompressed sizes are 8 bytes each.
// workitem 8098: ok (restore)
long posn = ze.ArchiveStream.Position;
// Here, we're going to loop until we find a ZipEntryDataDescriptorSignature and
// a consistent data record after that. To be consistent, the data record must
// indicate the length of the entry data.
bool wantMore = true;
long SizeOfDataRead = 0;
int tries = 0;
while (wantMore)
{
tries++;
// We call the FindSignature shared routine to find the specified signature
// in the already-opened zip archive, starting from the current cursor
// position in that filestream. If we cannot find the signature, then the
// routine returns -1, and the ReadHeader() method returns false,
// indicating we cannot read a legal entry header. If we have found it,
// then the FindSignature() method returns the number of bytes in the
// stream we had to seek forward, to find the sig. We need this to
// determine if the zip entry is valid, later.
if (ze._container.ZipFile != null)
ze._container.ZipFile.OnReadBytes(ze);
long d = Ionic.Zip.SharedUtilities.FindSignature(ze.ArchiveStream, ZipConstants.ZipEntryDataDescriptorSignature);
if (d == -1) return false;
// total size of data read (through all loops of this).
SizeOfDataRead += d;
if (ze._InputUsesZip64)
{
// read 1x 4-byte (CRC) and 2x 8-bytes (Compressed Size, Uncompressed Size)
block = new byte[20];
n = ze.ArchiveStream.Read(block, 0, block.Length);
if (n != 20) return false;
// do not increment bytesRead - it is for entry header only.
// the data we have just read is a footer (falls after the file data)
//bytesRead += n;
i = 0;
ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
ze._CompressedSize = BitConverter.ToInt64(block, i);
i += 8;
ze._UncompressedSize = BitConverter.ToInt64(block, i);
i += 8;
ze._LengthOfTrailer += 24; // bytes including sig, CRC, Comp and Uncomp sizes
}
else
{
// read 3x 4-byte fields (CRC, Compressed Size, Uncompressed Size)
block = new byte[12];
n = ze.ArchiveStream.Read(block, 0, block.Length);
if (n != 12) return false;
// do not increment bytesRead - it is for entry header only.
// the data we have just read is a footer (falls after the file data)
//bytesRead += n;
i = 0;
ze._Crc32 = (Int32)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
ze._CompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
ze._UncompressedSize = (uint)(block[i++] + block[i++] * 256 + block[i++] * 256 * 256 + block[i++] * 256 * 256 * 256);
ze._LengthOfTrailer += 16; // bytes including sig, CRC, Comp and Uncomp sizes
}
wantMore = (SizeOfDataRead != ze._CompressedSize);
if (wantMore)
{
// Seek back to un-read the last 12 bytes - maybe THEY contain
// the ZipEntryDataDescriptorSignature.
// (12 bytes for the CRC, Comp and Uncomp size.)
ze.ArchiveStream.Seek(-12, SeekOrigin.Current);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream);
// Adjust the size to account for the false signature read in
// FindSignature().
SizeOfDataRead += 4;
}
}
// seek back to previous position, to prepare to read file data
// workitem 8098: ok (restore)
ze.ArchiveStream.Seek(posn, SeekOrigin.Begin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(ze.ArchiveStream);
}
ze._CompressedFileDataSize = ze._CompressedSize;
// bit 0 set indicates that some kind of encryption is in use
if ((ze._BitField & 0x01) == 0x01)
{
#if AESCRYPTO
if (ze.Encryption == EncryptionAlgorithm.WinZipAes128 ||
ze.Encryption == EncryptionAlgorithm.WinZipAes256)
{
int bits = ZipEntry.GetKeyStrengthInBits(ze._Encryption_FromZipFile);
// read in the WinZip AES metadata: salt + PV. 18 bytes for AES256. 10 bytes for AES128.
ze._aesCrypto_forExtract = WinZipAesCrypto.ReadFromStream(null, bits, ze.ArchiveStream);
bytesRead += ze._aesCrypto_forExtract.SizeOfEncryptionMetadata - 10; // MAC (follows crypto bytes)
// according to WinZip, the CompressedSize includes the AES Crypto framing data.
ze._CompressedFileDataSize -= ze._aesCrypto_forExtract.SizeOfEncryptionMetadata;
ze._LengthOfTrailer += 10; // MAC
}
else
#endif
{
// read in the header data for "weak" encryption
ze._WeakEncryptionHeader = new byte[12];
bytesRead += ZipEntry.ReadWeakEncryptionHeader(ze._archiveStream, ze._WeakEncryptionHeader);
// decrease the filedata size by 12 bytes
ze._CompressedFileDataSize -= 12;
}
}
// Remember the size of the blob for this entry.
// We also have the starting position in the stream for this entry.
ze._LengthOfHeader = bytesRead;
ze._TotalEntrySize = ze._LengthOfHeader + ze._CompressedFileDataSize + ze._LengthOfTrailer;
// We've read in the regular entry header, the extra field, and any
// encryption header. The pointer in the file is now at the start of the
// filedata, which is potentially compressed and encrypted. Just ahead in
// the file, there are _CompressedFileDataSize bytes of data, followed by
// potentially a non-zero length trailer, consisting of optionally, some
// encryption stuff (10 byte MAC for AES), and the bit-3 trailer (16 or 24
// bytes).
return true;
}
internal static int ReadWeakEncryptionHeader(Stream s, byte[] buffer)
{
// PKZIP encrypts the compressed data stream. Encrypted files must
// be decrypted before they can be extracted.
// Each PKZIP-encrypted file has an extra 12 bytes stored at the start of the data
// area defining the encryption header for that file. The encryption header is
// originally set to random values, and then itself encrypted, using three, 32-bit
// keys. The key values are initialized using the supplied encryption password.
// After each byte is encrypted, the keys are then updated using pseudo-random
// number generation techniques in combination with the same CRC-32 algorithm used
// in PKZIP and implemented in the CRC32.cs module in this project.
// read the 12-byte encryption header
int additionalBytesRead = s.Read(buffer, 0, 12);
if (additionalBytesRead != 12)
throw new ZipException(String.Format("Unexpected end of data at position 0x{0:X8}", s.Position));
return additionalBytesRead;
}
private static bool IsNotValidSig(int signature)
{
return (signature != ZipConstants.ZipEntrySignature);
}
/// <summary>
/// Reads one <c>ZipEntry</c> from the given stream. The content for
/// the entry does not get decompressed or decrypted. This method
/// basically reads metadata, and seeks.
/// </summary>
/// <param name="zc">the ZipContainer this entry belongs to.</param>
/// <param name="first">
/// true of this is the first entry being read from the stream.
/// </param>
/// <returns>the <c>ZipEntry</c> read from the stream.</returns>
internal static ZipEntry ReadEntry(ZipContainer zc, bool first)
{
ZipFile zf = zc.ZipFile;
Stream s = zc.ReadStream;
System.Text.Encoding defaultEncoding = zc.AlternateEncoding;
ZipEntry entry = new ZipEntry();
entry._Source = ZipEntrySource.ZipFile;
entry._container = zc;
entry._archiveStream = s;
if (zf != null)
zf.OnReadEntry(true, null);
if (first) HandlePK00Prefix(s);
// Read entry header, including any encryption header
if (!ReadHeader(entry, defaultEncoding)) return null;
// Store the position in the stream for this entry
// change for workitem 8098
entry.__FileDataPosition = entry.ArchiveStream.Position;
// seek past the data without reading it. We will read on Extract()
s.Seek(entry._CompressedFileDataSize + entry._LengthOfTrailer, SeekOrigin.Current);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
// ReadHeader moves the file pointer to the end of the entry header,
// as well as any encryption header.
// CompressedFileDataSize includes:
// the maybe compressed, maybe encrypted file data
// the encryption trailer, if any
// the bit 3 descriptor, if any
// workitem 5306
// http://www.codeplex.com/DotNetZip/WorkItem/View.aspx?WorkItemId=5306
HandleUnexpectedDataDescriptor(entry);
if (zf != null)
{
zf.OnReadBytes(entry);
zf.OnReadEntry(false, entry);
}
return entry;
}
internal static void HandlePK00Prefix(Stream s)
{
// in some cases, the zip file begins with "PK00". This is a throwback and is rare,
// but we handle it anyway. We do not change behavior based on it.
uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
if (datum != ZipConstants.PackedToRemovableMedia)
{
s.Seek(-4, SeekOrigin.Current); // unread the block
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
}
}
private static void HandleUnexpectedDataDescriptor(ZipEntry entry)
{
Stream s = entry.ArchiveStream;
// In some cases, the "data descriptor" is present, without a signature, even when
// bit 3 of the BitField is NOT SET. This is the CRC, followed
// by the compressed length and the uncompressed length (4 bytes for each
// of those three elements). Need to check that here.
//
uint datum = (uint)Ionic.Zip.SharedUtilities.ReadInt(s);
if (datum == entry._Crc32)
{
int sz = Ionic.Zip.SharedUtilities.ReadInt(s);
if (sz == entry._CompressedSize)
{
sz = Ionic.Zip.SharedUtilities.ReadInt(s);
if (sz == entry._UncompressedSize)
{
// ignore everything and discard it.
}
else
{
s.Seek(-12, SeekOrigin.Current); // unread the three blocks
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
}
}
else
{
s.Seek(-8, SeekOrigin.Current); // unread the two blocks
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
}
}
else
{
s.Seek(-4, SeekOrigin.Current); // unread the block
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(s);
}
}
/// <summary>
/// Finds a particular segment in the given extra field.
/// This is used when modifying a previously-generated
/// extra field, in particular when removing the AES crypto
/// segment in the extra field.
/// </summary>
static internal int FindExtraFieldSegment(byte[] extra, int offx, UInt16 targetHeaderId)
{
int j = offx;
while (j + 3 < extra.Length)
{
UInt16 headerId = (UInt16)(extra[j++] + extra[j++] * 256);
if (headerId == targetHeaderId) return j-2;
// else advance to next segment
Int16 dataSize = (short)(extra[j++] + extra[j++] * 256);
j+= dataSize;
}
return -1;
}
/// <summary>
/// At current cursor position in the stream, read the extra
/// field, and set the properties on the ZipEntry instance
/// appropriately. This can be called when processing the
/// Extra field in the Central Directory, or in the local
/// header.
/// </summary>
internal int ProcessExtraField(Stream s, Int16 extraFieldLength)
{
int additionalBytesRead = 0;
if (extraFieldLength > 0)
{
byte[] buffer = this._Extra = new byte[extraFieldLength];
additionalBytesRead = s.Read(buffer, 0, buffer.Length);
long posn = s.Position - additionalBytesRead;
int j = 0;
while (j + 3 < buffer.Length)
{
int start = j;
UInt16 headerId = (UInt16)(buffer[j++] + buffer[j++] * 256);
Int16 dataSize = (short)(buffer[j++] + buffer[j++] * 256);
switch (headerId)
{
case 0x000a: // NTFS ctime, atime, mtime
j = ProcessExtraFieldWindowsTimes(buffer, j, dataSize, posn);
break;
case 0x5455: // Unix ctime, atime, mtime
j = ProcessExtraFieldUnixTimes(buffer, j, dataSize, posn);
break;
case 0x5855: // Info-zip Extra field (outdated)
// This is outdated, so the field is supported on
// read only.
j = ProcessExtraFieldInfoZipTimes(buffer, j, dataSize, posn);
break;
case 0x7855: // Unix uid/gid
// ignored. DotNetZip does not handle this field.
break;
case 0x7875: // ??
// ignored. I could not find documentation on this field,
// though it appears in some zip files.
break;
case 0x0001: // ZIP64
j = ProcessExtraFieldZip64(buffer, j, dataSize, posn);
break;
#if AESCRYPTO
case 0x9901: // WinZip AES encryption is in use. (workitem 6834)
// we will handle this extra field only if compressionmethod is 0x63
j = ProcessExtraFieldWinZipAes(buffer, j, dataSize, posn);
break;
#endif
case 0x0017: // workitem 7968: handle PKWare Strong encryption header
j = ProcessExtraFieldPkwareStrongEncryption(buffer, j);
break;
}
// move to the next Header in the extra field
j = start + dataSize + 4;
}
}
return additionalBytesRead;
}
private int ProcessExtraFieldPkwareStrongEncryption(byte[] Buffer, int j)
{
// Value Size Description
// ----- ---- -----------
// 0x0017 2 bytes Tag for this "extra" block type
// TSize 2 bytes Size of data that follows
// Format 2 bytes Format definition for this record
// AlgID 2 bytes Encryption algorithm identifier
// Bitlen 2 bytes Bit length of encryption key
// Flags 2 bytes Processing flags
// CertData TSize-8 Certificate decryption extra field data
// (refer to the explanation for CertData
// in the section describing the
// Certificate Processing Method under
// the Strong Encryption Specification)
j += 2;
_UnsupportedAlgorithmId = (UInt16)(Buffer[j++] + Buffer[j++] * 256);
_Encryption_FromZipFile = _Encryption = EncryptionAlgorithm.Unsupported;
// DotNetZip doesn't support this algorithm, but we don't need to throw
// here. we might just be reading the archive, which is fine. We'll
// need to throw if Extract() is called.
return j;
}
#if AESCRYPTO
private int ProcessExtraFieldWinZipAes(byte[] buffer, int j, Int16 dataSize, long posn)
{
if (this._CompressionMethod == 0x0063)
{
if ((this._BitField & 0x01) != 0x01)
throw new BadReadException(String.Format(" Inconsistent metadata at position 0x{0:X16}", posn));
this._sourceIsEncrypted = true;
//this._aesCrypto = new WinZipAesCrypto(this);
// see spec at http://www.winzip.com/aes_info.htm
if (dataSize != 7)
throw new BadReadException(String.Format(" Inconsistent size (0x{0:X4}) in WinZip AES field at position 0x{1:X16}", dataSize, posn));
this._WinZipAesMethod = BitConverter.ToInt16(buffer, j);
j += 2;
if (this._WinZipAesMethod != 0x01 && this._WinZipAesMethod != 0x02)
throw new BadReadException(String.Format(" Unexpected vendor version number (0x{0:X4}) for WinZip AES metadata at position 0x{1:X16}",
this._WinZipAesMethod, posn));
Int16 vendorId = BitConverter.ToInt16(buffer, j);
j += 2;
if (vendorId != 0x4541)
throw new BadReadException(String.Format(" Unexpected vendor ID (0x{0:X4}) for WinZip AES metadata at position 0x{1:X16}", vendorId, posn));
int keystrength = (buffer[j] == 1) ? 128 : (buffer[j] == 3) ? 256 : -1;
if (keystrength < 0)
throw new BadReadException(String.Format("Invalid key strength ({0})", keystrength));
_Encryption_FromZipFile = this._Encryption = (keystrength == 128)
? EncryptionAlgorithm.WinZipAes128
: EncryptionAlgorithm.WinZipAes256;
j++;
// set the actual compression method
this._CompressionMethod_FromZipFile =
this._CompressionMethod = BitConverter.ToInt16(buffer, j);
j += 2; // for the next segment of the extra field
}
return j;
}
#endif
private delegate T Func<T>();
private int ProcessExtraFieldZip64(byte[] buffer, int j, Int16 dataSize, long posn)
{
// The PKWare spec says that any of {UncompressedSize, CompressedSize,
// RelativeOffset} exceeding 0xFFFFFFFF can lead to the ZIP64 header,
// and the ZIP64 header may contain one or more of those. If the
// values are present, they will be found in the prescribed order.
// There may also be a 4-byte "disk start number."
// This means that the DataSize must be 28 bytes or less.
this._InputUsesZip64 = true;
// workitem 7941: check datasize before reading.
if (dataSize > 28)
throw new BadReadException(String.Format(" Inconsistent size (0x{0:X4}) for ZIP64 extra field at position 0x{1:X16}",
dataSize, posn));
int remainingData = dataSize;
var slurp = new Func<Int64>( () => {
if (remainingData < 8)
throw new BadReadException(String.Format(" Missing data for ZIP64 extra field, position 0x{0:X16}", posn));
var x = BitConverter.ToInt64(buffer, j);
j+= 8;
remainingData -= 8;
return x;
});
if (this._UncompressedSize == 0xFFFFFFFF)
this._UncompressedSize = slurp();
if (this._CompressedSize == 0xFFFFFFFF)
this._CompressedSize = slurp();
if (this._RelativeOffsetOfLocalHeader == 0xFFFFFFFF)
this._RelativeOffsetOfLocalHeader = slurp();
// Ignore anything else. Potentially there are 4 more bytes for the
// disk start number. DotNetZip currently doesn't handle multi-disk
// archives.
return j;
}
private int ProcessExtraFieldInfoZipTimes(byte[] buffer, int j, Int16 dataSize, long posn)
{
if (dataSize != 12 && dataSize != 8)
throw new BadReadException(String.Format(" Unexpected size (0x{0:X4}) for InfoZip v1 extra field at position 0x{1:X16}", dataSize, posn));
Int32 timet = BitConverter.ToInt32(buffer, j);
this._Mtime = _unixEpoch.AddSeconds(timet);
j += 4;
timet = BitConverter.ToInt32(buffer, j);
this._Atime = _unixEpoch.AddSeconds(timet);
j += 4;
this._Ctime = DateTime.UtcNow;
_ntfsTimesAreSet = true;
_timestamp |= ZipEntryTimestamp.InfoZip1; return j;
}
private int ProcessExtraFieldUnixTimes(byte[] buffer, int j, Int16 dataSize, long posn)
{
// The Unix filetimes are 32-bit unsigned integers,
// storing seconds since Unix epoch.
if (dataSize != 13 && dataSize != 9 && dataSize != 5)
throw new BadReadException(String.Format(" Unexpected size (0x{0:X4}) for Extended Timestamp extra field at position 0x{1:X16}", dataSize, posn));
int remainingData = dataSize;
var slurp = new Func<DateTime>( () => {
Int32 timet = BitConverter.ToInt32(buffer, j);
j += 4;
remainingData -= 4;
return _unixEpoch.AddSeconds(timet);
});
if (dataSize == 13 || _readExtraDepth > 0)
{
byte flag = buffer[j++];
remainingData--;
if ((flag & 0x0001) != 0 && remainingData >= 4)
this._Mtime = slurp();
this._Atime = ((flag & 0x0002) != 0 && remainingData >= 4)
? slurp()
: DateTime.UtcNow;
this._Ctime = ((flag & 0x0004) != 0 && remainingData >= 4)
? slurp()
:DateTime.UtcNow;
_timestamp |= ZipEntryTimestamp.Unix;
_ntfsTimesAreSet = true;
_emitUnixTimes = true;
}
else
ReadExtraField(); // will recurse
return j;
}
private int ProcessExtraFieldWindowsTimes(byte[] buffer, int j, Int16 dataSize, long posn)
{
// The NTFS filetimes are 64-bit unsigned integers, stored in Intel
// (least significant byte first) byte order. They are expressed as the
// number of 1.0E-07 seconds (1/10th microseconds!) past WinNT "epoch",
// which is "01-Jan-1601 00:00:00 UTC".
//
// HeaderId 2 bytes 0x000a == NTFS stuff
// Datasize 2 bytes ?? (usually 32)
// reserved 4 bytes ??
// timetag 2 bytes 0x0001 == time
// size 2 bytes 24 == 8 bytes each for ctime, mtime, atime
// mtime 8 bytes win32 ticks since win32epoch
// atime 8 bytes win32 ticks since win32epoch
// ctime 8 bytes win32 ticks since win32epoch
if (dataSize != 32)
throw new BadReadException(String.Format(" Unexpected size (0x{0:X4}) for NTFS times extra field at position 0x{1:X16}", dataSize, posn));
j += 4; // reserved
Int16 timetag = (Int16)(buffer[j] + buffer[j + 1] * 256);
Int16 addlsize = (Int16)(buffer[j + 2] + buffer[j + 3] * 256);
j += 4; // tag and size
if (timetag == 0x0001 && addlsize == 24)
{
Int64 z = BitConverter.ToInt64(buffer, j);
this._Mtime = DateTime.FromFileTimeUtc(z);
j += 8;
// At this point the library *could* set the LastModified value
// to coincide with the Mtime value. In theory, they refer to
// the same property of the file, and should be the same anyway,
// allowing for differences in precision. But they are
// independent quantities in the zip archive, and this library
// will keep them separate in the object model. There is no ill
// effect from this, because as files are extracted, the
// higher-precision value (Mtime) is used if it is present.
// Apps may wish to compare the Mtime versus LastModified
// values, but any difference when both are present is not
// germaine to the correctness of the library. but note: when
// explicitly setting either value, both are set. See the setter
// for LastModified or the SetNtfsTimes() method.
z = BitConverter.ToInt64(buffer, j);
this._Atime = DateTime.FromFileTimeUtc(z);
j += 8;
z = BitConverter.ToInt64(buffer, j);
this._Ctime = DateTime.FromFileTimeUtc(z);
j += 8;
_ntfsTimesAreSet = true;
_timestamp |= ZipEntryTimestamp.Windows;
_emitNtfsTimes = true;
}
return j;
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,69 @@
// ZipEntrySource.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-November-19 11:18:42>
//
// ------------------------------------------------------------------
//
namespace Ionic.Zip
{
/// <summary>
/// An enum that specifies the source of the ZipEntry.
/// </summary>
public enum ZipEntrySource
{
/// <summary>
/// Default value. Invalid on a bonafide ZipEntry.
/// </summary>
None = 0,
/// <summary>
/// The entry was instantiated by calling AddFile() or another method that
/// added an entry from the filesystem.
/// </summary>
FileSystem,
/// <summary>
/// The entry was instantiated via <see cref="Ionic.Zip.ZipFile.AddEntry(string,string)"/> or
/// <see cref="Ionic.Zip.ZipFile.AddEntry(string,System.IO.Stream)"/> .
/// </summary>
Stream,
/// <summary>
/// The ZipEntry was instantiated by reading a zipfile.
/// </summary>
ZipFile,
/// <summary>
/// The content for the ZipEntry will be or was provided by the WriteDelegate.
/// </summary>
WriteDelegate,
/// <summary>
/// The content for the ZipEntry will be obtained from the stream dispensed by the <c>OpenDelegate</c>.
/// The entry was instantiated via <see cref="Ionic.Zip.ZipFile.AddEntry(string,OpenDelegate,CloseDelegate)"/>.
/// </summary>
JitStream,
/// <summary>
/// The content for the ZipEntry will be or was obtained from a <c>ZipOutputStream</c>.
/// </summary>
ZipOutputStream,
}
}
@@ -0,0 +1,97 @@
// ZipErrorAction.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-September-01 18:43:20>
//
// ------------------------------------------------------------------
//
// This module defines the ZipErrorAction enum, which provides
// an action to take when errors occur when opening or reading
// files to be added to a zip file.
//
// ------------------------------------------------------------------
namespace Ionic.Zip
{
/// <summary>
/// An enum providing the options when an error occurs during opening or reading
/// of a file or directory that is being saved to a zip file.
/// </summary>
///
/// <remarks>
/// <para>
/// This enum describes the actions that the library can take when an error occurs
/// opening or reading a file, as it is being saved into a Zip archive.
/// </para>
///
/// <para>
/// In some cases an error will occur when DotNetZip tries to open a file to be
/// added to the zip archive. In other cases, an error might occur after the
/// file has been successfully opened, while DotNetZip is reading the file.
/// </para>
///
/// <para>
/// The first problem might occur when calling AddDirectory() on a directory
/// that contains a Clipper .dbf file; the file is locked by Clipper and
/// cannot be opened by another process. An example of the second problem is
/// the ERROR_LOCK_VIOLATION that results when a file is opened by another
/// process, but not locked, and a range lock has been taken on the file.
/// Microsoft Outlook takes range locks on .PST files.
/// </para>
/// </remarks>
public enum ZipErrorAction
{
/// <summary>
/// Throw an exception when an error occurs while zipping. This is the default
/// behavior. (For COM clients, this is a 0 (zero).)
/// </summary>
Throw,
/// <summary>
/// When an error occurs during zipping, for example a file cannot be opened,
/// skip the file causing the error, and continue zipping. (For COM clients,
/// this is a 1.)
/// </summary>
Skip,
/// <summary>
/// When an error occurs during zipping, for example a file cannot be opened,
/// retry the operation that caused the error. Be careful with this option. If
/// the error is not temporary, the library will retry forever. (For COM
/// clients, this is a 2.)
/// </summary>
Retry,
/// <summary>
/// When an error occurs, invoke the zipError event. The event type used is
/// <see cref="ZipProgressEventType.Error_Saving"/>. A typical use of this option:
/// a GUI application may wish to pop up a dialog to allow the user to view the
/// error that occurred, and choose an appropriate action. After your
/// processing in the error event, if you want to skip the file, set <see
/// cref="ZipEntry.ZipErrorAction"/> on the
/// <c>ZipProgressEventArgs.CurrentEntry</c> to <c>Skip</c>. If you want the
/// exception to be thrown, set <c>ZipErrorAction</c> on the <c>CurrentEntry</c>
/// to <c>Throw</c>. If you want to cancel the zip, set
/// <c>ZipProgressEventArgs.Cancel</c> to true. Cancelling differs from using
/// Skip in that a cancel will not save any further entries, if there are any.
/// (For COM clients, the value of this enum is a 3.)
/// </summary>
InvokeErrorEvent,
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,352 @@
// ZipFile.Check.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009-2011 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-31 14:40:50>
//
// ------------------------------------------------------------------
//
// This module defines the methods for doing Checks on zip files.
// These are not necessary to include in the Reduced or CF
// version of the library.
//
// ------------------------------------------------------------------
//
using System;
using System.IO;
using System.Collections.Generic;
namespace Ionic.Zip
{
public partial class ZipFile
{
/// <summary>
/// Checks a zip file to see if its directory is consistent.
/// </summary>
///
/// <remarks>
///
/// <para>
/// In cases of data error, the directory within a zip file can get out
/// of synch with the entries in the zip file. This method checks the
/// given zip file and returns true if this has occurred.
/// </para>
///
/// <para> This method may take a long time to run for large zip files. </para>
///
/// <para>
/// This method is not supported in the Reduced or Compact Framework
/// versions of DotNetZip.
/// </para>
///
/// <para>
/// Developers using COM can use the <see
/// cref="ComHelper.CheckZip(String)">ComHelper.CheckZip(String)</see>
/// method.
/// </para>
///
/// </remarks>
///
/// <param name="zipFileName">The filename to of the zip file to check.</param>
///
/// <returns>true if the named zip file checks OK. Otherwise, false. </returns>
///
/// <seealso cref="FixZipDirectory(string)"/>
/// <seealso cref="CheckZip(string,bool,System.IO.TextWriter)"/>
public static bool CheckZip(string zipFileName)
{
return CheckZip(zipFileName, false, null);
}
/// <summary>
/// Checks a zip file to see if its directory is consistent,
/// and optionally fixes the directory if necessary.
/// </summary>
///
/// <remarks>
///
/// <para>
/// In cases of data error, the directory within a zip file can get out of
/// synch with the entries in the zip file. This method checks the given
/// zip file, and returns true if this has occurred. It also optionally
/// fixes the zipfile, saving the fixed copy in <em>Name</em>_Fixed.zip.
/// </para>
///
/// <para>
/// This method may take a long time to run for large zip files. It
/// will take even longer if the file actually needs to be fixed, and if
/// <c>fixIfNecessary</c> is true.
/// </para>
///
/// <para>
/// This method is not supported in the Reduced or Compact
/// Framework versions of DotNetZip.
/// </para>
///
/// </remarks>
///
/// <param name="zipFileName">The filename to of the zip file to check.</param>
///
/// <param name="fixIfNecessary">If true, the method will fix the zip file if
/// necessary.</param>
///
/// <param name="writer">
/// a TextWriter in which messages generated while checking will be written.
/// </param>
///
/// <returns>true if the named zip is OK; false if the file needs to be fixed.</returns>
///
/// <seealso cref="CheckZip(string)"/>
/// <seealso cref="FixZipDirectory(string)"/>
public static bool CheckZip(string zipFileName, bool fixIfNecessary,
TextWriter writer)
{
ZipFile zip1 = null, zip2 = null;
bool isOk = true;
try
{
zip1 = new ZipFile();
zip1.FullScan = true;
zip1.Initialize(zipFileName);
zip2 = ZipFile.Read(zipFileName);
foreach (var e1 in zip1)
{
foreach (var e2 in zip2)
{
if (e1.FileName == e2.FileName)
{
if (e1._RelativeOffsetOfLocalHeader != e2._RelativeOffsetOfLocalHeader)
{
isOk = false;
if (writer != null)
writer.WriteLine("{0}: mismatch in RelativeOffsetOfLocalHeader (0x{1:X16} != 0x{2:X16})",
e1.FileName, e1._RelativeOffsetOfLocalHeader,
e2._RelativeOffsetOfLocalHeader);
}
if (e1._CompressedSize != e2._CompressedSize)
{
isOk = false;
if (writer != null)
writer.WriteLine("{0}: mismatch in CompressedSize (0x{1:X16} != 0x{2:X16})",
e1.FileName, e1._CompressedSize,
e2._CompressedSize);
}
if (e1._UncompressedSize != e2._UncompressedSize)
{
isOk = false;
if (writer != null)
writer.WriteLine("{0}: mismatch in UncompressedSize (0x{1:X16} != 0x{2:X16})",
e1.FileName, e1._UncompressedSize,
e2._UncompressedSize);
}
if (e1.CompressionMethod != e2.CompressionMethod)
{
isOk = false;
if (writer != null)
writer.WriteLine("{0}: mismatch in CompressionMethod (0x{1:X4} != 0x{2:X4})",
e1.FileName, e1.CompressionMethod,
e2.CompressionMethod);
}
if (e1.Crc != e2.Crc)
{
isOk = false;
if (writer != null)
writer.WriteLine("{0}: mismatch in Crc32 (0x{1:X4} != 0x{2:X4})",
e1.FileName, e1.Crc,
e2.Crc);
}
// found a match, so stop the inside loop
break;
}
}
}
zip2.Dispose();
zip2 = null;
if (!isOk && fixIfNecessary)
{
string newFileName = Path.GetFileNameWithoutExtension(zipFileName);
newFileName = System.String.Format("{0}_fixed.zip", newFileName);
zip1.Save(newFileName);
}
}
finally
{
if (zip1 != null) zip1.Dispose();
if (zip2 != null) zip2.Dispose();
}
return isOk;
}
/// <summary>
/// Rewrite the directory within a zipfile.
/// </summary>
///
/// <remarks>
///
/// <para>
/// In cases of data error, the directory in a zip file can get out of
/// synch with the entries in the zip file. This method attempts to fix
/// the zip file if this has occurred.
/// </para>
///
/// <para> This can take a long time for large zip files. </para>
///
/// <para> This won't work if the zip file uses a non-standard
/// code page - neither IBM437 nor UTF-8. </para>
///
/// <para>
/// This method is not supported in the Reduced or Compact Framework
/// versions of DotNetZip.
/// </para>
///
/// <para>
/// Developers using COM can use the <see
/// cref="ComHelper.FixZipDirectory(String)">ComHelper.FixZipDirectory(String)</see>
/// method.
/// </para>
///
/// </remarks>
///
/// <param name="zipFileName">The filename to of the zip file to fix.</param>
///
/// <seealso cref="CheckZip(string)"/>
/// <seealso cref="CheckZip(string,bool,System.IO.TextWriter)"/>
public static void FixZipDirectory(string zipFileName)
{
using (var zip = new ZipFile())
{
zip.FullScan = true;
zip.Initialize(zipFileName);
zip.Save(zipFileName);
}
}
/// <summary>
/// Verify the password on a zip file.
/// </summary>
///
/// <remarks>
/// <para>
/// Keep in mind that passwords in zipfiles are applied to
/// zip entries, not to the entire zip file. So testing a
/// zipfile for a particular password doesn't work in the
/// general case. On the other hand, it's often the case
/// that a single password will be used on all entries in a
/// zip file. This method works for that case.
/// </para>
/// <para>
/// There is no way to check a password without doing the
/// decryption. So this code decrypts and extracts the given
/// zipfile into <see cref="System.IO.Stream.Null"/>
/// </para>
/// </remarks>
///
/// <param name="zipFileName">The filename to of the zip file to fix.</param>
///
/// <param name="password">The password to check.</param>
///
/// <returns>a bool indicating whether the password matches.</returns>
public static bool CheckZipPassword(string zipFileName, string password)
{
// workitem 13664
bool success = false;
try
{
using (ZipFile zip1 = ZipFile.Read(zipFileName))
{
foreach (var e in zip1)
{
if (!e.IsDirectory && e.UsesEncryption)
{
e.ExtractWithPassword(System.IO.Stream.Null, password);
}
}
}
success = true;
}
catch(Ionic.Zip.BadPasswordException) { }
return success;
}
/// <summary>
/// Provides a human-readable string with information about the ZipFile.
/// </summary>
///
/// <remarks>
/// <para>
/// The information string contains 10 lines or so, about each ZipEntry,
/// describing whether encryption is in use, the compressed and uncompressed
/// length of the entry, the offset of the entry, and so on. As a result the
/// information string can be very long for zip files that contain many
/// entries.
/// </para>
/// <para>
/// This information is mostly useful for diagnostic purposes.
/// </para>
/// </remarks>
public string Info
{
get
{
var builder = new System.Text.StringBuilder();
builder.Append(string.Format(" ZipFile: {0}\n", this.Name));
if (!string.IsNullOrEmpty(this._Comment))
{
builder.Append(string.Format(" Comment: {0}\n", this._Comment));
}
if (this._versionMadeBy != 0)
{
builder.Append(string.Format(" version made by: 0x{0:X4}\n", this._versionMadeBy));
}
if (this._versionNeededToExtract != 0)
{
builder.Append(string.Format("needed to extract: 0x{0:X4}\n", this._versionNeededToExtract));
}
builder.Append(string.Format(" uses ZIP64: {0}\n", this.InputUsesZip64));
builder.Append(string.Format(" disk with CD: {0}\n", this._diskNumberWithCd));
if (this._OffsetOfCentralDirectory == 0xFFFFFFFF)
builder.Append(string.Format(" CD64 offset: 0x{0:X16}\n", this._OffsetOfCentralDirectory64));
else
builder.Append(string.Format(" CD offset: 0x{0:X8}\n", this._OffsetOfCentralDirectory));
builder.Append("\n");
foreach (ZipEntry entry in this._entries.Values)
{
builder.Append(entry.Info);
}
return builder.ToString();
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,298 @@
// ZipFile.Extract.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-31 14:45:18>
//
// ------------------------------------------------------------------
//
// This module defines the methods for Extract operations on zip files.
//
// ------------------------------------------------------------------
//
using System;
using System.IO;
using System.Collections.Generic;
namespace Ionic.Zip
{
public partial class ZipFile
{
/// <summary>
/// Extracts all of the items in the zip archive, to the specified path in the
/// filesystem. The path can be relative or fully-qualified.
/// </summary>
///
/// <remarks>
/// <para>
/// This method will extract all entries in the <c>ZipFile</c> to the
/// specified path.
/// </para>
///
/// <para>
/// If an extraction of a file from the zip archive would overwrite an
/// existing file in the filesystem, the action taken is dictated by the
/// ExtractExistingFile property, which overrides any setting you may have
/// made on individual ZipEntry instances. By default, if you have not
/// set that property on the <c>ZipFile</c> instance, the entry will not
/// be extracted, the existing file will not be overwritten and an
/// exception will be thrown. To change this, set the property, or use the
/// <see cref="ZipFile.ExtractAll(string,
/// Ionic.Zip.ExtractExistingFileAction)" /> overload that allows you to
/// specify an ExtractExistingFileAction parameter.
/// </para>
///
/// <para>
/// The action to take when an extract would overwrite an existing file
/// applies to all entries. If you want to set this on a per-entry basis,
/// then you must use one of the <see
/// cref="ZipEntry.Extract()">ZipEntry.Extract</see> methods.
/// </para>
///
/// <para>
/// This method will send verbose output messages to the <see
/// cref="StatusMessageTextWriter"/>, if it is set on the <c>ZipFile</c>
/// instance.
/// </para>
///
/// <para>
/// You may wish to take advantage of the <c>ExtractProgress</c> event.
/// </para>
///
/// <para>
/// About timestamps: When extracting a file entry from a zip archive, the
/// extracted file gets the last modified time of the entry as stored in
/// the archive. The archive may also store extended file timestamp
/// information, including last accessed and created times. If these are
/// present in the <c>ZipEntry</c>, then the extracted file will also get
/// these times.
/// </para>
///
/// <para>
/// A Directory entry is somewhat different. It will get the times as
/// described for a file entry, but, if there are file entries in the zip
/// archive that, when extracted, appear in the just-created directory,
/// then when those file entries are extracted, the last modified and last
/// accessed times of the directory will change, as a side effect. The
/// result is that after an extraction of a directory and a number of
/// files within the directory, the last modified and last accessed
/// timestamps on the directory will reflect the time that the last file
/// was extracted into the directory, rather than the time stored in the
/// zip archive for the directory.
/// </para>
///
/// <para>
/// To compensate, when extracting an archive with <c>ExtractAll</c>,
/// DotNetZip will extract all the file and directory entries as described
/// above, but it will then make a second pass on the directories, and
/// reset the times on the directories to reflect what is stored in the
/// zip archive.
/// </para>
///
/// <para>
/// This compensation is performed only within the context of an
/// <c>ExtractAll</c>. If you call <c>ZipEntry.Extract</c> on a directory
/// entry, the timestamps on directory in the filesystem will reflect the
/// times stored in the zip. If you then call <c>ZipEntry.Extract</c> on
/// a file entry, which is extracted into the directory, the timestamps on
/// the directory will be updated to the current time.
/// </para>
/// </remarks>
///
/// <example>
/// This example extracts all the entries in a zip archive file, to the
/// specified target directory. The extraction will overwrite any
/// existing files silently.
///
/// <code>
/// String TargetDirectory= "unpack";
/// using(ZipFile zip= ZipFile.Read(ZipFileToExtract))
/// {
/// zip.ExtractExistingFile= ExtractExistingFileAction.OverwriteSilently;
/// zip.ExtractAll(TargetDirectory);
/// }
/// </code>
///
/// <code lang="VB">
/// Dim TargetDirectory As String = "unpack"
/// Using zip As ZipFile = ZipFile.Read(ZipFileToExtract)
/// zip.ExtractExistingFile= ExtractExistingFileAction.OverwriteSilently
/// zip.ExtractAll(TargetDirectory)
/// End Using
/// </code>
/// </example>
///
/// <seealso cref="Ionic.Zip.ZipFile.ExtractProgress"/>
/// <seealso cref="Ionic.Zip.ZipFile.ExtractExistingFile"/>
///
/// <param name="path">
/// The path to which the contents of the zipfile will be extracted.
/// The path can be relative or fully-qualified.
/// </param>
///
public void ExtractAll(string path)
{
_InternalExtractAll(path, true);
}
/// <summary>
/// Extracts all of the items in the zip archive, to the specified path in the
/// filesystem, using the specified behavior when extraction would overwrite an
/// existing file.
/// </summary>
///
/// <remarks>
///
/// <para>
/// This method will extract all entries in the <c>ZipFile</c> to the specified
/// path. For an extraction that would overwrite an existing file, the behavior
/// is dictated by <paramref name="extractExistingFile"/>, which overrides any
/// setting you may have made on individual ZipEntry instances.
/// </para>
///
/// <para>
/// The action to take when an extract would overwrite an existing file
/// applies to all entries. If you want to set this on a per-entry basis,
/// then you must use <see cref="ZipEntry.Extract(String,
/// ExtractExistingFileAction)" /> or one of the similar methods.
/// </para>
///
/// <para>
/// Calling this method is equivalent to setting the <see
/// cref="ExtractExistingFile"/> property and then calling <see
/// cref="ExtractAll(String)"/>.
/// </para>
///
/// <para>
/// This method will send verbose output messages to the
/// <see cref="StatusMessageTextWriter"/>, if it is set on the <c>ZipFile</c> instance.
/// </para>
/// </remarks>
///
/// <example>
/// This example extracts all the entries in a zip archive file, to the
/// specified target directory. It does not overwrite any existing files.
/// <code>
/// String TargetDirectory= "c:\\unpack";
/// using(ZipFile zip= ZipFile.Read(ZipFileToExtract))
/// {
/// zip.ExtractAll(TargetDirectory, ExtractExistingFileAction.DontOverwrite);
/// }
/// </code>
///
/// <code lang="VB">
/// Dim TargetDirectory As String = "c:\unpack"
/// Using zip As ZipFile = ZipFile.Read(ZipFileToExtract)
/// zip.ExtractAll(TargetDirectory, ExtractExistingFileAction.DontOverwrite)
/// End Using
/// </code>
/// </example>
///
/// <param name="path">
/// The path to which the contents of the zipfile will be extracted.
/// The path can be relative or fully-qualified.
/// </param>
///
/// <param name="extractExistingFile">
/// The action to take if extraction would overwrite an existing file.
/// </param>
/// <seealso cref="ExtractSelectedEntries(String,ExtractExistingFileAction)"/>
public void ExtractAll(string path, ExtractExistingFileAction extractExistingFile)
{
ExtractExistingFile = extractExistingFile;
_InternalExtractAll(path, true);
}
private void _InternalExtractAll(string path, bool overrideExtractExistingProperty)
{
bool header = Verbose;
_inExtractAll = true;
try
{
OnExtractAllStarted(path);
int n = 0;
foreach (ZipEntry e in _entries.Values)
{
if (header)
{
StatusMessageTextWriter.WriteLine("\n{1,-22} {2,-8} {3,4} {4,-8} {0}",
"Name", "Modified", "Size", "Ratio", "Packed");
StatusMessageTextWriter.WriteLine(new System.String('-', 72));
header = false;
}
if (Verbose)
{
StatusMessageTextWriter.WriteLine("{1,-22} {2,-8} {3,4:F0}% {4,-8} {0}",
e.FileName,
e.LastModified.ToString("yyyy-MM-dd HH:mm:ss"),
e.UncompressedSize,
e.CompressionRatio,
e.CompressedSize);
if (!String.IsNullOrEmpty(e.Comment))
StatusMessageTextWriter.WriteLine(" Comment: {0}", e.Comment);
}
e.Password = _Password; // this may be null
OnExtractEntry(n, true, e, path);
if (overrideExtractExistingProperty)
e.ExtractExistingFile = this.ExtractExistingFile;
e.Extract(path);
n++;
OnExtractEntry(n, false, e, path);
if (_extractOperationCanceled)
break;
}
if (!_extractOperationCanceled)
{
// workitem 8264:
// now, set times on directory entries, again.
// The problem is, extracting a file changes the times on the parent
// directory. So after all files have been extracted, we have to
// run through the directories again.
foreach (ZipEntry e in _entries.Values)
{
// check if it is a directory
if ((e.IsDirectory) || (e.FileName.EndsWith("/")))
{
string outputFile = (e.FileName.StartsWith("/"))
? Path.Combine(path, e.FileName.Substring(1))
: Path.Combine(path, e.FileName);
e._SetTimes(outputFile, false);
}
}
OnExtractAllCompleted(path);
}
}
finally
{
_inExtractAll = false;
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,964 @@
// ZipFile.Save.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-August-05 13:31:23>
//
// ------------------------------------------------------------------
//
// This module defines the methods for Save operations on zip files.
//
// ------------------------------------------------------------------
//
using System;
using System.IO;
using System.Collections.Generic;
namespace Ionic.Zip
{
public partial class ZipFile
{
/// <summary>
/// Delete file with retry on UnauthorizedAccessException.
/// </summary>
///
/// <remarks>
/// <para>
/// When calling File.Delete() on a file that has been "recently"
/// created, the call sometimes fails with
/// UnauthorizedAccessException. This method simply retries the Delete 3
/// times with a sleep between tries.
/// </para>
/// </remarks>
///
/// <param name='filename'>the name of the file to be deleted</param>
private void DeleteFileWithRetry(string filename)
{
bool done = false;
int nRetries = 3;
for (int i=0; i < nRetries && !done; i++)
{
try
{
File.Delete(filename);
done = true;
}
catch (System.UnauthorizedAccessException)
{
Console.WriteLine("************************************************** Retry delete.");
System.Threading.Thread.Sleep(200+i*200);
}
}
}
/// <summary>
/// Saves the Zip archive to a file, specified by the Name property of the
/// <c>ZipFile</c>.
/// </summary>
///
/// <remarks>
/// <para>
/// The <c>ZipFile</c> instance is written to storage, typically a zip file
/// in a filesystem, only when the caller calls <c>Save</c>. In the typical
/// case, the Save operation writes the zip content to a temporary file, and
/// then renames the temporary file to the desired name. If necessary, this
/// method will delete a pre-existing file before the rename.
/// </para>
///
/// <para>
/// The <see cref="ZipFile.Name"/> property is specified either explicitly,
/// or implicitly using one of the parameterized ZipFile constructors. For
/// COM Automation clients, the <c>Name</c> property must be set explicitly,
/// because COM Automation clients cannot call parameterized constructors.
/// </para>
///
/// <para>
/// When using a filesystem file for the Zip output, it is possible to call
/// <c>Save</c> multiple times on the <c>ZipFile</c> instance. With each
/// call the zip content is re-written to the same output file.
/// </para>
///
/// <para>
/// Data for entries that have been added to the <c>ZipFile</c> instance is
/// written to the output when the <c>Save</c> method is called. This means
/// that the input streams for those entries must be available at the time
/// the application calls <c>Save</c>. If, for example, the application
/// adds entries with <c>AddEntry</c> using a dynamically-allocated
/// <c>MemoryStream</c>, the memory stream must not have been disposed
/// before the call to <c>Save</c>. See the <see
/// cref="ZipEntry.InputStream"/> property for more discussion of the
/// availability requirements of the input stream for an entry, and an
/// approach for providing just-in-time stream lifecycle management.
/// </para>
///
/// </remarks>
///
/// <seealso cref="Ionic.Zip.ZipFile.AddEntry(String, System.IO.Stream)"/>
///
/// <exception cref="Ionic.Zip.BadStateException">
/// Thrown if you haven't specified a location or stream for saving the zip,
/// either in the constructor or by setting the Name property, or if you try
/// to save a regular zip archive to a filename with a .exe extension.
/// </exception>
///
/// <exception cref="System.OverflowException">
/// Thrown if <see cref="MaxOutputSegmentSize"/> is non-zero, and the number
/// of segments that would be generated for the spanned zip file during the
/// save operation exceeds 99. If this happens, you need to increase the
/// segment size.
/// </exception>
///
public void Save()
{
try
{
bool thisSaveUsedZip64 = false;
_saveOperationCanceled = false;
_numberOfSegmentsForMostRecentSave = 0;
OnSaveStarted();
if (WriteStream == null)
throw new BadStateException("You haven't specified where to save the zip.");
if (_name != null && _name.EndsWith(".exe") && !_SavingSfx)
throw new BadStateException("You specified an EXE for a plain zip file.");
// check if modified, before saving.
if (!_contentsChanged)
{
OnSaveCompleted();
if (Verbose) StatusMessageTextWriter.WriteLine("No save is necessary....");
return;
}
Reset(true);
if (Verbose) StatusMessageTextWriter.WriteLine("saving....");
// validate the number of entries
if (_entries.Count >= 0xFFFF && _zip64 == Zip64Option.Never)
throw new ZipException("The number of entries is 65535 or greater. Consider setting the UseZip64WhenSaving property on the ZipFile instance.");
// write an entry in the zip for each file
int n = 0;
// workitem 9831
ICollection<ZipEntry> c = (SortEntriesBeforeSaving) ? EntriesSorted : Entries;
foreach (ZipEntry e in c) // _entries.Values
{
OnSaveEntry(n, e, true);
e.Write(WriteStream);
if (_saveOperationCanceled)
break;
n++;
OnSaveEntry(n, e, false);
if (_saveOperationCanceled)
break;
// Some entries can be skipped during the save.
if (e.IncludedInMostRecentSave)
thisSaveUsedZip64 |= e.OutputUsedZip64.Value;
}
if (_saveOperationCanceled)
return;
var zss = WriteStream as ZipSegmentedStream;
_numberOfSegmentsForMostRecentSave = (zss!=null)
? zss.CurrentSegment
: 1;
bool directoryNeededZip64 =
ZipOutput.WriteCentralDirectoryStructure
(WriteStream,
c,
_numberOfSegmentsForMostRecentSave,
_zip64,
Comment,
new ZipContainer(this));
OnSaveEvent(ZipProgressEventType.Saving_AfterSaveTempArchive);
_hasBeenSaved = true;
_contentsChanged = false;
thisSaveUsedZip64 |= directoryNeededZip64;
_OutputUsesZip64 = new Nullable<bool>(thisSaveUsedZip64);
// do the rename as necessary
if (_name != null &&
(_temporaryFileName!=null || zss != null))
{
// _temporaryFileName may remain null if we are writing to a stream.
// only close the stream if there is a file behind it.
#if NETCF
WriteStream.Close();
#else
WriteStream.Dispose();
#endif
if (_saveOperationCanceled)
return;
if (_fileAlreadyExists && this._readstream != null)
{
// This means we opened and read a zip file.
// If we are now saving to the same file, we need to close the
// orig file, first.
this._readstream.Close();
this._readstream = null;
// the archiveStream for each entry needs to be null
foreach (var e in c)
{
var zss1 = e._archiveStream as ZipSegmentedStream;
if (zss1 != null)
#if NETCF
zss1.Close();
#else
zss1.Dispose();
#endif
e._archiveStream = null;
}
}
string tmpName = null;
if (File.Exists(_name))
{
// the steps:
//
// 1. Delete tmpName
// 2. move existing zip to tmpName
// 3. rename (File.Move) working file to name of existing zip
// 4. delete tmpName
//
// This series of steps avoids the exception,
// System.IO.IOException:
// "Cannot create a file when that file already exists."
//
// Cannot just call File.Replace() here because
// there is a possibility that the TEMP volume is different
// that the volume for the final file (c:\ vs d:\).
// So we need to do a Delete+Move pair.
//
// But, when doing the delete, Windows allows a process to
// delete the file, even though it is held open by, say, a
// virus scanner. It gets internally marked as "delete
// pending". The file does not actually get removed from the
// file system, it is still there after the File.Delete
// call.
//
// Therefore, we need to move the existing zip, which may be
// held open, to some other name. Then rename our working
// file to the desired name, then delete (possibly delete
// pending) the "other name".
//
// Ideally this would be transactional. It's possible that the
// delete succeeds and the move fails. Lacking transactions, if
// this kind of failure happens, we're hosed, and this logic will
// throw on the next File.Move().
//
//File.Delete(_name);
// workitem 10447
#if NETCF || SILVERLIGHT
tmpName = _name + "." + SharedUtilities.GenerateRandomStringImpl(8,0) + ".tmp";
#else
tmpName = _name + "." + Path.GetRandomFileName();
#endif
if (File.Exists(tmpName))
DeleteFileWithRetry(tmpName);
File.Move(_name, tmpName);
}
OnSaveEvent(ZipProgressEventType.Saving_BeforeRenameTempArchive);
File.Move((zss != null) ? zss.CurrentTempName : _temporaryFileName,
_name);
OnSaveEvent(ZipProgressEventType.Saving_AfterRenameTempArchive);
if (tmpName != null)
{
try
{
// not critical
if (File.Exists(tmpName))
File.Delete(tmpName);
}
catch
{
// don't care about exceptions here.
}
}
_fileAlreadyExists = true;
}
NotifyEntriesSaveComplete(c);
OnSaveCompleted();
_JustSaved = true;
}
// workitem 5043
finally
{
CleanupAfterSaveOperation();
}
return;
}
private static void NotifyEntriesSaveComplete(ICollection<ZipEntry> c)
{
foreach (ZipEntry e in c)
{
e.NotifySaveComplete();
}
}
private void RemoveTempFile()
{
try
{
if (File.Exists(_temporaryFileName))
{
File.Delete(_temporaryFileName);
}
}
catch (IOException ex1)
{
if (Verbose)
StatusMessageTextWriter.WriteLine("ZipFile::Save: could not delete temp file: {0}.", ex1.Message);
}
}
private void CleanupAfterSaveOperation()
{
if (_name != null)
{
// close the stream if there is a file behind it.
if (_writestream != null)
{
try
{
// workitem 7704
#if NETCF
_writestream.Close();
#else
_writestream.Dispose();
#endif
}
catch (System.IO.IOException) { }
}
_writestream = null;
if (_temporaryFileName != null)
{
RemoveTempFile();
_temporaryFileName = null;
}
}
}
/// <summary>
/// Save the file to a new zipfile, with the given name.
/// </summary>
///
/// <remarks>
/// <para>
/// This method allows the application to explicitly specify the name of the zip
/// file when saving. Use this when creating a new zip file, or when
/// updating a zip archive.
/// </para>
///
/// <para>
/// An application can also save a zip archive in several places by calling this
/// method multiple times in succession, with different filenames.
/// </para>
///
/// <para>
/// The <c>ZipFile</c> instance is written to storage, typically a zip file in a
/// filesystem, only when the caller calls <c>Save</c>. The Save operation writes
/// the zip content to a temporary file, and then renames the temporary file
/// to the desired name. If necessary, this method will delete a pre-existing file
/// before the rename.
/// </para>
///
/// </remarks>
///
/// <exception cref="System.ArgumentException">
/// Thrown if you specify a directory for the filename.
/// </exception>
///
/// <param name="fileName">
/// The name of the zip archive to save to. Existing files will
/// be overwritten with great prejudice.
/// </param>
///
/// <example>
/// This example shows how to create and Save a zip file.
/// <code>
/// using (ZipFile zip = new ZipFile())
/// {
/// zip.AddDirectory(@"c:\reports\January");
/// zip.Save("January.zip");
/// }
/// </code>
///
/// <code lang="VB">
/// Using zip As New ZipFile()
/// zip.AddDirectory("c:\reports\January")
/// zip.Save("January.zip")
/// End Using
/// </code>
///
/// </example>
///
/// <example>
/// This example shows how to update a zip file.
/// <code>
/// using (ZipFile zip = ZipFile.Read("ExistingArchive.zip"))
/// {
/// zip.AddFile("NewData.csv");
/// zip.Save("UpdatedArchive.zip");
/// }
/// </code>
///
/// <code lang="VB">
/// Using zip As ZipFile = ZipFile.Read("ExistingArchive.zip")
/// zip.AddFile("NewData.csv")
/// zip.Save("UpdatedArchive.zip")
/// End Using
/// </code>
///
/// </example>
public void Save(String fileName)
{
// Check for the case where we are re-saving a zip archive
// that was originally instantiated with a stream. In that case,
// the _name will be null. If so, we set _writestream to null,
// which insures that we'll cons up a new WriteStream (with a filesystem
// file backing it) in the Save() method.
if (_name == null)
_writestream = null;
else _readName = _name; // workitem 13915
_name = fileName;
if (Directory.Exists(_name))
throw new ZipException("Bad Directory", new System.ArgumentException("That name specifies an existing directory. Please specify a filename.", "fileName"));
_contentsChanged = true;
_fileAlreadyExists = File.Exists(_name);
Save();
}
/// <summary>
/// Save the zip archive to the specified stream.
/// </summary>
///
/// <remarks>
/// <para>
/// The <c>ZipFile</c> instance is written to storage - typically a zip file
/// in a filesystem, but using this overload, the storage can be anything
/// accessible via a writable stream - only when the caller calls <c>Save</c>.
/// </para>
///
/// <para>
/// Use this method to save the zip content to a stream directly. A common
/// scenario is an ASP.NET application that dynamically generates a zip file
/// and allows the browser to download it. The application can call
/// <c>Save(Response.OutputStream)</c> to write a zipfile directly to the
/// output stream, without creating a zip file on the disk on the ASP.NET
/// server.
/// </para>
///
/// <para>
/// Be careful when saving a file to a non-seekable stream, including
/// <c>Response.OutputStream</c>. When DotNetZip writes to a non-seekable
/// stream, the zip archive is formatted in such a way that may not be
/// compatible with all zip tools on all platforms. It's a perfectly legal
/// and compliant zip file, but some people have reported problems opening
/// files produced this way using the Mac OS archive utility.
/// </para>
///
/// </remarks>
///
/// <example>
///
/// This example saves the zipfile content into a MemoryStream, and
/// then gets the array of bytes from that MemoryStream.
///
/// <code lang="C#">
/// using (var zip = new Ionic.Zip.ZipFile())
/// {
/// zip.CompressionLevel= Ionic.Zlib.CompressionLevel.BestCompression;
/// zip.Password = "VerySecret.";
/// zip.Encryption = EncryptionAlgorithm.WinZipAes128;
/// zip.AddFile(sourceFileName);
/// MemoryStream output = new MemoryStream();
/// zip.Save(output);
///
/// byte[] zipbytes = output.ToArray();
/// }
/// </code>
/// </example>
///
/// <example>
/// <para>
/// This example shows a pitfall you should avoid. DO NOT read
/// from a stream, then try to save to the same stream. DO
/// NOT DO THIS:
/// </para>
///
/// <code lang="C#">
/// using (var fs = new FileSteeam(filename, FileMode.Open))
/// {
/// using (var zip = Ionic.Zip.ZipFile.Read(inputStream))
/// {
/// zip.AddEntry("Name1.txt", "this is the content");
/// zip.Save(inputStream); // NO NO NO!!
/// }
/// }
/// </code>
///
/// <para>
/// Better like this:
/// </para>
///
/// <code lang="C#">
/// using (var zip = Ionic.Zip.ZipFile.Read(filename))
/// {
/// zip.AddEntry("Name1.txt", "this is the content");
/// zip.Save(); // YES!
/// }
/// </code>
///
/// </example>
///
/// <param name="outputStream">
/// The <c>System.IO.Stream</c> to write to. It must be
/// writable. If you created the ZipFile instanct by calling
/// ZipFile.Read(), this stream must not be the same stream
/// you passed to ZipFile.Read().
/// </param>
public void Save(Stream outputStream)
{
if (outputStream == null)
throw new ArgumentNullException("outputStream");
if (!outputStream.CanWrite)
throw new ArgumentException("Must be a writable stream.", "outputStream");
// if we had a filename to save to, we are now obliterating it.
_name = null;
_writestream = new CountingStream(outputStream);
_contentsChanged = true;
_fileAlreadyExists = false;
Save();
}
}
internal static class ZipOutput
{
public static bool WriteCentralDirectoryStructure(Stream s,
ICollection<ZipEntry> entries,
uint numSegments,
Zip64Option zip64,
String comment,
ZipContainer container)
{
var zss = s as ZipSegmentedStream;
if (zss != null)
zss.ContiguousWrite = true;
// write to a memory stream in order to keep the
// CDR contiguous
Int64 aLength = 0;
using (var ms = new MemoryStream())
{
foreach (ZipEntry e in entries)
{
if (e.IncludedInMostRecentSave)
{
// this writes a ZipDirEntry corresponding to the ZipEntry
e.WriteCentralDirectoryEntry(ms);
}
}
var a = ms.ToArray();
s.Write(a, 0, a.Length);
aLength = a.Length;
}
// We need to keep track of the start and
// Finish of the Central Directory Structure.
// Cannot always use WriteStream.Length or Position; some streams do
// not support these. (eg, ASP.NET Response.OutputStream) In those
// cases we have a CountingStream.
// Also, we cannot just set Start as s.Position bfore the write, and Finish
// as s.Position after the write. In a split zip, the write may actually
// flip to the next segment. In that case, Start will be zero. But we
// don't know that til after we know the size of the thing to write. So the
// answer is to compute the directory, then ask the ZipSegmentedStream which
// segment that directory would fall in, it it were written. Then, include
// that data into the directory, and finally, write the directory to the
// output stream.
var output = s as CountingStream;
long Finish = (output != null) ? output.ComputedPosition : s.Position; // BytesWritten
long Start = Finish - aLength;
// need to know which segment the EOCD record starts in
UInt32 startSegment = (zss != null)
? zss.CurrentSegment
: 0;
Int64 SizeOfCentralDirectory = Finish - Start;
int countOfEntries = CountEntries(entries);
bool needZip64CentralDirectory =
zip64 == Zip64Option.Always ||
countOfEntries >= 0xFFFF ||
SizeOfCentralDirectory > 0xFFFFFFFF ||
Start > 0xFFFFFFFF;
byte[] a2 = null;
// emit ZIP64 extensions as required
if (needZip64CentralDirectory)
{
if (zip64 == Zip64Option.Never)
{
#if NETCF
throw new ZipException("The archive requires a ZIP64 Central Directory. Consider enabling ZIP64 extensions.");
#else
System.Diagnostics.StackFrame sf = new System.Diagnostics.StackFrame(1);
if (sf.GetMethod().DeclaringType == typeof(ZipFile))
throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipFile.UseZip64WhenSaving property.");
else
throw new ZipException("The archive requires a ZIP64 Central Directory. Consider setting the ZipOutputStream.EnableZip64 property.");
#endif
}
var a = GenZip64EndOfCentralDirectory(Start, Finish, countOfEntries, numSegments);
a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container);
if (startSegment != 0)
{
UInt32 thisSegment = zss.ComputeSegment(a.Length + a2.Length);
int i = 16;
// number of this disk
Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
i += 4;
// number of the disk with the start of the central directory
//Array.Copy(BitConverter.GetBytes(startSegment), 0, a, i, 4);
Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
i = 60;
// offset 60
// number of the disk with the start of the zip64 eocd
Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
i += 4;
i += 8;
// offset 72
// total number of disks
Array.Copy(BitConverter.GetBytes(thisSegment), 0, a, i, 4);
}
s.Write(a, 0, a.Length);
}
else
a2 = GenCentralDirectoryFooter(Start, Finish, zip64, countOfEntries, comment, container);
// now, the regular footer
if (startSegment != 0)
{
// The assumption is the central directory is never split across
// segment boundaries.
UInt16 thisSegment = (UInt16) zss.ComputeSegment(a2.Length);
int i = 4;
// number of this disk
Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2);
i += 2;
// number of the disk with the start of the central directory
//Array.Copy(BitConverter.GetBytes((UInt16)startSegment), 0, a2, i, 2);
Array.Copy(BitConverter.GetBytes(thisSegment), 0, a2, i, 2);
i += 2;
}
s.Write(a2, 0, a2.Length);
// reset the contiguous write property if necessary
if (zss != null)
zss.ContiguousWrite = false;
return needZip64CentralDirectory;
}
private static System.Text.Encoding GetEncoding(ZipContainer container, string t)
{
switch (container.AlternateEncodingUsage)
{
case ZipOption.Always:
return container.AlternateEncoding;
case ZipOption.Never:
return container.DefaultEncoding;
}
// AsNecessary is in force
var e = container.DefaultEncoding;
if (t == null) return e;
var bytes = e.GetBytes(t);
var t2 = e.GetString(bytes,0,bytes.Length);
if (t2.Equals(t)) return e;
return container.AlternateEncoding;
}
private static byte[] GenCentralDirectoryFooter(long StartOfCentralDirectory,
long EndOfCentralDirectory,
Zip64Option zip64,
int entryCount,
string comment,
ZipContainer container)
{
System.Text.Encoding encoding = GetEncoding(container, comment);
int j = 0;
int bufferLength = 22;
byte[] block = null;
Int16 commentLength = 0;
if ((comment != null) && (comment.Length != 0))
{
block = encoding.GetBytes(comment);
commentLength = (Int16)block.Length;
}
bufferLength += commentLength;
byte[] bytes = new byte[bufferLength];
int i = 0;
// signature
byte[] sig = BitConverter.GetBytes(ZipConstants.EndOfCentralDirectorySignature);
Array.Copy(sig, 0, bytes, i, 4);
i+=4;
// number of this disk
// (this number may change later)
bytes[i++] = 0;
bytes[i++] = 0;
// number of the disk with the start of the central directory
// (this number may change later)
bytes[i++] = 0;
bytes[i++] = 0;
// handle ZIP64 extensions for the end-of-central-directory
if (entryCount >= 0xFFFF || zip64 == Zip64Option.Always)
{
// the ZIP64 version.
for (j = 0; j < 4; j++)
bytes[i++] = 0xFF;
}
else
{
// the standard version.
// total number of entries in the central dir on this disk
bytes[i++] = (byte)(entryCount & 0x00FF);
bytes[i++] = (byte)((entryCount & 0xFF00) >> 8);
// total number of entries in the central directory
bytes[i++] = (byte)(entryCount & 0x00FF);
bytes[i++] = (byte)((entryCount & 0xFF00) >> 8);
}
// size of the central directory
Int64 SizeOfCentralDirectory = EndOfCentralDirectory - StartOfCentralDirectory;
if (SizeOfCentralDirectory >= 0xFFFFFFFF || StartOfCentralDirectory >= 0xFFFFFFFF)
{
// The actual data is in the ZIP64 central directory structure
for (j = 0; j < 8; j++)
bytes[i++] = 0xFF;
}
else
{
// size of the central directory (we just get the low 4 bytes)
bytes[i++] = (byte)(SizeOfCentralDirectory & 0x000000FF);
bytes[i++] = (byte)((SizeOfCentralDirectory & 0x0000FF00) >> 8);
bytes[i++] = (byte)((SizeOfCentralDirectory & 0x00FF0000) >> 16);
bytes[i++] = (byte)((SizeOfCentralDirectory & 0xFF000000) >> 24);
// offset of the start of the central directory (we just get the low 4 bytes)
bytes[i++] = (byte)(StartOfCentralDirectory & 0x000000FF);
bytes[i++] = (byte)((StartOfCentralDirectory & 0x0000FF00) >> 8);
bytes[i++] = (byte)((StartOfCentralDirectory & 0x00FF0000) >> 16);
bytes[i++] = (byte)((StartOfCentralDirectory & 0xFF000000) >> 24);
}
// zip archive comment
if ((comment == null) || (comment.Length == 0))
{
// no comment!
bytes[i++] = (byte)0;
bytes[i++] = (byte)0;
}
else
{
// the size of our buffer defines the max length of the comment we can write
if (commentLength + i + 2 > bytes.Length) commentLength = (Int16)(bytes.Length - i - 2);
bytes[i++] = (byte)(commentLength & 0x00FF);
bytes[i++] = (byte)((commentLength & 0xFF00) >> 8);
if (commentLength != 0)
{
// now actually write the comment itself into the byte buffer
for (j = 0; (j < commentLength) && (i + j < bytes.Length); j++)
{
bytes[i + j] = block[j];
}
i += j;
}
}
// s.Write(bytes, 0, i);
return bytes;
}
private static byte[] GenZip64EndOfCentralDirectory(long StartOfCentralDirectory,
long EndOfCentralDirectory,
int entryCount,
uint numSegments)
{
const int bufferLength = 12 + 44 + 20;
byte[] bytes = new byte[bufferLength];
int i = 0;
// signature
byte[] sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryRecordSignature);
Array.Copy(sig, 0, bytes, i, 4);
i+=4;
// There is a possibility to include "Extensible" data in the zip64
// end-of-central-dir record. I cannot figure out what it might be used to
// store, so the size of this record is always fixed. Maybe it is used for
// strong encryption data? That is for another day.
long DataSize = 44;
Array.Copy(BitConverter.GetBytes(DataSize), 0, bytes, i, 8);
i += 8;
// offset 12
// VersionMadeBy = 45;
bytes[i++] = 45;
bytes[i++] = 0x00;
// VersionNeededToExtract = 45;
bytes[i++] = 45;
bytes[i++] = 0x00;
// offset 16
// number of the disk, and the disk with the start of the central dir.
// (this may change later)
for (int j = 0; j < 8; j++)
bytes[i++] = 0x00;
// offset 24
long numberOfEntries = entryCount;
Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8);
i += 8;
Array.Copy(BitConverter.GetBytes(numberOfEntries), 0, bytes, i, 8);
i += 8;
// offset 40
Int64 SizeofCentraldirectory = EndOfCentralDirectory - StartOfCentralDirectory;
Array.Copy(BitConverter.GetBytes(SizeofCentraldirectory), 0, bytes, i, 8);
i += 8;
Array.Copy(BitConverter.GetBytes(StartOfCentralDirectory), 0, bytes, i, 8);
i += 8;
// offset 56
// now, the locator
// signature
sig = BitConverter.GetBytes(ZipConstants.Zip64EndOfCentralDirectoryLocatorSignature);
Array.Copy(sig, 0, bytes, i, 4);
i+=4;
// offset 60
// number of the disk with the start of the zip64 eocd
// (this will change later) (it will?)
uint x2 = (numSegments==0)?0:(uint)(numSegments-1);
Array.Copy(BitConverter.GetBytes(x2), 0, bytes, i, 4);
i+=4;
// offset 64
// relative offset of the zip64 eocd
Array.Copy(BitConverter.GetBytes(EndOfCentralDirectory), 0, bytes, i, 8);
i += 8;
// offset 72
// total number of disks
// (this will change later)
Array.Copy(BitConverter.GetBytes(numSegments), 0, bytes, i, 4);
i+=4;
return bytes;
}
private static int CountEntries(ICollection<ZipEntry> _entries)
{
// Cannot just emit _entries.Count, because some of the entries
// may have been skipped.
int count = 0;
foreach (var entry in _entries)
if (entry.IncludedInMostRecentSave) count++;
return count;
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,154 @@
// ZipFile.x-IEnumerable.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2006, 2007, 2008, 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2009-December-26 15:13:26>
//
// ------------------------------------------------------------------
//
// This module defines smoe methods for IEnumerable support. It is
// particularly important for COM to have these things in a separate module.
//
// ------------------------------------------------------------------
namespace Ionic.Zip
{
// For some weird reason, the method with the DispId(-4) attribute, which is used as
// the _NewEnum() method, and which is required to get enumeration to work from COM
// environments like VBScript and Javascript (etc) must be the LAST MEMBER in the
// source. In the event of Partial classes, it needs to be the last member defined
// in the last source module. The source modules are ordered alphabetically by
// filename. Not sure why this is true. In any case, we put the enumeration stuff
// here in this oddly-named module, for this reason.
//
public partial class ZipFile
{
/// <summary>
/// Generic IEnumerator support, for use of a ZipFile in an enumeration.
/// </summary>
///
/// <remarks>
/// You probably do not want to call <c>GetEnumerator</c> explicitly. Instead
/// it is implicitly called when you use a <see langword="foreach"/> loop in C#, or a
/// <c>For Each</c> loop in VB.NET.
/// </remarks>
///
/// <example>
/// This example reads a zipfile of a given name, then enumerates the
/// entries in that zip file, and displays the information about each
/// entry on the Console.
/// <code>
/// using (ZipFile zip = ZipFile.Read(zipfile))
/// {
/// bool header = true;
/// foreach (ZipEntry e in zip)
/// {
/// if (header)
/// {
/// System.Console.WriteLine("Zipfile: {0}", zip.Name);
/// System.Console.WriteLine("Version Needed: 0x{0:X2}", e.VersionNeeded);
/// System.Console.WriteLine("BitField: 0x{0:X2}", e.BitField);
/// System.Console.WriteLine("Compression Method: 0x{0:X2}", e.CompressionMethod);
/// System.Console.WriteLine("\n{1,-22} {2,-6} {3,4} {4,-8} {0}",
/// "Filename", "Modified", "Size", "Ratio", "Packed");
/// System.Console.WriteLine(new System.String('-', 72));
/// header = false;
/// }
///
/// System.Console.WriteLine("{1,-22} {2,-6} {3,4:F0}% {4,-8} {0}",
/// e.FileName,
/// e.LastModified.ToString("yyyy-MM-dd HH:mm:ss"),
/// e.UncompressedSize,
/// e.CompressionRatio,
/// e.CompressedSize);
///
/// e.Extract();
/// }
/// }
/// </code>
///
/// <code lang="VB">
/// Dim ZipFileToExtract As String = "c:\foo.zip"
/// Using zip As ZipFile = ZipFile.Read(ZipFileToExtract)
/// Dim header As Boolean = True
/// Dim e As ZipEntry
/// For Each e In zip
/// If header Then
/// Console.WriteLine("Zipfile: {0}", zip.Name)
/// Console.WriteLine("Version Needed: 0x{0:X2}", e.VersionNeeded)
/// Console.WriteLine("BitField: 0x{0:X2}", e.BitField)
/// Console.WriteLine("Compression Method: 0x{0:X2}", e.CompressionMethod)
/// Console.WriteLine(ChrW(10) &amp; "{1,-22} {2,-6} {3,4} {4,-8} {0}", _
/// "Filename", "Modified", "Size", "Ratio", "Packed" )
/// Console.WriteLine(New String("-"c, 72))
/// header = False
/// End If
/// Console.WriteLine("{1,-22} {2,-6} {3,4:F0}% {4,-8} {0}", _
/// e.FileName, _
/// e.LastModified.ToString("yyyy-MM-dd HH:mm:ss"), _
/// e.UncompressedSize, _
/// e.CompressionRatio, _
/// e.CompressedSize )
/// e.Extract
/// Next
/// End Using
/// </code>
/// </example>
///
/// <returns>A generic enumerator suitable for use within a foreach loop.</returns>
public System.Collections.Generic.IEnumerator<ZipEntry> GetEnumerator()
{
foreach (ZipEntry e in _entries.Values)
yield return e;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
/// <summary>
/// An IEnumerator, for use of a ZipFile in a foreach construct.
/// </summary>
///
/// <remarks>
/// This method is included for COM support. An application generally does not call
/// this method directly. It is called implicitly by COM clients when enumerating
/// the entries in the ZipFile instance. In VBScript, this is done with a <c>For Each</c>
/// statement. In Javascript, this is done with <c>new Enumerator(zipfile)</c>.
/// </remarks>
///
/// <returns>
/// The IEnumerator over the entries in the ZipFile.
/// </returns>
[System.Runtime.InteropServices.DispId(-4)]
public System.Collections.IEnumerator GetNewEnum() // the name of this method is not significant
{
return GetEnumerator();
}
}
}
@@ -0,0 +1,827 @@
// ZipInputStream.cs
//
// ------------------------------------------------------------------
//
// Copyright (c) 2009-2010 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-31 14:48:30>
//
// ------------------------------------------------------------------
//
// This module defines the ZipInputStream class, which is a stream metaphor for
// reading zip files. This class does not depend on Ionic.Zip.ZipFile, but rather
// stands alongside it as an alternative "container" for ZipEntry, when reading zips.
//
// It adds one interesting method to the normal "stream" interface: GetNextEntry.
//
// ------------------------------------------------------------------
//
using System;
using System.Threading;
using System.Collections.Generic;
using System.IO;
using Ionic.Zip;
namespace Ionic.Zip
{
/// <summary>
/// Provides a stream metaphor for reading zip files.
/// </summary>
///
/// <remarks>
/// <para>
/// This class provides an alternative programming model for reading zip files to
/// the one enabled by the <see cref="ZipFile"/> class. Use this when reading zip
/// files, as an alternative to the <see cref="ZipFile"/> class, when you would
/// like to use a Stream class to read the file.
/// </para>
///
/// <para>
/// Some application designs require a readable stream for input. This stream can
/// be used to read a zip file, and extract entries.
/// </para>
///
/// <para>
/// Both the <c>ZipInputStream</c> class and the <c>ZipFile</c> class can be used
/// to read and extract zip files. Both of them support many of the common zip
/// features, including Unicode, different compression levels, and ZIP64. The
/// programming models differ. For example, when extracting entries via calls to
/// the <c>GetNextEntry()</c> and <c>Read()</c> methods on the
/// <c>ZipInputStream</c> class, the caller is responsible for creating the file,
/// writing the bytes into the file, setting the attributes on the file, and
/// setting the created, last modified, and last accessed timestamps on the
/// file. All of these things are done automatically by a call to <see
/// cref="ZipEntry.Extract()">ZipEntry.Extract()</see>. For this reason, the
/// <c>ZipInputStream</c> is generally recommended for when your application wants
/// to extract the data, without storing that data into a file.
/// </para>
///
/// <para>
/// Aside from the obvious differences in programming model, there are some
/// differences in capability between the <c>ZipFile</c> class and the
/// <c>ZipInputStream</c> class.
/// </para>
///
/// <list type="bullet">
/// <item>
/// <c>ZipFile</c> can be used to create or update zip files, or read and
/// extract zip files. <c>ZipInputStream</c> can be used only to read and
/// extract zip files. If you want to use a stream to create zip files, check
/// out the <see cref="ZipOutputStream"/>.
/// </item>
///
/// <item>
/// <c>ZipInputStream</c> cannot read segmented or spanned
/// zip files.
/// </item>
///
/// <item>
/// <c>ZipInputStream</c> will not read Zip file comments.
/// </item>
///
/// <item>
/// When reading larger files, <c>ZipInputStream</c> will always underperform
/// <c>ZipFile</c>. This is because the <c>ZipInputStream</c> does a full scan on the
/// zip file, while the <c>ZipFile</c> class reads the central directory of the
/// zip file.
/// </item>
///
/// </list>
///
/// </remarks>
public class ZipInputStream : Stream
{
/// <summary>
/// Create a <c>ZipInputStream</c>, wrapping it around an existing stream.
/// </summary>
///
/// <remarks>
///
/// <para>
/// While the <see cref="ZipFile"/> class is generally easier
/// to use, this class provides an alternative to those
/// applications that want to read from a zipfile directly,
/// using a <see cref="System.IO.Stream"/>.
/// </para>
///
/// <para>
/// Both the <c>ZipInputStream</c> class and the <c>ZipFile</c> class can be used
/// to read and extract zip files. Both of them support many of the common zip
/// features, including Unicode, different compression levels, and ZIP64. The
/// programming models differ. For example, when extracting entries via calls to
/// the <c>GetNextEntry()</c> and <c>Read()</c> methods on the
/// <c>ZipInputStream</c> class, the caller is responsible for creating the file,
/// writing the bytes into the file, setting the attributes on the file, and
/// setting the created, last modified, and last accessed timestamps on the
/// file. All of these things are done automatically by a call to <see
/// cref="ZipEntry.Extract()">ZipEntry.Extract()</see>. For this reason, the
/// <c>ZipInputStream</c> is generally recommended for when your application wants
/// to extract the data, without storing that data into a file.
/// </para>
///
/// <para>
/// Aside from the obvious differences in programming model, there are some
/// differences in capability between the <c>ZipFile</c> class and the
/// <c>ZipInputStream</c> class.
/// </para>
///
/// <list type="bullet">
/// <item>
/// <c>ZipFile</c> can be used to create or update zip files, or read and extract
/// zip files. <c>ZipInputStream</c> can be used only to read and extract zip
/// files. If you want to use a stream to create zip files, check out the <see
/// cref="ZipOutputStream"/>.
/// </item>
///
/// <item>
/// <c>ZipInputStream</c> cannot read segmented or spanned
/// zip files.
/// </item>
///
/// <item>
/// <c>ZipInputStream</c> will not read Zip file comments.
/// </item>
///
/// <item>
/// When reading larger files, <c>ZipInputStream</c> will always underperform
/// <c>ZipFile</c>. This is because the <c>ZipInputStream</c> does a full scan on the
/// zip file, while the <c>ZipFile</c> class reads the central directory of the
/// zip file.
/// </item>
///
/// </list>
///
/// </remarks>
///
/// <param name="stream">
/// The stream to read. It must be readable. This stream will be closed at
/// the time the <c>ZipInputStream</c> is closed.
/// </param>
///
/// <example>
///
/// This example shows how to read a zip file, and extract entries, using the
/// <c>ZipInputStream</c> class.
///
/// <code lang="C#">
/// private void Unzip()
/// {
/// byte[] buffer= new byte[2048];
/// int n;
/// using (var raw = File.Open(inputFileName, FileMode.Open, FileAccess.Read))
/// {
/// using (var input= new ZipInputStream(raw))
/// {
/// ZipEntry e;
/// while (( e = input.GetNextEntry()) != null)
/// {
/// if (e.IsDirectory) continue;
/// string outputPath = Path.Combine(extractDir, e.FileName);
/// using (var output = File.Open(outputPath, FileMode.Create, FileAccess.ReadWrite))
/// {
/// while ((n= input.Read(buffer, 0, buffer.Length)) > 0)
/// {
/// output.Write(buffer,0,n);
/// }
/// }
/// }
/// }
/// }
/// }
/// </code>
///
/// <code lang="VB">
/// Private Sub UnZip()
/// Dim inputFileName As String = "MyArchive.zip"
/// Dim extractDir As String = "extract"
/// Dim buffer As Byte() = New Byte(2048) {}
/// Using raw As FileStream = File.Open(inputFileName, FileMode.Open, FileAccess.Read)
/// Using input As ZipInputStream = New ZipInputStream(raw)
/// Dim e As ZipEntry
/// Do While (Not e = input.GetNextEntry Is Nothing)
/// If Not e.IsDirectory Then
/// Using output As FileStream = File.Open(Path.Combine(extractDir, e.FileName), _
/// FileMode.Create, FileAccess.ReadWrite)
/// Dim n As Integer
/// Do While (n = input.Read(buffer, 0, buffer.Length) > 0)
/// output.Write(buffer, 0, n)
/// Loop
/// End Using
/// End If
/// Loop
/// End Using
/// End Using
/// End Sub
/// </code>
/// </example>
public ZipInputStream(Stream stream) : this (stream, false) { }
/// <summary>
/// Create a <c>ZipInputStream</c>, given the name of an existing zip file.
/// </summary>
///
/// <remarks>
///
/// <para>
/// This constructor opens a <c>FileStream</c> for the given zipfile, and
/// wraps a <c>ZipInputStream</c> around that. See the documentation for the
/// <see cref="ZipInputStream(Stream)"/> constructor for full details.
/// </para>
///
/// <para>
/// While the <see cref="ZipFile"/> class is generally easier
/// to use, this class provides an alternative to those
/// applications that want to read from a zipfile directly,
/// using a <see cref="System.IO.Stream"/>.
/// </para>
///
/// </remarks>
///
/// <param name="fileName">
/// The name of the filesystem file to read.
/// </param>
///
/// <example>
///
/// This example shows how to read a zip file, and extract entries, using the
/// <c>ZipInputStream</c> class.
///
/// <code lang="C#">
/// private void Unzip()
/// {
/// byte[] buffer= new byte[2048];
/// int n;
/// using (var input= new ZipInputStream(inputFileName))
/// {
/// ZipEntry e;
/// while (( e = input.GetNextEntry()) != null)
/// {
/// if (e.IsDirectory) continue;
/// string outputPath = Path.Combine(extractDir, e.FileName);
/// using (var output = File.Open(outputPath, FileMode.Create, FileAccess.ReadWrite))
/// {
/// while ((n= input.Read(buffer, 0, buffer.Length)) > 0)
/// {
/// output.Write(buffer,0,n);
/// }
/// }
/// }
/// }
/// }
/// </code>
///
/// <code lang="VB">
/// Private Sub UnZip()
/// Dim inputFileName As String = "MyArchive.zip"
/// Dim extractDir As String = "extract"
/// Dim buffer As Byte() = New Byte(2048) {}
/// Using input As ZipInputStream = New ZipInputStream(inputFileName)
/// Dim e As ZipEntry
/// Do While (Not e = input.GetNextEntry Is Nothing)
/// If Not e.IsDirectory Then
/// Using output As FileStream = File.Open(Path.Combine(extractDir, e.FileName), _
/// FileMode.Create, FileAccess.ReadWrite)
/// Dim n As Integer
/// Do While (n = input.Read(buffer, 0, buffer.Length) > 0)
/// output.Write(buffer, 0, n)
/// Loop
/// End Using
/// End If
/// Loop
/// End Using
/// End Sub
/// </code>
/// </example>
public ZipInputStream(String fileName)
{
Stream stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.Read );
_Init(stream, false, fileName);
}
/// <summary>
/// Create a <c>ZipInputStream</c>, explicitly specifying whether to
/// keep the underlying stream open.
/// </summary>
///
/// <remarks>
/// See the documentation for the <see
/// cref="ZipInputStream(Stream)">ZipInputStream(Stream)</see>
/// constructor for a discussion of the class, and an example of how to use the class.
/// </remarks>
///
/// <param name="stream">
/// The stream to read from. It must be readable.
/// </param>
///
/// <param name="leaveOpen">
/// true if the application would like the stream
/// to remain open after the <c>ZipInputStream</c> has been closed.
/// </param>
public ZipInputStream(Stream stream, bool leaveOpen)
{
_Init(stream, leaveOpen, null);
}
private void _Init(Stream stream, bool leaveOpen, string name)
{
_inputStream = stream;
if (!_inputStream.CanRead)
throw new ZipException("The stream must be readable.");
_container= new ZipContainer(this);
_provisionalAlternateEncoding = System.Text.Encoding.GetEncoding("IBM437");
_leaveUnderlyingStreamOpen = leaveOpen;
_findRequired= true;
_name = name ?? "(stream)";
}
/// <summary>Provides a string representation of the instance.</summary>
/// <remarks>
/// <para>
/// This can be useful for debugging purposes.
/// </para>
/// </remarks>
/// <returns>a string representation of the instance.</returns>
public override String ToString()
{
return String.Format ("ZipInputStream::{0}(leaveOpen({1})))", _name, _leaveUnderlyingStreamOpen);
}
/// <summary>
/// The text encoding to use when reading entries into the zip archive, for
/// those entries whose filenames or comments cannot be encoded with the
/// default (IBM437) encoding.
/// </summary>
///
/// <remarks>
/// <para>
/// In <see href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">its
/// zip specification</see>, PKWare describes two options for encoding
/// filenames and comments: using IBM437 or UTF-8. But, some archiving tools
/// or libraries do not follow the specification, and instead encode
/// characters using the system default code page. For example, WinRAR when
/// run on a machine in Shanghai may encode filenames with the Big-5 Chinese
/// (950) code page. This behavior is contrary to the Zip specification, but
/// it occurs anyway.
/// </para>
///
/// <para>
/// When using DotNetZip to read zip archives that use something other than
/// UTF-8 or IBM437, set this property to specify the code page to use when
/// reading encoded filenames and comments for each <c>ZipEntry</c> in the zip
/// file.
/// </para>
///
/// <para>
/// This property is "provisional". When the entry in the zip archive is not
/// explicitly marked as using UTF-8, then IBM437 is used to decode filenames
/// and comments. If a loss of data would result from using IBM436 -
/// specifically when encoding and decoding is not reflexive - the codepage
/// specified here is used. It is possible, therefore, to have a given entry
/// with a <c>Comment</c> encoded in IBM437 and a <c>FileName</c> encoded with
/// the specified "provisional" codepage.
/// </para>
///
/// <para>
/// When a zip file uses an arbitrary, non-UTF8 code page for encoding, there
/// is no standard way for the reader application - whether DotNetZip, WinZip,
/// WinRar, or something else - to know which codepage has been used for the
/// entries. Readers of zip files are not able to inspect the zip file and
/// determine the codepage that was used for the entries contained within it.
/// It is left to the application or user to determine the necessary codepage
/// when reading zip files encoded this way. If you use an incorrect codepage
/// when reading a zipfile, you will get entries with filenames that are
/// incorrect, and the incorrect filenames may even contain characters that
/// are not legal for use within filenames in Windows. Extracting entries with
/// illegal characters in the filenames will lead to exceptions. It's too bad,
/// but this is just the way things are with code pages in zip files. Caveat
/// Emptor.
/// </para>
///
/// </remarks>
public System.Text.Encoding ProvisionalAlternateEncoding
{
get
{
return _provisionalAlternateEncoding;
}
set
{
_provisionalAlternateEncoding = value;
}
}
/// <summary>
/// Size of the work buffer to use for the ZLIB codec during decompression.
/// </summary>
///
/// <remarks>
/// Setting this affects the performance and memory efficiency of compression
/// and decompression. For larger files, setting this to a larger size may
/// improve performance, but the exact numbers vary depending on available
/// memory, and a bunch of other variables. I don't have good firm
/// recommendations on how to set it. You'll have to test it yourself. Or
/// just leave it alone and accept the default.
/// </remarks>
public int CodecBufferSize
{
get;
set;
}
/// <summary>
/// Sets the password to be used on the <c>ZipInputStream</c> instance.
/// </summary>
///
/// <remarks>
///
/// <para>
/// When reading a zip archive, this password is used to read and decrypt the
/// entries that are encrypted within the zip file. When entries within a zip
/// file use different passwords, set the appropriate password for the entry
/// before the first call to <c>Read()</c> for each entry.
/// </para>
///
/// <para>
/// When reading an entry that is not encrypted, the value of this property is
/// ignored.
/// </para>
///
/// </remarks>
///
/// <example>
///
/// This example uses the ZipInputStream to read and extract entries from a
/// zip file, using a potentially different password for each entry.
///
/// <code lang="C#">
/// byte[] buffer= new byte[2048];
/// int n;
/// using (var raw = File.Open(_inputFileName, FileMode.Open, FileAccess.Read ))
/// {
/// using (var input= new ZipInputStream(raw))
/// {
/// ZipEntry e;
/// while (( e = input.GetNextEntry()) != null)
/// {
/// input.Password = PasswordForEntry(e.FileName);
/// if (e.IsDirectory) continue;
/// string outputPath = Path.Combine(_extractDir, e.FileName);
/// using (var output = File.Open(outputPath, FileMode.Create, FileAccess.ReadWrite))
/// {
/// while ((n= input.Read(buffer,0,buffer.Length)) > 0)
/// {
/// output.Write(buffer,0,n);
/// }
/// }
/// }
/// }
/// }
///
/// </code>
/// </example>
public String Password
{
set
{
if (_closed)
{
_exceptionPending = true;
throw new System.InvalidOperationException("The stream has been closed.");
}
_Password = value;
}
}
private void SetupStream()
{
// Seek to the correct posn in the file, and open a
// stream that can be read.
_crcStream= _currentEntry.InternalOpenReader(_Password);
_LeftToRead = _crcStream.Length;
_needSetup = false;
}
internal Stream ReadStream
{
get
{
return _inputStream;
}
}
/// <summary>
/// Read the data from the stream into the buffer.
/// </summary>
///
/// <remarks>
/// <para>
/// The data for the zipentry will be decrypted and uncompressed, as
/// necessary, before being copied into the buffer.
/// </para>
///
/// <para>
/// You must set the <see cref="Password"/> property before calling
/// <c>Read()</c> the first time for an encrypted entry. To determine if an
/// entry is encrypted and requires a password, check the <see
/// cref="ZipEntry.Encryption">ZipEntry.Encryption</see> property.
/// </para>
/// </remarks>
///
/// <param name="buffer">The buffer to hold the data read from the stream.</param>
/// <param name="offset">the offset within the buffer to copy the first byte read.</param>
/// <param name="count">the number of bytes to read.</param>
/// <returns>the number of bytes read, after decryption and decompression.</returns>
public override int Read(byte[] buffer, int offset, int count)
{
if (_closed)
{
_exceptionPending = true;
throw new System.InvalidOperationException("The stream has been closed.");
}
if (_needSetup)
SetupStream();
if (_LeftToRead == 0) return 0;
int len = (_LeftToRead > count) ? count : (int)_LeftToRead;
int n = _crcStream.Read(buffer, offset, len);
_LeftToRead -= n;
if (_LeftToRead == 0)
{
int CrcResult = _crcStream.Crc;
_currentEntry.VerifyCrcAfterExtract(CrcResult);
_inputStream.Seek(_endOfEntry, SeekOrigin.Begin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_inputStream);
}
return n;
}
/// <summary>
/// Read the next entry from the zip file.
/// </summary>
///
/// <remarks>
/// <para>
/// Call this method just before calling <see cref="Read(byte[], int, int)"/>,
/// to position the pointer in the zip file to the next entry that can be
/// read. Subsequent calls to <c>Read()</c>, will decrypt and decompress the
/// data in the zip file, until <c>Read()</c> returns 0.
/// </para>
///
/// <para>
/// Each time you call <c>GetNextEntry()</c>, the pointer in the wrapped
/// stream is moved to the next entry in the zip file. If you call <see
/// cref="Seek(long, SeekOrigin)"/>, and thus re-position the pointer within
/// the file, you will need to call <c>GetNextEntry()</c> again, to insure
/// that the file pointer is positioned at the beginning of a zip entry.
/// </para>
///
/// <para>
/// This method returns the <c>ZipEntry</c>. Using a stream approach, you will
/// read the raw bytes for an entry in a zip file via calls to <c>Read()</c>.
/// Alternatively, you can extract an entry into a file, or a stream, by
/// calling <see cref="ZipEntry.Extract()"/>, or one of its siblings.
/// </para>
///
/// </remarks>
///
/// <returns>
/// The <c>ZipEntry</c> read. Returns null (or Nothing in VB) if there are no more
/// entries in the zip file.
/// </returns>
///
public ZipEntry GetNextEntry()
{
if (_findRequired)
{
// find the next signature
long d = SharedUtilities.FindSignature(_inputStream, ZipConstants.ZipEntrySignature);
if (d == -1) return null;
// back up 4 bytes: ReadEntry assumes the file pointer is positioned before the entry signature
_inputStream.Seek(-4, SeekOrigin.Current);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_inputStream);
}
// workitem 10923
else if (_firstEntry)
{
// we've already read one entry.
// Seek to the end of it.
_inputStream.Seek(_endOfEntry, SeekOrigin.Begin);
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_inputStream);
}
_currentEntry = ZipEntry.ReadEntry(_container, !_firstEntry);
// ReadEntry leaves the file position after all the entry
// data and the optional bit-3 data descriptpr. This is
// where the next entry would normally start.
_endOfEntry = _inputStream.Position;
_firstEntry = true;
_needSetup = true;
_findRequired= false;
return _currentEntry;
}
/// <summary>
/// Dispose the stream.
/// </summary>
///
/// <remarks>
/// <para>
/// This method disposes the ZipInputStream. It may also close the
/// underlying stream, depending on which constructor was used.
/// </para>
///
/// <para>
/// Typically the application will call <c>Dispose()</c> implicitly, via
/// a <c>using</c> statement in C#, or a <c>Using</c> statement in VB.
/// </para>
///
/// <para>
/// Application code won't call this code directly. This method may
/// be invoked in two distinct scenarios. If disposing == true, the
/// method has been called directly or indirectly by a user's code,
/// for example via the public Dispose() method. In this case, both
/// managed and unmanaged resources can be referenced and disposed.
/// If disposing == false, the method has been called by the runtime
/// from inside the object finalizer and this method should not
/// reference other objects; in that case only unmanaged resources
/// must be referenced or disposed.
/// </para>
/// </remarks>
///
/// <param name="disposing">
/// true if the Dispose method was invoked by user code.
/// </param>
protected override void Dispose(bool disposing)
{
if (_closed) return;
if (disposing) // not called from finalizer
{
// When ZipInputStream is used within a using clause, and an
// exception is thrown, Close() is invoked. But we don't want to
// try to write anything in that case. Eventually the exception
// will be propagated to the application.
if (_exceptionPending) return;
if (!_leaveUnderlyingStreamOpen)
{
#if NETCF
_inputStream.Close();
#else
_inputStream.Dispose();
#endif
}
}
_closed= true;
}
/// <summary>
/// Always returns true.
/// </summary>
public override bool CanRead { get { return true; }}
/// <summary>
/// Returns the value of <c>CanSeek</c> for the underlying (wrapped) stream.
/// </summary>
public override bool CanSeek { get { return _inputStream.CanSeek; } }
/// <summary>
/// Always returns false.
/// </summary>
public override bool CanWrite { get { return false; } }
/// <summary>
/// Returns the length of the underlying stream.
/// </summary>
public override long Length { get { return _inputStream.Length; }}
/// <summary>
/// Gets or sets the position of the underlying stream.
/// </summary>
/// <remarks>
/// Setting the position is equivalent to calling <c>Seek(value, SeekOrigin.Begin)</c>.
/// </remarks>
public override long Position
{
get { return _inputStream.Position;}
set { Seek(value, SeekOrigin.Begin); }
}
/// <summary>
/// This is a no-op.
/// </summary>
public override void Flush()
{
throw new NotSupportedException("Flush");
}
/// <summary>
/// This method always throws a NotSupportedException.
/// </summary>
/// <param name="buffer">ignored</param>
/// <param name="offset">ignored</param>
/// <param name="count">ignored</param>
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException("Write");
}
/// <summary>
/// This method seeks in the underlying stream.
/// </summary>
///
/// <remarks>
/// <para>
/// Call this method if you want to seek around within the zip file for random access.
/// </para>
///
/// <para>
/// Applications can intermix calls to <c>Seek()</c> with calls to <see
/// cref="GetNextEntry()"/>. After a call to <c>Seek()</c>,
/// <c>GetNextEntry()</c> will get the next <c>ZipEntry</c> that falls after
/// the current position in the input stream. You're on your own for finding
/// out just where to seek in the stream, to get to the various entries.
/// </para>
///
/// </remarks>
///
/// <param name="offset">the offset point to seek to</param>
/// <param name="origin">the reference point from which to seek</param>
/// <returns>The new position</returns>
public override long Seek(long offset, SeekOrigin origin)
{
_findRequired= true;
var x = _inputStream.Seek(offset, origin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_inputStream);
return x;
}
/// <summary>
/// This method always throws a NotSupportedException.
/// </summary>
/// <param name="value">ignored</param>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
private Stream _inputStream;
private System.Text.Encoding _provisionalAlternateEncoding;
private ZipEntry _currentEntry;
private bool _firstEntry;
private bool _needSetup;
private ZipContainer _container;
private Ionic.Crc.CrcCalculatorStream _crcStream;
private Int64 _LeftToRead;
internal String _Password;
private Int64 _endOfEntry;
private string _name;
private bool _leaveUnderlyingStreamOpen;
private bool _closed;
private bool _findRequired;
private bool _exceptionPending;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,571 @@
// ZipSegmentedStream.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009-2011 Dino Chiesa.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2011-July-13 22:25:45>
//
// ------------------------------------------------------------------
//
// This module defines logic for zip streams that span disk files.
//
// ------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.IO;
namespace Ionic.Zip
{
internal class ZipSegmentedStream : System.IO.Stream
{
enum RwMode
{
None = 0,
ReadOnly = 1,
Write = 2,
//Update = 3
}
private RwMode rwMode;
private bool _exceptionPending; // **see note below
private string _baseName;
private string _baseDir;
//private bool _isDisposed;
private string _currentName;
private string _currentTempName;
private uint _currentDiskNumber;
private uint _maxDiskNumber;
private int _maxSegmentSize;
private System.IO.Stream _innerStream;
// **Note regarding exceptions:
//
// When ZipSegmentedStream is employed within a using clause,
// which is the typical scenario, and an exception is thrown
// within the scope of the using, Dispose() is invoked
// implicitly before processing the initial exception. If that
// happens, this class sets _exceptionPending to true, and then
// within the Dispose(bool), takes special action as
// appropriate. Need to be careful: any additional exceptions
// will mask the original one.
private ZipSegmentedStream() : base()
{
_exceptionPending = false;
}
public static ZipSegmentedStream ForReading(string name,
uint initialDiskNumber,
uint maxDiskNumber)
{
ZipSegmentedStream zss = new ZipSegmentedStream()
{
rwMode = RwMode.ReadOnly,
CurrentSegment = initialDiskNumber,
_maxDiskNumber = maxDiskNumber,
_baseName = name,
};
// Console.WriteLine("ZSS: ForReading ({0})",
// Path.GetFileName(zss.CurrentName));
zss._SetReadStream();
return zss;
}
public static ZipSegmentedStream ForWriting(string name, int maxSegmentSize)
{
ZipSegmentedStream zss = new ZipSegmentedStream()
{
rwMode = RwMode.Write,
CurrentSegment = 0,
_baseName = name,
_maxSegmentSize = maxSegmentSize,
_baseDir = Path.GetDirectoryName(name)
};
// workitem 9522
if (zss._baseDir=="") zss._baseDir=".";
zss._SetWriteStream(0);
// Console.WriteLine("ZSS: ForWriting ({0})",
// Path.GetFileName(zss.CurrentName));
return zss;
}
/// <summary>
/// Sort-of like a factory method, ForUpdate is used only when
/// the application needs to update the zip entry metadata for
/// a segmented zip file, when the starting segment is earlier
/// than the ending segment, for a particular entry.
/// </summary>
/// <remarks>
/// <para>
/// The update is always contiguous, never rolls over. As a
/// result, this method doesn't need to return a ZSS; it can
/// simply return a FileStream. That's why it's "sort of"
/// like a Factory method.
/// </para>
/// <para>
/// Caller must Close/Dispose the stream object returned by
/// this method.
/// </para>
/// </remarks>
public static Stream ForUpdate(string name, uint diskNumber)
{
if (diskNumber >= 99)
throw new ArgumentOutOfRangeException("diskNumber");
string fname =
String.Format("{0}.z{1:D2}",
Path.Combine(Path.GetDirectoryName(name),
Path.GetFileNameWithoutExtension(name)),
diskNumber + 1);
// Console.WriteLine("ZSS: ForUpdate ({0})",
// Path.GetFileName(fname));
// This class assumes that the update will not expand the
// size of the segment. Update is used only for an in-place
// update of zip metadata. It never will try to write beyond
// the end of a segment.
return File.Open(fname,
FileMode.Open,
FileAccess.ReadWrite,
FileShare.None);
}
public bool ContiguousWrite
{
get;
set;
}
public UInt32 CurrentSegment
{
get
{
return _currentDiskNumber;
}
private set
{
_currentDiskNumber = value;
_currentName = null; // it will get updated next time referenced
}
}
/// <summary>
/// Name of the filesystem file corresponding to the current segment.
/// </summary>
/// <remarks>
/// <para>
/// The name is not always the name currently being used in the
/// filesystem. When rwMode is RwMode.Write, the filesystem file has a
/// temporary name until the stream is closed or until the next segment is
/// started.
/// </para>
/// </remarks>
public String CurrentName
{
get
{
if (_currentName==null)
_currentName = _NameForSegment(CurrentSegment);
return _currentName;
}
}
public String CurrentTempName
{
get
{
return _currentTempName;
}
}
private string _NameForSegment(uint diskNumber)
{
if (diskNumber >= 99)
{
_exceptionPending = true;
throw new OverflowException("The number of zip segments would exceed 99.");
}
return String.Format("{0}.z{1:D2}",
Path.Combine(Path.GetDirectoryName(_baseName),
Path.GetFileNameWithoutExtension(_baseName)),
diskNumber + 1);
}
// Returns the segment that WILL be current if writing
// a block of the given length.
// This isn't exactly true. It could roll over beyond
// this number.
public UInt32 ComputeSegment(int length)
{
if (_innerStream.Position + length > _maxSegmentSize)
// the block will go AT LEAST into the next segment
return CurrentSegment + 1;
// it will fit in the current segment
return CurrentSegment;
}
public override String ToString()
{
return String.Format("{0}[{1}][{2}], pos=0x{3:X})",
"ZipSegmentedStream", CurrentName,
rwMode.ToString(),
this.Position);
}
private void _SetReadStream()
{
if (_innerStream != null)
{
#if NETCF
_innerStream.Close();
#else
_innerStream.Dispose();
#endif
}
if (CurrentSegment + 1 == _maxDiskNumber)
_currentName = _baseName;
// Console.WriteLine("ZSS: SRS ({0})",
// Path.GetFileName(CurrentName));
_innerStream = File.OpenRead(CurrentName);
}
/// <summary>
/// Read from the stream
/// </summary>
/// <param name="buffer">the buffer to read</param>
/// <param name="offset">the offset at which to start</param>
/// <param name="count">the number of bytes to read</param>
/// <returns>the number of bytes actually read</returns>
public override int Read(byte[] buffer, int offset, int count)
{
if (rwMode != RwMode.ReadOnly)
{
_exceptionPending = true;
throw new InvalidOperationException("Stream Error: Cannot Read.");
}
int r = _innerStream.Read(buffer, offset, count);
int r1 = r;
while (r1 != count)
{
if (_innerStream.Position != _innerStream.Length)
{
_exceptionPending = true;
throw new ZipException(String.Format("Read error in file {0}", CurrentName));
}
if (CurrentSegment + 1 == _maxDiskNumber)
return r; // no more to read
CurrentSegment++;
_SetReadStream();
offset += r1;
count -= r1;
r1 = _innerStream.Read(buffer, offset, count);
r += r1;
}
return r;
}
private void _SetWriteStream(uint increment)
{
if (_innerStream != null)
{
#if NETCF
_innerStream.Close();
#else
_innerStream.Dispose();
#endif
if (File.Exists(CurrentName))
File.Delete(CurrentName);
File.Move(_currentTempName, CurrentName);
// Console.WriteLine("ZSS: SWS close ({0})",
// Path.GetFileName(CurrentName));
}
if (increment > 0)
CurrentSegment += increment;
SharedUtilities.CreateAndOpenUniqueTempFile(_baseDir,
out _innerStream,
out _currentTempName);
// Console.WriteLine("ZSS: SWS open ({0})",
// Path.GetFileName(_currentTempName));
if (CurrentSegment == 0)
_innerStream.Write(BitConverter.GetBytes(ZipConstants.SplitArchiveSignature), 0, 4);
}
/// <summary>
/// Write to the stream.
/// </summary>
/// <param name="buffer">the buffer from which to write</param>
/// <param name="offset">the offset at which to start writing</param>
/// <param name="count">the number of bytes to write</param>
public override void Write(byte[] buffer, int offset, int count)
{
if (rwMode != RwMode.Write)
{
_exceptionPending = true;
throw new InvalidOperationException("Stream Error: Cannot Write.");
}
if (ContiguousWrite)
{
// enough space for a contiguous write?
if (_innerStream.Position + count > _maxSegmentSize)
_SetWriteStream(1);
}
else
{
while (_innerStream.Position + count > _maxSegmentSize)
{
int c = unchecked(_maxSegmentSize - (int)_innerStream.Position);
_innerStream.Write(buffer, offset, c);
_SetWriteStream(1);
count -= c;
offset += c;
}
}
_innerStream.Write(buffer, offset, count);
}
public long TruncateBackward(uint diskNumber, long offset)
{
// Console.WriteLine("***ZSS.Trunc to disk {0}", diskNumber);
// Console.WriteLine("***ZSS.Trunc: current disk {0}", CurrentSegment);
if (diskNumber >= 99)
throw new ArgumentOutOfRangeException("diskNumber");
if (rwMode != RwMode.Write)
{
_exceptionPending = true;
throw new ZipException("bad state.");
}
// Seek back in the segmented stream to a (maybe) prior segment.
// Check if it is the same segment. If it is, very simple.
if (diskNumber == CurrentSegment)
{
var x =_innerStream.Seek(offset, SeekOrigin.Begin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream);
return x;
}
// Seeking back to a prior segment.
// The current segment and any intervening segments must be removed.
// First, close the current segment, and then remove it.
if (_innerStream != null)
{
#if NETCF
_innerStream.Close();
#else
_innerStream.Dispose();
#endif
if (File.Exists(_currentTempName))
File.Delete(_currentTempName);
}
// Now, remove intervening segments.
for (uint j= CurrentSegment-1; j > diskNumber; j--)
{
string s = _NameForSegment(j);
// Console.WriteLine("***ZSS.Trunc: removing file {0}", s);
if (File.Exists(s))
File.Delete(s);
}
// now, open the desired segment. It must exist.
CurrentSegment = diskNumber;
// get a new temp file, try 3 times:
for (int i = 0; i < 3; i++)
{
try
{
_currentTempName = SharedUtilities.InternalGetTempFileName();
// move the .z0x file back to a temp name
File.Move(CurrentName, _currentTempName);
break; // workitem 12403
}
catch(IOException)
{
if (i == 2) throw;
}
}
// open it
_innerStream = new FileStream(_currentTempName, FileMode.Open);
var r = _innerStream.Seek(offset, SeekOrigin.Begin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream);
return r;
}
public override bool CanRead
{
get
{
return (rwMode == RwMode.ReadOnly &&
(_innerStream != null) &&
_innerStream.CanRead);
}
}
public override bool CanSeek
{
get
{
return (_innerStream != null) &&
_innerStream.CanSeek;
}
}
public override bool CanWrite
{
get
{
return (rwMode == RwMode.Write) &&
(_innerStream != null) &&
_innerStream.CanWrite;
}
}
public override void Flush()
{
_innerStream.Flush();
}
public override long Length
{
get
{
return _innerStream.Length;
}
}
public override long Position
{
get { return _innerStream.Position; }
set { _innerStream.Position = value; }
}
public override long Seek(long offset, System.IO.SeekOrigin origin)
{
var x = _innerStream.Seek(offset, origin);
// workitem 10178
Ionic.Zip.SharedUtilities.Workaround_Ladybug318918(_innerStream);
return x;
}
public override void SetLength(long value)
{
if (rwMode != RwMode.Write)
{
_exceptionPending = true;
throw new InvalidOperationException();
}
_innerStream.SetLength(value);
}
protected override void Dispose(bool disposing)
{
// this gets called by Stream.Close()
// if (_isDisposed) return;
// _isDisposed = true;
//Console.WriteLine("Dispose (mode={0})\n", rwMode.ToString());
try
{
if (_innerStream != null)
{
#if NETCF
_innerStream.Close();
#else
_innerStream.Dispose();
#endif
//_innerStream = null;
if (rwMode == RwMode.Write)
{
if (_exceptionPending)
{
// possibly could try to clean up all the
// temp files created so far...
}
else
{
// // move the final temp file to the .zNN name
// if (File.Exists(CurrentName))
// File.Delete(CurrentName);
// if (File.Exists(_currentTempName))
// File.Move(_currentTempName, CurrentName);
}
}
}
}
finally
{
base.Dispose(disposing);
}
}
}
}