2022-12-20 15:06:44 +00:00
|
|
|
using System;
|
2018-06-28 11:46:51 +01:00
|
|
|
using System.IO;
|
2025-10-27 09:00:38 +00:00
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
2018-06-28 11:46:51 +01:00
|
|
|
|
2022-12-20 15:20:49 +00:00
|
|
|
namespace SharpCompress.Common;
|
|
|
|
|
|
|
|
|
|
internal static class ExtractionMethods
|
2018-06-28 11:46:51 +01:00
|
|
|
{
|
2022-12-20 15:20:49 +00:00
|
|
|
/// <summary>
|
|
|
|
|
/// Extract to specific directory, retaining filename
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static void WriteEntryToDirectory(
|
|
|
|
|
IEntry entry,
|
|
|
|
|
string destinationDirectory,
|
|
|
|
|
ExtractionOptions? options,
|
|
|
|
|
Action<string, ExtractionOptions?> write
|
|
|
|
|
)
|
2018-06-28 11:46:51 +01:00
|
|
|
{
|
2022-12-20 15:20:49 +00:00
|
|
|
string destinationFileName;
|
2024-03-14 08:38:12 +00:00
|
|
|
var fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory);
|
2022-12-20 15:20:49 +00:00
|
|
|
|
|
|
|
|
//check for trailing slash.
|
|
|
|
|
if (
|
|
|
|
|
fullDestinationDirectoryPath[fullDestinationDirectoryPath.Length - 1]
|
|
|
|
|
!= Path.DirectorySeparatorChar
|
2022-12-20 13:45:47 +00:00
|
|
|
)
|
2018-06-28 11:46:51 +01:00
|
|
|
{
|
2022-12-20 15:20:49 +00:00
|
|
|
fullDestinationDirectoryPath += Path.DirectorySeparatorChar;
|
|
|
|
|
}
|
2021-10-02 15:29:03 +01:00
|
|
|
|
2022-12-20 15:20:49 +00:00
|
|
|
if (!Directory.Exists(fullDestinationDirectoryPath))
|
|
|
|
|
{
|
|
|
|
|
throw new ExtractionException(
|
|
|
|
|
$"Directory does not exist to extract to: {fullDestinationDirectoryPath}"
|
|
|
|
|
);
|
|
|
|
|
}
|
2021-09-12 08:47:30 +01:00
|
|
|
|
2022-12-20 15:20:49 +00:00
|
|
|
options ??= new ExtractionOptions() { Overwrite = true };
|
2018-06-28 11:46:51 +01:00
|
|
|
|
2024-04-18 13:24:03 +01:00
|
|
|
var file = Path.GetFileName(entry.Key.NotNull("Entry Key is null")).NotNull("File is null");
|
2024-07-26 00:16:44 +05:30
|
|
|
file = Utility.ReplaceInvalidFileNameChars(file);
|
2022-12-20 15:20:49 +00:00
|
|
|
if (options.ExtractFullPath)
|
|
|
|
|
{
|
2024-04-22 14:17:08 +01:00
|
|
|
var folder = Path.GetDirectoryName(entry.Key.NotNull("Entry Key is null"))
|
|
|
|
|
.NotNull("Directory is null");
|
2024-03-14 08:38:12 +00:00
|
|
|
var destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder));
|
2018-06-28 11:46:51 +01:00
|
|
|
|
2022-12-20 15:20:49 +00:00
|
|
|
if (!Directory.Exists(destdir))
|
2018-06-28 11:46:51 +01:00
|
|
|
{
|
2022-12-20 15:20:49 +00:00
|
|
|
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
|
2018-06-28 11:46:51 +01:00
|
|
|
{
|
2022-12-20 15:20:49 +00:00
|
|
|
throw new ExtractionException(
|
|
|
|
|
"Entry is trying to create a directory outside of the destination directory."
|
|
|
|
|
);
|
2018-06-28 11:46:51 +01:00
|
|
|
}
|
2022-12-20 15:20:49 +00:00
|
|
|
|
|
|
|
|
Directory.CreateDirectory(destdir);
|
2018-06-28 11:46:51 +01:00
|
|
|
}
|
2022-12-20 15:20:49 +00:00
|
|
|
destinationFileName = Path.Combine(destdir, file);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
destinationFileName = Path.Combine(fullDestinationDirectoryPath, file);
|
|
|
|
|
}
|
2018-06-28 11:46:51 +01:00
|
|
|
|
2022-12-20 15:20:49 +00:00
|
|
|
if (!entry.IsDirectory)
|
|
|
|
|
{
|
|
|
|
|
destinationFileName = Path.GetFullPath(destinationFileName);
|
2018-06-28 11:46:51 +01:00
|
|
|
|
2022-12-20 15:20:49 +00:00
|
|
|
if (
|
|
|
|
|
!destinationFileName.StartsWith(
|
|
|
|
|
fullDestinationDirectoryPath,
|
|
|
|
|
StringComparison.Ordinal
|
2022-12-20 13:45:47 +00:00
|
|
|
)
|
2022-12-20 15:20:49 +00:00
|
|
|
)
|
2018-06-28 11:46:51 +01:00
|
|
|
{
|
2022-12-20 15:20:49 +00:00
|
|
|
throw new ExtractionException(
|
|
|
|
|
"Entry is trying to write a file outside of the destination directory."
|
|
|
|
|
);
|
2018-06-28 11:46:51 +01:00
|
|
|
}
|
2022-12-20 15:20:49 +00:00
|
|
|
write(destinationFileName, options);
|
|
|
|
|
}
|
|
|
|
|
else if (options.ExtractFullPath && !Directory.Exists(destinationFileName))
|
|
|
|
|
{
|
|
|
|
|
Directory.CreateDirectory(destinationFileName);
|
2018-06-28 11:46:51 +01:00
|
|
|
}
|
2022-12-20 15:20:49 +00:00
|
|
|
}
|
2021-01-09 13:33:34 +00:00
|
|
|
|
2022-12-20 15:20:49 +00:00
|
|
|
public static void WriteEntryToFile(
|
|
|
|
|
IEntry entry,
|
|
|
|
|
string destinationFileName,
|
|
|
|
|
ExtractionOptions? options,
|
|
|
|
|
Action<string, FileMode> openAndWrite
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
if (entry.LinkTarget != null)
|
2018-06-28 11:46:51 +01:00
|
|
|
{
|
2022-12-20 15:20:49 +00:00
|
|
|
if (options?.WriteSymbolicLink is null)
|
2018-06-28 11:46:51 +01:00
|
|
|
{
|
2022-12-20 15:20:49 +00:00
|
|
|
throw new ExtractionException(
|
|
|
|
|
"Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null"
|
|
|
|
|
);
|
2018-06-28 11:46:51 +01:00
|
|
|
}
|
2022-12-20 15:20:49 +00:00
|
|
|
options.WriteSymbolicLink(destinationFileName, entry.LinkTarget);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2024-03-14 08:38:12 +00:00
|
|
|
var fm = FileMode.Create;
|
2022-12-20 15:20:49 +00:00
|
|
|
options ??= new ExtractionOptions() { Overwrite = true };
|
2018-11-01 21:48:48 +00:00
|
|
|
|
2022-12-20 15:20:49 +00:00
|
|
|
if (!options.Overwrite)
|
|
|
|
|
{
|
|
|
|
|
fm = FileMode.CreateNew;
|
2018-11-01 21:48:48 +00:00
|
|
|
}
|
2022-12-20 15:20:49 +00:00
|
|
|
|
|
|
|
|
openAndWrite(destinationFileName, fm);
|
|
|
|
|
entry.PreserveExtractionOptions(destinationFileName, options);
|
2018-06-28 11:46:51 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-27 09:00:38 +00:00
|
|
|
|
|
|
|
|
public static async Task WriteEntryToDirectoryAsync(
|
|
|
|
|
IEntry entry,
|
|
|
|
|
string destinationDirectory,
|
|
|
|
|
ExtractionOptions? options,
|
|
|
|
|
Func<string, ExtractionOptions?, Task> writeAsync,
|
|
|
|
|
CancellationToken cancellationToken = default
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
string destinationFileName;
|
|
|
|
|
var fullDestinationDirectoryPath = Path.GetFullPath(destinationDirectory);
|
|
|
|
|
|
|
|
|
|
//check for trailing slash.
|
|
|
|
|
if (
|
|
|
|
|
fullDestinationDirectoryPath[fullDestinationDirectoryPath.Length - 1]
|
|
|
|
|
!= Path.DirectorySeparatorChar
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
fullDestinationDirectoryPath += Path.DirectorySeparatorChar;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Directory.Exists(fullDestinationDirectoryPath))
|
|
|
|
|
{
|
|
|
|
|
throw new ExtractionException(
|
|
|
|
|
$"Directory does not exist to extract to: {fullDestinationDirectoryPath}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
options ??= new ExtractionOptions() { Overwrite = true };
|
|
|
|
|
|
|
|
|
|
var file = Path.GetFileName(entry.Key.NotNull("Entry Key is null")).NotNull("File is null");
|
|
|
|
|
file = Utility.ReplaceInvalidFileNameChars(file);
|
|
|
|
|
if (options.ExtractFullPath)
|
|
|
|
|
{
|
|
|
|
|
var folder = Path.GetDirectoryName(entry.Key.NotNull("Entry Key is null"))
|
|
|
|
|
.NotNull("Directory is null");
|
|
|
|
|
var destdir = Path.GetFullPath(Path.Combine(fullDestinationDirectoryPath, folder));
|
|
|
|
|
|
|
|
|
|
if (!Directory.Exists(destdir))
|
|
|
|
|
{
|
|
|
|
|
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
|
|
|
|
|
{
|
|
|
|
|
throw new ExtractionException(
|
|
|
|
|
"Entry is trying to create a directory outside of the destination directory."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Directory.CreateDirectory(destdir);
|
|
|
|
|
}
|
|
|
|
|
destinationFileName = Path.Combine(destdir, file);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
destinationFileName = Path.Combine(fullDestinationDirectoryPath, file);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!entry.IsDirectory)
|
|
|
|
|
{
|
|
|
|
|
destinationFileName = Path.GetFullPath(destinationFileName);
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
!destinationFileName.StartsWith(
|
|
|
|
|
fullDestinationDirectoryPath,
|
|
|
|
|
StringComparison.Ordinal
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
throw new ExtractionException(
|
|
|
|
|
"Entry is trying to write a file outside of the destination directory."
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
await writeAsync(destinationFileName, options).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
else if (options.ExtractFullPath && !Directory.Exists(destinationFileName))
|
|
|
|
|
{
|
|
|
|
|
Directory.CreateDirectory(destinationFileName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static async Task WriteEntryToFileAsync(
|
|
|
|
|
IEntry entry,
|
|
|
|
|
string destinationFileName,
|
|
|
|
|
ExtractionOptions? options,
|
|
|
|
|
Func<string, FileMode, Task> openAndWriteAsync,
|
|
|
|
|
CancellationToken cancellationToken = default
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
if (entry.LinkTarget != null)
|
|
|
|
|
{
|
|
|
|
|
if (options?.WriteSymbolicLink is null)
|
|
|
|
|
{
|
|
|
|
|
throw new ExtractionException(
|
|
|
|
|
"Entry is a symbolic link but ExtractionOptions.WriteSymbolicLink delegate is null"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
options.WriteSymbolicLink(destinationFileName, entry.LinkTarget);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var fm = FileMode.Create;
|
|
|
|
|
options ??= new ExtractionOptions() { Overwrite = true };
|
|
|
|
|
|
|
|
|
|
if (!options.Overwrite)
|
|
|
|
|
{
|
|
|
|
|
fm = FileMode.CreateNew;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await openAndWriteAsync(destinationFileName, fm).ConfigureAwait(false);
|
|
|
|
|
entry.PreserveExtractionOptions(destinationFileName, options);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-12-20 13:45:47 +00:00
|
|
|
}
|