8b9b85e7f5
Former-commit-id: 172c8e3c300b39d5785c7a3e8dfb08ebdbc1a99b
340 lines
12 KiB
C#
340 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using SharpCompress.Common;
|
|
using SharpCompress.Common.Zip;
|
|
using SharpCompress.Common.Zip.Headers;
|
|
using SharpCompress.Compressor;
|
|
#if BZIP2
|
|
using SharpCompress.Compressor.BZip2;
|
|
#endif
|
|
using SharpCompress.Compressor.Deflate;
|
|
#if LZMA
|
|
using SharpCompress.Compressor.LZMA;
|
|
#endif
|
|
#if PPMd
|
|
using SharpCompress.Compressor.PPMd;
|
|
#endif
|
|
using SharpCompress.IO;
|
|
#if DEFLATE
|
|
using DeflateStream = SharpCompress.Compressor.Deflate.DeflateStream;
|
|
#endif
|
|
|
|
namespace SharpCompress.Writer.Zip
|
|
{
|
|
internal class ZipWriter : AbstractWriter
|
|
{
|
|
private readonly ZipCompressionMethod compression;
|
|
private readonly CompressionLevel deflateCompressionLevel;
|
|
|
|
private readonly List<ZipCentralDirectoryEntry> entries = new List<ZipCentralDirectoryEntry>();
|
|
private readonly string zipComment;
|
|
private long streamPosition;
|
|
|
|
#if PPMd
|
|
private readonly PpmdProperties ppmdProperties; // Caching properties to speed up PPMd.
|
|
#endif
|
|
|
|
public ZipWriter(Stream destination, CompressionInfo compressionInfo, string zipComment)
|
|
: base(ArchiveType.Zip)
|
|
{
|
|
this.zipComment = zipComment ?? string.Empty;
|
|
|
|
switch (compressionInfo.Type)
|
|
{
|
|
case CompressionType.None:
|
|
{
|
|
compression = ZipCompressionMethod.None;
|
|
}
|
|
break;
|
|
case CompressionType.Deflate:
|
|
{
|
|
compression = ZipCompressionMethod.Deflate;
|
|
deflateCompressionLevel = compressionInfo.DeflateCompressionLevel;
|
|
}
|
|
break;
|
|
case CompressionType.BZip2:
|
|
{
|
|
compression = ZipCompressionMethod.BZip2;
|
|
}
|
|
break;
|
|
#if LZMA
|
|
case CompressionType.LZMA:
|
|
{
|
|
compression = ZipCompressionMethod.LZMA;
|
|
}
|
|
break;
|
|
#endif
|
|
#if PPMd
|
|
case CompressionType.PPMd:
|
|
{
|
|
ppmdProperties = new PpmdProperties();
|
|
compression = ZipCompressionMethod.PPMd;
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
throw new InvalidFormatException("Invalid compression method: " + compressionInfo.Type);
|
|
}
|
|
InitalizeStream(destination, false);
|
|
}
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
if (isDisposing)
|
|
{
|
|
uint size = 0;
|
|
foreach (ZipCentralDirectoryEntry entry in entries)
|
|
{
|
|
size += entry.Write(OutputStream, compression);
|
|
}
|
|
WriteEndRecord(size);
|
|
}
|
|
base.Dispose(isDisposing);
|
|
}
|
|
|
|
public override void Write(string entryPath, Stream source, DateTime? modificationTime)
|
|
{
|
|
Write(entryPath, source, modificationTime, null);
|
|
}
|
|
|
|
public void Write(string entryPath, Stream source, DateTime? modificationTime, string comment)
|
|
{
|
|
using (Stream output = WriteToStream(entryPath, modificationTime, comment))
|
|
{
|
|
source.TransferTo(output);
|
|
}
|
|
}
|
|
|
|
public Stream WriteToStream(string entryPath, DateTime? modificationTime, string comment)
|
|
{
|
|
entryPath = NormalizeFilename(entryPath);
|
|
modificationTime = modificationTime ?? DateTime.Now;
|
|
comment = comment ?? "";
|
|
var entry = new ZipCentralDirectoryEntry
|
|
{
|
|
Comment = comment,
|
|
FileName = entryPath,
|
|
ModificationTime = modificationTime,
|
|
HeaderOffset = (uint) streamPosition,
|
|
};
|
|
var headersize = (uint) WriteHeader(entryPath, modificationTime);
|
|
streamPosition += headersize;
|
|
return new ZipWritingStream(this, OutputStream, entry);
|
|
}
|
|
|
|
private string NormalizeFilename(string filename)
|
|
{
|
|
filename = filename.Replace('\\', '/');
|
|
|
|
int pos = filename.IndexOf(':');
|
|
if (pos >= 0)
|
|
filename = filename.Remove(0, pos + 1);
|
|
|
|
return filename;
|
|
}
|
|
|
|
private int WriteHeader(string filename, DateTime? modificationTime)
|
|
{
|
|
byte[] encodedFilename = Encoding.UTF8.GetBytes(filename);
|
|
|
|
OutputStream.Write(BitConverter.GetBytes(ZipHeaderFactory.ENTRY_HEADER_BYTES), 0, 4);
|
|
OutputStream.Write(new byte[] {63, 0}, 0, 2); //version
|
|
HeaderFlags flags = HeaderFlags.UTF8;
|
|
if (!OutputStream.CanSeek)
|
|
{
|
|
flags |= HeaderFlags.UsePostDataDescriptor;
|
|
if (compression == ZipCompressionMethod.LZMA)
|
|
{
|
|
flags |= HeaderFlags.Bit1; // eos marker
|
|
}
|
|
}
|
|
OutputStream.Write(BitConverter.GetBytes((ushort) flags), 0, 2);
|
|
OutputStream.Write(BitConverter.GetBytes((ushort) compression), 0, 2); // zipping method
|
|
OutputStream.Write(BitConverter.GetBytes(modificationTime.DateTimeToDosTime()), 0, 4);
|
|
// zipping date and time
|
|
OutputStream.Write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0, 12);
|
|
// unused CRC, un/compressed size, updated later
|
|
OutputStream.Write(BitConverter.GetBytes((ushort) encodedFilename.Length), 0, 2); // filename length
|
|
OutputStream.Write(BitConverter.GetBytes((ushort) 0), 0, 2); // extra length
|
|
OutputStream.Write(encodedFilename, 0, encodedFilename.Length);
|
|
|
|
return 6 + 2 + 2 + 4 + 12 + 2 + 2 + encodedFilename.Length;
|
|
}
|
|
|
|
private void WriteFooter(uint crc, uint compressed, uint uncompressed)
|
|
{
|
|
OutputStream.Write(BitConverter.GetBytes(crc), 0, 4);
|
|
OutputStream.Write(BitConverter.GetBytes(compressed), 0, 4);
|
|
OutputStream.Write(BitConverter.GetBytes(uncompressed), 0, 4);
|
|
}
|
|
|
|
private void WriteEndRecord(uint size)
|
|
{
|
|
byte[] encodedComment = Encoding.UTF8.GetBytes(zipComment);
|
|
|
|
OutputStream.Write(new byte[] {80, 75, 5, 6, 0, 0, 0, 0}, 0, 8);
|
|
OutputStream.Write(BitConverter.GetBytes((ushort) entries.Count), 0, 2);
|
|
OutputStream.Write(BitConverter.GetBytes((ushort) entries.Count), 0, 2);
|
|
OutputStream.Write(BitConverter.GetBytes(size), 0, 4);
|
|
OutputStream.Write(BitConverter.GetBytes((uint) streamPosition), 0, 4);
|
|
OutputStream.Write(BitConverter.GetBytes((ushort) encodedComment.Length), 0, 2);
|
|
OutputStream.Write(encodedComment, 0, encodedComment.Length);
|
|
}
|
|
|
|
#region Nested type: ZipWritingStream
|
|
|
|
internal class ZipWritingStream : Stream
|
|
{
|
|
private readonly CRC32 crc = new CRC32();
|
|
private readonly ZipCentralDirectoryEntry entry;
|
|
private readonly Stream originalStream;
|
|
private readonly Stream writeStream;
|
|
private readonly ZipWriter writer;
|
|
private CountingWritableSubStream counting;
|
|
private uint decompressed;
|
|
|
|
internal ZipWritingStream(ZipWriter writer, Stream originalStream, ZipCentralDirectoryEntry entry)
|
|
{
|
|
this.writer = writer;
|
|
this.originalStream = originalStream;
|
|
writeStream = GetWriteStream(originalStream);
|
|
this.writer = writer;
|
|
this.entry = entry;
|
|
}
|
|
|
|
public override bool CanRead
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
public override bool CanSeek
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
public override bool CanWrite
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
public override long Length
|
|
{
|
|
get { throw new NotSupportedException(); }
|
|
}
|
|
|
|
public override long Position
|
|
{
|
|
get { throw new NotSupportedException(); }
|
|
set { throw new NotSupportedException(); }
|
|
}
|
|
|
|
private Stream GetWriteStream(Stream writeStream)
|
|
{
|
|
counting = new CountingWritableSubStream(writeStream);
|
|
Stream output = counting;
|
|
switch (writer.compression)
|
|
{
|
|
case ZipCompressionMethod.None:
|
|
{
|
|
return output;
|
|
}
|
|
case ZipCompressionMethod.Deflate:
|
|
{
|
|
return new System.IO.Compression.DeflateStream(counting,
|
|
System.IO.Compression.CompressionMode.Compress, true);
|
|
}
|
|
#if BZIP2
|
|
case ZipCompressionMethod.BZip2:
|
|
{
|
|
return new BZip2Stream(counting, CompressionMode.Compress, true);
|
|
}
|
|
#endif
|
|
#if LZMA
|
|
case ZipCompressionMethod.LZMA:
|
|
{
|
|
counting.WriteByte(9);
|
|
counting.WriteByte(20);
|
|
counting.WriteByte(5);
|
|
counting.WriteByte(0);
|
|
|
|
LzmaStream lzmaStream = new LzmaStream(new LzmaEncoderProperties(!originalStream.CanSeek),
|
|
false, counting);
|
|
counting.Write(lzmaStream.Properties, 0, lzmaStream.Properties.Length);
|
|
return lzmaStream;
|
|
}
|
|
#endif
|
|
#if PPMd
|
|
case ZipCompressionMethod.PPMd:
|
|
{
|
|
counting.Write(writer.ppmdProperties.Properties, 0, 2);
|
|
return new PpmdStream(writer.ppmdProperties, counting, true);
|
|
}
|
|
#endif
|
|
default:
|
|
{
|
|
throw new NotSupportedException("CompressionMethod: " + writer.compression);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
base.Dispose(disposing);
|
|
if (disposing)
|
|
{
|
|
writeStream.Dispose();
|
|
entry.Crc = (uint) crc.Crc32Result;
|
|
entry.Compressed = counting.Count;
|
|
entry.Decompressed = decompressed;
|
|
if (originalStream.CanSeek)
|
|
{
|
|
originalStream.Position = entry.HeaderOffset + 6;
|
|
originalStream.WriteByte(0);
|
|
originalStream.Position = entry.HeaderOffset + 14;
|
|
writer.WriteFooter(entry.Crc, counting.Count, decompressed);
|
|
originalStream.Position = writer.streamPosition + entry.Compressed;
|
|
writer.streamPosition += entry.Compressed;
|
|
}
|
|
else
|
|
{
|
|
originalStream.Write(BitConverter.GetBytes(ZipHeaderFactory.POST_DATA_DESCRIPTOR), 0, 4);
|
|
writer.WriteFooter(entry.Crc, counting.Count, decompressed);
|
|
writer.streamPosition += entry.Compressed + 16;
|
|
}
|
|
writer.entries.Add(entry);
|
|
}
|
|
}
|
|
|
|
public override void Flush()
|
|
{
|
|
writeStream.Flush();
|
|
}
|
|
|
|
public override int Read(byte[] buffer, int offset, int count)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override long Seek(long offset, SeekOrigin origin)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override void SetLength(long value)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
decompressed += (uint) count;
|
|
crc.SlurpBlock(buffer, offset, count);
|
|
writeStream.Write(buffer, offset, count);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |