mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-05-20 23:17:00 +00:00
Merge branch 'master' into async-reader-methods
This commit is contained in:
@@ -96,6 +96,9 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
DateTime? modified
|
||||
) => AddEntry(key, source, closeStream, size, modified);
|
||||
|
||||
IArchiveEntry IWritableArchive.AddDirectoryEntry(string key, DateTime? modified) =>
|
||||
AddDirectoryEntry(key, modified);
|
||||
|
||||
public TEntry AddEntry(
|
||||
string key,
|
||||
Stream source,
|
||||
@@ -136,6 +139,22 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
return false;
|
||||
}
|
||||
|
||||
public TEntry AddDirectoryEntry(string key, DateTime? modified = null)
|
||||
{
|
||||
if (key.Length > 0 && key[0] is '/' or '\\')
|
||||
{
|
||||
key = key.Substring(1);
|
||||
}
|
||||
if (DoesKeyMatchExisting(key))
|
||||
{
|
||||
throw new ArchiveException("Cannot add entry with duplicate key: " + key);
|
||||
}
|
||||
var entry = CreateDirectoryEntry(key, modified);
|
||||
newEntries.Add(entry);
|
||||
RebuildModifiedCollection();
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void SaveTo(Stream stream, WriterOptions options)
|
||||
{
|
||||
//reset streams of new entries
|
||||
@@ -180,6 +199,8 @@ public abstract class AbstractWritableArchive<TEntry, TVolume>
|
||||
bool closeStream
|
||||
);
|
||||
|
||||
protected abstract TEntry CreateDirectoryEntry(string key, DateTime? modified);
|
||||
|
||||
protected abstract void SaveTo(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
|
||||
@@ -185,6 +185,11 @@ public class GZipArchive : AbstractWritableArchive<GZipArchiveEntry, GZipVolume>
|
||||
return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
|
||||
}
|
||||
|
||||
protected override GZipArchiveEntry CreateDirectoryEntry(
|
||||
string directoryPath,
|
||||
DateTime? modified
|
||||
) => throw new NotSupportedException("GZip archives do not support directory entries.");
|
||||
|
||||
protected override void SaveTo(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
|
||||
@@ -18,6 +18,8 @@ public interface IWritableArchive : IArchive
|
||||
DateTime? modified = null
|
||||
);
|
||||
|
||||
IArchiveEntry AddDirectoryEntry(string key, DateTime? modified = null);
|
||||
|
||||
void SaveTo(Stream stream, WriterOptions options);
|
||||
|
||||
Task SaveToAsync(
|
||||
|
||||
@@ -224,6 +224,11 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
closeStream
|
||||
);
|
||||
|
||||
protected override TarArchiveEntry CreateDirectoryEntry(
|
||||
string directoryPath,
|
||||
DateTime? modified
|
||||
) => new TarWritableArchiveEntry(this, directoryPath, modified);
|
||||
|
||||
protected override void SaveTo(
|
||||
Stream stream,
|
||||
WriterOptions options,
|
||||
@@ -232,15 +237,25 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
)
|
||||
{
|
||||
using var writer = new TarWriter(stream, new TarWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
foreach (var entry in oldEntries.Concat(newEntries))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
entry.Size
|
||||
);
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
writer.WriteDirectory(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
entry.Size
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,18 +268,31 @@ public class TarArchive : AbstractWritableArchive<TarArchiveEntry, TarVolume>
|
||||
)
|
||||
{
|
||||
using var writer = new TarWriter(stream, new TarWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
foreach (var entry in oldEntries.Concat(newEntries))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
await writer
|
||||
.WriteAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
entry.Size,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
await writer
|
||||
.WriteDirectoryAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entry.LastModifiedTime,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
await writer
|
||||
.WriteAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime,
|
||||
entry.Size,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace SharpCompress.Archives.Tar;
|
||||
internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiveEntry
|
||||
{
|
||||
private readonly bool closeStream;
|
||||
private readonly Stream stream;
|
||||
private readonly Stream? stream;
|
||||
private readonly bool isDirectory;
|
||||
|
||||
internal TarWritableArchiveEntry(
|
||||
TarArchive archive,
|
||||
@@ -27,6 +28,22 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
|
||||
Size = size;
|
||||
LastModifiedTime = lastModified;
|
||||
this.closeStream = closeStream;
|
||||
isDirectory = false;
|
||||
}
|
||||
|
||||
internal TarWritableArchiveEntry(
|
||||
TarArchive archive,
|
||||
string directoryPath,
|
||||
DateTime? lastModified
|
||||
)
|
||||
: base(archive, null, CompressionType.None)
|
||||
{
|
||||
stream = null;
|
||||
Key = directoryPath;
|
||||
Size = 0;
|
||||
LastModifiedTime = lastModified;
|
||||
closeStream = false;
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
public override long Crc => 0;
|
||||
@@ -47,15 +64,19 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => false;
|
||||
public override bool IsDirectory => isDirectory;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
|
||||
Stream IWritableArchiveEntry.Stream => stream;
|
||||
Stream IWritableArchiveEntry.Stream => stream ?? Stream.Null;
|
||||
|
||||
public override Stream OpenEntryStream()
|
||||
{
|
||||
if (stream is null)
|
||||
{
|
||||
return Stream.Null;
|
||||
}
|
||||
//ensure new stream is at the start, this could be reset
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return SharpCompressStream.Create(stream, leaveOpen: true);
|
||||
@@ -63,7 +84,7 @@ internal sealed class TarWritableArchiveEntry : TarArchiveEntry, IWritableArchiv
|
||||
|
||||
internal override void Close()
|
||||
{
|
||||
if (closeStream)
|
||||
if (closeStream && stream is not null)
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
@@ -308,14 +308,24 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
)
|
||||
{
|
||||
using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
foreach (var entry in oldEntries.Concat(newEntries))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
writer.WriteDirectory(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
writer.Write(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
entry.LastModifiedTime
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,12 +338,29 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
)
|
||||
{
|
||||
using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
|
||||
foreach (var entry in oldEntries.Concat(newEntries).Where(x => !x.IsDirectory))
|
||||
foreach (var entry in oldEntries.Concat(newEntries))
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
await writer
|
||||
.WriteAsync(entry.Key.NotNull("Entry Key is null"), entryStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (entry.IsDirectory)
|
||||
{
|
||||
await writer
|
||||
.WriteDirectoryAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entry.LastModifiedTime,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
using var entryStream = entry.OpenEntryStream();
|
||||
await writer
|
||||
.WriteAsync(
|
||||
entry.Key.NotNull("Entry Key is null"),
|
||||
entryStream,
|
||||
cancellationToken
|
||||
)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,6 +372,11 @@ public class ZipArchive : AbstractWritableArchive<ZipArchiveEntry, ZipVolume>
|
||||
bool closeStream
|
||||
) => new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
|
||||
|
||||
protected override ZipArchiveEntry CreateDirectoryEntry(
|
||||
string directoryPath,
|
||||
DateTime? modified
|
||||
) => new ZipWritableArchiveEntry(this, directoryPath, modified);
|
||||
|
||||
public static ZipArchive Create() => new();
|
||||
|
||||
protected override IReader CreateReaderForSolidExtraction()
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace SharpCompress.Archives.Zip;
|
||||
internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
|
||||
{
|
||||
private readonly bool closeStream;
|
||||
private readonly Stream stream;
|
||||
private readonly Stream? stream;
|
||||
private readonly bool isDirectory;
|
||||
private bool isDisposed;
|
||||
|
||||
internal ZipWritableArchiveEntry(
|
||||
@@ -27,6 +28,22 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
|
||||
Size = size;
|
||||
LastModifiedTime = lastModified;
|
||||
this.closeStream = closeStream;
|
||||
isDirectory = false;
|
||||
}
|
||||
|
||||
internal ZipWritableArchiveEntry(
|
||||
ZipArchive archive,
|
||||
string directoryPath,
|
||||
DateTime? lastModified
|
||||
)
|
||||
: base(archive, null)
|
||||
{
|
||||
stream = null;
|
||||
Key = directoryPath;
|
||||
Size = 0;
|
||||
LastModifiedTime = lastModified;
|
||||
closeStream = false;
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
public override long Crc => 0;
|
||||
@@ -47,16 +64,20 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
|
||||
public override bool IsDirectory => false;
|
||||
public override bool IsDirectory => isDirectory;
|
||||
|
||||
public override bool IsSplitAfter => false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
|
||||
|
||||
Stream IWritableArchiveEntry.Stream => stream;
|
||||
Stream IWritableArchiveEntry.Stream => stream ?? Stream.Null;
|
||||
|
||||
public override Stream OpenEntryStream()
|
||||
{
|
||||
if (stream is null)
|
||||
{
|
||||
return Stream.Null;
|
||||
}
|
||||
//ensure new stream is at the start, this could be reset
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return SharpCompressStream.Create(stream, leaveOpen: true);
|
||||
@@ -64,7 +85,7 @@ internal class ZipWritableArchiveEntry : ZipArchiveEntry, IWritableArchiveEntry
|
||||
|
||||
internal override void Close()
|
||||
{
|
||||
if (closeStream && !isDisposed)
|
||||
if (closeStream && !isDisposed && stream is not null)
|
||||
{
|
||||
stream.Dispose();
|
||||
isDisposed = true;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -7,6 +8,15 @@ namespace SharpCompress.Common;
|
||||
|
||||
internal static class ExtractionMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the appropriate StringComparison for path checks based on the file system.
|
||||
/// Windows uses case-insensitive file systems, while Unix-like systems use case-sensitive file systems.
|
||||
/// </summary>
|
||||
private static StringComparison PathComparison =>
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? StringComparison.OrdinalIgnoreCase
|
||||
: StringComparison.Ordinal;
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific directory, retaining filename
|
||||
/// </summary>
|
||||
@@ -48,7 +58,7 @@ internal static class ExtractionMethods
|
||||
|
||||
if (!Directory.Exists(destdir))
|
||||
{
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, PathComparison))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to create a directory outside of the destination directory."
|
||||
@@ -68,12 +78,7 @@ internal static class ExtractionMethods
|
||||
{
|
||||
destinationFileName = Path.GetFullPath(destinationFileName);
|
||||
|
||||
if (
|
||||
!destinationFileName.StartsWith(
|
||||
fullDestinationDirectoryPath,
|
||||
StringComparison.Ordinal
|
||||
)
|
||||
)
|
||||
if (!destinationFileName.StartsWith(fullDestinationDirectoryPath, PathComparison))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to write a file outside of the destination directory."
|
||||
@@ -158,7 +163,7 @@ internal static class ExtractionMethods
|
||||
|
||||
if (!Directory.Exists(destdir))
|
||||
{
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, StringComparison.Ordinal))
|
||||
if (!destdir.StartsWith(fullDestinationDirectoryPath, PathComparison))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to create a directory outside of the destination directory."
|
||||
@@ -178,12 +183,7 @@ internal static class ExtractionMethods
|
||||
{
|
||||
destinationFileName = Path.GetFullPath(destinationFileName);
|
||||
|
||||
if (
|
||||
!destinationFileName.StartsWith(
|
||||
fullDestinationDirectoryPath,
|
||||
StringComparison.Ordinal
|
||||
)
|
||||
)
|
||||
if (!destinationFileName.StartsWith(fullDestinationDirectoryPath, PathComparison))
|
||||
{
|
||||
throw new ExtractionException(
|
||||
"Entry is trying to write a file outside of the destination directory."
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA;
|
||||
|
||||
/// <summary>
|
||||
/// The exception that is thrown when an error in input stream occurs during decoding.
|
||||
/// </summary>
|
||||
internal class DataErrorException : Exception
|
||||
internal class DataErrorException : SharpCompressException
|
||||
{
|
||||
public DataErrorException()
|
||||
: base("Data Error") { }
|
||||
@@ -15,7 +16,7 @@ internal class DataErrorException : Exception
|
||||
/// <summary>
|
||||
/// The exception that is thrown when the value of an argument is outside the allowable range.
|
||||
/// </summary>
|
||||
internal class InvalidParamException : Exception
|
||||
internal class InvalidParamException : SharpCompressException
|
||||
{
|
||||
public InvalidParamException()
|
||||
: base("Invalid Parameter") { }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Compressors.Xz;
|
||||
|
||||
public class XZIndexMarkerReachedException : Exception { }
|
||||
public class XZIndexMarkerReachedException : SharpCompressException { }
|
||||
|
||||
@@ -37,6 +37,20 @@ public abstract class AbstractWriter(ArchiveType type, WriterOptions writerOptio
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public abstract void WriteDirectory(string directoryName, DateTime? modificationTime);
|
||||
|
||||
public virtual async Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Default implementation calls synchronous version
|
||||
// Derived classes should override for true async behavior
|
||||
WriteDirectory(directoryName, modificationTime);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
|
||||
@@ -50,4 +50,7 @@ public sealed class GZipWriter : AbstractWriter
|
||||
source.CopyTo(stream);
|
||||
_wroteToStream = true;
|
||||
}
|
||||
|
||||
public override void WriteDirectory(string directoryName, DateTime? modificationTime) =>
|
||||
throw new NotSupportedException("GZip archives do not support directory entries.");
|
||||
}
|
||||
|
||||
@@ -16,4 +16,10 @@ public interface IWriter : IDisposable
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
void WriteDirectory(string directoryName, DateTime? modificationTime);
|
||||
Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,9 @@ public static class IWriterExtensions
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteDirectory(this IWriter writer, string directoryName) =>
|
||||
writer.WriteDirectory(directoryName, null);
|
||||
|
||||
// Async extensions
|
||||
public static Task WriteAsync(
|
||||
this IWriter writer,
|
||||
@@ -121,4 +124,10 @@ public static class IWriterExtensions
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task WriteDirectoryAsync(
|
||||
this IWriter writer,
|
||||
string directoryName,
|
||||
CancellationToken cancellationToken = default
|
||||
) => writer.WriteDirectoryAsync(directoryName, null, cancellationToken);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,44 @@ public class TarWriter : AbstractWriter
|
||||
return filename.Trim('/');
|
||||
}
|
||||
|
||||
private string NormalizeDirectoryName(string directoryName)
|
||||
{
|
||||
directoryName = NormalizeFilename(directoryName);
|
||||
// Ensure directory name ends with '/' for tar format
|
||||
if (!string.IsNullOrEmpty(directoryName) && !directoryName.EndsWith('/'))
|
||||
{
|
||||
directoryName += '/';
|
||||
}
|
||||
return directoryName;
|
||||
}
|
||||
|
||||
public override void WriteDirectory(string directoryName, DateTime? modificationTime)
|
||||
{
|
||||
var normalizedName = NormalizeDirectoryName(directoryName);
|
||||
if (string.IsNullOrEmpty(normalizedName))
|
||||
{
|
||||
return; // Skip empty or root directory
|
||||
}
|
||||
|
||||
var header = new TarHeader(WriterOptions.ArchiveEncoding);
|
||||
header.LastModifiedTime = modificationTime ?? TarHeader.EPOCH;
|
||||
header.Name = normalizedName;
|
||||
header.Size = 0;
|
||||
header.EntryType = EntryType.Directory;
|
||||
header.Write(OutputStream);
|
||||
}
|
||||
|
||||
public override async Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Synchronous implementation is sufficient for header-only write
|
||||
WriteDirectory(directoryName, modificationTime);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public void Write(string filename, Stream source, DateTime? modificationTime, long? size)
|
||||
{
|
||||
if (!source.CanSeek && size is null)
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Zip;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
@@ -135,6 +137,73 @@ public class ZipWriter : AbstractWriter
|
||||
return filename.Trim('/');
|
||||
}
|
||||
|
||||
private string NormalizeDirectoryName(string directoryName)
|
||||
{
|
||||
directoryName = NormalizeFilename(directoryName);
|
||||
// Ensure directory name ends with '/' for zip format
|
||||
if (!string.IsNullOrEmpty(directoryName) && !directoryName.EndsWith('/'))
|
||||
{
|
||||
directoryName += '/';
|
||||
}
|
||||
return directoryName;
|
||||
}
|
||||
|
||||
public override void WriteDirectory(string directoryName, DateTime? modificationTime)
|
||||
{
|
||||
var normalizedName = NormalizeDirectoryName(directoryName);
|
||||
if (string.IsNullOrEmpty(normalizedName))
|
||||
{
|
||||
return; // Skip empty or root directory
|
||||
}
|
||||
|
||||
var options = new ZipWriterEntryOptions { ModificationDateTime = modificationTime };
|
||||
WriteDirectoryEntry(normalizedName, options);
|
||||
}
|
||||
|
||||
public override async Task WriteDirectoryAsync(
|
||||
string directoryName,
|
||||
DateTime? modificationTime,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
// Synchronous implementation is sufficient for directory entries
|
||||
WriteDirectory(directoryName, modificationTime);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void WriteDirectoryEntry(string directoryPath, ZipWriterEntryOptions options)
|
||||
{
|
||||
var compression = ZipCompressionMethod.None;
|
||||
|
||||
options.ModificationDateTime ??= DateTime.Now;
|
||||
options.EntryComment ??= string.Empty;
|
||||
|
||||
var entry = new ZipCentralDirectoryEntry(
|
||||
compression,
|
||||
directoryPath,
|
||||
(ulong)streamPosition,
|
||||
WriterOptions.ArchiveEncoding
|
||||
)
|
||||
{
|
||||
Comment = options.EntryComment,
|
||||
ModificationTime = options.ModificationDateTime,
|
||||
Crc = 0,
|
||||
Compressed = 0,
|
||||
Decompressed = 0,
|
||||
};
|
||||
|
||||
// Use the archive default setting for zip64 and allow overrides
|
||||
var useZip64 = isZip64;
|
||||
if (options.EnableZip64.HasValue)
|
||||
{
|
||||
useZip64 = options.EnableZip64.Value;
|
||||
}
|
||||
|
||||
var headersize = (uint)WriteHeader(directoryPath, options, entry, useZip64);
|
||||
streamPosition += headersize;
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
private int WriteHeader(
|
||||
string filename,
|
||||
ZipWriterEntryOptions zipWriterEntryOptions,
|
||||
|
||||
116
tests/SharpCompress.Test/ExceptionHierarchyTests.cs
Normal file
116
tests/SharpCompress.Test/ExceptionHierarchyTests.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test;
|
||||
|
||||
public class ExceptionHierarchyTests
|
||||
{
|
||||
[Fact]
|
||||
public void AllSharpCompressExceptions_InheritFromSharpCompressException()
|
||||
{
|
||||
// Verify that ArchiveException inherits from SharpCompressException
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(typeof(ArchiveException)));
|
||||
|
||||
// Verify that ExtractionException inherits from SharpCompressException
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(typeof(ExtractionException)));
|
||||
|
||||
// Verify that InvalidFormatException inherits from SharpCompressException (through ExtractionException)
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(InvalidFormatException))
|
||||
);
|
||||
|
||||
// Verify that CryptographicException inherits from SharpCompressException
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(CryptographicException))
|
||||
);
|
||||
|
||||
// Verify that IncompleteArchiveException inherits from SharpCompressException (through ArchiveException)
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(IncompleteArchiveException))
|
||||
);
|
||||
|
||||
// Verify that ReaderCancelledException inherits from SharpCompressException
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(ReaderCancelledException))
|
||||
);
|
||||
|
||||
// Verify that MultipartStreamRequiredException inherits from SharpCompressException (through ExtractionException)
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(
|
||||
typeof(MultipartStreamRequiredException)
|
||||
)
|
||||
);
|
||||
|
||||
// Verify that MultiVolumeExtractionException inherits from SharpCompressException (through ExtractionException)
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(MultiVolumeExtractionException))
|
||||
);
|
||||
|
||||
// Verify that ZlibException inherits from SharpCompressException
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(typeof(ZlibException)));
|
||||
|
||||
// Verify that XZIndexMarkerReachedException inherits from SharpCompressException
|
||||
Assert.True(
|
||||
typeof(SharpCompressException).IsAssignableFrom(typeof(XZIndexMarkerReachedException))
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SharpCompressException_CanBeCaughtByBaseType()
|
||||
{
|
||||
// Test that a derived exception can be caught as SharpCompressException
|
||||
var exception = new InvalidFormatException("Test message");
|
||||
var caughtException = false;
|
||||
|
||||
try
|
||||
{
|
||||
throw exception;
|
||||
}
|
||||
catch (SharpCompressException ex)
|
||||
{
|
||||
caughtException = true;
|
||||
Assert.Same(exception, ex);
|
||||
}
|
||||
|
||||
Assert.True(caughtException, "Exception should have been caught as SharpCompressException");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InternalLzmaExceptions_InheritFromSharpCompressException()
|
||||
{
|
||||
// Use reflection to verify internal exception types
|
||||
var dataErrorExceptionType = Type.GetType(
|
||||
"SharpCompress.Compressors.LZMA.DataErrorException, SharpCompress"
|
||||
);
|
||||
Assert.NotNull(dataErrorExceptionType);
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(dataErrorExceptionType));
|
||||
|
||||
var invalidParamExceptionType = Type.GetType(
|
||||
"SharpCompress.Compressors.LZMA.InvalidParamException, SharpCompress"
|
||||
);
|
||||
Assert.NotNull(invalidParamExceptionType);
|
||||
Assert.True(typeof(SharpCompressException).IsAssignableFrom(invalidParamExceptionType));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExceptionConstructors_WorkCorrectly()
|
||||
{
|
||||
// Test parameterless constructor
|
||||
var ex1 = new SharpCompressException();
|
||||
Assert.NotNull(ex1);
|
||||
|
||||
// Test message constructor
|
||||
var ex2 = new SharpCompressException("Test message");
|
||||
Assert.Equal("Test message", ex2.Message);
|
||||
|
||||
// Test message and inner exception constructor
|
||||
var inner = new InvalidOperationException("Inner");
|
||||
var ex3 = new SharpCompressException("Test message", inner);
|
||||
Assert.Equal("Test message", ex3.Message);
|
||||
Assert.Same(inner, ex3.InnerException);
|
||||
}
|
||||
}
|
||||
99
tests/SharpCompress.Test/ExtractionTests.cs
Normal file
99
tests/SharpCompress.Test/ExtractionTests.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Writers;
|
||||
using SharpCompress.Writers.Zip;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test;
|
||||
|
||||
public class ExtractionTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void Extraction_ShouldHandleCaseInsensitivePathsOnWindows()
|
||||
{
|
||||
// This test validates that extraction succeeds when Path.GetFullPath returns paths
|
||||
// with casing that matches the platform's file system behavior. On Windows,
|
||||
// Path.GetFullPath can return different casing than the actual directory on disk
|
||||
// (e.g., "system32" vs "System32"), and the extraction should succeed because
|
||||
// Windows file systems are case-insensitive. On Unix-like systems, this test
|
||||
// verifies that the case-sensitive comparison is used correctly.
|
||||
|
||||
var testArchive = Path.Combine(SCRATCH2_FILES_PATH, "test-extraction.zip");
|
||||
var extractPath = SCRATCH_FILES_PATH;
|
||||
|
||||
// Create a simple test archive with a single file
|
||||
using (var stream = File.Create(testArchive))
|
||||
{
|
||||
using var writer = (ZipWriter)
|
||||
WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate);
|
||||
|
||||
// Create a test file to add to the archive
|
||||
var testFilePath = Path.Combine(SCRATCH2_FILES_PATH, "testfile.txt");
|
||||
File.WriteAllText(testFilePath, "Test content");
|
||||
|
||||
writer.Write("testfile.txt", testFilePath);
|
||||
}
|
||||
|
||||
// Extract the archive - this should succeed regardless of path casing
|
||||
using (var stream = File.OpenRead(testArchive))
|
||||
{
|
||||
using var reader = ReaderFactory.Open(stream);
|
||||
|
||||
// This should not throw an exception even if Path.GetFullPath returns
|
||||
// a path with different casing than the actual directory
|
||||
var exception = Record.Exception(() =>
|
||||
reader.WriteAllToDirectory(
|
||||
extractPath,
|
||||
new ExtractionOptions { ExtractFullPath = false, Overwrite = true }
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Null(exception);
|
||||
}
|
||||
|
||||
// Verify the file was extracted successfully
|
||||
var extractedFile = Path.Combine(extractPath, "testfile.txt");
|
||||
Assert.True(File.Exists(extractedFile));
|
||||
Assert.Equal("Test content", File.ReadAllText(extractedFile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Extraction_ShouldPreventPathTraversalAttacks()
|
||||
{
|
||||
// This test ensures that the security check still works to prevent
|
||||
// path traversal attacks (e.g., using "../" to escape the destination directory)
|
||||
|
||||
var testArchive = Path.Combine(SCRATCH2_FILES_PATH, "test-traversal.zip");
|
||||
var extractPath = SCRATCH_FILES_PATH;
|
||||
|
||||
// Create a test archive with a path traversal attempt
|
||||
using (var stream = File.Create(testArchive))
|
||||
{
|
||||
using var writer = (ZipWriter)
|
||||
WriterFactory.Open(stream, ArchiveType.Zip, CompressionType.Deflate);
|
||||
|
||||
var testFilePath = Path.Combine(SCRATCH2_FILES_PATH, "testfile2.txt");
|
||||
File.WriteAllText(testFilePath, "Test content");
|
||||
|
||||
// Try to write with a path that attempts to escape the destination directory
|
||||
writer.Write("../../evil.txt", testFilePath);
|
||||
}
|
||||
|
||||
// Extract the archive - this should throw an exception for path traversal
|
||||
using (var stream = File.OpenRead(testArchive))
|
||||
{
|
||||
using var reader = ReaderFactory.Open(stream);
|
||||
|
||||
var exception = Assert.Throws<ExtractionException>(() =>
|
||||
reader.WriteAllToDirectory(
|
||||
extractPath,
|
||||
new ExtractionOptions { ExtractFullPath = true, Overwrite = true }
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Contains("outside of the destination", exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
tests/SharpCompress.Test/GZip/GZipArchiveDirectoryTests.cs
Normal file
19
tests/SharpCompress.Test/GZip/GZipArchiveDirectoryTests.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Archives.GZip;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.GZip;
|
||||
|
||||
public class GZipArchiveDirectoryTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void GZipArchive_AddDirectoryEntry_ThrowsNotSupportedException()
|
||||
{
|
||||
using var archive = GZipArchive.Create();
|
||||
|
||||
Assert.Throws<NotSupportedException>(() =>
|
||||
archive.AddDirectoryEntry("test-dir", DateTime.Now)
|
||||
);
|
||||
}
|
||||
}
|
||||
19
tests/SharpCompress.Test/GZip/GZipWriterDirectoryTests.cs
Normal file
19
tests/SharpCompress.Test/GZip/GZipWriterDirectoryTests.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Writers.GZip;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.GZip;
|
||||
|
||||
public class GZipWriterDirectoryTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void GZipWriter_WriteDirectory_ThrowsNotSupportedException()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using var writer = new GZipWriter(memoryStream, new GZipWriterOptions());
|
||||
|
||||
Assert.Throws<NotSupportedException>(() => writer.WriteDirectory("test-dir", DateTime.Now));
|
||||
}
|
||||
}
|
||||
112
tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs
Normal file
112
tests/SharpCompress.Test/Tar/TarArchiveDirectoryTests.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Tar;
|
||||
|
||||
public class TarArchiveDirectoryTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void TarArchive_AddDirectoryEntry_CreatesDirectoryEntry()
|
||||
{
|
||||
using var archive = TarArchive.Create();
|
||||
|
||||
archive.AddDirectoryEntry("test-dir", DateTime.Now);
|
||||
|
||||
var entries = archive.Entries.ToList();
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("test-dir", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarArchive_AddDirectoryEntry_MultipleDirectories()
|
||||
{
|
||||
using var archive = TarArchive.Create();
|
||||
|
||||
archive.AddDirectoryEntry("dir1", DateTime.Now);
|
||||
archive.AddDirectoryEntry("dir2", DateTime.Now);
|
||||
archive.AddDirectoryEntry("dir1/subdir", DateTime.Now);
|
||||
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
Assert.Equal(3, entries.Count);
|
||||
Assert.True(entries.All(e => e.IsDirectory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarArchive_AddDirectoryEntry_MixedWithFiles()
|
||||
{
|
||||
using var archive = TarArchive.Create();
|
||||
|
||||
archive.AddDirectoryEntry("dir1", DateTime.Now);
|
||||
|
||||
using var contentStream = new MemoryStream(
|
||||
System.Text.Encoding.UTF8.GetBytes("test content")
|
||||
);
|
||||
archive.AddEntry("dir1/file.txt", contentStream, false, contentStream.Length, DateTime.Now);
|
||||
|
||||
archive.AddDirectoryEntry("dir2", DateTime.Now);
|
||||
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
Assert.Equal(3, entries.Count);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
Assert.False(entries[1].IsDirectory);
|
||||
Assert.True(entries[2].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarArchive_AddDirectoryEntry_SaveAndReload()
|
||||
{
|
||||
var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "tar-directory-test.tar");
|
||||
|
||||
using (var archive = TarArchive.Create())
|
||||
{
|
||||
archive.AddDirectoryEntry("dir1", DateTime.Now);
|
||||
archive.AddDirectoryEntry("dir2", DateTime.Now);
|
||||
|
||||
using var contentStream = new MemoryStream(
|
||||
System.Text.Encoding.UTF8.GetBytes("test content")
|
||||
);
|
||||
archive.AddEntry(
|
||||
"dir1/file.txt",
|
||||
contentStream,
|
||||
false,
|
||||
contentStream.Length,
|
||||
DateTime.Now
|
||||
);
|
||||
|
||||
using (var fileStream = File.Create(scratchPath))
|
||||
{
|
||||
archive.SaveTo(fileStream, CompressionType.None);
|
||||
}
|
||||
}
|
||||
|
||||
using (var archive = TarArchive.Open(scratchPath))
|
||||
{
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
Assert.Equal(3, entries.Count);
|
||||
|
||||
Assert.Equal("dir1/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
|
||||
Assert.Equal("dir1/file.txt", entries[1].Key);
|
||||
Assert.False(entries[1].IsDirectory);
|
||||
|
||||
Assert.Equal("dir2/", entries[2].Key);
|
||||
Assert.True(entries[2].IsDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarArchive_AddDirectoryEntry_DuplicateKey_ThrowsException()
|
||||
{
|
||||
using var archive = TarArchive.Create();
|
||||
|
||||
archive.AddDirectoryEntry("test-dir", DateTime.Now);
|
||||
|
||||
Assert.Throws<ArchiveException>(() => archive.AddDirectoryEntry("test-dir", DateTime.Now));
|
||||
}
|
||||
}
|
||||
164
tests/SharpCompress.Test/Tar/TarWriterDirectoryTests.cs
Normal file
164
tests/SharpCompress.Test/Tar/TarWriterDirectoryTests.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Writers.Tar;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Tar;
|
||||
|
||||
public class TarWriterDirectoryTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void TarWriter_WriteDirectory_CreatesDirectoryEntry()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new TarWriter(
|
||||
memoryStream,
|
||||
new TarWriterOptions(CompressionType.None, true)
|
||||
)
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("test-dir", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = TarArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.ToList();
|
||||
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("test-dir/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarWriter_WriteDirectory_WithTrailingSlash()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new TarWriter(
|
||||
memoryStream,
|
||||
new TarWriterOptions(CompressionType.None, true)
|
||||
)
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("test-dir/", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = TarArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.ToList();
|
||||
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("test-dir/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarWriter_WriteDirectory_WithBackslash()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new TarWriter(
|
||||
memoryStream,
|
||||
new TarWriterOptions(CompressionType.None, true)
|
||||
)
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("test-dir\\subdir", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = TarArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.ToList();
|
||||
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("test-dir/subdir/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarWriter_WriteDirectory_EmptyString_IsSkipped()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new TarWriter(
|
||||
memoryStream,
|
||||
new TarWriterOptions(CompressionType.None, true)
|
||||
)
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = TarArchive.Open(memoryStream);
|
||||
|
||||
Assert.Empty(archive.Entries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarWriter_WriteDirectory_MultipleDirectories()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new TarWriter(
|
||||
memoryStream,
|
||||
new TarWriterOptions(CompressionType.None, true)
|
||||
)
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("dir1", DateTime.Now);
|
||||
writer.WriteDirectory("dir2", DateTime.Now);
|
||||
writer.WriteDirectory("dir1/subdir", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = TarArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
|
||||
Assert.Equal(3, entries.Count);
|
||||
Assert.Equal("dir1/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
Assert.Equal("dir1/subdir/", entries[1].Key);
|
||||
Assert.True(entries[1].IsDirectory);
|
||||
Assert.Equal("dir2/", entries[2].Key);
|
||||
Assert.True(entries[2].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TarWriter_WriteDirectory_MixedWithFiles()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new TarWriter(
|
||||
memoryStream,
|
||||
new TarWriterOptions(CompressionType.None, true)
|
||||
)
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("dir1", DateTime.Now);
|
||||
|
||||
using var contentStream = new MemoryStream(
|
||||
System.Text.Encoding.UTF8.GetBytes("test content")
|
||||
);
|
||||
writer.Write("dir1/file.txt", contentStream, DateTime.Now);
|
||||
|
||||
writer.WriteDirectory("dir2", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = TarArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
|
||||
Assert.Equal(3, entries.Count);
|
||||
Assert.Equal("dir1/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
Assert.Equal("dir1/file.txt", entries[1].Key);
|
||||
Assert.False(entries[1].IsDirectory);
|
||||
Assert.Equal("dir2/", entries[2].Key);
|
||||
Assert.True(entries[2].IsDirectory);
|
||||
}
|
||||
}
|
||||
112
tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs
Normal file
112
tests/SharpCompress.Test/Zip/ZipArchiveDirectoryTests.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Zip;
|
||||
|
||||
public class ZipArchiveDirectoryTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void ZipArchive_AddDirectoryEntry_CreatesDirectoryEntry()
|
||||
{
|
||||
using var archive = ZipArchive.Create();
|
||||
|
||||
archive.AddDirectoryEntry("test-dir", DateTime.Now);
|
||||
|
||||
var entries = archive.Entries.ToList();
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("test-dir", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipArchive_AddDirectoryEntry_MultipleDirectories()
|
||||
{
|
||||
using var archive = ZipArchive.Create();
|
||||
|
||||
archive.AddDirectoryEntry("dir1", DateTime.Now);
|
||||
archive.AddDirectoryEntry("dir2", DateTime.Now);
|
||||
archive.AddDirectoryEntry("dir1/subdir", DateTime.Now);
|
||||
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
Assert.Equal(3, entries.Count);
|
||||
Assert.True(entries.All(e => e.IsDirectory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipArchive_AddDirectoryEntry_MixedWithFiles()
|
||||
{
|
||||
using var archive = ZipArchive.Create();
|
||||
|
||||
archive.AddDirectoryEntry("dir1", DateTime.Now);
|
||||
|
||||
using var contentStream = new MemoryStream(
|
||||
System.Text.Encoding.UTF8.GetBytes("test content")
|
||||
);
|
||||
archive.AddEntry("dir1/file.txt", contentStream, false, contentStream.Length, DateTime.Now);
|
||||
|
||||
archive.AddDirectoryEntry("dir2", DateTime.Now);
|
||||
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
Assert.Equal(3, entries.Count);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
Assert.False(entries[1].IsDirectory);
|
||||
Assert.True(entries[2].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipArchive_AddDirectoryEntry_SaveAndReload()
|
||||
{
|
||||
var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "zip-directory-test.zip");
|
||||
|
||||
using (var archive = ZipArchive.Create())
|
||||
{
|
||||
archive.AddDirectoryEntry("dir1", DateTime.Now);
|
||||
archive.AddDirectoryEntry("dir2", DateTime.Now);
|
||||
|
||||
using var contentStream = new MemoryStream(
|
||||
System.Text.Encoding.UTF8.GetBytes("test content")
|
||||
);
|
||||
archive.AddEntry(
|
||||
"dir1/file.txt",
|
||||
contentStream,
|
||||
false,
|
||||
contentStream.Length,
|
||||
DateTime.Now
|
||||
);
|
||||
|
||||
using (var fileStream = File.Create(scratchPath))
|
||||
{
|
||||
archive.SaveTo(fileStream, CompressionType.Deflate);
|
||||
}
|
||||
}
|
||||
|
||||
using (var archive = ZipArchive.Open(scratchPath))
|
||||
{
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
Assert.Equal(3, entries.Count);
|
||||
|
||||
Assert.Equal("dir1/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
|
||||
Assert.Equal("dir1/file.txt", entries[1].Key);
|
||||
Assert.False(entries[1].IsDirectory);
|
||||
|
||||
Assert.Equal("dir2/", entries[2].Key);
|
||||
Assert.True(entries[2].IsDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipArchive_AddDirectoryEntry_DuplicateKey_ThrowsException()
|
||||
{
|
||||
using var archive = ZipArchive.Create();
|
||||
|
||||
archive.AddDirectoryEntry("test-dir", DateTime.Now);
|
||||
|
||||
Assert.Throws<ArchiveException>(() => archive.AddDirectoryEntry("test-dir", DateTime.Now));
|
||||
}
|
||||
}
|
||||
146
tests/SharpCompress.Test/Zip/ZipWriterDirectoryTests.cs
Normal file
146
tests/SharpCompress.Test/Zip/ZipWriterDirectoryTests.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Writers.Zip;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Zip;
|
||||
|
||||
public class ZipWriterDirectoryTests : TestBase
|
||||
{
|
||||
[Fact]
|
||||
public void ZipWriter_WriteDirectory_CreatesDirectoryEntry()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new ZipWriter(memoryStream, new ZipWriterOptions(CompressionType.Deflate))
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("test-dir", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = ZipArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.ToList();
|
||||
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("test-dir/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipWriter_WriteDirectory_WithTrailingSlash()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new ZipWriter(memoryStream, new ZipWriterOptions(CompressionType.Deflate))
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("test-dir/", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = ZipArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.ToList();
|
||||
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("test-dir/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipWriter_WriteDirectory_WithBackslash()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new ZipWriter(memoryStream, new ZipWriterOptions(CompressionType.Deflate))
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("test-dir\\subdir", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = ZipArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.ToList();
|
||||
|
||||
Assert.Single(entries);
|
||||
Assert.Equal("test-dir/subdir/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipWriter_WriteDirectory_EmptyString_IsSkipped()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new ZipWriter(memoryStream, new ZipWriterOptions(CompressionType.Deflate))
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = ZipArchive.Open(memoryStream);
|
||||
|
||||
Assert.Empty(archive.Entries);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipWriter_WriteDirectory_MultipleDirectories()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new ZipWriter(memoryStream, new ZipWriterOptions(CompressionType.Deflate))
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("dir1", DateTime.Now);
|
||||
writer.WriteDirectory("dir2", DateTime.Now);
|
||||
writer.WriteDirectory("dir1/subdir", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = ZipArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
|
||||
Assert.Equal(3, entries.Count);
|
||||
Assert.Equal("dir1/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
Assert.Equal("dir1/subdir/", entries[1].Key);
|
||||
Assert.True(entries[1].IsDirectory);
|
||||
Assert.Equal("dir2/", entries[2].Key);
|
||||
Assert.True(entries[2].IsDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZipWriter_WriteDirectory_MixedWithFiles()
|
||||
{
|
||||
using var memoryStream = new MemoryStream();
|
||||
using (
|
||||
var writer = new ZipWriter(memoryStream, new ZipWriterOptions(CompressionType.Deflate))
|
||||
)
|
||||
{
|
||||
writer.WriteDirectory("dir1", DateTime.Now);
|
||||
|
||||
using var contentStream = new MemoryStream(
|
||||
System.Text.Encoding.UTF8.GetBytes("test content")
|
||||
);
|
||||
writer.Write("dir1/file.txt", contentStream, DateTime.Now);
|
||||
|
||||
writer.WriteDirectory("dir2", DateTime.Now);
|
||||
}
|
||||
|
||||
memoryStream.Position = 0;
|
||||
using var archive = ZipArchive.Open(memoryStream);
|
||||
var entries = archive.Entries.OrderBy(e => e.Key).ToList();
|
||||
|
||||
Assert.Equal(3, entries.Count);
|
||||
Assert.Equal("dir1/", entries[0].Key);
|
||||
Assert.True(entries[0].IsDirectory);
|
||||
Assert.Equal("dir1/file.txt", entries[1].Key);
|
||||
Assert.False(entries[1].IsDirectory);
|
||||
Assert.Equal("dir2/", entries[2].Key);
|
||||
Assert.True(entries[2].IsDirectory);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user