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 long streamPosition;
36 private readonly PpmdProperties ppmdProperties; // Caching properties to speed up PPMd.
39 public ZipWriter(Stream destination, CompressionInfo compressionInfo, string zipComment)
40 : base(ArchiveType.Zip)
42 this.zipComment = zipComment ?? string.Empty;
44 switch (compressionInfo.Type)
46 case CompressionType.None:
48 compression = ZipCompressionMethod.None;
51 case CompressionType.Deflate:
53 compression = ZipCompressionMethod.Deflate;
54 deflateCompressionLevel = compressionInfo.DeflateCompressionLevel;
57 case CompressionType.BZip2:
59 compression = ZipCompressionMethod.BZip2;
63 case CompressionType.LZMA:
65 compression = ZipCompressionMethod.LZMA;
70 case CompressionType.PPMd:
72 ppmdProperties = new PpmdProperties();
73 compression = ZipCompressionMethod.PPMd;
78 throw new InvalidFormatException("Invalid compression method: " + compressionInfo.Type);
80 InitalizeStream(destination, false);
83 protected override void Dispose(bool isDisposing)
88 foreach (ZipCentralDirectoryEntry entry in entries)
90 size += entry.Write(OutputStream, compression);
94 base.Dispose(isDisposing);
97 public override void Write(string entryPath, Stream source, DateTime? modificationTime)
99 Write(entryPath, source, modificationTime, null);
102 public void Write(string entryPath, Stream source, DateTime? modificationTime, string comment)
104 using (Stream output = WriteToStream(entryPath, modificationTime, comment))
106 source.TransferTo(output);
110 public Stream WriteToStream(string entryPath, DateTime? modificationTime, string comment)
112 entryPath = NormalizeFilename(entryPath);
113 modificationTime = modificationTime ?? DateTime.Now;
114 comment = comment ?? "";
115 var entry = new ZipCentralDirectoryEntry
118 FileName = entryPath,
119 ModificationTime = modificationTime,
120 HeaderOffset = (uint) streamPosition,
122 var headersize = (uint) WriteHeader(entryPath, modificationTime);
123 streamPosition += headersize;
124 return new ZipWritingStream(this, OutputStream, entry);
127 private string NormalizeFilename(string filename)
129 filename = filename.Replace('\\', '/');
131 int pos = filename.IndexOf(':');
133 filename = filename.Remove(0, pos + 1);
138 private int WriteHeader(string filename, DateTime? modificationTime)
140 byte[] encodedFilename = Encoding.UTF8.GetBytes(filename);
142 OutputStream.Write(BitConverter.GetBytes(ZipHeaderFactory.ENTRY_HEADER_BYTES), 0, 4);
143 OutputStream.Write(new byte[] {63, 0}, 0, 2); //version
144 HeaderFlags flags = HeaderFlags.UTF8;
145 if (!OutputStream.CanSeek)
147 flags |= HeaderFlags.UsePostDataDescriptor;
148 if (compression == ZipCompressionMethod.LZMA)
150 flags |= HeaderFlags.Bit1; // eos marker
153 OutputStream.Write(BitConverter.GetBytes((ushort) flags), 0, 2);
154 OutputStream.Write(BitConverter.GetBytes((ushort) compression), 0, 2); // zipping method
155 OutputStream.Write(BitConverter.GetBytes(modificationTime.DateTimeToDosTime()), 0, 4);
156 // zipping date and time
157 OutputStream.Write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, 0, 12);
158 // unused CRC, un/compressed size, updated later
159 OutputStream.Write(BitConverter.GetBytes((ushort) encodedFilename.Length), 0, 2); // filename length
160 OutputStream.Write(BitConverter.GetBytes((ushort) 0), 0, 2); // extra length
161 OutputStream.Write(encodedFilename, 0, encodedFilename.Length);
163 return 6 + 2 + 2 + 4 + 12 + 2 + 2 + encodedFilename.Length;
166 private void WriteFooter(uint crc, uint compressed, uint uncompressed)
168 OutputStream.Write(BitConverter.GetBytes(crc), 0, 4);
169 OutputStream.Write(BitConverter.GetBytes(compressed), 0, 4);
170 OutputStream.Write(BitConverter.GetBytes(uncompressed), 0, 4);
173 private void WriteEndRecord(uint size)
175 byte[] encodedComment = Encoding.UTF8.GetBytes(zipComment);
177 OutputStream.Write(new byte[] {80, 75, 5, 6, 0, 0, 0, 0}, 0, 8);
178 OutputStream.Write(BitConverter.GetBytes((ushort) entries.Count), 0, 2);
179 OutputStream.Write(BitConverter.GetBytes((ushort) entries.Count), 0, 2);
180 OutputStream.Write(BitConverter.GetBytes(size), 0, 4);
181 OutputStream.Write(BitConverter.GetBytes((uint) streamPosition), 0, 4);
182 OutputStream.Write(BitConverter.GetBytes((ushort) encodedComment.Length), 0, 2);
183 OutputStream.Write(encodedComment, 0, encodedComment.Length);
186 #region Nested type: ZipWritingStream
188 internal class ZipWritingStream : Stream
190 private readonly CRC32 crc = new CRC32();
191 private readonly ZipCentralDirectoryEntry entry;
192 private readonly Stream originalStream;
193 private readonly Stream writeStream;
194 private readonly ZipWriter writer;
195 private CountingWritableSubStream counting;
196 private uint decompressed;
198 internal ZipWritingStream(ZipWriter writer, Stream originalStream, ZipCentralDirectoryEntry entry)
200 this.writer = writer;
201 this.originalStream = originalStream;
202 writeStream = GetWriteStream(originalStream);
203 this.writer = writer;
207 public override bool CanRead
209 get { return false; }
212 public override bool CanSeek
214 get { return false; }
217 public override bool CanWrite
222 public override long Length
224 get { throw new NotSupportedException(); }
227 public override long Position
229 get { throw new NotSupportedException(); }
230 set { throw new NotSupportedException(); }
233 private Stream GetWriteStream(Stream writeStream)
235 counting = new CountingWritableSubStream(writeStream);
236 Stream output = counting;
237 switch (writer.compression)
239 case ZipCompressionMethod.None:
243 case ZipCompressionMethod.Deflate:
245 return new System.IO.Compression.DeflateStream(counting,
246 System.IO.Compression.CompressionMode.Compress, true);
249 case ZipCompressionMethod.BZip2:
251 return new BZip2Stream(counting, CompressionMode.Compress, true);
255 case ZipCompressionMethod.LZMA:
257 counting.WriteByte(9);
258 counting.WriteByte(20);
259 counting.WriteByte(5);
260 counting.WriteByte(0);
262 LzmaStream lzmaStream = new LzmaStream(new LzmaEncoderProperties(!originalStream.CanSeek),
264 counting.Write(lzmaStream.Properties, 0, lzmaStream.Properties.Length);
269 case ZipCompressionMethod.PPMd:
271 counting.Write(writer.ppmdProperties.Properties, 0, 2);
272 return new PpmdStream(writer.ppmdProperties, counting, true);
277 throw new NotSupportedException("CompressionMethod: " + writer.compression);
282 protected override void Dispose(bool disposing)
284 base.Dispose(disposing);
287 writeStream.Dispose();
288 entry.Crc = (uint) crc.Crc32Result;
289 entry.Compressed = counting.Count;
290 entry.Decompressed = decompressed;
291 if (originalStream.CanSeek)
293 originalStream.Position = entry.HeaderOffset + 6;
294 originalStream.WriteByte(0);
295 originalStream.Position = entry.HeaderOffset + 14;
296 writer.WriteFooter(entry.Crc, counting.Count, decompressed);
297 originalStream.Position = writer.streamPosition + entry.Compressed;
298 writer.streamPosition += entry.Compressed;
302 originalStream.Write(BitConverter.GetBytes(ZipHeaderFactory.POST_DATA_DESCRIPTOR), 0, 4);
303 writer.WriteFooter(entry.Crc, counting.Count, decompressed);
304 writer.streamPosition += entry.Compressed + 16;
306 writer.entries.Add(entry);
310 public override void Flush()
315 public override int Read(byte[] buffer, int offset, int count)
317 throw new NotSupportedException();
320 public override long Seek(long offset, SeekOrigin origin)
322 throw new NotSupportedException();
325 public override void SetLength(long value)
327 throw new NotSupportedException();
330 public override void Write(byte[] buffer, int offset, int count)
332 decompressed += (uint) count;
333 crc.SlurpBlock(buffer, offset, count);
334 writeStream.Write(buffer, offset, count);