[tests] Avoid "Address already in use"
[mono.git] / mcs / class / System.IO.Compression / SharpCompress / Archive / Zip / ZipArchive.cs
1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Linq;
5 using SharpCompress.Common;
6 using SharpCompress.Common.Zip;
7 using SharpCompress.Common.Zip.Headers;
8 using SharpCompress.Compressor.Deflate;
9 using SharpCompress.Reader;
10 using SharpCompress.Reader.Zip;
11 using SharpCompress.Writer.Zip;
12
13 namespace SharpCompress.Archive.Zip
14 {
15     internal class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
16     {
17         private readonly SeekableZipHeaderFactory headerFactory;
18
19         /// <summary>
20         /// Gets or sets the compression level applied to files added to the archive,
21         /// if the compression method is set to deflate
22         /// </summary>
23         public CompressionLevel DeflateCompressionLevel { get; set; }
24
25 #if !PORTABLE && !NETFX_CORE
26         /// <summary>
27         /// Constructor expects a filepath to an existing file.
28         /// </summary>
29         /// <param name="filePath"></param>
30         /// <param name="password"></param>
31         public static ZipArchive Open(string filePath, string password = null)
32         {
33             return Open(filePath, Options.None, password);
34         }
35
36         /// <summary>
37         /// Constructor with a FileInfo object to an existing file.
38         /// </summary>
39         /// <param name="fileInfo"></param>
40         /// <param name="password"></param>
41         public static ZipArchive Open(FileInfo fileInfo, string password = null)
42         {
43             return Open(fileInfo, Options.None, password);
44         }
45
46         /// <summary>
47         /// Constructor expects a filepath to an existing file.
48         /// </summary>
49         /// <param name="filePath"></param>
50         /// <param name="options"></param>
51         /// <param name="password"></param>
52         public static ZipArchive Open(string filePath, Options options, string password = null)
53         {
54             filePath.CheckNotNullOrEmpty("filePath");
55             return Open(new FileInfo(filePath), options, password);
56         }
57
58         /// <summary>
59         /// Constructor with a FileInfo object to an existing file.
60         /// </summary>
61         /// <param name="fileInfo"></param>
62         /// <param name="options"></param>
63         /// <param name="password"></param>
64         public static ZipArchive Open(FileInfo fileInfo, Options options, string password = null)
65         {
66             fileInfo.CheckNotNull("fileInfo");
67             return new ZipArchive(fileInfo, options, password);
68         }
69 #endif
70
71         /// <summary>
72         /// Takes a seekable Stream as a source
73         /// </summary>
74         /// <param name="stream"></param>
75         /// <param name="password"></param>
76         public static ZipArchive Open(Stream stream, string password = null)
77         {
78             stream.CheckNotNull("stream");
79             return Open(stream, Options.None, password);
80         }
81
82         /// <summary>
83         /// Takes a seekable Stream as a source
84         /// </summary>
85         /// <param name="stream"></param>
86         /// <param name="options"></param>
87         /// <param name="password"></param>
88         public static ZipArchive Open(Stream stream, Options options, string password = null)
89         {
90             stream.CheckNotNull("stream");
91             return new ZipArchive(stream, options, password);
92         }
93
94 #if !PORTABLE && !NETFX_CORE
95         public static bool IsZipFile(string filePath, string password = null)
96         {
97             return IsZipFile(new FileInfo(filePath), password);
98         }
99
100         public static bool IsZipFile(FileInfo fileInfo, string password = null)
101         {
102             if (!fileInfo.Exists)
103             {
104                 return false;
105             }
106             using (Stream stream = fileInfo.OpenRead())
107             {
108                 return IsZipFile(stream, password);
109             }
110         }
111 #endif
112
113         public static bool IsZipFile(Stream stream, string password = null)
114         {
115             StreamingZipHeaderFactory headerFactory = new StreamingZipHeaderFactory(password);
116             try
117             {
118                 ZipHeader header =
119                     headerFactory.ReadStreamHeader(stream).FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
120                 if (header == null)
121                 {
122                     return false;
123                 }
124                 return Enum.IsDefined(typeof (ZipHeaderType), header.ZipHeaderType);
125             }
126             catch (CryptographicException)
127             {
128                 return true;
129             }
130             catch
131             {
132                 return false;
133             }
134         }
135
136 #if !PORTABLE && !NETFX_CORE
137         /// <summary>
138         /// Constructor with a FileInfo object to an existing file.
139         /// </summary>
140         /// <param name="fileInfo"></param>
141         /// <param name="options"></param>
142         /// <param name="password"></param>
143         internal ZipArchive(FileInfo fileInfo, Options options, string password = null)
144             : base(ArchiveType.Zip, fileInfo, options)
145         {
146             headerFactory = new SeekableZipHeaderFactory(password);
147         }
148
149         protected override IEnumerable<ZipVolume> LoadVolumes(FileInfo file, Options options)
150         {
151             if (FlagUtility.HasFlag(options, Options.KeepStreamsOpen))
152             {
153                 options = (Options)FlagUtility.SetFlag(options, Options.KeepStreamsOpen, false);
154             }
155             return new ZipVolume(file.OpenRead(), options).AsEnumerable();
156         }
157 #endif
158
159         internal ZipArchive()
160             : base(ArchiveType.Zip)
161         {
162         }
163
164         /// <summary>
165         /// Takes multiple seekable Streams for a multi-part archive
166         /// </summary>
167         /// <param name="stream"></param>
168         /// <param name="options"></param>
169         /// <param name="password"></param>
170         internal ZipArchive(Stream stream, Options options, string password = null)
171             : base(ArchiveType.Zip, stream, options)
172         {
173             headerFactory = new SeekableZipHeaderFactory(password);
174         }
175
176         protected override IEnumerable<ZipVolume> LoadVolumes(IEnumerable<Stream> streams, Options options)
177         {
178             return new ZipVolume(streams.First(), options).AsEnumerable();
179         }
180
181         protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
182         {
183             var volume = volumes.Single();
184             Stream stream = volume.Stream;
185             foreach (ZipHeader h in headerFactory.ReadSeekableHeader(stream))
186             {
187                 if (h != null)
188                 {
189                     switch (h.ZipHeaderType)
190                     {
191                         case ZipHeaderType.DirectoryEntry:
192                             {
193                                 yield return new ZipArchiveEntry(this,
194                                                                  new SeekableZipFilePart(headerFactory,
195                                                                                          h as DirectoryEntryHeader,
196                                                                                          stream));
197                             }
198                             break;
199                         case ZipHeaderType.DirectoryEnd:
200                             {
201                                 byte[] bytes = (h as DirectoryEndHeader).Comment;
202                                 volume.Comment = ArchiveEncoding.Default.GetString(bytes, 0, bytes.Length);
203                                 yield break;
204                             }
205                     }
206                 }
207             }
208         }
209
210         protected override void SaveTo(Stream stream, CompressionInfo compressionInfo,
211                                        IEnumerable<ZipArchiveEntry> oldEntries,
212                                        IEnumerable<ZipArchiveEntry> newEntries)
213         {
214             using (var writer = new ZipWriter(stream, compressionInfo, string.Empty))
215             {
216                 foreach (var entry in oldEntries.Concat(newEntries)
217                                                 .Where(x => !x.IsDirectory))
218                 {
219                     using (var entryStream = entry.OpenEntryStream())
220                     {
221                         writer.Write(entry.Key, entryStream, entry.LastModifiedTime, string.Empty);
222                     }
223                 }
224             }
225         }
226
227         protected override ZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified,
228                                                        bool closeStream)
229         {
230             return new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
231         }
232
233         public static ZipArchive Create()
234         {
235             return new ZipArchive();
236         }
237
238         protected override IReader CreateReaderForSolidExtraction()
239         {
240             var stream = Volumes.Single().Stream;
241             stream.Position = 0;
242             return ZipReader.Open(stream);
243         }
244     }
245 }