private static void DoCreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel?compressionLevel, bool includeBaseDirectory, Encoding?entryNameEncoding) { // Rely on Path.GetFullPath for validation of sourceDirectoryName and destinationArchive // Checking of compressionLevel is passed down to DeflateStream and the IDeflater implementation // as it is a pluggable component that completely encapsulates the meaning of compressionLevel. sourceDirectoryName = Path.GetFullPath(sourceDirectoryName); destinationArchiveFileName = Path.GetFullPath(destinationArchiveFileName); using (ZipArchive archive = Open(destinationArchiveFileName, ZipArchiveMode.Create, entryNameEncoding)) { bool directoryIsEmpty = true; //add files and directories DirectoryInfo di = new DirectoryInfo(sourceDirectoryName); string basePath = di.FullName; if (includeBaseDirectory && di.Parent != null) { basePath = di.Parent.FullName; } foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) { directoryIsEmpty = false; if (file is FileInfo) { // Create entry for file: string entryName = ArchivingUtils.EntryFromPath(file.FullName.AsSpan(basePath.Length)); ZipFileExtensions.DoCreateEntryFromFile(archive, file.FullName, entryName, compressionLevel); } else { // Entry marking an empty dir: if (file is DirectoryInfo possiblyEmpty && ArchivingUtils.IsDirEmpty(possiblyEmpty)) { // FullName never returns a directory separator character on the end, // but Zip archives require it to specify an explicit directory: string entryName = ArchivingUtils.EntryFromPath(file.FullName.AsSpan(basePath.Length), appendPathSeparator: true); archive.CreateEntry(entryName); } } } // If no entries create an empty root directory entry: if (includeBaseDirectory && directoryIsEmpty) { archive.CreateEntry(ArchivingUtils.EntryFromPath(di.Name, appendPathSeparator: true)); } } }
// Constructs the entry name used for a filesystem entry when creating an archive. private static string GetEntryNameForFileSystemInfo(FileSystemInfo file, int basePathLength, ref char[] entryNameBuffer) { int entryNameLength = file.FullName.Length - basePathLength; Debug.Assert(entryNameLength > 0); bool isDirectory = file.Attributes.HasFlag(FileAttributes.Directory); return(ArchivingUtils.EntryFromPath(file.FullName, basePathLength, entryNameLength, ref entryNameBuffer, appendPathSeparator: isDirectory)); }
// /// <summary> // /// Asynchronously extracts the contents of a tar file into the specified directory. // /// </summary> // /// <param name="sourceFileName">The path of the tar file to extract.</param> // /// <param name="destinationDirectoryName">The path of the destination directory where the filesystem entries should be extracted.</param> // /// <param name="overwriteFiles"><see langword="true"/> to overwrite files and directories in <paramref name="destinationDirectoryName"/>; <see langword="false"/> to avoid overwriting, and throw if any files or directories are found with existing names.</param> // /// <param name="cancellationToken">The token to monitor for cancellation requests. The default value is <see cref="CancellationToken.None" />.</param> // /// <returns>A task that represents the asynchronous extraction operation.</returns> // /// <remarks><para>Files of type <see cref="TarEntryType.BlockDevice"/>, <see cref="TarEntryType.CharacterDevice"/> or <see cref="TarEntryType.Fifo"/> can only be extracted in Unix platforms.</para> // /// <para>Elevation is required to extract a <see cref="TarEntryType.BlockDevice"/> or <see cref="TarEntryType.CharacterDevice"/> to disk.</para></remarks> // /// <exception cref="UnauthorizedAccessException">Operation not permitted due to insufficient permissions.</exception> // public static Task ExtractToDirectoryAsync(string sourceFileName, string destinationDirectoryName, bool overwriteFiles, CancellationToken cancellationToken = default) // { // throw new NotImplementedException(); // } // Creates an archive from the contents of a directory. // It assumes the sourceDirectoryName is a fully qualified path, and allows choosing if the archive stream should be left open or not. private static void CreateFromDirectoryInternal(string sourceDirectoryName, Stream destination, bool includeBaseDirectory, bool leaveOpen) { Debug.Assert(!string.IsNullOrEmpty(sourceDirectoryName)); Debug.Assert(destination != null); Debug.Assert(Path.IsPathFullyQualified(sourceDirectoryName)); Debug.Assert(destination.CanWrite); using (TarWriter writer = new TarWriter(destination, TarEntryFormat.Pax, leaveOpen)) { bool baseDirectoryIsEmpty = true; DirectoryInfo di = new(sourceDirectoryName); string basePath = di.FullName; if (includeBaseDirectory && di.Parent != null) { basePath = di.Parent.FullName; } // Windows' MaxPath (260) is used as an arbitrary default capacity, as it is likely // to be greater than the length of typical entry names from the file system, even // on non-Windows platforms. The capacity will be increased, if needed. const int DefaultCapacity = 260; char[] entryNameBuffer = ArrayPool <char> .Shared.Rent(DefaultCapacity); try { foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) { baseDirectoryIsEmpty = false; int entryNameLength = file.FullName.Length - basePath.Length; Debug.Assert(entryNameLength > 0); bool isDirectory = file.Attributes.HasFlag(FileAttributes.Directory); string entryName = ArchivingUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer, appendPathSeparator: isDirectory); writer.WriteEntry(file.FullName, entryName); } if (includeBaseDirectory && baseDirectoryIsEmpty) { string entryName = ArchivingUtils.EntryFromPath(di.Name, 0, di.Name.Length, ref entryNameBuffer, appendPathSeparator: true); PaxTarEntry entry = new PaxTarEntry(TarEntryType.Directory, entryName); writer.WriteEntry(entry); } } finally { ArrayPool <char> .Shared.Return(entryNameBuffer); } } }
/// <summary> /// Creates a file on the file system with the entry?s contents and the specified name. /// The last write time of the file is set to the entry?s last write time. /// This method does allows overwriting of an existing file with the same name. /// </summary> /// /// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception> /// <exception cref="ArgumentException">destinationFileName is a zero-length string, contains only whitespace, /// or contains one or more invalid characters as defined by InvalidPathChars. -or- destinationFileName specifies a directory.</exception> /// <exception cref="ArgumentNullException">destinationFileName is null.</exception> /// <exception cref="PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. /// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception> /// <exception cref="DirectoryNotFoundException">The path specified in destinationFileName is invalid /// (for example, it is on an unmapped drive).</exception> /// <exception cref="IOException">An I/O error has occurred. /// -or- The entry is currently open for writing. /// -or- The entry has been deleted from the archive.</exception> /// <exception cref="NotSupportedException">destinationFileName is in an invalid format /// -or- The ZipArchive that this entry belongs to was opened in a write-only mode.</exception> /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read /// -or- The entry has been compressed using a compression method that is not supported.</exception> /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception> /// <param name="source">The zip archive entry to extract a file from.</param> /// <param name="destinationFileName">The name of the file that will hold the contents of the entry. /// The path is permitted to specify relative or absolute path information. /// Relative path information is interpreted as relative to the current working directory.</param> /// <param name="overwrite">True to indicate overwrite.</param> public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName, bool overwrite) { ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(destinationFileName); // Rely on FileStream's ctor for further checking destinationFileName parameter FileMode fMode = overwrite ? FileMode.Create : FileMode.CreateNew; using (FileStream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, useAsync: false)) { using (Stream es = source.Open()) es.CopyTo(fs); ExtractExternalAttributes(fs, source); } ArchivingUtils.AttemptSetLastWriteTime(destinationFileName, source.LastWriteTime); }
/// <summary> /// Creates a file on the file system with the entry?s contents and the specified name. /// The last write time of the file is set to the entry?s last write time. /// This method does allows overwriting of an existing file with the same name. /// </summary> /// /// <exception cref="UnauthorizedAccessException">The caller does not have the required permission.</exception> /// <exception cref="ArgumentException">destinationFileName is a zero-length string, contains only whitespace, /// or contains one or more invalid characters as defined by InvalidPathChars. -or- destinationFileName specifies a directory.</exception> /// <exception cref="ArgumentNullException">destinationFileName is null.</exception> /// <exception cref="PathTooLongException">The specified path, file name, or both exceed the system-defined maximum length. /// For example, on Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 characters.</exception> /// <exception cref="DirectoryNotFoundException">The path specified in destinationFileName is invalid /// (for example, it is on an unmapped drive).</exception> /// <exception cref="IOException">An I/O error has occurred. /// -or- The entry is currently open for writing. /// -or- The entry has been deleted from the archive.</exception> /// <exception cref="NotSupportedException">destinationFileName is in an invalid format /// -or- The ZipArchive that this entry belongs to was opened in a write-only mode.</exception> /// <exception cref="InvalidDataException">The entry is missing from the archive or is corrupt and cannot be read /// -or- The entry has been compressed using a compression method that is not supported.</exception> /// <exception cref="ObjectDisposedException">The ZipArchive that this entry belongs to has been disposed.</exception> /// <param name="source">The zip archive entry to extract a file from.</param> /// <param name="destinationFileName">The name of the file that will hold the contents of the entry. /// The path is permitted to specify relative or absolute path information. /// Relative path information is interpreted as relative to the current working directory.</param> /// <param name="overwrite">True to indicate overwrite.</param> public static void ExtractToFile(this ZipArchiveEntry source, string destinationFileName, bool overwrite) { ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(destinationFileName); FileStreamOptions fileStreamOptions = new() { Access = FileAccess.Write, Mode = overwrite ? FileMode.Create : FileMode.CreateNew, Share = FileShare.None, BufferSize = 0x1000 }; const UnixFileMode OwnershipPermissions = UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | UnixFileMode.GroupRead | UnixFileMode.GroupWrite | UnixFileMode.GroupExecute | UnixFileMode.OtherRead | UnixFileMode.OtherWrite | UnixFileMode.OtherExecute; // Restore Unix permissions. // For security, limit to ownership permissions, and respect umask (through UnixCreateMode). // We don't apply UnixFileMode.None because .zip files created on Windows and .zip files created // with previous versions of .NET don't include permissions. UnixFileMode mode = (UnixFileMode)(source.ExternalAttributes >> 16) & OwnershipPermissions; if (mode != UnixFileMode.None && !OperatingSystem.IsWindows()) { fileStreamOptions.UnixCreateMode = mode; } using (FileStream fs = new FileStream(destinationFileName, fileStreamOptions)) { using (Stream es = source.Open()) es.CopyTo(fs); } ArchivingUtils.AttemptSetLastWriteTime(destinationFileName, source.LastWriteTime); }
// Constructs a PaxTarEntry for a base directory entry when creating an archive. private static PaxTarEntry GetEntryForBaseDirectory(string name, ref char[] entryNameBuffer) { string entryName = ArchivingUtils.EntryFromPath(name, 0, name.Length, ref entryNameBuffer, appendPathSeparator: true); return(new PaxTarEntry(TarEntryType.Directory, entryName)); }
private static void DoCreateFromDirectory(string sourceDirectoryName, string destinationArchiveFileName, CompressionLevel?compressionLevel, bool includeBaseDirectory, Encoding?entryNameEncoding) { // Rely on Path.GetFullPath for validation of sourceDirectoryName and destinationArchive // Checking of compressionLevel is passed down to DeflateStream and the IDeflater implementation // as it is a pluggable component that completely encapsulates the meaning of compressionLevel. sourceDirectoryName = Path.GetFullPath(sourceDirectoryName); destinationArchiveFileName = Path.GetFullPath(destinationArchiveFileName); using (ZipArchive archive = Open(destinationArchiveFileName, ZipArchiveMode.Create, entryNameEncoding)) { bool directoryIsEmpty = true; //add files and directories DirectoryInfo di = new DirectoryInfo(sourceDirectoryName); string basePath = di.FullName; if (includeBaseDirectory && di.Parent != null) { basePath = di.Parent.FullName; } // Windows' MaxPath (260) is used as an arbitrary default capacity, as it is likely // to be greater than the length of typical entry names from the file system, even // on non-Windows platforms. The capacity will be increased, if needed. const int DefaultCapacity = 260; char[] entryNameBuffer = ArrayPool <char> .Shared.Rent(DefaultCapacity); try { foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) { directoryIsEmpty = false; int entryNameLength = file.FullName.Length - basePath.Length; Debug.Assert(entryNameLength > 0); if (file is FileInfo) { // Create entry for file: string entryName = ArchivingUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer); ZipFileExtensions.DoCreateEntryFromFile(archive, file.FullName, entryName, compressionLevel); } else { // Entry marking an empty dir: if (file is DirectoryInfo possiblyEmpty && ArchivingUtils.IsDirEmpty(possiblyEmpty)) { // FullName never returns a directory separator character on the end, // but Zip archives require it to specify an explicit directory: string entryName = ArchivingUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer, appendPathSeparator: true); archive.CreateEntry(entryName); } } } // foreach // If no entries create an empty root directory entry: if (includeBaseDirectory && directoryIsEmpty) { archive.CreateEntry(ArchivingUtils.EntryFromPath(di.Name, 0, di.Name.Length, ref entryNameBuffer, appendPathSeparator: true)); } } finally { ArrayPool <char> .Shared.Return(entryNameBuffer); } } }
internal static void ExtractRelativeToDirectory(this ZipArchiveEntry source, string destinationDirectoryName, bool overwrite) { ArgumentNullException.ThrowIfNull(source); ArgumentNullException.ThrowIfNull(destinationDirectoryName); // Note that this will give us a good DirectoryInfo even if destinationDirectoryName exists: DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName); string destinationDirectoryFullPath = di.FullName; if (!destinationDirectoryFullPath.EndsWith(Path.DirectorySeparatorChar)) { destinationDirectoryFullPath += Path.DirectorySeparatorChar; } string fileDestinationPath = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, ArchivingUtils.SanitizeEntryFilePath(source.FullName))); if (!fileDestinationPath.StartsWith(destinationDirectoryFullPath, PathInternal.StringComparison)) { throw new IOException(SR.IO_ExtractingResultsInOutside); } if (Path.GetFileName(fileDestinationPath).Length == 0) { // If it is a directory: if (source.Length != 0) { throw new IOException(SR.IO_DirectoryNameWithData); } Directory.CreateDirectory(fileDestinationPath); } else { // If it is a file: // Create containing directory: Directory.CreateDirectory(Path.GetDirectoryName(fileDestinationPath) !); source.ExtractToFile(fileDestinationPath, overwrite: overwrite); } }