/
ZipFile.Create.cs
424 lines (395 loc) · 34.8 KB
/
ZipFile.Create.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace System.IO.Compression
{
public static partial class ZipFile
{
/// <summary>
/// Opens a <code>ZipArchive</code> on the specified path for reading. The specified file is opened with <code>FileMode.Open</code>.
/// </summary>
///
/// <exception cref="ArgumentException">archiveFileName is a zero-length string, contains only whitespace, or contains one
/// or more invalid characters as defined by InvalidPathChars.</exception>
/// <exception cref="ArgumentNullException">archiveFileName is null.</exception>
/// <exception cref="PathTooLongException">The specified archiveFileName exceeds the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters,
/// and file names must be less than 260 characters.</exception>
/// <exception cref="DirectoryNotFoundException">The specified archiveFileName is invalid, (for example, it is on an unmapped drive).</exception>
/// <exception cref="IOException">An unspecified I/O error occurred while opening the file.</exception>
/// <exception cref="UnauthorizedAccessException">archiveFileName specified a directory.
/// -OR- The caller does not have the required permission.</exception>
/// <exception cref="FileNotFoundException">The file specified in archiveFileName was not found.</exception>
/// <exception cref="NotSupportedException">archiveFileName is in an invalid format. </exception>
/// <exception cref="InvalidDataException">The specified file could not be interpreted as a Zip file.</exception>
///
/// <param name="archiveFileName">A string specifying the path on the filesystem to open the archive on. The path is permitted
/// to specify relative or absolute path information. Relative path information is interpreted as relative to the current working directory.</param>
public static ZipArchive OpenRead(string archiveFileName) => Open(archiveFileName, ZipArchiveMode.Read);
/// <summary>
/// Opens a <code>ZipArchive</code> on the specified <code>archiveFileName</code> in the specified <code>ZipArchiveMode</code> mode.
/// </summary>
///
/// <exception cref="ArgumentException">archiveFileName is a zero-length string, contains only whitespace,
/// or contains one or more invalid characters as defined by InvalidPathChars.</exception>
/// <exception cref="ArgumentNullException">path is null.</exception>
/// <exception cref="PathTooLongException">The specified archiveFileName exceeds the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters,
/// and file names must be less than 260 characters.</exception>
/// <exception cref="DirectoryNotFoundException">The specified archiveFileName is invalid, (for example, it is on an unmapped drive).</exception>
/// <exception cref="IOException">An unspecified I/O error occurred while opening the file.</exception>
/// <exception cref="UnauthorizedAccessException">archiveFileName specified a directory.
/// -OR- The caller does not have the required permission.</exception>
/// <exception cref="ArgumentOutOfRangeException"><code>mode</code> specified an invalid value.</exception>
/// <exception cref="FileNotFoundException">The file specified in <code>archiveFileName</code> was not found. </exception>
/// <exception cref="NotSupportedException"><code>archiveFileName</code> is in an invalid format.</exception>
/// <exception cref="InvalidDataException">The specified file could not be interpreted as a Zip file.
/// -OR- <code>mode</code> is <code>Update</code> and an entry is missing from the archive or
/// is corrupt and cannot be read.
/// -OR- <code>mode</code> is <code>Update</code> and an entry is too large to fit into memory.</exception>
///
/// <param name="archiveFileName">A string specifying the path on the filesystem to open the archive on.
/// The path is permitted to specify relative or absolute path information.
/// Relative path information is interpreted as relative to the current working directory.</param>
/// <param name="mode">See the description of the <code>ZipArchiveMode</code> enum.
/// If <code>Read</code> is specified, the file is opened with <code>System.IO.FileMode.Open</code>, and will throw
/// a <code>FileNotFoundException</code> if the file does not exist.
/// If <code>Create</code> is specified, the file is opened with <code>System.IO.FileMode.CreateNew</code>, and will throw
/// a <code>System.IO.IOException</code> if the file already exists.
/// If <code>Update</code> is specified, the file is opened with <code>System.IO.FileMode.OpenOrCreate</code>.
/// If the file exists and is a Zip file, its entries will become accessible, and may be modified, and new entries may be created.
/// If the file exists and is not a Zip file, a <code>ZipArchiveException</code> will be thrown.
/// If the file exists and is empty or does not exist, a new Zip file will be created.
/// Note that creating a Zip file with the <code>ZipArchiveMode.Create</code> mode is more efficient when creating a new Zip file.</param>
public static ZipArchive Open(string archiveFileName, ZipArchiveMode mode) => Open(archiveFileName, mode, entryNameEncoding: null);
/// <summary>
/// Opens a <code>ZipArchive</code> on the specified <code>archiveFileName</code> in the specified <code>ZipArchiveMode</code> mode.
/// </summary>
///
/// <exception cref="ArgumentException">archiveFileName is a zero-length string, contains only whitespace,
/// or contains one or more invalid characters as defined by InvalidPathChars.</exception>
/// <exception cref="ArgumentNullException">path is null.</exception>
/// <exception cref="PathTooLongException">The specified archiveFileName exceeds the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters,
/// and file names must be less than 260 characters.</exception>
/// <exception cref="DirectoryNotFoundException">The specified archiveFileName is invalid, (for example, it is on an unmapped drive).</exception>
/// <exception cref="IOException">An unspecified I/O error occurred while opening the file.</exception>
/// <exception cref="UnauthorizedAccessException">archiveFileName specified a directory.
/// -OR- The caller does not have the required permission.</exception>
/// <exception cref="ArgumentOutOfRangeException"><code>mode</code> specified an invalid value.</exception>
/// <exception cref="FileNotFoundException">The file specified in <code>archiveFileName</code> was not found. </exception>
/// <exception cref="NotSupportedException"><code>archiveFileName</code> is in an invalid format.</exception>
/// <exception cref="InvalidDataException">The specified file could not be interpreted as a Zip file.
/// -OR- <code>mode</code> is <code>Update</code> and an entry is missing from the archive or
/// is corrupt and cannot be read.
/// -OR- <code>mode</code> is <code>Update</code> and an entry is too large to fit into memory.</exception>
///
/// <param name="archiveFileName">A string specifying the path on the filesystem to open the archive on.
/// The path is permitted to specify relative or absolute path information.
/// Relative path information is interpreted as relative to the current working directory.</param>
/// <param name="mode">See the description of the <code>ZipArchiveMode</code> enum.
/// If <code>Read</code> is specified, the file is opened with <code>System.IO.FileMode.Open</code>, and will throw
/// a <code>FileNotFoundException</code> if the file does not exist.
/// If <code>Create</code> is specified, the file is opened with <code>System.IO.FileMode.CreateNew</code>, and will throw
/// a <code>System.IO.IOException</code> if the file already exists.
/// If <code>Update</code> is specified, the file is opened with <code>System.IO.FileMode.OpenOrCreate</code>.
/// If the file exists and is a Zip file, its entries will become accessible, and may be modified, and new entries may be created.
/// If the file exists and is not a Zip file, a <code>ZipArchiveException</code> will be thrown.
/// If the file exists and is empty or does not exist, a new Zip file will be created.
/// Note that creating a Zip file with the <code>ZipArchiveMode.Create</code> mode is more efficient when creating a new Zip file.</param>
/// <param name="entryNameEncoding">The encoding to use when reading or writing entry names in this ZipArchive.
/// /// <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
/// However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support
/// UTF-8 encoding for entry names.<br />
/// This value is used as follows:</para>
/// <para><strong>Reading (opening) ZIP archive files:</strong></para>
/// <para>If <c>entryNameEncoding</c> is not specified (<c>== null</c>):</para>
/// <list>
/// <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is <em>not</em> set,
/// use the current system default code page (<c>Encoding.Default</c>) in order to decode the entry name.</item>
/// <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header <em>is</em> set,
/// use UTF-8 (<c>Encoding.UTF8</c>) in order to decode the entry name.</item>
/// </list>
/// <para>If <c>entryNameEncoding</c> is specified (<c>!= null</c>):</para>
/// <list>
/// <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is <em>not</em> set,
/// use the specified <c>entryNameEncoding</c> in order to decode the entry name.</item>
/// <item>For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header <em>is</em> set,
/// use UTF-8 (<c>Encoding.UTF8</c>) in order to decode the entry name.</item>
/// </list>
/// <para><strong>Writing (saving) ZIP archive files:</strong></para>
/// <para>If <c>entryNameEncoding</c> is not specified (<c>== null</c>):</para>
/// <list>
/// <item>For entry names that contain characters outside the ASCII range,
/// the language encoding flag (EFS) will be set in the general purpose bit flag of the local file header,
/// and UTF-8 (<c>Encoding.UTF8</c>) will be used in order to encode the entry name into bytes.</item>
/// <item>For entry names that do not contain characters outside the ASCII range,
/// the language encoding flag (EFS) will not be set in the general purpose bit flag of the local file header,
/// and the current system default code page (<c>Encoding.Default</c>) will be used to encode the entry names into bytes.</item>
/// </list>
/// <para>If <c>entryNameEncoding</c> is specified (<c>!= null</c>):</para>
/// <list>
/// <item>The specified <c>entryNameEncoding</c> will always be used to encode the entry names into bytes.
/// The language encoding flag (EFS) in the general purpose bit flag of the local file header will be set if and only
/// if the specified <c>entryNameEncoding</c> is a UTF-8 encoding.</item>
/// </list>
/// <para>Note that Unicode encodings other than UTF-8 may not be currently used for the <c>entryNameEncoding</c>,
/// otherwise an <see cref="ArgumentException"/> is thrown.</para>
/// </param>
public static ZipArchive Open(string archiveFileName, ZipArchiveMode mode, Encoding? entryNameEncoding)
{
// Relies on FileStream's ctor for checking of archiveFileName
FileMode fileMode;
FileAccess access;
FileShare fileShare;
switch (mode)
{
case ZipArchiveMode.Read:
fileMode = FileMode.Open;
access = FileAccess.Read;
fileShare = FileShare.Read;
break;
case ZipArchiveMode.Create:
fileMode = FileMode.CreateNew;
access = FileAccess.Write;
fileShare = FileShare.None;
break;
case ZipArchiveMode.Update:
fileMode = FileMode.OpenOrCreate;
access = FileAccess.ReadWrite;
fileShare = FileShare.None;
break;
default:
throw new ArgumentOutOfRangeException(nameof(mode));
}
// Suppress CA2000: fs gets passed to the new ZipArchive, which stores it internally.
// The stream will then be owned by the archive and be disposed when the archive is disposed.
// If the ctor completes without throwing, we know fs has been successfully stores in the archive;
// If the ctor throws, we need to close it here.
FileStream fs = new FileStream(archiveFileName, fileMode, access, fileShare, bufferSize: 0x1000, useAsync: false);
try
{
return new ZipArchive(fs, mode, leaveOpen: false, entryNameEncoding: entryNameEncoding);
}
catch
{
fs.Dispose();
throw;
}
}
/// <summary>
/// <p>Creates a Zip archive at the path <code>destinationArchiveFileName</code> that contains the files and directories from
/// the directory specified by <code>sourceDirectoryName</code>. The directory structure is preserved in the archive, and a
/// recursive search is done for files to be archived. The archive must not exist. If the directory is empty, an empty
/// archive will be created. If a file in the directory cannot be added to the archive, the archive will be left incomplete
/// and invalid and the method will throw an exception. This method does not include the base directory into the archive.
/// If an error is encountered while adding files to the archive, this method will stop adding files and leave the archive
/// in an invalid state. The paths are permitted to specify relative or absolute path information. Relative path information
/// is interpreted as relative to the current working directory. If a file in the archive has data in the last write time
/// field that is not a valid Zip timestamp, an indicator value of 1980 January 1 at midnight will be used for the file's
/// last modified time.</p>
///
/// <p>If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.</p>
///
/// <p>Since no <code>CompressionLevel</code> is specified, the default provided by the implementation of the underlying compression
/// algorithm will be used; the <code>ZipArchive</code> will not impose its own default.
/// (Currently, the underlying compression algorithm is provided by the <code>System.IO.Compression.DeflateStream</code> class.)</p>
/// </summary>
///
/// <exception cref="ArgumentException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code> is a zero-length
/// string, contains only whitespace, or contains one or more invalid characters as defined by
/// <code>InvalidPathChars</code>.</exception>
/// <exception cref="ArgumentNullException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code> is null.</exception>
/// <exception cref="PathTooLongException">In <code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code>, the specified
/// path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters, and file
/// names must be less than 260 characters.</exception>
/// <exception cref="DirectoryNotFoundException">The path specified in <code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code>
/// is invalid, (for example, it is on an unmapped drive).
/// -OR- The directory specified by <code>sourceDirectoryName</code> does not exist.</exception>
/// <exception cref="IOException"><code>destinationArchiveFileName</code> already exists.
/// -OR- An I/O error occurred while opening a file to be archived.</exception>
/// <exception cref="UnauthorizedAccessException"><code>destinationArchiveFileName</code> specified a directory.
/// -OR- The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code> is
/// in an invalid format.</exception>
///
/// <param name="sourceDirectoryName">The path to the directory on the file system to be archived. </param>
/// <param name="destinationArchiveFileName">The name of the archive to be created.</param>
public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) =>
DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, compressionLevel: null, includeBaseDirectory: false, entryNameEncoding: null);
/// <summary>
/// <p>Creates a Zip archive at the path <code>destinationArchiveFileName</code> that contains the files and directories in the directory
/// specified by <code>sourceDirectoryName</code>. The directory structure is preserved in the archive, and a recursive search is
/// done for files to be archived. The archive must not exist. If the directory is empty, an empty archive will be created.
/// If a file in the directory cannot be added to the archive, the archive will be left incomplete and invalid and the
/// method will throw an exception. This method optionally includes the base directory in the archive.
/// If an error is encountered while adding files to the archive, this method will stop adding files and leave the archive
/// in an invalid state. The paths are permitted to specify relative or absolute path information. Relative path information
/// is interpreted as relative to the current working directory. If a file in the archive has data in the last write time
/// field that is not a valid Zip timestamp, an indicator value of 1980 January 1 at midnight will be used for the file's
/// last modified time.</p>
///
/// <p>If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.</p>
///
/// <p>Since no <code>CompressionLevel</code> is specified, the default provided by the implementation of the underlying compression
/// algorithm will be used; the <code>ZipArchive</code> will not impose its own default.
/// (Currently, the underlying compression algorithm is provided by the <code>System.IO.Compression.DeflateStream</code> class.)</p>
/// </summary>
///
/// <exception cref="ArgumentException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code> is a zero-length
/// string, contains only whitespace, or contains one or more invalid characters as defined by
/// <code>InvalidPathChars</code>.</exception>
/// <exception cref="ArgumentNullException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code> is null.</exception>
/// <exception cref="PathTooLongException">In <code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code>, the
/// specified path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters,
/// and file names must be less than 260 characters.</exception>
/// <exception cref="DirectoryNotFoundException">The path specified in <code>sourceDirectoryName</code> or
/// <code>destinationArchiveFileName</code> is invalid, (for example, it is on an unmapped drive).
/// -OR- The directory specified by <code>sourceDirectoryName</code> does not exist.</exception>
/// <exception cref="IOException"><code>destinationArchiveFileName</code> already exists.
/// -OR- An I/O error occurred while opening a file to be archived.</exception>
/// <exception cref="UnauthorizedAccessException"><code>destinationArchiveFileName</code> specified a directory.
/// -OR- The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code>
/// is in an invalid format.</exception>
///
/// <param name="sourceDirectoryName">The path to the directory on the file system to be archived.</param>
/// <param name="destinationArchiveFileName">The name of the archive to be created.</param>
/// <param name="compressionLevel">The level of the compression (speed/memory vs. compressed size trade-off).</param>
/// <param name="includeBaseDirectory"><code>true</code> to indicate that a directory named <code>sourceDirectoryName</code> should
/// be included at the root of the archive. <code>false</code> to indicate that the files and directories in <code>sourceDirectoryName</code>
/// should be included directly in the archive.</param>
public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel, bool includeBaseDirectory) =>
DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, compressionLevel, includeBaseDirectory, entryNameEncoding: null);
/// <summary>
/// <p>Creates a Zip archive at the path <code>destinationArchiveFileName</code> that contains the files and directories in the directory
/// specified by <code>sourceDirectoryName</code>. The directory structure is preserved in the archive, and a recursive search is
/// done for files to be archived. The archive must not exist. If the directory is empty, an empty archive will be created.
/// If a file in the directory cannot be added to the archive, the archive will be left incomplete and invalid and the
/// method will throw an exception. This method optionally includes the base directory in the archive.
/// If an error is encountered while adding files to the archive, this method will stop adding files and leave the archive
/// in an invalid state. The paths are permitted to specify relative or absolute path information. Relative path information
/// is interpreted as relative to the current working directory. If a file in the archive has data in the last write time
/// field that is not a valid Zip timestamp, an indicator value of 1980 January 1 at midnight will be used for the file's
/// last modified time.</p>
///
/// <p>If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.</p>
///
/// <p>Since no <code>CompressionLevel</code> is specified, the default provided by the implementation of the underlying compression
/// algorithm will be used; the <code>ZipArchive</code> will not impose its own default.
/// (Currently, the underlying compression algorithm is provided by the <code>System.IO.Compression.DeflateStream</code> class.)</p>
/// </summary>
///
/// <exception cref="ArgumentException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code> is a zero-length
/// string, contains only whitespace, or contains one or more invalid characters as defined by
/// <code>InvalidPathChars</code>.</exception>
/// <exception cref="ArgumentNullException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code> is null.</exception>
/// <exception cref="PathTooLongException">In <code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code>, the
/// specified path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters,
/// and file names must be less than 260 characters.</exception>
/// <exception cref="DirectoryNotFoundException">The path specified in <code>sourceDirectoryName</code> or
/// <code>destinationArchiveFileName</code> is invalid, (for example, it is on an unmapped drive).
/// -OR- The directory specified by <code>sourceDirectoryName</code> does not exist.</exception>
/// <exception cref="IOException"><code>destinationArchiveFileName</code> already exists.
/// -OR- An I/O error occurred while opening a file to be archived.</exception>
/// <exception cref="UnauthorizedAccessException"><code>destinationArchiveFileName</code> specified a directory.
/// -OR- The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException"><code>sourceDirectoryName</code> or <code>destinationArchiveFileName</code>
/// is in an invalid format.</exception>
///
/// <param name="sourceDirectoryName">The path to the directory on the file system to be archived.</param>
/// <param name="destinationArchiveFileName">The name of the archive to be created.</param>
/// <param name="compressionLevel">The level of the compression (speed/memory vs. compressed size trade-off).</param>
/// <param name="includeBaseDirectory"><code>true</code> to indicate that a directory named <code>sourceDirectoryName</code> should
/// be included at the root of the archive. <code>false</code> to indicate that the files and directories in <code>sourceDirectoryName</code>
/// should be included directly in the archive.</param>
/// <param name="entryNameEncoding">The encoding to use when reading or writing entry names in this ZipArchive.
/// /// <para>NOTE: Specifying this parameter to values other than <c>null</c> is discouraged.
/// However, this may be necessary for interoperability with ZIP archive tools and libraries that do not correctly support
/// UTF-8 encoding for entry names.<br />
/// This value is used as follows while creating the archive:</para>
/// <para>If <c>entryNameEncoding</c> is not specified (<c>== null</c>):</para>
/// <list>
/// <item>For file names that contain characters outside the ASCII range:<br />
/// The language encoding flag (EFS) will be set in the general purpose bit flag of the local file header of the corresponding entry,
/// and UTF-8 (<c>Encoding.UTF8</c>) will be used in order to encode the entry name into bytes.</item>
/// <item>For file names that do not contain characters outside the ASCII range:<br />
/// the language encoding flag (EFS) will not be set in the general purpose bit flag of the local file header of the corresponding entry,
/// and the current system default code page (<c>Encoding.Default</c>) will be used to encode the entry names into bytes.</item>
/// </list>
/// <para>If <c>entryNameEncoding</c> is specified (<c>!= null</c>):</para>
/// <list>
/// <item>The specified <c>entryNameEncoding</c> will always be used to encode the entry names into bytes.
/// The language encoding flag (EFS) in the general purpose bit flag of the local file header for each entry will be set if and only
/// if the specified <c>entryNameEncoding</c> is a UTF-8 encoding.</item>
/// </list>
/// <para>Note that Unicode encodings other than UTF-8 may not be currently used for the <c>entryNameEncoding</c>,
/// otherwise an <see cref="ArgumentException"/> is thrown.</para>
/// </param>
public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName,
CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding) =>
DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, compressionLevel, includeBaseDirectory, entryNameEncoding);
private static void DoCreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName,
CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding? entryNameEncoding)
{
// Rely on Path.GetFullPath for validation of sourceDirectoryName and destinationArchive
// Checking of compressionLevel is passed down to DeflateStream and the IDeflater implementation
// as it is a pluggable component that completely encapsulates the meaning of compressionLevel.
sourceDirectoryName = Path.GetFullPath(sourceDirectoryName);
destinationArchiveFileName = Path.GetFullPath(destinationArchiveFileName);
using (ZipArchive archive = Open(destinationArchiveFileName, ZipArchiveMode.Create, entryNameEncoding))
{
bool directoryIsEmpty = true;
//add files and directories
DirectoryInfo di = new DirectoryInfo(sourceDirectoryName);
string basePath = di.FullName;
if (includeBaseDirectory && di.Parent != null)
basePath = di.Parent.FullName;
// Windows' MaxPath (260) is used as an arbitrary default capacity, as it is likely
// to be greater than the length of typical entry names from the file system, even
// on non-Windows platforms. The capacity will be increased, if needed.
const int DefaultCapacity = 260;
char[] entryNameBuffer = ArrayPool<char>.Shared.Rent(DefaultCapacity);
try
{
foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories))
{
directoryIsEmpty = false;
int entryNameLength = file.FullName.Length - basePath.Length;
Debug.Assert(entryNameLength > 0);
if (file is FileInfo)
{
// Create entry for file:
string entryName = ArchivingUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer);
ZipFileExtensions.DoCreateEntryFromFile(archive, file.FullName, entryName, compressionLevel);
}
else
{
// Entry marking an empty dir:
if (file is DirectoryInfo possiblyEmpty && ArchivingUtils.IsDirEmpty(possiblyEmpty))
{
// FullName never returns a directory separator character on the end,
// but Zip archives require it to specify an explicit directory:
string entryName = ArchivingUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer, appendPathSeparator: true);
archive.CreateEntry(entryName);
}
}
} // foreach
// If no entries create an empty root directory entry:
if (includeBaseDirectory && directoryIsEmpty)
archive.CreateEntry(ArchivingUtils.EntryFromPath(di.Name, 0, di.Name.Length, ref entryNameBuffer, appendPathSeparator: true));
}
finally
{
ArrayPool<char>.Shared.Return(entryNameBuffer);
}
}
}
}
}