2015-08-26 22:42:40 +02:00
|
|
|
|
// ZipStorer, by Jaime Olivares
|
|
|
|
|
// Website: zipstorer.codeplex.com
|
|
|
|
|
// Version: 2.35 (March 14, 2010)
|
|
|
|
|
|
|
|
|
|
// Simplified to extract-only by Nikse - August 18, 2010
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
namespace Nikse.SubtitleEdit.Core
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Zip archive decompression. Represents a Zip file.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class ZipExtractor : IDisposable
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Compression method enumeration
|
|
|
|
|
/// </summary>
|
|
|
|
|
public enum Compression : ushort
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Uncompressed storage</summary>
|
|
|
|
|
Store = 0,
|
|
|
|
|
/// <summary>Deflate compression method</summary>
|
|
|
|
|
Deflate = 8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Represents an entry in Zip file directory
|
|
|
|
|
/// </summary>
|
|
|
|
|
public struct ZipFileEntry
|
|
|
|
|
{
|
|
|
|
|
/// <summary>Compression method</summary>
|
|
|
|
|
public Compression Method;
|
|
|
|
|
/// <summary>Full path and filename as stored in Zip</summary>
|
|
|
|
|
public string FilenameInZip;
|
|
|
|
|
/// <summary>Original file size</summary>
|
|
|
|
|
public uint FileSize;
|
|
|
|
|
/// <summary>Compressed file size</summary>
|
|
|
|
|
public uint CompressedSize;
|
|
|
|
|
/// <summary>Offset of header information inside Zip storage</summary>
|
|
|
|
|
public uint HeaderOffset;
|
|
|
|
|
/// <summary>Offset of file inside Zip storage</summary>
|
|
|
|
|
public uint FileOffset;
|
|
|
|
|
/// <summary>Size of header information</summary>
|
|
|
|
|
public uint HeaderSize;
|
|
|
|
|
/// <summary>32-bit checksum of entire file</summary>
|
|
|
|
|
public uint Crc32;
|
|
|
|
|
/// <summary>Last modification time of file</summary>
|
|
|
|
|
public DateTime ModifyTime;
|
|
|
|
|
/// <summary>User comment for file</summary>
|
|
|
|
|
public string Comment;
|
|
|
|
|
/// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary>
|
|
|
|
|
public bool EncodeUTF8;
|
|
|
|
|
|
|
|
|
|
/// <summary>Overriden method</summary>
|
|
|
|
|
/// <returns>Filename in Zip</returns>
|
|
|
|
|
public override string ToString()
|
|
|
|
|
{
|
|
|
|
|
return this.FilenameInZip;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region Public fields
|
|
|
|
|
|
|
|
|
|
/// <summary>True if UTF8 encoding for filename and comments, false if default (CP 437)</summary>
|
|
|
|
|
public bool EncodeUTF8 = false;
|
|
|
|
|
/// <summary>Force deflate algotithm even if it inflates the stored file. Off by default.</summary>
|
|
|
|
|
public bool ForceDeflating = false;
|
|
|
|
|
|
|
|
|
|
#endregion Public fields
|
|
|
|
|
|
|
|
|
|
#region Private fields
|
|
|
|
|
|
|
|
|
|
// Stream object of storage file
|
|
|
|
|
private Stream ZipFileStream;
|
|
|
|
|
// Central dir image
|
|
|
|
|
private byte[] CentralDirImage = null;
|
|
|
|
|
// Static CRC32 Table
|
|
|
|
|
private static UInt32[] CrcTable = null;
|
|
|
|
|
// Default filename encoder
|
|
|
|
|
private static Encoding DefaultEncoding = Encoding.GetEncoding(437);
|
|
|
|
|
|
|
|
|
|
#endregion Private fields
|
|
|
|
|
|
|
|
|
|
#region Public methods
|
|
|
|
|
|
|
|
|
|
// Static constructor. Just invoked once in order to create the CRC32 lookup table.
|
|
|
|
|
static ZipExtractor()
|
|
|
|
|
{
|
|
|
|
|
// Generate CRC32 table
|
|
|
|
|
CrcTable = new UInt32[256];
|
|
|
|
|
for (int i = 0; i < CrcTable.Length; i++)
|
|
|
|
|
{
|
|
|
|
|
UInt32 c = (UInt32)i;
|
|
|
|
|
for (int j = 0; j < 8; j++)
|
|
|
|
|
{
|
|
|
|
|
if ((c & 1) != 0)
|
|
|
|
|
c = 3988292384 ^ (c >> 1);
|
|
|
|
|
else
|
|
|
|
|
c >>= 1;
|
|
|
|
|
}
|
|
|
|
|
CrcTable[i] = c;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Method to open an existing storage file
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="filename">Full path of Zip file to open</param>
|
|
|
|
|
/// <returns>A valid ZipStorer object</returns>
|
|
|
|
|
public static ZipExtractor Open(string filename)
|
|
|
|
|
{
|
|
|
|
|
Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
|
|
|
|
|
ZipExtractor zip = Open(stream);
|
|
|
|
|
return zip;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Method to open an existing storage from stream
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="stream">Already opened stream with zip contents</param>
|
|
|
|
|
/// <returns>A valid ZipStorer object</returns>
|
|
|
|
|
public static ZipExtractor Open(Stream stream)
|
|
|
|
|
{
|
|
|
|
|
ZipExtractor zip = new ZipExtractor();
|
|
|
|
|
zip.ZipFileStream = stream;
|
|
|
|
|
|
|
|
|
|
if (zip.ReadFileInfo())
|
|
|
|
|
return zip;
|
|
|
|
|
|
|
|
|
|
throw new System.IO.InvalidDataException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Close the Zip storage
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>This is a required step, unless automatic dispose is used</remarks>
|
|
|
|
|
public void Close()
|
|
|
|
|
{
|
|
|
|
|
if (this.ZipFileStream != null)
|
|
|
|
|
{
|
|
|
|
|
this.ZipFileStream.Flush();
|
|
|
|
|
this.ZipFileStream.Dispose();
|
|
|
|
|
this.ZipFileStream = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Read all the file records in the central directory
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>List of all entries in directory</returns>
|
|
|
|
|
public List<ZipFileEntry> ReadCentralDir()
|
|
|
|
|
{
|
|
|
|
|
if (this.CentralDirImage == null)
|
|
|
|
|
throw new InvalidOperationException("Central directory currently does not exist");
|
|
|
|
|
|
|
|
|
|
List<ZipFileEntry> result = new List<ZipFileEntry>();
|
|
|
|
|
|
2016-01-24 11:51:04 +01:00
|
|
|
|
for (int pointer = 0; pointer < this.CentralDirImage.Length;)
|
2015-08-26 22:42:40 +02:00
|
|
|
|
{
|
|
|
|
|
uint signature = BitConverter.ToUInt32(CentralDirImage, pointer);
|
|
|
|
|
if (signature != 0x02014b50)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
bool encodeUTF8 = (BitConverter.ToUInt16(CentralDirImage, pointer + 8) & 0x0800) != 0;
|
|
|
|
|
ushort method = BitConverter.ToUInt16(CentralDirImage, pointer + 10);
|
|
|
|
|
uint modifyTime = BitConverter.ToUInt32(CentralDirImage, pointer + 12);
|
|
|
|
|
uint crc32 = BitConverter.ToUInt32(CentralDirImage, pointer + 16);
|
|
|
|
|
uint comprSize = BitConverter.ToUInt32(CentralDirImage, pointer + 20);
|
|
|
|
|
uint fileSize = BitConverter.ToUInt32(CentralDirImage, pointer + 24);
|
|
|
|
|
ushort filenameSize = BitConverter.ToUInt16(CentralDirImage, pointer + 28);
|
|
|
|
|
ushort extraSize = BitConverter.ToUInt16(CentralDirImage, pointer + 30);
|
|
|
|
|
ushort commentSize = BitConverter.ToUInt16(CentralDirImage, pointer + 32);
|
|
|
|
|
uint headerOffset = BitConverter.ToUInt32(CentralDirImage, pointer + 42);
|
|
|
|
|
uint headerSize = (uint)(46 + filenameSize + extraSize + commentSize);
|
|
|
|
|
|
|
|
|
|
Encoding encoder = encodeUTF8 ? Encoding.UTF8 : DefaultEncoding;
|
|
|
|
|
|
|
|
|
|
ZipFileEntry zfe = new ZipFileEntry();
|
|
|
|
|
zfe.Method = (Compression)method;
|
|
|
|
|
zfe.FilenameInZip = encoder.GetString(CentralDirImage, pointer + 46, filenameSize);
|
|
|
|
|
zfe.FileOffset = GetFileOffset(headerOffset);
|
|
|
|
|
zfe.FileSize = fileSize;
|
|
|
|
|
zfe.CompressedSize = comprSize;
|
|
|
|
|
zfe.HeaderOffset = headerOffset;
|
|
|
|
|
zfe.HeaderSize = headerSize;
|
|
|
|
|
zfe.Crc32 = crc32;
|
|
|
|
|
zfe.ModifyTime = DosTimeToDateTime(modifyTime);
|
|
|
|
|
if (commentSize > 0)
|
|
|
|
|
zfe.Comment = encoder.GetString(CentralDirImage, pointer + 46 + filenameSize + extraSize, commentSize);
|
|
|
|
|
|
|
|
|
|
result.Add(zfe);
|
|
|
|
|
pointer += (46 + filenameSize + extraSize + commentSize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Copy the contents of a stored file into a physical file
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="zfe">Entry information of file to extract</param>
|
|
|
|
|
/// <param name="filename">Name of file to store uncompressed data</param>
|
|
|
|
|
/// <returns>True if success, false if not.</returns>
|
|
|
|
|
/// <remarks>Unique compression methods are Store and Deflate</remarks>
|
|
|
|
|
public bool ExtractFile(ZipFileEntry zfe, string filename)
|
|
|
|
|
{
|
|
|
|
|
// Make sure the parent directory exist
|
|
|
|
|
string path = System.IO.Path.GetDirectoryName(filename);
|
|
|
|
|
if (!Directory.Exists(path))
|
|
|
|
|
Directory.CreateDirectory(path);
|
|
|
|
|
|
|
|
|
|
// Check it is directory. If so, do nothing
|
|
|
|
|
if (Directory.Exists(filename))
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
bool result = false;
|
|
|
|
|
using (Stream output = new FileStream(filename, FileMode.Create, FileAccess.Write))
|
|
|
|
|
{
|
|
|
|
|
result = ExtractFile(zfe, output);
|
|
|
|
|
}
|
|
|
|
|
File.SetCreationTime(filename, zfe.ModifyTime);
|
|
|
|
|
File.SetLastWriteTime(filename, zfe.ModifyTime);
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Copy the contents of a stored file into an opened stream
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="zfe">Entry information of file to extract</param>
|
|
|
|
|
/// <param name="stream">Stream to store the uncompressed data</param>
|
|
|
|
|
/// <returns>True if success, false if not.</returns>
|
|
|
|
|
/// <remarks>Unique compression methods are Store and Deflate</remarks>
|
|
|
|
|
public bool ExtractFile(ZipFileEntry zfe, Stream stream)
|
|
|
|
|
{
|
|
|
|
|
if (!stream.CanWrite)
|
|
|
|
|
throw new InvalidOperationException("Stream cannot be written");
|
|
|
|
|
|
|
|
|
|
// check signature
|
|
|
|
|
byte[] signature = new byte[4];
|
|
|
|
|
this.ZipFileStream.Seek(zfe.HeaderOffset, SeekOrigin.Begin);
|
|
|
|
|
this.ZipFileStream.Read(signature, 0, 4);
|
|
|
|
|
if (BitConverter.ToUInt32(signature, 0) != 0x04034b50)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Select input stream for inflating or just reading
|
|
|
|
|
Stream inStream;
|
|
|
|
|
if (zfe.Method == Compression.Store)
|
|
|
|
|
inStream = this.ZipFileStream;
|
|
|
|
|
else if (zfe.Method == Compression.Deflate)
|
|
|
|
|
inStream = new DeflateStream(this.ZipFileStream, CompressionMode.Decompress, true);
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Buffered copy
|
|
|
|
|
byte[] buffer = new byte[16384];
|
|
|
|
|
this.ZipFileStream.Seek(zfe.FileOffset, SeekOrigin.Begin);
|
|
|
|
|
uint bytesPending = zfe.FileSize;
|
|
|
|
|
while (bytesPending > 0)
|
|
|
|
|
{
|
|
|
|
|
int bytesRead = inStream.Read(buffer, 0, (int)Math.Min(bytesPending, buffer.Length));
|
|
|
|
|
stream.Write(buffer, 0, bytesRead);
|
|
|
|
|
bytesPending -= (uint)bytesRead;
|
|
|
|
|
}
|
|
|
|
|
stream.Flush();
|
|
|
|
|
|
|
|
|
|
if (zfe.Method == Compression.Deflate)
|
|
|
|
|
inStream.Dispose();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Public methods
|
|
|
|
|
|
|
|
|
|
#region Private methods
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Calculate the file offset by reading the corresponding local header
|
|
|
|
|
/// </summary>
|
|
|
|
|
private uint GetFileOffset(uint headerOffset)
|
|
|
|
|
{
|
|
|
|
|
byte[] buffer = new byte[2];
|
|
|
|
|
|
|
|
|
|
this.ZipFileStream.Seek(headerOffset + 26, SeekOrigin.Begin);
|
|
|
|
|
this.ZipFileStream.Read(buffer, 0, 2);
|
|
|
|
|
ushort filenameSize = BitConverter.ToUInt16(buffer, 0);
|
|
|
|
|
this.ZipFileStream.Read(buffer, 0, 2);
|
|
|
|
|
ushort extraSize = BitConverter.ToUInt16(buffer, 0);
|
|
|
|
|
|
|
|
|
|
return (uint)(30 + filenameSize + extraSize + headerOffset);
|
|
|
|
|
}
|
|
|
|
|
/* Local file header:
|
|
|
|
|
local file header signature 4 bytes (0x04034b50)
|
|
|
|
|
version needed to extract 2 bytes
|
|
|
|
|
general purpose bit flag 2 bytes
|
|
|
|
|
compression method 2 bytes
|
|
|
|
|
last mod file time 2 bytes
|
|
|
|
|
last mod file date 2 bytes
|
|
|
|
|
crc-32 4 bytes
|
|
|
|
|
compressed size 4 bytes
|
|
|
|
|
uncompressed size 4 bytes
|
|
|
|
|
filename length 2 bytes
|
|
|
|
|
extra field length 2 bytes
|
|
|
|
|
|
|
|
|
|
filename (variable size)
|
|
|
|
|
extra field (variable size)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Central directory's File header:
|
|
|
|
|
central file header signature 4 bytes (0x02014b50)
|
|
|
|
|
version made by 2 bytes
|
|
|
|
|
version needed to extract 2 bytes
|
|
|
|
|
general purpose bit flag 2 bytes
|
|
|
|
|
compression method 2 bytes
|
|
|
|
|
last mod file time 2 bytes
|
|
|
|
|
last mod file date 2 bytes
|
|
|
|
|
crc-32 4 bytes
|
|
|
|
|
compressed size 4 bytes
|
|
|
|
|
uncompressed size 4 bytes
|
|
|
|
|
filename length 2 bytes
|
|
|
|
|
extra field length 2 bytes
|
|
|
|
|
file comment length 2 bytes
|
|
|
|
|
disk number start 2 bytes
|
|
|
|
|
internal file attributes 2 bytes
|
|
|
|
|
external file attributes 4 bytes
|
|
|
|
|
relative offset of local header 4 bytes
|
|
|
|
|
|
|
|
|
|
filename (variable size)
|
|
|
|
|
extra field (variable size)
|
|
|
|
|
file comment (variable size)
|
|
|
|
|
|
|
|
|
|
*
|
|
|
|
|
/* End of central dir record:
|
|
|
|
|
end of central dir signature 4 bytes (0x06054b50)
|
|
|
|
|
number of this disk 2 bytes
|
|
|
|
|
number of the disk with the
|
|
|
|
|
start of the central directory 2 bytes
|
|
|
|
|
total number of entries in
|
|
|
|
|
the central dir on this disk 2 bytes
|
|
|
|
|
total number of entries in
|
|
|
|
|
the central dir 2 bytes
|
|
|
|
|
size of the central directory 4 bytes
|
|
|
|
|
offset of start of central
|
|
|
|
|
directory with respect to
|
|
|
|
|
the starting disk number 4 bytes
|
|
|
|
|
zipfile comment length 2 bytes
|
|
|
|
|
zipfile comment (variable size)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* DOS Date and time:
|
|
|
|
|
MS-DOS date. The date is a packed value with the following format. Bits Description
|
|
|
|
|
0-4 Day of the month (1–31)
|
|
|
|
|
5-8 Month (1 = January, 2 = February, and so on)
|
|
|
|
|
9-15 Year offset from 1980 (add 1980 to get actual year)
|
|
|
|
|
MS-DOS time. The time is a packed value with the following format. Bits Description
|
|
|
|
|
0-4 Second divided by 2
|
|
|
|
|
5-10 Minute (0–59)
|
|
|
|
|
11-15 Hour (0–23 on a 24-hour clock)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
private static DateTime DosTimeToDateTime(uint dt)
|
|
|
|
|
{
|
|
|
|
|
return new DateTime(
|
|
|
|
|
(int)(dt >> 25) + 1980,
|
|
|
|
|
(int)(dt >> 21) & 15,
|
|
|
|
|
(int)(dt >> 16) & 31,
|
|
|
|
|
(int)(dt >> 11) & 31,
|
|
|
|
|
(int)(dt >> 5) & 63,
|
|
|
|
|
(int)(dt & 31) * 2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reads the end-of-central-directory record
|
|
|
|
|
private bool ReadFileInfo()
|
|
|
|
|
{
|
|
|
|
|
if (this.ZipFileStream.Length < 22)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
this.ZipFileStream.Seek(-17, SeekOrigin.End);
|
|
|
|
|
BinaryReader br = new BinaryReader(this.ZipFileStream);
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
this.ZipFileStream.Seek(-5, SeekOrigin.Current);
|
|
|
|
|
UInt32 sig = br.ReadUInt32();
|
|
|
|
|
if (sig == 0x06054b50)
|
|
|
|
|
{
|
|
|
|
|
this.ZipFileStream.Seek(6, SeekOrigin.Current);
|
|
|
|
|
|
|
|
|
|
UInt16 entries = br.ReadUInt16();
|
|
|
|
|
Int32 centralSize = br.ReadInt32();
|
|
|
|
|
UInt32 centralDirOffset = br.ReadUInt32();
|
|
|
|
|
UInt16 commentSize = br.ReadUInt16();
|
|
|
|
|
|
|
|
|
|
// check if comment field is the very last data in file
|
|
|
|
|
if (this.ZipFileStream.Position + commentSize != this.ZipFileStream.Length)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Copy entire central directory to a memory buffer
|
|
|
|
|
this.CentralDirImage = new byte[centralSize];
|
|
|
|
|
this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
|
|
|
|
|
this.ZipFileStream.Read(this.CentralDirImage, 0, centralSize);
|
|
|
|
|
|
|
|
|
|
// Leave the pointer at the begining of central dir, to append new files
|
|
|
|
|
this.ZipFileStream.Seek(centralDirOffset, SeekOrigin.Begin);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} while (this.ZipFileStream.Position > 0);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion Private methods
|
|
|
|
|
|
|
|
|
|
#region IDisposable Members
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
|
|
|
|
this.Close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion IDisposable Members
|
|
|
|
|
}
|
2016-01-24 11:51:04 +01:00
|
|
|
|
}
|