Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'uap-Windows_NT-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'uap-Windows_NT-Release|AnyCPU'" />
<ItemGroup>
<Compile Include="System\IO\Compression\ZipFile.cs" />
<Compile Include="System\IO\Compression\ZipFileExtensions.cs" />
<Compile Include="System\IO\Compression\ZipFile.Create.cs" />
<Compile Include="System\IO\Compression\ZipFile.Extract.cs" />
<Compile Include="System\IO\Compression\ZipFile.Utils.cs" />
<Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchive.Create.cs" />
<Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchive.Extract.cs" />
<Compile Include="System\IO\Compression\ZipFileExtensions.ZipArchiveEntry.Extract.cs" />
<Compile Include="$(CommonPath)\System\IO\PathInternal.CaseSensitivity.cs">
<Link>Common\System\IO\PathInternal.CaseSensitivity.cs</Link>
</Compile>
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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<char>.Shared.Return(buffer);
buffer = ArrayPool<char>.Shared.Rent(newCapacity);
}
}

public static bool IsDirEmpty(DirectoryInfo possiblyEmptyDir)
{
using (IEnumerator<string> enumerator = Directory.EnumerateFileSystemEntries(possiblyEmptyDir.FullName).GetEnumerator())
return !enumerator.MoveNext();
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// <p>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.</p>
///
/// <p>If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.</p>
///
/// <p>Since no <code>CompressionLevel</code> is specified, the default provided by the implementation of the underlying compression
/// algorithm will be used; the <code>ZipArchive</code> will not impose its own default.
/// (Currently, the underlying compression algorithm is provided by the <code>System.IO.Compression.DeflateStream</code> class.)</p>
/// </summary>
///
/// <exception cref="ArgumentException">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.</exception>
/// <exception cref="ArgumentNullException">sourceFileName or entryName is null.</exception>
/// <exception cref="PathTooLongException">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.</exception>
/// <exception cref="DirectoryNotFoundException">The specified sourceFileName is invalid, (for example, it is on an unmapped drive).</exception>
/// <exception cref="IOException">An I/O error occurred while opening the file specified by sourceFileName.</exception>
/// <exception cref="UnauthorizedAccessException">sourceFileName specified a directory. -or- The caller does not have the
/// required permission.</exception>
/// <exception cref="FileNotFoundException">The file specified in sourceFileName was not found. </exception>
/// <exception cref="NotSupportedException">sourceFileName is in an invalid format or the ZipArchive does not support writing.</exception>
/// <exception cref="ObjectDisposedException">The ZipArchive has already been closed.</exception>
///
/// <param name="sourceFileName">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.</param>
/// <param name="entryName">The name of the entry to be created.</param>
/// <returns>A wrapper for the newly created entry.</returns>
public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination, string sourceFileName, string entryName) =>
DoCreateEntryFromFile(destination, sourceFileName, entryName, null);


/// <summary>
/// <p>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.</p>
/// <p>If an entry with the specified name already exists in the archive, a second entry will be created that has an identical name.</p>
/// </summary>
/// <exception cref="ArgumentException">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.</exception>
/// <exception cref="ArgumentNullException">sourceFileName or entryName is null.</exception>
/// <exception cref="PathTooLongException">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.</exception>
/// <exception cref="DirectoryNotFoundException">The specified sourceFileName is invalid, (for example, it is on an unmapped drive).</exception>
/// <exception cref="IOException">An I/O error occurred while opening the file specified by sourceFileName.</exception>
/// <exception cref="UnauthorizedAccessException">sourceFileName specified a directory.
/// -or- The caller does not have the required permission.</exception>
/// <exception cref="FileNotFoundException">The file specified in sourceFileName was not found. </exception>
/// <exception cref="NotSupportedException">sourceFileName is in an invalid format or the ZipArchive does not support writing.</exception>
/// <exception cref="ObjectDisposedException">The ZipArchive has already been closed.</exception>
///
/// <param name="sourceFileName">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.</param>
/// <param name="entryName">The name of the entry to be created.</param>
/// <param name="compressionLevel">The level of the compression (speed/memory vs. compressed size trade-off).</param>
/// <returns>A wrapper for the newly created entry.</returns>
public static ZipArchiveEntry CreateEntryFromFile(this ZipArchive destination,
string sourceFileName, string entryName, CompressionLevel compressionLevel) =>
DoCreateEntryFromFile(destination, sourceFileName, entryName, compressionLevel);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and also this line


internal static ZipArchiveEntry DoCreateEntryFromFile(this ZipArchive destination,
string sourceFileName, string entryName, CompressionLevel? compressionLevel)
{
if (destination == null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: indenting, here and the line above

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;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// 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.
/// </summary>
///
/// <exception cref="ArgumentException">destinationDirectoryName is a zero-length string, contains only whitespace,
/// or contains one or more invalid characters as defined by InvalidPathChars.</exception>
/// <exception cref="ArgumentNullException">destinationDirectoryName is null.</exception>
/// <exception cref="PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
/// <exception cref="DirectoryNotFoundException">The specified path is invalid, (for example, it is on an unmapped drive).</exception>
/// <exception cref="IOException">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.</exception>
/// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException">destinationDirectoryName is in an invalid format. </exception>
/// <exception cref="InvalidDataException">An archive entry was not found or was corrupt.
/// -or- An archive entry has been compressed using a compression method that is not supported.</exception>
///
/// <param name="destinationDirectoryName">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.</param>
public static void ExtractToDirectory(this ZipArchive source, string destinationDirectoryName) => ExtractToDirectory(source, destinationDirectoryName, overwrite: false);

/// <summary>
/// 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.
/// </summary>
///
/// <exception cref="ArgumentException">destinationDirectoryName is a zero-length string, contains only whitespace,
/// or contains one or more invalid characters as defined by InvalidPathChars.</exception>
/// <exception cref="ArgumentNullException">destinationDirectoryName is null.</exception>
/// <exception cref="PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length.
/// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception>
/// <exception cref="DirectoryNotFoundException">The specified path is invalid, (for example, it is on an unmapped drive).</exception>
/// <exception cref="IOException">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.</exception>
/// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception>
/// <exception cref="NotSupportedException">destinationDirectoryName is in an invalid format. </exception>
/// <exception cref="InvalidDataException">An archive entry was not found or was corrupt.
/// -or- An archive entry has been compressed using a compression method that is not supported.</exception>
///
/// <param name="destinationDirectoryName">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.</param>
/// <param name="overwrite">True to indicate overwrite.</param>
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);
}
}
}
}
Loading