Merge pull request #1871 from saper/EADDRINUSE
[mono.git] / mcs / class / System.IO.Compression / SharpCompress / Writer / Zip / ZipWriter.cs
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Text;
5 using SharpCompress.Common;
6 using SharpCompress.Common.Zip;
7 using SharpCompress.Common.Zip.Headers;
8 using SharpCompress.Compressor;
9 #if BZIP2
10 using SharpCompress.Compressor.BZip2;
11 #endif
12 using SharpCompress.Compressor.Deflate;
13 #if LZMA
14 using SharpCompress.Compressor.LZMA;
15 #endif
16 #if PPMd
17 using SharpCompress.Compressor.PPMd;
18 #endif
19 using SharpCompress.IO;
20 #if DEFLATE
21 using DeflateStream = SharpCompress.Compressor.Deflate.DeflateStream;
22 #endif
23
24 namespace SharpCompress.Writer.Zip
25 {
26     internal class ZipWriter : AbstractWriter
27     {
28         private readonly ZipCompressionMethod compression;
29         private readonly CompressionLevel deflateCompressionLevel;
30
31         private readonly List<ZipCentralDirectoryEntry> entries = new List<ZipCentralDirectoryEntry>();
32         private readonly string zipComment;
33         private readonly Encoding encoding;
34         private long streamPosition;
35
36 #if PPMd
37         private readonly PpmdProperties ppmdProperties; // Caching properties to speed up PPMd.
38 #endif
39
40         public ZipWriter(Stream destination, CompressionInfo compressionInfo, string zipComment, Encoding encoding = null)
41             : base(ArchiveType.Zip)
42         {
43             this.zipComment = zipComment ?? string.Empty;
44             this.encoding = encoding ?? ArchiveEncoding.Default;
45
46             switch (compressionInfo.Type)
47             {
48                 case CompressionType.None:
49                     {
50                         compression = ZipCompressionMethod.None;
51                     }
52                     break;
53                 case CompressionType.Deflate:
54                     {
55                         compression = ZipCompressionMethod.Deflate;
56                         deflateCompressionLevel = compressionInfo.DeflateCompressionLevel;
57                     }
58                     break;
59                 case CompressionType.BZip2:
60                     {
61                         compression = ZipCompressionMethod.BZip2;
62                     }
63                     break;
64 #if LZMA
65                 case CompressionType.LZMA:
66                     {
67                         compression = ZipCompressionMethod.LZMA;
68                     }
69                     break;
70 #endif
71 #if PPMd
72                 case CompressionType.PPMd:
73                     {
74                         ppmdProperties = new PpmdProperties();
75                         compression = ZipCompressionMethod.PPMd;
76                     }
77                     break;
78 #endif
79                 default:
80                     throw new InvalidFormatException("Invalid compression method: " + compressionInfo.Type);
81             }
82             InitalizeStream(destination, false);
83         }
84
85         protected override void Dispose(bool isDisposing)
86         {
87             if (isDisposing)
88             {
89                 uint size = 0;
90                 foreach (ZipCentralDirectoryEntry entry in entries)
91                 {
92                     size += entry.Write(OutputStream, compression);
93                 }
94                 WriteEndRecord(size);
95             }
96             base.Dispose(isDisposing);
97         }
98
99         public override void Write(string entryPath, Stream source, DateTime? modificationTime)
100         {
101             Write(entryPath, source, modificationTime, null);
102         }
103
104         public void Write(string entryPath, Stream source, DateTime? modificationTime, string comment)
105         {
106             using (Stream output = WriteToStream(entryPath, modificationTime, comment))
107             {
108                 source.TransferTo(output);
109             }
110         }
111
112         public Stream WriteToStream(string entryPath, DateTime? modificationTime, string comment)
113         {
114             entryPath = NormalizeFilename(entryPath);
115             modificationTime = modificationTime ?? DateTime.Now;
116             comment = comment ?? "";
117             var entry = new ZipCentralDirectoryEntry
118                             {
119                                 Comment = comment,
120                                 FileName = entryPath,
121                                 ModificationTime = modificationTime,
122                                 HeaderOffset = (uint) streamPosition,
123                             };
124             var headersize = (uint) WriteHeader(entryPath, modificationTime);
125             streamPosition += headersize;
126             return new ZipWritingStream(this, OutputStream, entry);
127         }
128
129         private string NormalizeFilename(string filename)
130         {
131             filename = filename.Replace('\\', '/');
132
133             int pos = filename.IndexOf(':');
134             if (pos >= 0)
135                 filename = filename.Remove(0, pos + 1);
136
137             return filename;
138         }
139
140         private int WriteHeader(string filename, DateTime? modificationTime)
141         {
142             byte[] encodedFilename = encoding.GetBytes(filename);
143
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)
148             {
149                 flags |= HeaderFlags.UsePostDataDescriptor;
150                 if (compression == ZipCompressionMethod.LZMA)
151                 {
152                     flags |= HeaderFlags.Bit1; // eos marker
153                 }
154             }
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);
164
165             return 6 + 2 + 2 + 4 + 12 + 2 + 2 + encodedFilename.Length;
166         }
167
168         private void WriteFooter(uint crc, uint compressed, uint uncompressed)
169         {
170             OutputStream.Write(BitConverter.GetBytes(crc), 0, 4);
171             OutputStream.Write(BitConverter.GetBytes(compressed), 0, 4);
172             OutputStream.Write(BitConverter.GetBytes(uncompressed), 0, 4);
173         }
174
175         private void WriteEndRecord(uint size)
176         {
177             byte[] encodedComment = encoding.GetBytes(zipComment);
178
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);
186         }
187
188         #region Nested type: ZipWritingStream
189
190         internal class ZipWritingStream : Stream
191         {
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;
199
200             internal ZipWritingStream(ZipWriter writer, Stream originalStream, ZipCentralDirectoryEntry entry)
201             {
202                 this.writer = writer;
203                 this.originalStream = originalStream;
204                 writeStream = GetWriteStream(originalStream);
205                 this.writer = writer;
206                 this.entry = entry;
207             }
208
209             public override bool CanRead
210             {
211                 get { return false; }
212             }
213
214             public override bool CanSeek
215             {
216                 get { return false; }
217             }
218
219             public override bool CanWrite
220             {
221                 get { return true; }
222             }
223
224             public override long Length
225             {
226                 get { throw new NotSupportedException(); }
227             }
228
229             public override long Position
230             {
231                 get { throw new NotSupportedException(); }
232                 set { throw new NotSupportedException(); }
233             }
234
235             private Stream GetWriteStream(Stream writeStream)
236             {
237                 counting = new CountingWritableSubStream(writeStream);
238                 Stream output = counting;
239                 switch (writer.compression)
240                 {
241                     case ZipCompressionMethod.None:
242                         {
243                             return output;
244                         }
245                     case ZipCompressionMethod.Deflate:
246                         {
247                             return new System.IO.Compression.DeflateStream(counting,
248                                 System.IO.Compression.CompressionMode.Compress, true);
249                         }
250 #if BZIP2
251                     case ZipCompressionMethod.BZip2:
252                         {
253                             return new BZip2Stream(counting, CompressionMode.Compress, true);
254                         }
255 #endif
256 #if LZMA
257                     case ZipCompressionMethod.LZMA:
258                         {
259                             counting.WriteByte(9);
260                             counting.WriteByte(20);
261                             counting.WriteByte(5);
262                             counting.WriteByte(0);
263
264                             LzmaStream lzmaStream = new LzmaStream(new LzmaEncoderProperties(!originalStream.CanSeek),
265                                                                    false, counting);
266                             counting.Write(lzmaStream.Properties, 0, lzmaStream.Properties.Length);
267                             return lzmaStream;
268                         }
269 #endif
270 #if PPMd
271                     case ZipCompressionMethod.PPMd:
272                         {
273                             counting.Write(writer.ppmdProperties.Properties, 0, 2);
274                             return new PpmdStream(writer.ppmdProperties, counting, true);
275                         }
276 #endif
277                     default:
278                         {
279                             throw new NotSupportedException("CompressionMethod: " + writer.compression);
280                         }
281                 }
282             }
283
284             protected override void Dispose(bool disposing)
285             {
286                 base.Dispose(disposing);
287                 if (disposing)
288                 {
289                     writeStream.Dispose();
290                     entry.Crc = (uint) crc.Crc32Result;
291                     entry.Compressed = counting.Count;
292                     entry.Decompressed = decompressed;
293                     if (originalStream.CanSeek)
294                     {
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;
301                     }
302                     else
303                     {
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;
307                     }
308                     writer.entries.Add(entry);
309                 }
310             }
311
312             public override void Flush()
313             {
314                 writeStream.Flush();
315             }
316
317             public override int Read(byte[] buffer, int offset, int count)
318             {
319                 throw new NotSupportedException();
320             }
321
322             public override long Seek(long offset, SeekOrigin origin)
323             {
324                 throw new NotSupportedException();
325             }
326
327             public override void SetLength(long value)
328             {
329                 throw new NotSupportedException();
330             }
331
332             public override void Write(byte[] buffer, int offset, int count)
333             {
334                 decompressed += (uint) count;
335                 crc.SlurpBlock(buffer, offset, count);
336                 writeStream.Write(buffer, offset, count);
337             }
338         }
339
340         #endregion
341     }
342 }