kopia lustrzana https://github.com/dl2alf/AirScout
965 wiersze
36 KiB
C#
965 wiersze
36 KiB
C#
// 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;
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
}
|