From c64068e57d37f7b494dc56c7c426f67a451aafdd Mon Sep 17 00:00:00 2001 From: Ian Hays Date: Wed, 13 Jun 2018 12:56:30 -0700 Subject: [PATCH 1/2] Refactor, clean up ZipFile assembly. This is a net-zero change PR to move around some files in the System.IO.Compression.ZipFile assembly in preparation for some upcoming major additions. --- .../src/System.IO.Compression.ZipFile.csproj | 8 +- .../{ZipFile.cs => ZipFile.Create.cs} | 307 +---------------- .../System/IO/Compression/ZipFile.Extract.cs | 197 +++++++++++ .../System/IO/Compression/ZipFile.Utils.cs | 80 +++++ .../ZipFileExtensions.ZipArchive.Create.cs | 118 +++++++ .../ZipFileExtensions.ZipArchive.Extract.cs | 82 +++++ ...pFileExtensions.ZipArchiveEntry.Extract.cs | 123 +++++++ .../IO/Compression/ZipFileExtensions.cs | 316 ------------------ ...System.IO.Compression.ZipFile.Tests.csproj | 13 +- .../ZipArchiveEntry.ExtractToDirectory.cs | 97 ++++++ ...eInvalidFileTests.cs => ZipFile.Create.cs} | 224 ++++++++++--- .../tests/ZipFile.Extract.cs | 164 +++++++++ .../ZipFile.ExtractToDirectoryFiltered.cs | 31 ++ .../tests/ZipFileConvenienceMethods.cs | 228 ------------- ...ZipFileConvenienceMethods.netcoreapp1.1.cs | 72 ---- .../ZipFileExtensions.ZipArchive.Create.cs | 39 +++ .../ZipFileExtensions.ZipArchive.Extract.cs | 37 ++ ...pFileExtensions.ZipArchiveEntry.Extract.cs | 46 +++ .../tests/ZipFileReadOpenUpdateTests.cs | 107 ------ 19 files changed, 1218 insertions(+), 1071 deletions(-) rename src/System.IO.Compression.ZipFile/src/System/IO/Compression/{ZipFile.cs => ZipFile.Create.cs} (60%) create mode 100644 src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs create mode 100644 src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Utils.cs create mode 100644 src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs create mode 100644 src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs create mode 100644 src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs delete mode 100644 src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.cs create mode 100644 src/System.IO.Compression.ZipFile/tests/ZipArchiveEntry.ExtractToDirectory.cs rename src/System.IO.Compression.ZipFile/tests/{ZipFileInvalidFileTests.cs => ZipFile.Create.cs} (54%) create mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs create mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFile.ExtractToDirectoryFiltered.cs delete mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFileConvenienceMethods.cs delete mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFileConvenienceMethods.netcoreapp1.1.cs create mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchive.Create.cs create mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchive.Extract.cs create mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchiveEntry.Extract.cs delete mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFileReadOpenUpdateTests.cs diff --git a/src/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj b/src/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj index ecb7160124e1..fac1716bc7ea 100644 --- a/src/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj +++ b/src/System.IO.Compression.ZipFile/src/System.IO.Compression.ZipFile.csproj @@ -12,8 +12,12 @@ - - + + + + + + Common\System\IO\PathInternal.CaseSensitivity.cs diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs similarity index 60% rename from src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.cs rename to src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs index a9dab48515ba..7cf8ee61c9e2 100644 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.cs +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs @@ -9,11 +9,8 @@ namespace System.IO.Compression { - public static class ZipFile + public static partial class ZipFile { - // Per the .ZIP File Format Specification 4.4.17.1 all slashes should be forward slashes - private const char PathSeparator = '/'; - /// /// Opens a ZipArchive on the specified path for reading. The specified file is opened with FileMode.Open. /// @@ -34,11 +31,7 @@ public static class ZipFile /// /// 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. - public static ZipArchive OpenRead(string archiveFileName) - { - return Open(archiveFileName, ZipArchiveMode.Read); - } - + public static ZipArchive OpenRead(string archiveFileName) => Open(archiveFileName, ZipArchiveMode.Read); /// /// Opens a ZipArchive on the specified archiveFileName in the specified ZipArchiveMode mode. @@ -76,11 +69,7 @@ public static ZipArchive OpenRead(string archiveFileName) /// 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 ZipArchiveMode.Create mode is more efficient when creating a new Zip file. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] // See comment in the body. - public static ZipArchive Open(string archiveFileName, ZipArchiveMode mode) - { - return Open(archiveFileName, mode, entryNameEncoding: null); - } - + public static ZipArchive Open(string archiveFileName, ZipArchiveMode mode) => Open(archiveFileName, mode, entryNameEncoding: null); /// /// Opens a ZipArchive on the specified archiveFileName in the specified ZipArchiveMode mode. @@ -207,7 +196,6 @@ public static ZipArchive Open(string archiveFileName, ZipArchiveMode mode, Encod } } - /// ///

Creates a Zip archive at the path destinationArchiveFileName that contains the files and directories from /// the directory specified by sourceDirectoryName. The directory structure is preserved in the archive, and a @@ -247,12 +235,8 @@ public static ZipArchive Open(string archiveFileName, ZipArchiveMode mode, Encod /// /// The path to the directory on the file system to be archived. /// The name of the archive to be created. - public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) - { - DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, - compressionLevel: null, includeBaseDirectory: false, entryNameEncoding: null); - } - + public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName) => + DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, compressionLevel: null, includeBaseDirectory: false, entryNameEncoding: null); ///

///

Creates a Zip archive at the path destinationArchiveFileName that contains the files and directories in the directory @@ -297,12 +281,8 @@ public static void CreateFromDirectory(string sourceDirectoryName, string destin /// true to indicate that a directory named sourceDirectoryName should /// be included at the root of the archive. false to indicate that the files and directories in sourceDirectoryName /// should be included directly in the archive. - public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, - CompressionLevel compressionLevel, bool includeBaseDirectory) - { + public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel compressionLevel, bool includeBaseDirectory) => DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, compressionLevel, includeBaseDirectory, entryNameEncoding: null); - } - ///

///

Creates a Zip archive at the path destinationArchiveFileName that contains the files and directories in the directory @@ -371,208 +351,12 @@ public static void CreateFromDirectory(string sourceDirectoryName, string destin /// otherwise an is thrown. /// public static void CreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, - CompressionLevel compressionLevel, bool includeBaseDirectory, - Encoding entryNameEncoding) - { - DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, compressionLevel, includeBaseDirectory, entryNameEncoding); - } - - - ///

- /// Extracts all of the files in the specified archive to a directory on the file system. - /// The specified directory must not exist. This method will create all subdirectories and the specified directory. - /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will - /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry - /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information - /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified - /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used. - /// - /// - /// sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace, - /// or contains one or more invalid characters as defined by InvalidPathChars. - /// sourceArchive or destinationDirectoryName is null. - /// sourceArchive or destinationDirectoryName specifies a 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. - /// The path specified by sourceArchive or destinationDirectoryName is invalid, - /// (for example, it is on an unmapped drive). - /// The directory specified by destinationDirectoryName already exists. - /// -or- An I/O error has occurred. -or- An archive entry?s name is zero-length, contains only whitespace, or contains one or - /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive. - /// The caller does not have the required permission. - /// sourceArchive or destinationDirectoryName is in an invalid format. - /// sourceArchive was not found. - /// The archive specified by sourceArchive: Is not a valid ZipArchive - /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method - /// that is not supported. - /// - /// The path to the archive on the file system that is to be extracted. - /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName) - { - ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null); - } - - /// - /// Extracts all of the files in the specified archive to a directory on the file system. - /// The specified directory must not exist. This method will create all subdirectories and the specified directory. - /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will - /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry - /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information - /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified - /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used. - /// - /// - /// sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace, - /// or contains one or more invalid characters as defined by InvalidPathChars. - /// sourceArchive or destinationDirectoryName is null. - /// sourceArchive or destinationDirectoryName specifies a 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. - /// The path specified by sourceArchive or destinationDirectoryName is invalid, - /// (for example, it is on an unmapped drive). - /// The directory specified by destinationDirectoryName already exists. - /// -or- An I/O error has occurred. -or- An archive entry?s name is zero-length, contains only whitespace, or contains one or - /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive. - /// The caller does not have the required permission. - /// sourceArchive or destinationDirectoryName is in an invalid format. - /// sourceArchive was not found. - /// The archive specified by sourceArchive: Is not a valid ZipArchive - /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method - /// that is not supported. - /// - /// The path to the archive on the file system that is to be extracted. - /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. - /// True to indicate overwrite. - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwrite) - { - ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: overwrite); - } - - /// - /// Extracts all of the files in the specified archive to a directory on the file system. - /// The specified directory must not exist. This method will create all subdirectories and the specified directory. - /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will - /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry - /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information - /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified - /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used. - /// - /// - /// sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace, - /// or contains one or more invalid characters as defined by InvalidPathChars. - /// sourceArchive or destinationDirectoryName is null. - /// sourceArchive or destinationDirectoryName specifies a 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. - /// The path specified by sourceArchive or destinationDirectoryName is invalid, - /// (for example, it is on an unmapped drive). - /// The directory specified by destinationDirectoryName already exists. - /// -or- An I/O error has occurred. -or- An archive entry?s name is zero-length, contains only whitespace, or contains one or - /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive. - /// The caller does not have the required permission. - /// sourceArchive or destinationDirectoryName is in an invalid format. - /// sourceArchive was not found. - /// The archive specified by sourceArchive: Is not a valid ZipArchive - /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method - /// that is not supported. - /// - /// The path to the archive on the file system that is to be extracted. - /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. - /// The encoding to use when reading or writing entry names in this ZipArchive. - /// /// NOTE: Specifying this parameter to values other than null 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.
- /// This value is used as follows:
- /// If entryNameEncoding is not specified (== null): - /// - /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is not set, - /// use the current system default code page (Encoding.Default) in order to decode the entry name. - /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is set, - /// use UTF-8 (Encoding.UTF8) in order to decode the entry name. - /// - /// If entryNameEncoding is specified (!= null): - /// - /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is not set, - /// use the specified entryNameEncoding in order to decode the entry name. - /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is set, - /// use UTF-8 (Encoding.UTF8) in order to decode the entry name. - /// - /// Note that Unicode encodings other than UTF-8 may not be currently used for the entryNameEncoding, - /// otherwise an is thrown. - /// - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding) - { - ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, overwrite: false, entryNameEncoding: entryNameEncoding); - } - - /// - /// Extracts all of the files in the specified archive to a directory on the file system. - /// The specified directory must not exist. This method will create all subdirectories and the specified directory. - /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will - /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry - /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information - /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified - /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used. - /// - /// - /// sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace, - /// or contains one or more invalid characters as defined by InvalidPathChars. - /// sourceArchive or destinationDirectoryName is null. - /// sourceArchive or destinationDirectoryName specifies a 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. - /// The path specified by sourceArchive or destinationDirectoryName is invalid, - /// (for example, it is on an unmapped drive). - /// The directory specified by destinationDirectoryName already exists. - /// -or- An I/O error has occurred. -or- An archive entry?s name is zero-length, contains only whitespace, or contains one or - /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive. - /// The caller does not have the required permission. - /// sourceArchive or destinationDirectoryName is in an invalid format. - /// sourceArchive was not found. - /// The archive specified by sourceArchive: Is not a valid ZipArchive - /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method - /// that is not supported. - /// - /// The path to the archive on the file system that is to be extracted. - /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. - /// True to indicate overwrite. - /// The encoding to use when reading or writing entry names in this ZipArchive. - /// /// NOTE: Specifying this parameter to values other than null 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.
- /// This value is used as follows:
- /// If entryNameEncoding is not specified (== null): - /// - /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is not set, - /// use the current system default code page (Encoding.Default) in order to decode the entry name. - /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is set, - /// use UTF-8 (Encoding.UTF8) in order to decode the entry name. - /// - /// If entryNameEncoding is specified (!= null): - /// - /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is not set, - /// use the specified entryNameEncoding in order to decode the entry name. - /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is set, - /// use UTF-8 (Encoding.UTF8) in order to decode the entry name. - /// - /// Note that Unicode encodings other than UTF-8 may not be currently used for the entryNameEncoding, - /// otherwise an is thrown. - /// - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding, bool overwrite) - { - if (sourceArchiveFileName == null) - throw new ArgumentNullException(nameof(sourceArchiveFileName)); - - using (ZipArchive archive = Open(sourceArchiveFileName, ZipArchiveMode.Read, entryNameEncoding)) - { - archive.ExtractToDirectory(destinationDirectoryName, overwrite); - } - } + CompressionLevel compressionLevel, bool includeBaseDirectory, Encoding entryNameEncoding) => + DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, compressionLevel, includeBaseDirectory, entryNameEncoding: null); private static void DoCreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, - CompressionLevel? compressionLevel, bool includeBaseDirectory, - Encoding entryNameEncoding) + CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding entryNameEncoding) + { // Rely on Path.GetFullPath for validation of sourceDirectoryName and destinationArchive @@ -612,18 +396,18 @@ private static void DoCreateFromDirectory(string sourceDirectoryName, string des if (file is FileInfo) { // Create entry for file: - string entryName = EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer); + string entryName = ZipFileUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer); ZipFileExtensions.DoCreateEntryFromFile(archive, file.FullName, entryName, compressionLevel); } else { // Entry marking an empty dir: DirectoryInfo possiblyEmpty = file as DirectoryInfo; - if (possiblyEmpty != null && IsDirEmpty(possiblyEmpty)) + if (possiblyEmpty != null && ZipFileUtils.IsDirEmpty(possiblyEmpty)) { // FullName never returns a directory separator character on the end, // but Zip archives require it to specify an explicit directory: - string entryName = EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer, appendPathSeparator: true); + string entryName = ZipFileUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer, appendPathSeparator: true); archive.CreateEntry(entryName); } } @@ -631,73 +415,14 @@ private static void DoCreateFromDirectory(string sourceDirectoryName, string des // If no entries create an empty root directory entry: if (includeBaseDirectory && directoryIsEmpty) - archive.CreateEntry(EntryFromPath(di.Name, 0, di.Name.Length, ref entryNameBuffer, appendPathSeparator: true)); + archive.CreateEntry(ZipFileUtils.EntryFromPath(di.Name, 0, di.Name.Length, ref entryNameBuffer, appendPathSeparator: true)); } finally { ArrayPool.Shared.Return(entryNameBuffer); } - } // using - } // DoCreateFromDirectory - - private static string EntryFromPath(string entry, int offset, int length, ref char[] buffer, bool appendPathSeparator = false) - { - Debug.Assert(length <= entry.Length - offset); - Debug.Assert(buffer != null); - - // Remove any leading slashes from the entry name: - while (length > 0) - { - if (entry[offset] != Path.DirectorySeparatorChar && - entry[offset] != Path.AltDirectorySeparatorChar) - break; - - offset++; - length--; } - - if (length == 0) - return appendPathSeparator ? PathSeparator.ToString() : string.Empty; - - int resultLength = appendPathSeparator ? length + 1 : length; - EnsureCapacity(ref buffer, resultLength); - entry.CopyTo(offset, buffer, 0, length); - - // '/' is a more broadly recognized directory separator on all platforms (eg: mac, linux) - // We don't use Path.DirectorySeparatorChar or AltDirectorySeparatorChar because this is - // explicitly trying to standardize to '/' - for (int i = 0; i < length; i++) - { - char ch = buffer[i]; - if (ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar) - buffer[i] = PathSeparator; - } - - if (appendPathSeparator) - buffer[length] = PathSeparator; - - return new string(buffer, 0, resultLength); - } - - private static void EnsureCapacity(ref char[] buffer, int min) - { - Debug.Assert(buffer != null); - Debug.Assert(min > 0); - - if (buffer.Length < min) - { - int newCapacity = buffer.Length * 2; - if (newCapacity < min) newCapacity = min; - ArrayPool.Shared.Return(buffer); - buffer = ArrayPool.Shared.Rent(newCapacity); - } - } - - private static bool IsDirEmpty(DirectoryInfo possiblyEmptyDir) - { - using (IEnumerator enumerator = Directory.EnumerateFileSystemEntries(possiblyEmptyDir.FullName).GetEnumerator()) - return !enumerator.MoveNext(); } - } // class ZipFile -} // namespace + } +} diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs new file mode 100644 index 000000000000..dab9affe758a --- /dev/null +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs @@ -0,0 +1,197 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace System.IO.Compression +{ + public static partial class ZipFile + { + /// + /// Extracts all of the files in the specified archive to a directory on the file system. + /// The specified directory must not exist. This method will create all subdirectories and the specified directory. + /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will + /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry + /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information + /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified + /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used. + /// + /// + /// sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace, + /// or contains one or more invalid characters as defined by InvalidPathChars. + /// sourceArchive or destinationDirectoryName is null. + /// sourceArchive or destinationDirectoryName specifies a 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. + /// The path specified by sourceArchive or destinationDirectoryName is invalid, + /// (for example, it is on an unmapped drive). + /// The directory specified by destinationDirectoryName already exists. + /// -or- An I/O error has occurred. -or- An archive entry?s name is zero-length, contains only whitespace, or contains one or + /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive. + /// The caller does not have the required permission. + /// sourceArchive or destinationDirectoryName is in an invalid format. + /// sourceArchive was not found. + /// The archive specified by sourceArchive: Is not a valid ZipArchive + /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method + /// that is not supported. + /// + /// The path to the archive on the file system that is to be extracted. + /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName) => ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: false); + + /// + /// Extracts all of the files in the specified archive to a directory on the file system. + /// The specified directory must not exist. This method will create all subdirectories and the specified directory. + /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will + /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry + /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information + /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified + /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used. + /// + /// + /// sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace, + /// or contains one or more invalid characters as defined by InvalidPathChars. + /// sourceArchive or destinationDirectoryName is null. + /// sourceArchive or destinationDirectoryName specifies a 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. + /// The path specified by sourceArchive or destinationDirectoryName is invalid, + /// (for example, it is on an unmapped drive). + /// The directory specified by destinationDirectoryName already exists. + /// -or- An I/O error has occurred. -or- An archive entry?s name is zero-length, contains only whitespace, or contains one or + /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive. + /// The caller does not have the required permission. + /// sourceArchive or destinationDirectoryName is in an invalid format. + /// sourceArchive was not found. + /// The archive specified by sourceArchive: Is not a valid ZipArchive + /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method + /// that is not supported. + /// + /// The path to the archive on the file system that is to be extracted. + /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. + /// True to indicate overwrite. + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwrite) => ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: overwrite); + + /// + /// Extracts all of the files in the specified archive to a directory on the file system. + /// The specified directory must not exist. This method will create all subdirectories and the specified directory. + /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will + /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry + /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information + /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified + /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used. + /// + /// + /// sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace, + /// or contains one or more invalid characters as defined by InvalidPathChars. + /// sourceArchive or destinationDirectoryName is null. + /// sourceArchive or destinationDirectoryName specifies a 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. + /// The path specified by sourceArchive or destinationDirectoryName is invalid, + /// (for example, it is on an unmapped drive). + /// The directory specified by destinationDirectoryName already exists. + /// -or- An I/O error has occurred. -or- An archive entry?s name is zero-length, contains only whitespace, or contains one or + /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive. + /// The caller does not have the required permission. + /// sourceArchive or destinationDirectoryName is in an invalid format. + /// sourceArchive was not found. + /// The archive specified by sourceArchive: Is not a valid ZipArchive + /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method + /// that is not supported. + /// + /// The path to the archive on the file system that is to be extracted. + /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. + /// The encoding to use when reading or writing entry names in this ZipArchive. + /// /// NOTE: Specifying this parameter to values other than null 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.
+ /// This value is used as follows:
+ /// If entryNameEncoding is not specified (== null): + /// + /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is not set, + /// use the current system default code page (Encoding.Default) in order to decode the entry name. + /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is set, + /// use UTF-8 (Encoding.UTF8) in order to decode the entry name. + /// + /// If entryNameEncoding is specified (!= null): + /// + /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is not set, + /// use the specified entryNameEncoding in order to decode the entry name. + /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is set, + /// use UTF-8 (Encoding.UTF8) in order to decode the entry name. + /// + /// Note that Unicode encodings other than UTF-8 may not be currently used for the entryNameEncoding, + /// otherwise an is thrown. + /// + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding) => ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: entryNameEncoding, overwrite: false); + + /// + /// Extracts all of the files in the specified archive to a directory on the file system. + /// The specified directory must not exist. This method will create all subdirectories and the specified directory. + /// If there is an error while extracting the archive, the archive will remain partially extracted. Each entry will + /// be extracted such that the extracted file has the same relative path to the destinationDirectoryName as the entry + /// has to the archive. The path is permitted to specify relative or absolute path information. Relative path information + /// is interpreted as relative to the current working directory. If a file to be archived has an invalid last modified + /// time, the first datetime representable in the Zip timestamp format (midnight on January 1, 1980) will be used. + /// + /// + /// sourceArchive or destinationDirectoryName is a zero-length string, contains only whitespace, + /// or contains one or more invalid characters as defined by InvalidPathChars. + /// sourceArchive or destinationDirectoryName is null. + /// sourceArchive or destinationDirectoryName specifies a 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. + /// The path specified by sourceArchive or destinationDirectoryName is invalid, + /// (for example, it is on an unmapped drive). + /// The directory specified by destinationDirectoryName already exists. + /// -or- An I/O error has occurred. -or- An archive entry?s name is zero-length, contains only whitespace, or contains one or + /// more invalid characters as defined by InvalidPathChars. -or- Extracting an archive entry would result in a file destination that is outside the destination directory (for example, because of parent directory accessors). -or- An archive entry has the same name as an already extracted entry from the same archive. + /// The caller does not have the required permission. + /// sourceArchive or destinationDirectoryName is in an invalid format. + /// sourceArchive was not found. + /// The archive specified by sourceArchive: Is not a valid ZipArchive + /// -or- An archive entry was not found or was corrupt. -or- An archive entry has been compressed using a compression method + /// that is not supported. + /// + /// The path to the archive on the file system that is to be extracted. + /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. + /// True to indicate overwrite. + /// The encoding to use when reading or writing entry names in this ZipArchive. + /// /// NOTE: Specifying this parameter to values other than null 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.
+ /// This value is used as follows:
+ /// If entryNameEncoding is not specified (== null): + /// + /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is not set, + /// use the current system default code page (Encoding.Default) in order to decode the entry name. + /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is set, + /// use UTF-8 (Encoding.UTF8) in order to decode the entry name. + /// + /// If entryNameEncoding is specified (!= null): + /// + /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is not set, + /// use the specified entryNameEncoding in order to decode the entry name. + /// For entries where the language encoding flag (EFS) in the general purpose bit flag of the local file header is set, + /// use UTF-8 (Encoding.UTF8) in order to decode the entry name. + /// + /// Note that Unicode encodings other than UTF-8 may not be currently used for the entryNameEncoding, + /// otherwise an is thrown. + /// + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding, bool overwrite) + { + if (sourceArchiveFileName == null) + throw new ArgumentNullException(nameof(sourceArchiveFileName)); + + using (ZipArchive archive = Open(sourceArchiveFileName, ZipArchiveMode.Read, entryNameEncoding)) + { + archive.ExtractToDirectory(destinationDirectoryName, overwrite); + } + } + } +} diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Utils.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Utils.cs new file mode 100644 index 000000000000..ee19c317ea3c --- /dev/null +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Utils.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace System.IO.Compression +{ + internal static partial class ZipFileUtils + { + // Per the .ZIP File Format Specification 4.4.17.1 all slashes should be forward slashes + public const char PathSeparator = '/'; + + public static string EntryFromPath(string entry, int offset, int length, ref char[] buffer, bool appendPathSeparator = false) + { + Debug.Assert(length <= entry.Length - offset); + Debug.Assert(buffer != null); + + // Remove any leading slashes from the entry name: + while (length > 0) + { + if (entry[offset] != Path.DirectorySeparatorChar && + entry[offset] != Path.AltDirectorySeparatorChar) + break; + + offset++; + length--; + } + + if (length == 0) + return appendPathSeparator ? PathSeparator.ToString() : string.Empty; + + int resultLength = appendPathSeparator ? length + 1 : length; + EnsureCapacity(ref buffer, resultLength); + entry.CopyTo(offset, buffer, 0, length); + + // '/' is a more broadly recognized directory separator on all platforms (eg: mac, linux) + // We don't use Path.DirectorySeparatorChar or AltDirectorySeparatorChar because this is + // explicitly trying to standardize to '/' + for (int i = 0; i < length; i++) + { + char ch = buffer[i]; + if (ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar) + buffer[i] = PathSeparator; + } + + if (appendPathSeparator) + buffer[length] = PathSeparator; + + return new string(buffer, 0, resultLength); + } + + public static void EnsureCapacity(ref char[] buffer, int min) + { + Debug.Assert(buffer != null); + Debug.Assert(min > 0); + + if (buffer.Length < min) + { + int newCapacity = buffer.Length * 2; + if (newCapacity < min) + newCapacity = min; + ArrayPool.Shared.Return(buffer); + buffer = ArrayPool.Shared.Rent(newCapacity); + } + } + + public static bool IsDirEmpty(DirectoryInfo possiblyEmptyDir) + { + using (IEnumerator enumerator = Directory.EnumerateFileSystemEntries(possiblyEmptyDir.FullName).GetEnumerator()) + return !enumerator.MoveNext(); + } + } +} diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs new file mode 100644 index 000000000000..3b340746224c --- /dev/null +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.IO.Compression +{ + [EditorBrowsable(EditorBrowsableState.Never)] + public static partial class ZipFileExtensions + { + /// + ///

Adds a file from the file system to the archive under the specified entry name. + /// The new entry in the archive will contain the contents of the file. + /// The last write time of the archive entry is set to the last write time of the file on the file system. + /// If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name. + /// If the specified source file has an invalid last modified time, the first datetime representable in the Zip timestamp format + /// (midnight on January 1, 1980) will be used.

+ /// + ///

If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.

+ /// + ///

Since no CompressionLevel is specified, the default provided by the implementation of the underlying compression + /// algorithm will be used; the ZipArchive will not impose its own default. + /// (Currently, the underlying compression algorithm is provided by the System.IO.Compression.DeflateStream class.)

+ ///
+ /// + /// sourceFileName is a zero-length string, contains only whitespace, or contains one or more + /// invalid characters as defined by InvalidPathChars. -or- entryName is a zero-length string. + /// sourceFileName or entryName is null. + /// In sourceFileName, 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. + /// The specified sourceFileName is invalid, (for example, it is on an unmapped drive). + /// An I/O error occurred while opening the file specified by sourceFileName. + /// sourceFileName specified a directory. -or- The caller does not have the + /// required permission. + /// The file specified in sourceFileName was not found. + /// sourceFileName is in an invalid format or the ZipArchive does not support writing. + /// The ZipArchive has already been closed. + /// + /// The path to the file on the file system to be copied from. The path is permitted to specify + /// relative or absolute path information. Relative path information is interpreted as relative to the current working directory. + /// The name of the entry to be created. + /// A wrapper for the newly created entry. + public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, string sourceFileName, string entryName) => + DoCreateEntryFromFile(destination, sourceFileName, entryName, null); + + + /// + ///

Adds a file from the file system to the archive under the specified entry name. + /// The new entry in the archive will contain the contents of the file. + /// The last write time of the archive entry is set to the last write time of the file on the file system. + /// If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name. + /// If the specified source file has an invalid last modified time, the first datetime representable in the Zip timestamp format + /// (midnight on January 1, 1980) will be used.

+ ///

If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.

+ ///
+ /// sourceFileName is a zero-length string, contains only whitespace, or contains one or more + /// invalid characters as defined by InvalidPathChars. -or- entryName is a zero-length string. + /// sourceFileName or entryName is null. + /// In sourceFileName, 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. + /// The specified sourceFileName is invalid, (for example, it is on an unmapped drive). + /// An I/O error occurred while opening the file specified by sourceFileName. + /// sourceFileName specified a directory. + /// -or- The caller does not have the required permission. + /// The file specified in sourceFileName was not found. + /// sourceFileName is in an invalid format or the ZipArchive does not support writing. + /// The ZipArchive has already been closed. + /// + /// The path to the file on the file system to be copied from. The path is permitted to specify relative + /// or absolute path information. Relative path information is interpreted as relative to the current working directory. + /// The name of the entry to be created. + /// The level of the compression (speed/memory vs. compressed size trade-off). + /// A wrapper for the newly created entry. + public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, + string sourceFileName, string entryName, CompressionLevel compressionLevel) => + DoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel); + + internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destination, + string sourceFileName, string entryName, CompressionLevel? compressionLevel) + { + if (destination == null) + throw new ArgumentNullException(nameof(destination)); + + if (sourceFileName == null) + throw new ArgumentNullException(nameof(sourceFileName)); + + if (entryName == null) + throw new ArgumentNullException(nameof(entryName)); + + // 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. + + // Argument checking gets passed down to FileStream's ctor and CreateEntry + + using (Stream fs = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false)) + { + ZipArchiveEntry entry = compressionLevel.HasValue + ? destination.CreateEntry(entryName, compressionLevel.Value) + : destination.CreateEntry(entryName); + + DateTime lastWrite = File.GetLastWriteTime(sourceFileName); + + // If file to be archived has an invalid last modified time, use the first datetime representable in the Zip timestamp format + // (midnight on January 1, 1980): + if (lastWrite.Year < 1980 || lastWrite.Year > 2107) + lastWrite = new DateTime(1980, 1, 1, 0, 0, 0); + + entry.LastWriteTime = lastWrite; + + using (Stream es = entry.Open()) + fs.CopyTo(es); + + return entry; + } + } + } +} diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs new file mode 100644 index 000000000000..2a67e93ca214 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.IO.Compression +{ + public static partial class ZipFileExtensions + { + /// + /// Extracts all of the files in the archive to a directory on the file system. The specified directory may already exist. + /// This method will create all subdirectories and the specified directory if necessary. + /// If there is an error while extracting the archive, the archive will remain partially extracted. + /// Each entry will be extracted such that the extracted file has the same relative path to destinationDirectoryName as the + /// entry has to the root of the archive. If a file to be archived has an invalid last modified time, the first datetime + /// representable in the Zip timestamp format (midnight on January 1, 1980) will be used. + /// + /// + /// destinationDirectoryName is a zero-length string, contains only whitespace, + /// or contains one or more invalid characters as defined by InvalidPathChars. + /// destinationDirectoryName is null. + /// 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. + /// The specified path is invalid, (for example, it is on an unmapped drive). + /// An archive entry?s name is zero-length, contains only whitespace, or contains one or more invalid + /// characters as defined by InvalidPathChars. -or- Extracting an archive entry would have resulted in a destination + /// file that is outside destinationDirectoryName (for example, if the entry name contains parent directory accessors). + /// -or- An archive entry has the same name as an already extracted entry from the same archive. + /// The caller does not have the required permission. + /// destinationDirectoryName is in an invalid format. + /// An archive entry was not found or was corrupt. + /// -or- An archive entry has been compressed using a compression method that is not supported. + /// + /// The path to the directory on the file system. + /// The directory specified must not exist. The path is permitted to specify relative or absolute path information. + /// Relative path information is interpreted as relative to the current working directory. + public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName) => ExtractToDirectory(source, destinationDirectoryName, overwrite: false); + + /// + /// Extracts all of the files in the archive to a directory on the file system. The specified directory may already exist. + /// This method will create all subdirectories and the specified directory if necessary. + /// If there is an error while extracting the archive, the archive will remain partially extracted. + /// Each entry will be extracted such that the extracted file has the same relative path to destinationDirectoryName as the + /// entry has to the root of the archive. If a file to be archived has an invalid last modified time, the first datetime + /// representable in the Zip timestamp format (midnight on January 1, 1980) will be used. + /// + /// + /// destinationDirectoryName is a zero-length string, contains only whitespace, + /// or contains one or more invalid characters as defined by InvalidPathChars. + /// destinationDirectoryName is null. + /// 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. + /// The specified path is invalid, (for example, it is on an unmapped drive). + /// An archive entry?s name is zero-length, contains only whitespace, or contains one or more invalid + /// characters as defined by InvalidPathChars. -or- Extracting an archive entry would have resulted in a destination + /// file that is outside destinationDirectoryName (for example, if the entry name contains parent directory accessors). + /// -or- An archive entry has the same name as an already extracted entry from the same archive. + /// The caller does not have the required permission. + /// destinationDirectoryName is in an invalid format. + /// An archive entry was not found or was corrupt. + /// -or- An archive entry has been compressed using a compression method that is not supported. + /// + /// The path to the directory on the file system. + /// The directory specified must not exist. The path is permitted to specify relative or absolute path information. + /// Relative path information is interpreted as relative to the current working directory. + /// True to indicate overwrite. + public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwrite) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + if (destinationDirectoryName == null) + throw new ArgumentNullException(nameof(destinationDirectoryName)); + + foreach (ZipArchiveEntry entry in source.Entries) + { + entry.ExtractRelativeToDirectory(destinationDirectoryName, overwrite); + } + } + } +} diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs new file mode 100644 index 000000000000..ecc3ab652ee6 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.IO.Compression +{ + public static partial class ZipFileExtensions + { + /// + /// 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. + /// + /// + /// The caller does not have the required permission. + /// 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. + /// destinationFileName is null. + /// 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. + /// The path specified in destinationFileName is invalid (for example, it is on + /// an unmapped drive). + /// destinationFileName already exists. + /// -or- An I/O error has occurred. -or- The entry is currently open for writing. + /// -or- The entry has been deleted from the archive. + /// destinationFileName is in an invalid format + /// -or- The ZipArchive that this entry belongs to was opened in a write-only mode. + /// 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. + /// The ZipArchive that this entry belongs to has been disposed. + /// + /// 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. + public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName) => ExtractToFile(source, destinationFileName, false); + + /// + /// 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. + /// + /// + /// The caller does not have the required permission. + /// 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. + /// destinationFileName is null. + /// 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. + /// The path specified in destinationFileName is invalid + /// (for example, it is on an unmapped drive). + /// destinationFileName exists and overwrite is false. + /// -or- An I/O error has occurred. + /// -or- The entry is currently open for writing. + /// -or- The entry has been deleted from the archive. + /// destinationFileName is in an invalid format + /// -or- The ZipArchive that this entry belongs to was opened in a write-only mode. + /// 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. + /// The ZipArchive that this entry belongs to has been disposed. + /// 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. + /// True to indicate overwrite. + public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName, bool overwrite) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + if (destinationFileName == null) + throw new ArgumentNullException(nameof(destinationFileName)); + + // Rely on FileStream's ctor for further checking destinationFileName parameter + FileMode fMode = overwrite ? FileMode.Create : FileMode.CreateNew; + + using (Stream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, useAsync: false)) + { + using (Stream es = source.Open()) + es.CopyTo(fs); + } + + File.SetLastWriteTime(destinationFileName, source.LastWriteTime.DateTime); + } + + 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) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + if (destinationDirectoryName == null) + throw new ArgumentNullException(nameof(destinationDirectoryName)); + + // Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists: + DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName); + string destinationDirectoryFullPath = di.FullName; + + string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, 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); + } + } + } +} diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.cs deleted file mode 100644 index 3fef7883c953..000000000000 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.cs +++ /dev/null @@ -1,316 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.ComponentModel; - -namespace System.IO.Compression -{ - [EditorBrowsable(EditorBrowsableState.Never)] - public static class ZipFileExtensions - { - #region ZipArchive extensions - - - /// - ///

Adds a file from the file system to the archive under the specified entry name. - /// The new entry in the archive will contain the contents of the file. - /// The last write time of the archive entry is set to the last write time of the file on the file system. - /// If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name. - /// If the specified source file has an invalid last modified time, the first datetime representable in the Zip timestamp format - /// (midnight on January 1, 1980) will be used.

- /// - ///

If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.

- /// - ///

Since no CompressionLevel is specified, the default provided by the implementation of the underlying compression - /// algorithm will be used; the ZipArchive will not impose its own default. - /// (Currently, the underlying compression algorithm is provided by the System.IO.Compression.DeflateStream class.)

- ///
- /// - /// sourceFileName is a zero-length string, contains only whitespace, or contains one or more - /// invalid characters as defined by InvalidPathChars. -or- entryName is a zero-length string. - /// sourceFileName or entryName is null. - /// In sourceFileName, 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. - /// The specified sourceFileName is invalid, (for example, it is on an unmapped drive). - /// An I/O error occurred while opening the file specified by sourceFileName. - /// sourceFileName specified a directory. -or- The caller does not have the - /// required permission. - /// The file specified in sourceFileName was not found. - /// sourceFileName is in an invalid format or the ZipArchive does not support writing. - /// The ZipArchive has already been closed. - /// - /// The path to the file on the file system to be copied from. The path is permitted to specify - /// relative or absolute path information. Relative path information is interpreted as relative to the current working directory. - /// The name of the entry to be created. - /// A wrapper for the newly created entry. - public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, string sourceFileName, string entryName) - { - return DoCreateEntryFromFile(destination, sourceFileName, entryName, null); - } - - - /// - ///

Adds a file from the file system to the archive under the specified entry name. - /// The new entry in the archive will contain the contents of the file. - /// The last write time of the archive entry is set to the last write time of the file on the file system. - /// If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name. - /// If the specified source file has an invalid last modified time, the first datetime representable in the Zip timestamp format - /// (midnight on January 1, 1980) will be used.

- ///

If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.

- ///
- /// sourceFileName is a zero-length string, contains only whitespace, or contains one or more - /// invalid characters as defined by InvalidPathChars. -or- entryName is a zero-length string. - /// sourceFileName or entryName is null. - /// In sourceFileName, 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. - /// The specified sourceFileName is invalid, (for example, it is on an unmapped drive). - /// An I/O error occurred while opening the file specified by sourceFileName. - /// sourceFileName specified a directory. - /// -or- The caller does not have the required permission. - /// The file specified in sourceFileName was not found. - /// sourceFileName is in an invalid format or the ZipArchive does not support writing. - /// The ZipArchive has already been closed. - /// - /// The path to the file on the file system to be copied from. The path is permitted to specify relative - /// or absolute path information. Relative path information is interpreted as relative to the current working directory. - /// The name of the entry to be created. - /// The level of the compression (speed/memory vs. compressed size trade-off). - /// A wrapper for the newly created entry. - public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, - string sourceFileName, string entryName, CompressionLevel compressionLevel) - { - // 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. - - return DoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel); - } - - - /// - /// Extracts all of the files in the archive to a directory on the file system. The specified directory may already exist. - /// This method will create all subdirectories and the specified directory if necessary. - /// If there is an error while extracting the archive, the archive will remain partially extracted. - /// Each entry will be extracted such that the extracted file has the same relative path to destinationDirectoryName as the - /// entry has to the root of the archive. If a file to be archived has an invalid last modified time, the first datetime - /// representable in the Zip timestamp format (midnight on January 1, 1980) will be used. - /// - /// - /// destinationDirectoryName is a zero-length string, contains only whitespace, - /// or contains one or more invalid characters as defined by InvalidPathChars. - /// destinationDirectoryName is null. - /// 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. - /// The specified path is invalid, (for example, it is on an unmapped drive). - /// An archive entry?s name is zero-length, contains only whitespace, or contains one or more invalid - /// characters as defined by InvalidPathChars. -or- Extracting an archive entry would have resulted in a destination - /// file that is outside destinationDirectoryName (for example, if the entry name contains parent directory accessors). - /// -or- An archive entry has the same name as an already extracted entry from the same archive. - /// The caller does not have the required permission. - /// destinationDirectoryName is in an invalid format. - /// An archive entry was not found or was corrupt. - /// -or- An archive entry has been compressed using a compression method that is not supported. - /// - /// The path to the directory on the file system. - /// The directory specified must not exist. The path is permitted to specify relative or absolute path information. - /// Relative path information is interpreted as relative to the current working directory. - public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName) - { - ExtractToDirectory(source, destinationDirectoryName, overwrite: false); - } - - /// - /// Extracts all of the files in the archive to a directory on the file system. The specified directory may already exist. - /// This method will create all subdirectories and the specified directory if necessary. - /// If there is an error while extracting the archive, the archive will remain partially extracted. - /// Each entry will be extracted such that the extracted file has the same relative path to destinationDirectoryName as the - /// entry has to the root of the archive. If a file to be archived has an invalid last modified time, the first datetime - /// representable in the Zip timestamp format (midnight on January 1, 1980) will be used. - /// - /// - /// destinationDirectoryName is a zero-length string, contains only whitespace, - /// or contains one or more invalid characters as defined by InvalidPathChars. - /// destinationDirectoryName is null. - /// 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. - /// The specified path is invalid, (for example, it is on an unmapped drive). - /// An archive entry?s name is zero-length, contains only whitespace, or contains one or more invalid - /// characters as defined by InvalidPathChars. -or- Extracting an archive entry would have resulted in a destination - /// file that is outside destinationDirectoryName (for example, if the entry name contains parent directory accessors). - /// -or- An archive entry has the same name as an already extracted entry from the same archive. - /// The caller does not have the required permission. - /// destinationDirectoryName is in an invalid format. - /// An archive entry was not found or was corrupt. - /// -or- An archive entry has been compressed using a compression method that is not supported. - /// - /// The path to the directory on the file system. - /// The directory specified must not exist. The path is permitted to specify relative or absolute path information. - /// Relative path information is interpreted as relative to the current working directory. - /// True to indicate overwrite. - public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName, bool overwrite) - { - if (source == null) - throw new ArgumentNullException(nameof(source)); - - if (destinationDirectoryName == null) - throw new ArgumentNullException(nameof(destinationDirectoryName)); - - // Rely on Directory.CreateDirectory for validation of destinationDirectoryName. - - // Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists: - DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName); - string destinationDirectoryFullPath = di.FullName; - - foreach (ZipArchiveEntry entry in source.Entries) - { - string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, entry.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 (entry.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)); - entry.ExtractToFile(fileDestinationPath, overwrite: overwrite); - } - } - } - - internal static ZipArchiveEntry DoCreateEntryFromFile(ZipArchive destination, - string sourceFileName, string entryName, CompressionLevel? compressionLevel) - { - if (destination == null) - throw new ArgumentNullException(nameof(destination)); - - if (sourceFileName == null) - throw new ArgumentNullException(nameof(sourceFileName)); - - if (entryName == null) - throw new ArgumentNullException(nameof(entryName)); - - // 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. - - // Argument checking gets passed down to FileStream's ctor and CreateEntry - - using (Stream fs = new FileStream(sourceFileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 0x1000, useAsync: false)) - { - ZipArchiveEntry entry = compressionLevel.HasValue - ? destination.CreateEntry(entryName, compressionLevel.Value) - : destination.CreateEntry(entryName); - - DateTime lastWrite = File.GetLastWriteTime(sourceFileName); - - // If file to be archived has an invalid last modified time, use the first datetime representable in the Zip timestamp format - // (midnight on January 1, 1980): - if (lastWrite.Year < 1980 || lastWrite.Year > 2107) - lastWrite = new DateTime(1980, 1, 1, 0, 0, 0); - - entry.LastWriteTime = lastWrite; - - using (Stream es = entry.Open()) - fs.CopyTo(es); - - return entry; - } - } - - #endregion ZipArchive extensions - - - #region ZipArchiveEntry extensions - - /// - /// 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. - /// - /// - /// The caller does not have the required permission. - /// 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. - /// destinationFileName is null. - /// 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. - /// The path specified in destinationFileName is invalid (for example, it is on - /// an unmapped drive). - /// destinationFileName already exists. - /// -or- An I/O error has occurred. -or- The entry is currently open for writing. - /// -or- The entry has been deleted from the archive. - /// destinationFileName is in an invalid format - /// -or- The ZipArchive that this entry belongs to was opened in a write-only mode. - /// 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. - /// The ZipArchive that this entry belongs to has been disposed. - /// - /// 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. - public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName) - { - ExtractToFile(source, destinationFileName, false); - } - - - /// - /// 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. - /// - /// - /// The caller does not have the required permission. - /// 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. - /// destinationFileName is null. - /// 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. - /// The path specified in destinationFileName is invalid - /// (for example, it is on an unmapped drive). - /// destinationFileName exists and overwrite is false. - /// -or- An I/O error has occurred. - /// -or- The entry is currently open for writing. - /// -or- The entry has been deleted from the archive. - /// destinationFileName is in an invalid format - /// -or- The ZipArchive that this entry belongs to was opened in a write-only mode. - /// 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. - /// The ZipArchive that this entry belongs to has been disposed. - /// 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. - /// True to indicate overwrite. - public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName, bool overwrite) - { - if (source == null) - throw new ArgumentNullException(nameof(source)); - - if (destinationFileName == null) - throw new ArgumentNullException(nameof(destinationFileName)); - - // Rely on FileStream's ctor for further checking destinationFileName parameter - - FileMode fMode = overwrite ? FileMode.Create : FileMode.CreateNew; - - using (Stream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, useAsync: false)) - { - using (Stream es = source.Open()) - es.CopyTo(fs); - } - - File.SetLastWriteTime(destinationFileName, source.LastWriteTime.DateTime); - } - #endregion ZipArchiveEntry extensions - - } // class ZipFileExtensions -} // namespace diff --git a/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index b875e78c8f9c..3da8e1e850a7 100644 --- a/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -10,9 +10,11 @@ - - - + + + + + Common\System\IO\TempFile.cs @@ -38,13 +40,10 @@ Common\System\IO\Compression\ZipTestHelper.cs - - - %(RecursiveDir)%(Filename)%(Extension) - + \ No newline at end of file diff --git a/src/System.IO.Compression.ZipFile/tests/ZipArchiveEntry.ExtractToDirectory.cs b/src/System.IO.Compression.ZipFile/tests/ZipArchiveEntry.ExtractToDirectory.cs new file mode 100644 index 000000000000..5aa725fb9da2 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/tests/ZipArchiveEntry.ExtractToDirectory.cs @@ -0,0 +1,97 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.NetCore.Extensions; + +namespace System.IO.Compression.Tests +{ + public class ZipArchiveEntry_ExtractToDirectory : ZipFileTestBase + { + private static IEnumerable TestData() + { + yield return new string[] { "AbsoluteNoRelative.zip", "System.IO.IOException" }; + yield return new string[] { "AbsoluteWithRelativeOut.zip", "System.IO.IOException" }; + yield return new string[] { "DotMarkDevicePath.zip", @"System.IO.IOException" }; + yield return new string[] { "QuestionMarkDevicePath.zip", @"System.IO.IOException" }; + yield return new string[] { "RelativeFirst.zip", @"System.IO.IOException" }; + yield return new string[] { "ValidNestedRelative.zip", @"./foo/../sample.txt" }; + yield return new string[] { "NestedDotDevicePath.zip", @"System.IO.IOException" }; + yield return new string[] { "NestedQuestionDevicePath.zip", @"System.IO.IOException" }; + yield return new string[] { "RelativeAtStartAndEnd.zip", @"System.IO.IOException" }; + yield return new string[] { "JustDotdot.zip", @"System.IO.IOException" }; + yield return new string[] { "TrailingDotDot.zip", @"System.IO.IOException" }; + yield return new string[] { "MarkedDotDot.zip", @"System.IO.IOException" }; + yield return new string[] { "JustSlash.zip", @"System.IO.IOException" }; + yield return new string[] { "DotSlashDirectory.zip", @"./sample.txt" }; + yield return new string[] { "DoubleSlash.zip", @"foo//sample.txt" }; + yield return new string[] { "CurrentInsidePath.zip", @"parent/./child" }; + yield return new string[] { "Justdot.zip", @"System.IO.IOException" }; + yield return new string[] { "SlashDotStuff.zip", @"System.IO.IOException" }; + yield return new string[] { "TestNew1.zip", @"s/../stuff" }; + yield return new string[] { "TestNew2.zip", @"System.IO.IOException" }; + yield return new string[] { "TestNew3.zip", @"s/..\t\../j/try" }; + yield return new string[] { "TestNew4.zip", @"System.IO.IOException" }; + yield return new string[] { "TestNew5.zip", @"System.IO.IOException" }; + yield return new string[] { "TestNew6.zip", @"System.IO.IOException" }; + yield return new string[] { "TestNew7.zip", @"t/h/i/s/../../../../script" }; + yield return new string[] { "TestNew8.zip", @"System.IO.IOException" }; + yield return new string[] { "TestNew9.zip", @"System.IO.IOException" }; + yield return new string[] { "TestNew10.zip", @"System.IO.IOException" }; + yield return new string[] { "TestNew11.zip", @"./././././././5" }; + yield return new string[] { "InvalidChar1.zip", "System.ArgumentException" }; + yield return new string[] { "InvalidChar2.zip", @"sample.:xt" }; + yield return new string[] { "InvalidChar3.zip", "System.ArgumentException" }; + yield return new string[] { "EncodedDotDots.zip", "lib/%2E%2E/%2E%2E/%2E%2E/bad.exe" }; + yield return new string[] { "SpacedRelative.zip", "System.IO.DirectoryNotFoundException" }; + } + + [Theory] + [MemberData(nameof(TestData))] + public static void Entry_ExtractToDirectory(string zipFile, string expectedOutPath) + { + using (TempDirectory destinationDirectory = new TempDirectory()) + { + try + { + using (ZipArchive archive = ZipFile.OpenRead(zipFile)) + { + foreach (ZipArchiveEntry entry in archive.Entries) + { + entry.ExtractRelativeToDirectory(destinationDirectory.Path, false); + Assert.True(File.Exists(Path.Combine(destinationDirectory.Path, expectedOutPath))); + } + } + } + catch (Exception e) + { + Assert.Equal(expectedOutPath, e.GetType().ToString()); + } + } + } + + [Theory] + [MemberData(nameof(TestData))] + public static void Archive_ExtractToDirectory(string zipFile, string expectedOutPath) + { + using (TempDirectory destinationDirectory = new TempDirectory()) + { + try + { + ZipFile.ExtractToDirectory(zipFile, destinationDirectory.Path); + Assert.True(File.Exists(Path.Combine(destinationDirectory.Path, expectedOutPath))); + } + catch (Exception e) + { + Assert.Equal(expectedOutPath, e.GetType().ToString()); + } + } + } + } +} + diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFileInvalidFileTests.cs b/src/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs similarity index 54% rename from src/System.IO.Compression.ZipFile/tests/ZipFileInvalidFileTests.cs rename to src/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs index 53de20133964..9e4757285160 100644 --- a/src/System.IO.Compression.ZipFile/tests/ZipFileInvalidFileTests.cs +++ b/src/System.IO.Compression.ZipFile/tests/ZipFile.Create.cs @@ -1,13 +1,105 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using Xunit; namespace System.IO.Compression.Tests { - public partial class ZipFileTest_Invalid : ZipFileTestBase + public class ZipFile_Create : ZipFileTestBase { + [Fact] + public async Task CreateFromDirectoryNormal() + { + string folderName = zfolder("normal"); + string noBaseDir = GetTestFilePath(); + ZipFile.CreateFromDirectory(folderName, noBaseDir); + + await IsZipSameAsDirAsync(noBaseDir, folderName, ZipArchiveMode.Read, requireExplicit: false, checkTimes: false); + } + + [Fact] + public void CreateFromDirectory_IncludeBaseDirectory() + { + string folderName = zfolder("normal"); + string withBaseDir = GetTestFilePath(); + ZipFile.CreateFromDirectory(folderName, withBaseDir, CompressionLevel.Optimal, true); + + IEnumerable expected = Directory.EnumerateFiles(zfolder("normal"), "*", SearchOption.AllDirectories); + using (ZipArchive actual_withbasedir = ZipFile.Open(withBaseDir, ZipArchiveMode.Read)) + { + foreach (ZipArchiveEntry actualEntry in actual_withbasedir.Entries) + { + string expectedFile = expected.Single(i => Path.GetFileName(i).Equals(actualEntry.Name)); + Assert.True(actualEntry.FullName.StartsWith("normal")); + Assert.Equal(new FileInfo(expectedFile).Length, actualEntry.Length); + using (Stream expectedStream = File.OpenRead(expectedFile)) + using (Stream actualStream = actualEntry.Open()) + { + StreamsEqual(expectedStream, actualStream); + } + } + } + } + + [Fact] + public void CreateFromDirectoryUnicode() + { + string folderName = zfolder("unicode"); + string noBaseDir = GetTestFilePath(); + ZipFile.CreateFromDirectory(folderName, noBaseDir); + + using (ZipArchive archive = ZipFile.OpenRead(noBaseDir)) + { + IEnumerable actual = archive.Entries.Select(entry => entry.Name); + IEnumerable expected = Directory.EnumerateFileSystemEntries(zfolder("unicode"), "*", SearchOption.AllDirectories).ToList(); + Assert.True(Enumerable.SequenceEqual(expected.Select(i => Path.GetFileName(i)), actual.Select(i => i))); + } + } + + [Fact] + public void CreatedEmptyDirectoriesRoundtrip() + { + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + DirectoryInfo rootDir = new DirectoryInfo(tempFolder.Path); + rootDir.CreateSubdirectory("empty1"); + + string archivePath = GetTestFilePath(); + ZipFile.CreateFromDirectory( + rootDir.FullName, archivePath, + CompressionLevel.Optimal, false, Encoding.UTF8); + + using (ZipArchive archive = ZipFile.OpenRead(archivePath)) + { + Assert.Equal(1, archive.Entries.Count); + Assert.True(archive.Entries[0].FullName.StartsWith("empty1")); + } + } + } + + [Fact] + public void CreatedEmptyRootDirectoryRoundtrips() + { + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + DirectoryInfo emptyRoot = new DirectoryInfo(tempFolder.Path); + string archivePath = GetTestFilePath(); + ZipFile.CreateFromDirectory( + emptyRoot.FullName, archivePath, + CompressionLevel.Optimal, true); + + using (ZipArchive archive = ZipFile.OpenRead(archivePath)) + { + Assert.Equal(1, archive.Entries.Count); + } + } + } + [Fact] public void InvalidInstanceMethods() { @@ -209,62 +301,98 @@ public void DirectoryEntryWithData() Assert.Throws(() => ZipFile.ExtractToDirectory(archivePath, GetTestFilePath())); } - /// - /// This test ensures that a zipfile with path names that are invalid to this OS will throw errors - /// when an attempt is made to extract them. - /// - [ActiveIssue(25665)] - [Theory] - [InlineData("NullCharFileName_FromWindows")] - [InlineData("NullCharFileName_FromUnix")] - [PlatformSpecific(TestPlatforms.AnyUnix)] // Checks Unix-specific invalid file path - public void Unix_ZipWithInvalidFileNames_ThrowsArgumentException(string zipName) + [Fact] + public void ReadStreamOps() { - AssertExtensions.Throws("path", () => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); + using (ZipArchive archive = ZipFile.OpenRead(zfile("normal.zip"))) + { + foreach (ZipArchiveEntry e in archive.Entries) + { + using (Stream s = e.Open()) + { + Assert.True(s.CanRead, "Can read to read archive"); + Assert.False(s.CanWrite, "Can't write to read archive"); + Assert.False(s.CanSeek, "Can't seek on archive"); + Assert.Equal(LengthOfUnseekableStream(s), e.Length); + } + } + } } - [Theory] - [InlineData("backslashes_FromUnix", "aa\\bb\\cc\\dd")] - [InlineData("backslashes_FromWindows", "aa\\bb\\cc\\dd")] - [InlineData("WindowsInvalid_FromUnix", "aad")] - [InlineData("WindowsInvalid_FromWindows", "aad")] - [PlatformSpecific(TestPlatforms.AnyUnix)] // Checks Unix-specific invalid file path - public void Unix_ZipWithOSSpecificFileNames(string zipName, string fileName) + [Fact] + public void UpdateReadTwice() { - string tempDir = GetTestFilePath(); - ZipFile.ExtractToDirectory(compat(zipName) + ".zip", tempDir); - string[] results = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories); - Assert.Equal(1, results.Length); - Assert.Equal(fileName, Path.GetFileName(results[0])); + using (TempFile testArchive = CreateTempCopyFile(zfile("small.zip"), GetTestFilePath())) + using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) + { + ZipArchiveEntry entry = archive.Entries[0]; + string contents1, contents2; + using (StreamReader s = new StreamReader(entry.Open())) + { + contents1 = s.ReadToEnd(); + } + using (StreamReader s = new StreamReader(entry.Open())) + { + contents2 = s.ReadToEnd(); + } + Assert.Equal(contents1, contents2); + } } - /// - /// This test ensures that a zipfile with path names that are invalid to this OS will throw errors - /// when an attempt is made to extract them. - /// - [Theory] - [ActiveIssue(27269)] - [InlineData("WindowsInvalid_FromUnix", null)] - [InlineData("WindowsInvalid_FromWindows", null)] - [InlineData("NullCharFileName_FromWindows", "path")] - [InlineData("NullCharFileName_FromUnix", "path")] - [PlatformSpecific(TestPlatforms.Windows)] // Checks Windows-specific invalid file path - public void Windows_ZipWithInvalidFileNames_ThrowsArgumentException(string zipName, string paramName) + [Fact] + public async Task UpdateAddFile() { - AssertExtensions.Throws(paramName, null, () => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); + //add file + using (TempFile testArchive = CreateTempCopyFile(zfile("normal.zip"), GetTestFilePath())) + { + using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) + { + await UpdateArchive(archive, zmodified(Path.Combine("addFile", "added.txt")), "added.txt"); + } + await IsZipSameAsDirAsync(testArchive.Path, zmodified("addFile"), ZipArchiveMode.Read); + } + + //add file and read entries before + using (TempFile testArchive = CreateTempCopyFile(zfile("normal.zip"), GetTestFilePath())) + { + using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) + { + var x = archive.Entries; + + await UpdateArchive(archive, zmodified(Path.Combine("addFile", "added.txt")), "added.txt"); + } + await IsZipSameAsDirAsync(testArchive.Path, zmodified("addFile"), ZipArchiveMode.Read); + } + + //add file and read entries after + using (TempFile testArchive = CreateTempCopyFile(zfile("normal.zip"), GetTestFilePath())) + { + using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) + { + await UpdateArchive(archive, zmodified(Path.Combine("addFile", "added.txt")), "added.txt"); + + var x = archive.Entries; + } + await IsZipSameAsDirAsync(testArchive.Path, zmodified("addFile"), ZipArchiveMode.Read); + } } - [Theory] - [InlineData("backslashes_FromUnix", "dd")] - [InlineData("backslashes_FromWindows", "dd")] - [PlatformSpecific(TestPlatforms.Windows)] // Checks Windows-specific invalid file path - public void Windows_ZipWithOSSpecificFileNames(string zipName, string fileName) + private static async Task UpdateArchive(ZipArchive archive, string installFile, string entryName) { - string tempDir = GetTestFilePath(); - ZipFile.ExtractToDirectory(compat(zipName) + ".zip", tempDir); - string[] results = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories); - Assert.Equal(1, results.Length); - Assert.Equal(fileName, Path.GetFileName(results[0])); + string fileName = installFile; + ZipArchiveEntry e = archive.CreateEntry(entryName); + + var file = FileData.GetFile(fileName); + e.LastWriteTime = file.LastModifiedDate; + + using (var stream = await StreamHelpers.CreateTempCopyStream(fileName)) + { + using (Stream es = e.Open()) + { + es.SetLength(0); + stream.CopyTo(es); + } + } } } } diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs b/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs new file mode 100644 index 000000000000..f132999c0160 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs @@ -0,0 +1,164 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using Xunit; + +namespace System.IO.Compression.Tests +{ + public class ZipFile_Extract : ZipFileTestBase + { + [Theory] + [InlineData("normal.zip", "normal")] + [InlineData("empty.zip", "empty")] + [InlineData("explicitdir1.zip", "explicitdir")] + [InlineData("explicitdir2.zip", "explicitdir")] + [InlineData("appended.zip", "small")] + [InlineData("prepended.zip", "small")] + [InlineData("noexplicitdir.zip", "explicitdir")] + public void ExtractToDirectoryNormal(string file, string folder) + { + string zipFileName = zfile(file); + string folderName = zfolder(folder); + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path); + DirsEqual(tempFolder.Path, folderName); + } + } + + [Fact] + public void ExtractToDirectoryNull() + { + AssertExtensions.Throws("sourceArchiveFileName", () => ZipFile.ExtractToDirectory(null, GetTestFilePath())); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMacOsHighSierraOrHigher))] + public void ExtractToDirectoryUnicode() + { + string zipFileName = zfile("unicode.zip"); + string folderName = zfolder("unicode"); + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path); + DirFileNamesEqual(tempFolder.Path, folderName); + } + } + + + /// + /// This test ensures that a zipfile with path names that are invalid to this OS will throw errors + /// when an attempt is made to extract them. + /// + [ActiveIssue(25665)] + [Theory] + [InlineData("NullCharFileName_FromWindows")] + [InlineData("NullCharFileName_FromUnix")] + [PlatformSpecific(TestPlatforms.AnyUnix)] // Checks Unix-specific invalid file path + public void Unix_ZipWithInvalidFileNames_ThrowsArgumentException(string zipName) + { + AssertExtensions.Throws("path", () => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); + } + + [Theory] + [InlineData("backslashes_FromUnix", "aa\\bb\\cc\\dd")] + [InlineData("backslashes_FromWindows", "aa\\bb\\cc\\dd")] + [InlineData("WindowsInvalid_FromUnix", "aad")] + [InlineData("WindowsInvalid_FromWindows", "aad")] + [PlatformSpecific(TestPlatforms.AnyUnix)] // Checks Unix-specific invalid file path + public void Unix_ZipWithOSSpecificFileNames(string zipName, string fileName) + { + string tempDir = GetTestFilePath(); + ZipFile.ExtractToDirectory(compat(zipName) + ".zip", tempDir); + string[] results = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories); + Assert.Equal(1, results.Length); + Assert.Equal(fileName, Path.GetFileName(results[0])); + } + + /// + /// This test ensures that a zipfile with path names that are invalid to this OS will throw errors + /// when an attempt is made to extract them. + /// + [Theory] + [ActiveIssue(27269)] + [InlineData("WindowsInvalid_FromUnix", null)] + [InlineData("WindowsInvalid_FromWindows", null)] + [InlineData("NullCharFileName_FromWindows", "path")] + [InlineData("NullCharFileName_FromUnix", "path")] + [PlatformSpecific(TestPlatforms.Windows)] // Checks Windows-specific invalid file path + public void Windows_ZipWithInvalidFileNames_ThrowsArgumentException(string zipName, string paramName) + { + AssertExtensions.Throws(paramName, null, () => ZipFile.ExtractToDirectory(compat(zipName) + ".zip", GetTestFilePath())); + } + + [Theory] + [InlineData("backslashes_FromUnix", "dd")] + [InlineData("backslashes_FromWindows", "dd")] + [PlatformSpecific(TestPlatforms.Windows)] // Checks Windows-specific invalid file path + public void Windows_ZipWithOSSpecificFileNames(string zipName, string fileName) + { + string tempDir = GetTestFilePath(); + ZipFile.ExtractToDirectory(compat(zipName) + ".zip", tempDir); + string[] results = Directory.GetFiles(tempDir, "*", SearchOption.AllDirectories); + Assert.Equal(1, results.Length); + Assert.Equal(fileName, Path.GetFileName(results[0])); + } + +#if netcoreapp + [Fact] + public void ExtractToDirectoryOverwrite() + { + string zipFileName = zfile("normal.zip"); + string folderName = zfolder("normal"); + + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: false); + Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path /* default false */)); + Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: false)); + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: true); + + DirsEqual(tempFolder.Path, folderName); + } + } + + [Fact] + public void ExtractToDirectoryOverwriteEncoding() + { + string zipFileName = zfile("normal.zip"); + string folderName = zfolder("normal"); + + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: false); + Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8 /* default false */)); + Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: false)); + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: true); + + DirsEqual(tempFolder.Path, folderName); + } + } + + [Fact] + public void ExtractToDirectoryZipArchiveOverwrite() + { + string zipFileName = zfile("normal.zip"); + string folderName = zfolder("normal"); + + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + using (ZipArchive archive = ZipFile.Open(zipFileName, ZipArchiveMode.Read)) + { + archive.ExtractToDirectory(tempFolder.Path); + Assert.Throws(() => archive.ExtractToDirectory(tempFolder.Path /* default false */)); + Assert.Throws(() => archive.ExtractToDirectory(tempFolder.Path, overwriteFiles: false)); + archive.ExtractToDirectory(tempFolder.Path, overwriteFiles: true); + + DirsEqual(tempFolder.Path, folderName); + } + } + } +#endif + } +} diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFile.ExtractToDirectoryFiltered.cs b/src/System.IO.Compression.ZipFile/tests/ZipFile.ExtractToDirectoryFiltered.cs new file mode 100644 index 000000000000..54e73a96bae1 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/tests/ZipFile.ExtractToDirectoryFiltered.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Xunit; +using Xunit.NetCore.Extensions; +using System.Linq; +namespace System.IO.Compression.Tests +{ + public class ZipFile_ExtractToDirectoryFiltered : ZipFileTestBase + { + [Fact] + public static void ExtractFilteredZip() + { + using (TempDirectory destinationDirectory = new TempDirectory()) + { + ZipFile.ExtractToDirectory("Filter.zip", destinationDirectory.Path, (path) => path.EndsWith(".txt")); + Assert.True(File.Exists(Path.Combine(destinationDirectory.Path, ".txt"))); + Assert.True(File.Exists(Path.Combine(destinationDirectory.Path, ".pkg.txt"))); + Assert.True(File.Exists(Path.Combine(destinationDirectory.Path, ".txt.txt"))); + Assert.True(Directory.EnumerateFileSystemEntries(destinationDirectory.Path).Count() == 3); + } + } + + } +} + diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFileConvenienceMethods.cs b/src/System.IO.Compression.ZipFile/tests/ZipFileConvenienceMethods.cs deleted file mode 100644 index 69c822e3fc7e..000000000000 --- a/src/System.IO.Compression.ZipFile/tests/ZipFileConvenienceMethods.cs +++ /dev/null @@ -1,228 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Xunit; -using Xunit.NetCore.Extensions; - -namespace System.IO.Compression.Tests -{ - public partial class ZipFileTest_ConvenienceMethods : ZipFileTestBase - { - [Fact] - public async Task CreateFromDirectoryNormal() - { - string folderName = zfolder("normal"); - string noBaseDir = GetTestFilePath(); - ZipFile.CreateFromDirectory(folderName, noBaseDir); - - await IsZipSameAsDirAsync(noBaseDir, folderName, ZipArchiveMode.Read, requireExplicit: false, checkTimes: false); - } - - [Fact] - public void CreateFromDirectory_IncludeBaseDirectory() - { - string folderName = zfolder("normal"); - string withBaseDir = GetTestFilePath(); - ZipFile.CreateFromDirectory(folderName, withBaseDir, CompressionLevel.Optimal, true); - - IEnumerable expected = Directory.EnumerateFiles(zfolder("normal"), "*", SearchOption.AllDirectories); - using (ZipArchive actual_withbasedir = ZipFile.Open(withBaseDir, ZipArchiveMode.Read)) - { - foreach (ZipArchiveEntry actualEntry in actual_withbasedir.Entries) - { - string expectedFile = expected.Single(i => Path.GetFileName(i).Equals(actualEntry.Name)); - Assert.True(actualEntry.FullName.StartsWith("normal")); - Assert.Equal(new FileInfo(expectedFile).Length, actualEntry.Length); - using (Stream expectedStream = File.OpenRead(expectedFile)) - using (Stream actualStream = actualEntry.Open()) - { - StreamsEqual(expectedStream, actualStream); - } - } - } - } - - [Fact] - public void CreateFromDirectoryUnicode() - { - string folderName = zfolder("unicode"); - string noBaseDir = GetTestFilePath(); - ZipFile.CreateFromDirectory(folderName, noBaseDir); - - using (ZipArchive archive = ZipFile.OpenRead(noBaseDir)) - { - IEnumerable actual = archive.Entries.Select(entry => entry.Name); - IEnumerable expected = Directory.EnumerateFileSystemEntries(zfolder("unicode"), "*", SearchOption.AllDirectories).ToList(); - Assert.True(Enumerable.SequenceEqual(expected.Select(i => Path.GetFileName(i)), actual.Select(i => i))); - } - } - - [Theory] - [InlineData("normal.zip", "normal")] - [InlineData("empty.zip", "empty")] - [InlineData("explicitdir1.zip", "explicitdir")] - [InlineData("explicitdir2.zip", "explicitdir")] - [InlineData("appended.zip", "small")] - [InlineData("prepended.zip", "small")] - [InlineData("noexplicitdir.zip", "explicitdir")] - public void ExtractToDirectoryNormal(string file, string folder) - { - string zipFileName = zfile(file); - string folderName = zfolder(folder); - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path); - DirsEqual(tempFolder.Path, folderName); - } - } - - [Fact] - public void ExtractToDirectoryNull() - { - AssertExtensions.Throws("sourceArchiveFileName", () => ZipFile.ExtractToDirectory(null, GetTestFilePath())); - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMacOsHighSierraOrHigher))] - public void ExtractToDirectoryUnicode() - { - string zipFileName = zfile("unicode.zip"); - string folderName = zfolder("unicode"); - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path); - DirFileNamesEqual(tempFolder.Path, folderName); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task CreateEntryFromFileExtension(bool withCompressionLevel) - { - //add file - using (TempFile testArchive = CreateTempCopyFile(zfile("normal.zip"), GetTestFilePath())) - { - using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) - { - string entryName = "added.txt"; - string sourceFilePath = zmodified(Path.Combine("addFile", entryName)); - - Assert.Throws(() => ((ZipArchive)null).CreateEntryFromFile(sourceFilePath, entryName)); - Assert.Throws(() => archive.CreateEntryFromFile(null, entryName)); - Assert.Throws(() => archive.CreateEntryFromFile(sourceFilePath, null)); - - ZipArchiveEntry e = withCompressionLevel ? - archive.CreateEntryFromFile(sourceFilePath, entryName) : - archive.CreateEntryFromFile(sourceFilePath, entryName, CompressionLevel.Fastest); - Assert.NotNull(e); - } - await IsZipSameAsDirAsync(testArchive.Path, zmodified("addFile"), ZipArchiveMode.Read, requireExplicit: false, checkTimes: false); - } - } - - [Fact] - public void ExtractToFileExtension() - { - using (ZipArchive archive = ZipFile.Open(zfile("normal.zip"), ZipArchiveMode.Read)) - { - string file = GetTestFilePath(); - ZipArchiveEntry e = archive.GetEntry("first.txt"); - - Assert.Throws(() => ((ZipArchiveEntry)null).ExtractToFile(file)); - Assert.Throws(() => e.ExtractToFile(null)); - - //extract when there is nothing there - e.ExtractToFile(file); - - using (Stream fs = File.Open(file, FileMode.Open), es = e.Open()) - { - StreamsEqual(fs, es); - } - - Assert.Throws(() => e.ExtractToFile(file, false)); - - //truncate file - using (Stream fs = File.Open(file, FileMode.Truncate)) { } - - //now use overwrite mode - e.ExtractToFile(file, true); - - using (Stream fs = File.Open(file, FileMode.Open), es = e.Open()) - { - StreamsEqual(fs, es); - } - } - } - - [Fact] - public void ExtractToDirectoryExtension() - { - using (ZipArchive archive = ZipFile.Open(zfile("normal.zip"), ZipArchiveMode.Read)) - { - string tempFolder = GetTestFilePath(); - Assert.Throws(() => ((ZipArchive)null).ExtractToDirectory(tempFolder)); - Assert.Throws(() => archive.ExtractToDirectory(null)); - archive.ExtractToDirectory(tempFolder); - - DirsEqual(tempFolder, zfolder("normal")); - } - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMacOsHighSierraOrHigher))] - public void ExtractToDirectoryExtension_Unicode() - { - using (ZipArchive archive = ZipFile.OpenRead(zfile("unicode.zip"))) - { - string tempFolder = GetTestFilePath(); - archive.ExtractToDirectory(tempFolder); - DirFileNamesEqual(tempFolder, zfolder("unicode")); - } - } - - [Fact] - public void CreatedEmptyDirectoriesRoundtrip() - { - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - DirectoryInfo rootDir = new DirectoryInfo(tempFolder.Path); - rootDir.CreateSubdirectory("empty1"); - - string archivePath = GetTestFilePath(); - ZipFile.CreateFromDirectory( - rootDir.FullName, archivePath, - CompressionLevel.Optimal, false, Encoding.UTF8); - - using (ZipArchive archive = ZipFile.OpenRead(archivePath)) - { - Assert.Equal(1, archive.Entries.Count); - Assert.True(archive.Entries[0].FullName.StartsWith("empty1")); - } - } - } - - [Fact] - public void CreatedEmptyRootDirectoryRoundtrips() - { - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - DirectoryInfo emptyRoot = new DirectoryInfo(tempFolder.Path); - string archivePath = GetTestFilePath(); - ZipFile.CreateFromDirectory( - emptyRoot.FullName, archivePath, - CompressionLevel.Optimal, true); - - using (ZipArchive archive = ZipFile.OpenRead(archivePath)) - { - Assert.Equal(1, archive.Entries.Count); - } - } - } - } -} diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFileConvenienceMethods.netcoreapp1.1.cs b/src/System.IO.Compression.ZipFile/tests/ZipFileConvenienceMethods.netcoreapp1.1.cs deleted file mode 100644 index a66501505f48..000000000000 --- a/src/System.IO.Compression.ZipFile/tests/ZipFileConvenienceMethods.netcoreapp1.1.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Xunit; -using Xunit.NetCore.Extensions; - -namespace System.IO.Compression.Tests -{ - public partial class ZipFileTest_ConvenienceMethods : ZipFileTestBase - { - [Fact] - public void ExtractToDirectoryOverwrite() - { - string zipFileName = zfile("normal.zip"); - string folderName = zfolder("normal"); - - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: false); - Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path /* default false */)); - Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: false)); - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: true); - - DirsEqual(tempFolder.Path, folderName); - } - } - - [Fact] - public void ExtractToDirectoryOverwriteEncoding() - { - string zipFileName = zfile("normal.zip"); - string folderName = zfolder("normal"); - - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: false); - Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8 /* default false */)); - Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: false)); - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: true); - - DirsEqual(tempFolder.Path, folderName); - } - } - - [Fact] - public void ExtractToDirectoryZipArchiveOverwrite() - { - string zipFileName = zfile("normal.zip"); - string folderName = zfolder("normal"); - - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - using (ZipArchive archive = ZipFile.Open(zipFileName, ZipArchiveMode.Read)) - { - archive.ExtractToDirectory(tempFolder.Path); - Assert.Throws(() => archive.ExtractToDirectory(tempFolder.Path /* default false */)); - Assert.Throws(() => archive.ExtractToDirectory(tempFolder.Path, overwriteFiles: false)); - archive.ExtractToDirectory(tempFolder.Path, overwriteFiles: true); - - DirsEqual(tempFolder.Path, folderName); - } - } - } - } -} diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchive.Create.cs b/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchive.Create.cs new file mode 100644 index 000000000000..faec50d68d16 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchive.Create.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading.Tasks; +using Xunit; + +namespace System.IO.Compression.Tests +{ + public class ZipFile_ZipArchive_Create : ZipFileTestBase + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task CreateEntryFromFileExtension(bool withCompressionLevel) + { + //add file + using (TempFile testArchive = CreateTempCopyFile(zfile("normal.zip"), GetTestFilePath())) + { + using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) + { + string entryName = "added.txt"; + string sourceFilePath = zmodified(Path.Combine("addFile", entryName)); + + Assert.Throws(() => ((ZipArchive)null).CreateEntryFromFile(sourceFilePath, entryName)); + Assert.Throws(() => archive.CreateEntryFromFile(null, entryName)); + Assert.Throws(() => archive.CreateEntryFromFile(sourceFilePath, null)); + + ZipArchiveEntry e = withCompressionLevel ? + archive.CreateEntryFromFile(sourceFilePath, entryName) : + archive.CreateEntryFromFile(sourceFilePath, entryName, CompressionLevel.Fastest); + Assert.NotNull(e); + } + await IsZipSameAsDirAsync(testArchive.Path, zmodified("addFile"), ZipArchiveMode.Read, requireExplicit: false, checkTimes: false); + } + } + } +} diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchive.Extract.cs b/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchive.Extract.cs new file mode 100644 index 000000000000..9473ba7c1021 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchive.Extract.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.IO.Compression.Tests +{ + public class ZipFile_ZipArchive_Extract : ZipFileTestBase + { + [Fact] + public void ExtractToDirectoryExtension() + { + using (ZipArchive archive = ZipFile.Open(zfile("normal.zip"), ZipArchiveMode.Read)) + { + string tempFolder = GetTestFilePath(); + Assert.Throws(() => ((ZipArchive)null).ExtractToDirectory(tempFolder)); + Assert.Throws(() => archive.ExtractToDirectory(null)); + archive.ExtractToDirectory(tempFolder); + + DirsEqual(tempFolder, zfolder("normal")); + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMacOsHighSierraOrHigher))] + public void ExtractToDirectoryExtension_Unicode() + { + using (ZipArchive archive = ZipFile.OpenRead(zfile("unicode.zip"))) + { + string tempFolder = GetTestFilePath(); + archive.ExtractToDirectory(tempFolder); + DirFileNamesEqual(tempFolder, zfolder("unicode")); + } + } + + } +} diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchiveEntry.Extract.cs new file mode 100644 index 000000000000..06cd872d7657 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/tests/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.IO.Compression.Tests +{ + public class ZipFile_ZipArchiveEntry_Extract : ZipFileTestBase + { + [Fact] + public void ExtractToFileExtension() + { + using (ZipArchive archive = ZipFile.Open(zfile("normal.zip"), ZipArchiveMode.Read)) + { + string file = GetTestFilePath(); + ZipArchiveEntry e = archive.GetEntry("first.txt"); + + Assert.Throws(() => ((ZipArchiveEntry)null).ExtractToFile(file)); + Assert.Throws(() => e.ExtractToFile(null)); + + //extract when there is nothing there + e.ExtractToFile(file); + + using (Stream fs = File.Open(file, FileMode.Open), es = e.Open()) + { + StreamsEqual(fs, es); + } + + Assert.Throws(() => e.ExtractToFile(file, false)); + + //truncate file + using (Stream fs = File.Open(file, FileMode.Truncate)) + { } + + //now use overwrite mode + e.ExtractToFile(file, true); + + using (Stream fs = File.Open(file, FileMode.Open), es = e.Open()) + { + StreamsEqual(fs, es); + } + } + } + } +} diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFileReadOpenUpdateTests.cs b/src/System.IO.Compression.ZipFile/tests/ZipFileReadOpenUpdateTests.cs deleted file mode 100644 index 86369ac5af16..000000000000 --- a/src/System.IO.Compression.ZipFile/tests/ZipFileReadOpenUpdateTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Threading.Tasks; -using Xunit; - -namespace System.IO.Compression.Tests -{ - public partial class ZipFileTest_ReadOpenUpdate : ZipFileTestBase - { - [Fact] - public void ReadStreamOps() - { - using (ZipArchive archive = ZipFile.OpenRead(zfile("normal.zip"))) - { - foreach (ZipArchiveEntry e in archive.Entries) - { - using (Stream s = e.Open()) - { - Assert.True(s.CanRead, "Can read to read archive"); - Assert.False(s.CanWrite, "Can't write to read archive"); - Assert.False(s.CanSeek, "Can't seek on archive"); - Assert.Equal(LengthOfUnseekableStream(s), e.Length); - } - } - } - } - - [Fact] - public void UpdateReadTwice() - { - using (TempFile testArchive = CreateTempCopyFile(zfile("small.zip"), GetTestFilePath())) - using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) - { - ZipArchiveEntry entry = archive.Entries[0]; - string contents1, contents2; - using (StreamReader s = new StreamReader(entry.Open())) - { - contents1 = s.ReadToEnd(); - } - using (StreamReader s = new StreamReader(entry.Open())) - { - contents2 = s.ReadToEnd(); - } - Assert.Equal(contents1, contents2); - } - } - - [Fact] - public async Task UpdateAddFile() - { - //add file - using (TempFile testArchive = CreateTempCopyFile(zfile("normal.zip"), GetTestFilePath())) - { - using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) - { - await UpdateArchive(archive, zmodified(Path.Combine("addFile", "added.txt")), "added.txt"); - } - await IsZipSameAsDirAsync(testArchive.Path, zmodified("addFile"), ZipArchiveMode.Read); - } - - //add file and read entries before - using (TempFile testArchive = CreateTempCopyFile(zfile("normal.zip"), GetTestFilePath())) - { - using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) - { - var x = archive.Entries; - - await UpdateArchive(archive, zmodified(Path.Combine("addFile", "added.txt")), "added.txt"); - } - await IsZipSameAsDirAsync(testArchive.Path, zmodified("addFile"), ZipArchiveMode.Read); - } - - //add file and read entries after - using (TempFile testArchive = CreateTempCopyFile(zfile("normal.zip"), GetTestFilePath())) - { - using (ZipArchive archive = ZipFile.Open(testArchive.Path, ZipArchiveMode.Update)) - { - await UpdateArchive(archive, zmodified(Path.Combine("addFile", "added.txt")), "added.txt"); - - var x = archive.Entries; - } - await IsZipSameAsDirAsync(testArchive.Path, zmodified("addFile"), ZipArchiveMode.Read); - } - } - - private static async Task UpdateArchive(ZipArchive archive, string installFile, string entryName) - { - string fileName = installFile; - ZipArchiveEntry e = archive.CreateEntry(entryName); - - var file = FileData.GetFile(fileName); - e.LastWriteTime = file.LastModifiedDate; - - using (var stream = await StreamHelpers.CreateTempCopyStream(fileName)) - { - using (Stream es = e.Open()) - { - es.SetLength(0); - stream.CopyTo(es); - } - } - } - } -} - From 647f59a14bba448430e37e282e4da0a334aa8a70 Mon Sep 17 00:00:00 2001 From: Ian Hays Date: Wed, 13 Jun 2018 14:41:13 -0700 Subject: [PATCH 2/2] Fix some whitespace in ZipFile classes --- .../System/IO/Compression/ZipFile.Create.cs | 2 +- .../System/IO/Compression/ZipFile.Extract.cs | 9 ++- .../ZipFileExtensions.ZipArchive.Create.cs | 4 +- .../ZipFileExtensions.ZipArchive.Extract.cs | 3 +- ...pFileExtensions.ZipArchiveEntry.Extract.cs | 6 +- ...System.IO.Compression.ZipFile.Tests.csproj | 3 + .../tests/ZipFile.Extract.cs | 58 +--------------- .../tests/ZipFile.Extract.netcoreapp.cs | 66 +++++++++++++++++++ 8 files changed, 85 insertions(+), 66 deletions(-) create mode 100644 src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.netcoreapp.cs diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs index 7cf8ee61c9e2..c435b95ee2c5 100644 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Create.cs @@ -355,7 +355,7 @@ public static void CreateFromDirectory(string sourceDirectoryName, string destin DoCreateFromDirectory(sourceDirectoryName, destinationArchiveFileName, compressionLevel, includeBaseDirectory, entryNameEncoding: null); private static void DoCreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, - CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding entryNameEncoding) + CompressionLevel? compressionLevel, bool includeBaseDirectory, Encoding entryNameEncoding) { // Rely on Path.GetFullPath for validation of sourceDirectoryName and destinationArchive diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs index dab9affe758a..94025d4243b6 100644 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFile.Extract.cs @@ -41,7 +41,8 @@ public static partial class ZipFile /// /// The path to the archive on the file system that is to be extracted. /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName) => ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: false); + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName) => + ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: false); /// /// Extracts all of the files in the specified archive to a directory on the file system. @@ -74,7 +75,8 @@ public static partial class ZipFile /// The path to the archive on the file system that is to be extracted. /// The path to the directory on the file system. The directory specified must not exist, but the directory that it is contained in must exist. /// True to indicate overwrite. - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwrite) => ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: overwrite); + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, bool overwrite) => + ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: null, overwrite: overwrite); /// /// Extracts all of the files in the specified archive to a directory on the file system. @@ -128,7 +130,8 @@ public static partial class ZipFile /// Note that Unicode encodings other than UTF-8 may not be currently used for the entryNameEncoding, /// otherwise an is thrown. /// - public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding) => ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: entryNameEncoding, overwrite: false); + public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName, Encoding entryNameEncoding) => + ExtractToDirectory(sourceArchiveFileName, destinationDirectoryName, entryNameEncoding: entryNameEncoding, overwrite: false); /// /// Extracts all of the files in the specified archive to a directory on the file system. diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs index 3b340746224c..6377680b19e3 100644 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Create.cs @@ -77,9 +77,9 @@ public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, DoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel); internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destination, - string sourceFileName, string entryName, CompressionLevel? compressionLevel) + string sourceFileName, string entryName, CompressionLevel? compressionLevel) { - if (destination == null) + if (destination == null) throw new ArgumentNullException(nameof(destination)); if (sourceFileName == null) diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs index 2a67e93ca214..c7d16a265d7d 100644 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchive.Extract.cs @@ -35,7 +35,8 @@ public static partial class ZipFileExtensions /// The path to the directory on the file system. /// The directory specified must not exist. The path is permitted to specify relative or absolute path information. /// Relative path information is interpreted as relative to the current working directory. - public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName) => ExtractToDirectory(source, destinationDirectoryName, overwrite: false); + public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName) => + ExtractToDirectory(source, destinationDirectoryName, overwrite: false); /// /// Extracts all of the files in the archive to a directory on the file system. The specified directory may already exist. diff --git a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index ecc3ab652ee6..577d6d0ef377 100644 --- a/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -34,7 +34,8 @@ public static partial class ZipFileExtensions /// 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. - public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName) => ExtractToFile(source, destinationFileName, false); + public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName) => + ExtractToFile(source, destinationFileName, false); /// /// Creates a file on the file system with the entry?s contents and the specified name. @@ -83,7 +84,8 @@ public static void ExtractToFile(this ZipArchiveEntry source, string destination File.SetLastWriteTime(destinationFileName, source.LastWriteTime.DateTime); } - internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName) => ExtractRelativeToDirectory(source, destinationDirectoryName, overwrite: false); + 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) { diff --git a/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj b/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj index 3da8e1e850a7..53825b2e32e6 100644 --- a/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj +++ b/src/System.IO.Compression.ZipFile/tests/System.IO.Compression.ZipFile.Tests.csproj @@ -40,6 +40,9 @@ Common\System\IO\Compression\ZipTestHelper.cs + + + %(RecursiveDir)%(Filename)%(Extension) diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs b/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs index f132999c0160..b31303ab4b61 100644 --- a/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs +++ b/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.cs @@ -7,7 +7,7 @@ namespace System.IO.Compression.Tests { - public class ZipFile_Extract : ZipFileTestBase + public partial class ZipFile_Extract : ZipFileTestBase { [Theory] [InlineData("normal.zip", "normal")] @@ -104,61 +104,5 @@ public void Windows_ZipWithOSSpecificFileNames(string zipName, string fileName) Assert.Equal(1, results.Length); Assert.Equal(fileName, Path.GetFileName(results[0])); } - -#if netcoreapp - [Fact] - public void ExtractToDirectoryOverwrite() - { - string zipFileName = zfile("normal.zip"); - string folderName = zfolder("normal"); - - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: false); - Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path /* default false */)); - Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: false)); - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: true); - - DirsEqual(tempFolder.Path, folderName); - } - } - - [Fact] - public void ExtractToDirectoryOverwriteEncoding() - { - string zipFileName = zfile("normal.zip"); - string folderName = zfolder("normal"); - - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: false); - Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8 /* default false */)); - Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: false)); - ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: true); - - DirsEqual(tempFolder.Path, folderName); - } - } - - [Fact] - public void ExtractToDirectoryZipArchiveOverwrite() - { - string zipFileName = zfile("normal.zip"); - string folderName = zfolder("normal"); - - using (var tempFolder = new TempDirectory(GetTestFilePath())) - { - using (ZipArchive archive = ZipFile.Open(zipFileName, ZipArchiveMode.Read)) - { - archive.ExtractToDirectory(tempFolder.Path); - Assert.Throws(() => archive.ExtractToDirectory(tempFolder.Path /* default false */)); - Assert.Throws(() => archive.ExtractToDirectory(tempFolder.Path, overwriteFiles: false)); - archive.ExtractToDirectory(tempFolder.Path, overwriteFiles: true); - - DirsEqual(tempFolder.Path, folderName); - } - } - } -#endif } } diff --git a/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.netcoreapp.cs b/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.netcoreapp.cs new file mode 100644 index 000000000000..e641acb824f7 --- /dev/null +++ b/src/System.IO.Compression.ZipFile/tests/ZipFile.Extract.netcoreapp.cs @@ -0,0 +1,66 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using Xunit; + +namespace System.IO.Compression.Tests +{ + public partial class ZipFile_Extract : ZipFileTestBase + { + [Fact] + public void ExtractToDirectoryOverwrite() + { + string zipFileName = zfile("normal.zip"); + string folderName = zfolder("normal"); + + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: false); + Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path /* default false */)); + Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: false)); + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, overwriteFiles: true); + + DirsEqual(tempFolder.Path, folderName); + } + } + + [Fact] + public void ExtractToDirectoryOverwriteEncoding() + { + string zipFileName = zfile("normal.zip"); + string folderName = zfolder("normal"); + + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: false); + Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8 /* default false */)); + Assert.Throws(() => ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: false)); + ZipFile.ExtractToDirectory(zipFileName, tempFolder.Path, Encoding.UTF8, overwriteFiles: true); + + DirsEqual(tempFolder.Path, folderName); + } + } + + [Fact] + public void ExtractToDirectoryZipArchiveOverwrite() + { + string zipFileName = zfile("normal.zip"); + string folderName = zfolder("normal"); + + using (var tempFolder = new TempDirectory(GetTestFilePath())) + { + using (ZipArchive archive = ZipFile.Open(zipFileName, ZipArchiveMode.Read)) + { + archive.ExtractToDirectory(tempFolder.Path); + Assert.Throws(() => archive.ExtractToDirectory(tempFolder.Path /* default false */)); + Assert.Throws(() => archive.ExtractToDirectory(tempFolder.Path, overwriteFiles: false)); + archive.ExtractToDirectory(tempFolder.Path, overwriteFiles: true); + + DirsEqual(tempFolder.Path, folderName); + } + } + } + } +}