/
ZipFileExtensions.ZipArchiveEntry.Extract.cs
140 lines (125 loc) · 8.99 KB
/
ZipFileExtensions.ZipArchiveEntry.Extract.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
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
namespace System.IO.Compression
{
public static partial class ZipFileExtensions
{
/// <summary>
/// Creates a file on the file system with the entry?s contents and the specified name. The last write time of the file is set to the
/// entry?s last write time. This method does not allow overwriting of an existing file with the same name. Attempting to extract explicit
/// directories (entries with names that end in directory separator characters) will not result in the creation of a directory.
/// </summary>
///
/// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="ArgumentException">destinationFileName is a zero-length string, contains only whitespace, or contains one or more
/// invalid characters as defined by InvalidPathChars. -or- destinationFileName specifies a directory.</exception>
/// <exception cref="ArgumentNullException">destinationFileName is null.</exception>
/// <exception cref="PathTooLongException">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 destinationFileName is invalid (for example, it is on
/// an unmapped drive).</exception>
/// <exception cref="IOException">An I/O error has occurred. -or- The entry is currently open for writing.
/// -or- The entry has been deleted from the archive.</exception>
/// <exception cref="NotSupportedException">destinationFileName is in an invalid format
/// -or- The ZipArchive that this entry belongs to was opened in a write-only mode.</exception>
/// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read
/// -or- The entry has been compressed using a compression method that is not supported.</exception>
/// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
/// <param name="source">The zip archive entry to extract a file from.</param>
/// <param name="destinationFileName">The name of the file that will hold the contents of the entry.
/// 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 void ExtractToFile(this ZipArchiveEntry source, string destinationFileName) =>
ExtractToFile(source, destinationFileName, false);
/// <summary>
/// Creates a file on the file system with the entry?s contents and the specified name.
/// The last write time of the file is set to the entry?s last write time.
/// This method does allows overwriting of an existing file with the same name.
/// </summary>
///
/// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="ArgumentException">destinationFileName is a zero-length string, contains only whitespace,
/// or contains one or more invalid characters as defined by InvalidPathChars. -or- destinationFileName specifies a directory.</exception>
/// <exception cref="ArgumentNullException">destinationFileName is null.</exception>
/// <exception cref="PathTooLongException">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 destinationFileName is invalid
/// (for example, it is on an unmapped drive).</exception>
/// <exception cref="IOException">An I/O error has occurred.
/// -or- The entry is currently open for writing.
/// -or- The entry has been deleted from the archive.</exception>
/// <exception cref="NotSupportedException">destinationFileName is in an invalid format
/// -or- The ZipArchive that this entry belongs to was opened in a write-only mode.</exception>
/// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read
/// -or- The entry has been compressed using a compression method that is not supported.</exception>
/// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception>
/// <param name="source">The zip archive entry to extract a file from.</param>
/// <param name="destinationFileName">The name of the file that will hold the contents of the entry.
/// 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="overwrite">True to indicate overwrite.</param>
public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName, bool overwrite)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(destinationFileName);
FileStreamOptions fileStreamOptions = new()
{
Access = FileAccess.Write,
Mode = overwrite ? FileMode.Create : FileMode.CreateNew,
Share = FileShare.None,
BufferSize = 0x1000
};
const UnixFileMode OwnershipPermissions =
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute |
UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute |
UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute;
// Restore Unix permissions.
// For security, limit to ownership permissions, and respect umask (through UnixCreateMode).
// We don't apply UnixFileMode.None because .zip files created on Windows and .zip files created
// with previous versions of .NET don't include permissions.
UnixFileMode mode = (UnixFileMode)(source.ExternalAttributes >> 16) & OwnershipPermissions;
if (mode != UnixFileMode.None && !OperatingSystem.IsWindows())
{
fileStreamOptions.UnixCreateMode = mode;
}
using (FileStream fs = new FileStream(destinationFileName, fileStreamOptions))
{
using (Stream es = source.Open())
es.CopyTo(fs);
}
ArchivingUtils.AttemptSetLastWriteTime(destinationFileName, source.LastWriteTime);
}
internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName) =>
ExtractRelativeToDirectory(source, destinationDirectoryName, overwrite: false);
internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName, bool overwrite)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(destinationDirectoryName);
// Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists:
DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
string destinationDirectoryFullPath = di.FullName;
if (!destinationDirectoryFullPath.EndsWith(Path.DirectorySeparatorChar))
{
char sep = Path.DirectorySeparatorChar;
destinationDirectoryFullPath = string.Concat(destinationDirectoryFullPath, new ReadOnlySpan<char>(in sep));
}
string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, ArchivingUtils.SanitizeEntryFilePath(source.FullName)));
if (!fileDestinationPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison))
throw new IOException(SR.IO_ExtractingResultsInOutside);
if (Path.GetFileName(fileDestinationPath).Length == 0)
{
// If it is a directory:
if (source.Length != 0)
throw new IOException(SR.IO_DirectoryNameWithData);
Directory.CreateDirectory(fileDestinationPath);
}
else
{
// If it is a file:
// Create containing directory:
Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath)!);
source.ExtractToFile(fileDestinationPath, overwrite: overwrite);
}
}
}
}