2 using System.Collections.Generic;
5 using SharpCompress.Common;
6 using SharpCompress.Common.Zip;
7 using SharpCompress.Common.Zip.Headers;
8 using SharpCompress.Compressor;
10 using SharpCompress.Compressor.BZip2;
12 using SharpCompress.Compressor.Deflate;
14 using SharpCompress.Compressor.LZMA;
17 using SharpCompress.Compressor.PPMd;
19 using SharpCompress.IO;
21 using DeflateStream = SharpCompress.Compressor.Deflate.DeflateStream;
24 namespace SharpCompress.Writer.Zip
26 internal class ZipWriter : AbstractWriter
28 private readonly ZipCompressionMethod compression;
29 private readonly CompressionLevel deflateCompressionLevel;
31 private readonly List<ZipCentralDirectoryEntry> entries = new List<ZipCentralDirectoryEntry>();
32 private readonly string zipComment;
33 private readonly Encoding encoding;
34 private long streamPosition;
37 private readonly PpmdProperties ppmdProperties; // Caching properties to speed up PPMd.
40 public ZipWriter(Stream destination, CompressionInfo compressionInfo, string zipComment, Encoding encoding = null)
41 : base(ArchiveType.Zip)
43 this.zipComment = zipComment ?? string.Empty;
44 this.encoding = encoding ?? ArchiveEncoding.Default;
46 switch (compressionInfo.Type)
48 case CompressionType.None:
50 compression = ZipCompressionMethod.None;
53 case CompressionType.Deflate:
55 compression = ZipCompressionMethod.Deflate;
56 deflateCompressionLevel = compressionInfo.DeflateCompressionLevel;
59 case CompressionType.BZip2:
61 compression = ZipCompressionMethod.BZip2;
65 case CompressionType.LZMA:
67 compression = ZipCompressionMethod.LZMA;
72 case CompressionType.PPMd:
74 ppmdProperties = new PpmdProperties();
75 compression = ZipCompressionMethod.PPMd;
80 throw new InvalidFormatException("Invalid compression method: " + compressionInfo.Type);
82 InitalizeStream(destination, false);
85 protected override void Dispose(bool isDisposing)
90 foreach (ZipCentralDirectoryEntry entry in entries)
92 size += entry.Write(OutputStream, compression);
96 base.Dispose(isDisposing);
99 public override void Write(string entryPath, Stream source, DateTime? modificationTime)
101 Write(entryPath, source, modificationTime, null);
104 public void Write(string entryPath, Stream source, DateTime? modificationTime, string comment)
106 using (Stream output = WriteToStream(entryPath, modificationTime, comment))
108 source.TransferTo(output);
112 public Stream WriteToStream(string entryPath, DateTime? modificationTime, string comment)
114 entryPath = NormalizeFilename(entryPath);
115 modificationTime = modificationTime ?? DateTime.Now;
116 comment = comment ?? "";
117 var entry = new ZipCentralDirectoryEntry
120 FileName = entryPath,
121 ModificationTime = modificationTime,
122 HeaderOffset = (uint) streamPosition,
124 var headersize = (uint) WriteHeader(entryPath, modificationTime);
125 streamPosition += headersize;
126 return new ZipWritingStream(this, OutputStream, entry);
129 private string NormalizeFilename(string filename)
131 filename = filename.Replace('\\', '/');
133 int pos = filename.IndexOf(':');
135 filename = filename.Remove(0, pos + 1);
140 private int WriteHeader(string filename, DateTime? modificationTime)
142 byte[] encodedFilename = encoding.GetBytes(filename);
144 OutputStream.Write(BitConverter.GetBytes(ZipHeaderFactory.ENTRY_HEADER_BYTES), 0, 4);
145 OutputStream.Write(new byte[] {63, 0}, 0, 2); //version
146 HeaderFlags flags = encoding == Encoding.UTF8 ? HeaderFlags.UTF8 : (HeaderFlags)0;
147 if (!OutputStream.CanSeek)
149 flags |= HeaderFlags.UsePostDataDescriptor;
150 if (compression == ZipCompressionMethod.LZMA)
152 flags |= HeaderFlags.Bit1; // eos marker
155 OutputStream.Write(BitConverter.GetBytes((ushort) flags), 0, 2);
156 OutputStream.Write(BitConverter.GetBytes((ushort) compression), 0, 2); // zipping method
157 OutputStream.Write(BitConverter.GetBytes(modificationTime.DateTimeToDosTime()), 0, 4);
158 // zipping date and time
159 OutputStream.Write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0, 12);
160 // unused CRC, un/compressed size, updated later
161 OutputStream.Write(BitConverter.GetBytes((ushort) encodedFilename.Length), 0, 2); // filename length
162 OutputStream.Write(BitConverter.GetBytes((ushort) 0), 0, 2); // extra length
163 OutputStream.Write(encodedFilename, 0, encodedFilename.Length);
165 return 6 + 2 + 2 + 4 + 12 + 2 + 2 + encodedFilename.Length;
168 private void WriteFooter(uint crc, uint compressed, uint uncompressed)
170 OutputStream.Write(BitConverter.GetBytes(crc), 0, 4);
171 OutputStream.Write(BitConverter.GetBytes(compressed), 0, 4);
172 OutputStream.Write(BitConverter.GetBytes(uncompressed), 0, 4);
175 private void WriteEndRecord(uint size)
177 byte[] encodedComment = encoding.GetBytes(zipComment);
179 OutputStream.Write(new byte[] {80, 75, 5, 6, 0, 0, 0, 0}, 0, 8);
180 OutputStream.Write(BitConverter.GetBytes((ushort) entries.Count), 0, 2);
181 OutputStream.Write(BitConverter.GetBytes((ushort) entries.Count), 0, 2);
182 OutputStream.Write(BitConverter.GetBytes(size), 0, 4);
183 OutputStream.Write(BitConverter.GetBytes((uint) streamPosition), 0, 4);
184 OutputStream.Write(BitConverter.GetBytes((ushort) encodedComment.Length), 0, 2);
185 OutputStream.Write(encodedComment, 0, encodedComment.Length);
188 #region Nested type: ZipWritingStream
190 internal class ZipWritingStream : Stream
192 private readonly CRC32 crc = new CRC32();
193 private readonly ZipCentralDirectoryEntry entry;
194 private readonly Stream originalStream;
195 private readonly Stream writeStream;
196 private readonly ZipWriter writer;
197 private CountingWritableSubStream counting;
198 private uint decompressed;
200 internal ZipWritingStream(ZipWriter writer, Stream originalStream, ZipCentralDirectoryEntry entry)
202 this.writer = writer;
203 this.originalStream = originalStream;
204 writeStream = GetWriteStream(originalStream);
205 this.writer = writer;
209 public override bool CanRead
211 get { return false; }
214 public override bool CanSeek
216 get { return false; }
219 public override bool CanWrite
224 public override long Length
226 get { throw new NotSupportedException(); }
229 public override long Position
231 get { throw new NotSupportedException(); }
232 set { throw new NotSupportedException(); }
235 private Stream GetWriteStream(Stream writeStream)
237 counting = new CountingWritableSubStream(writeStream);
238 Stream output = counting;
239 switch (writer.compression)
241 case ZipCompressionMethod.None:
245 case ZipCompressionMethod.Deflate:
247 return new System.IO.Compression.DeflateStream(counting,
248 System.IO.Compression.CompressionMode.Compress, true);
251 case ZipCompressionMethod.BZip2:
253 return new BZip2Stream(counting, CompressionMode.Compress, true);
257 case ZipCompressionMethod.LZMA:
259 counting.WriteByte(9);
260 counting.WriteByte(20);
261 counting.WriteByte(5);
262 counting.WriteByte(0);
264 LzmaStream lzmaStream = new LzmaStream(new LzmaEncoderProperties(!originalStream.CanSeek),
266 counting.Write(lzmaStream.Properties, 0, lzmaStream.Properties.Length);
271 case ZipCompressionMethod.PPMd:
273 counting.Write(writer.ppmdProperties.Properties, 0, 2);
274 return new PpmdStream(writer.ppmdProperties, counting, true);
279 throw new NotSupportedException("CompressionMethod: " + writer.compression);
284 protected override void Dispose(bool disposing)
286 base.Dispose(disposing);
289 writeStream.Dispose();
290 entry.Crc = (uint) crc.Crc32Result;
291 entry.Compressed = counting.Count;
292 entry.Decompressed = decompressed;
293 if (originalStream.CanSeek)
295 originalStream.Position = entry.HeaderOffset + 6;
296 originalStream.WriteByte(0);
297 originalStream.Position = entry.HeaderOffset + 14;
298 writer.WriteFooter(entry.Crc, counting.Count, decompressed);
299 originalStream.Position = writer.streamPosition + entry.Compressed;
300 writer.streamPosition += entry.Compressed;
304 originalStream.Write(BitConverter.GetBytes(ZipHeaderFactory.POST_DATA_DESCRIPTOR), 0, 4);
305 writer.WriteFooter(entry.Crc, counting.Count, decompressed);
306 writer.streamPosition += entry.Compressed + 16;
308 writer.entries.Add(entry);
312 public override void Flush()
317 public override int Read(byte[] buffer, int offset, int count)
319 throw new NotSupportedException();
322 public override long Seek(long offset, SeekOrigin origin)
324 throw new NotSupportedException();
327 public override void SetLength(long value)
329 throw new NotSupportedException();
332 public override void Write(byte[] buffer, int offset, int count)
334 decompressed += (uint) count;
335 crc.SlurpBlock(buffer, offset, count);
336 writeStream.Write(buffer, offset, count);