2025-10-27 10:46:08 +00:00
|
|
|
using System;
|
2026-05-29 11:58:38 +00:00
|
|
|
using System.Collections.Generic;
|
2025-10-27 10:46:08 +00:00
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using SharpCompress.Archives;
|
|
|
|
|
using SharpCompress.Archives.Tar;
|
|
|
|
|
using SharpCompress.Common;
|
|
|
|
|
using SharpCompress.Readers;
|
|
|
|
|
using SharpCompress.Readers.Tar;
|
2026-01-13 14:28:45 +00:00
|
|
|
using SharpCompress.Test.Mocks;
|
2025-10-27 10:46:08 +00:00
|
|
|
using SharpCompress.Writers;
|
|
|
|
|
using SharpCompress.Writers.Tar;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
|
|
|
|
namespace SharpCompress.Test.Tar;
|
|
|
|
|
|
|
|
|
|
public class TarArchiveAsyncTests : ArchiveTests
|
|
|
|
|
{
|
|
|
|
|
public TarArchiveAsyncTests() => UseExtensionInsteadOfNameToVerify = true;
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask TarArchiveStreamRead_Async() => await ArchiveStreamReadAsync("Tar.tar");
|
2025-10-27 10:46:08 +00:00
|
|
|
|
2026-04-21 08:25:16 +01:00
|
|
|
[Fact]
|
2026-04-23 09:56:26 +01:00
|
|
|
public async ValueTask TarArchiveOpenAsyncStream_Throws_On_NonSeekable_Stream()
|
2026-04-21 08:25:16 +01:00
|
|
|
{
|
2026-05-15 11:41:26 +01:00
|
|
|
using Stream stream = new ForwardOnlyStream(
|
2026-04-21 08:25:16 +01:00
|
|
|
File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await Assert.ThrowsAsync<ArgumentException>(async () =>
|
|
|
|
|
await TarArchive.OpenAsyncArchive(new AsyncOnlyStream(stream))
|
2026-05-15 11:41:26 +01:00
|
|
|
);
|
2026-04-23 09:56:26 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 10:19:37 +01:00
|
|
|
[Fact]
|
|
|
|
|
public async ValueTask TarArchiveOpenAsyncStream_Throws_On_Unreadable_Stream()
|
|
|
|
|
{
|
|
|
|
|
using var stream = new TestStream(new MemoryStream(), false, true, true);
|
|
|
|
|
|
|
|
|
|
await Assert.ThrowsAsync<ArgumentException>(() =>
|
|
|
|
|
TarArchive.OpenAsyncArchive(stream).AsTask()
|
2026-04-21 08:25:16 +01:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 10:46:08 +00:00
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Tar_FileName_Exactly_100_Characters_Async()
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
var archive = "Tar_FileName_Exactly_100_Characters.tar";
|
|
|
|
|
|
|
|
|
|
// create the 100 char filename
|
|
|
|
|
var filename =
|
|
|
|
|
"filename_with_exactly_100_characters_______________________________________________________________X";
|
|
|
|
|
|
|
|
|
|
// Step 1: create a tar file containing a file with the test name
|
|
|
|
|
using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive)))
|
|
|
|
|
{
|
2026-04-28 10:24:36 +00:00
|
|
|
await using (
|
2026-02-12 11:53:19 +00:00
|
|
|
var writer = await WriterFactory.OpenAsyncWriter(
|
2026-01-22 16:24:07 +00:00
|
|
|
new AsyncOnlyStream(stream),
|
|
|
|
|
ArchiveType.Tar,
|
|
|
|
|
new WriterOptions(CompressionType.None) { LeaveStreamOpen = false }
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
using (Stream inputStream = new MemoryStream())
|
|
|
|
|
{
|
|
|
|
|
var sw = new StreamWriter(inputStream);
|
|
|
|
|
await sw.WriteAsync("dummy filecontent");
|
|
|
|
|
await sw.FlushAsync();
|
2025-10-27 10:46:08 +00:00
|
|
|
|
2026-01-22 16:24:07 +00:00
|
|
|
inputStream.Position = 0;
|
|
|
|
|
await writer.WriteAsync(filename, inputStream, null);
|
|
|
|
|
}
|
2025-10-27 10:46:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 2: check if the written tar file can be read correctly
|
|
|
|
|
var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive);
|
2026-01-16 09:50:36 +00:00
|
|
|
await using (
|
2026-02-12 10:18:43 +00:00
|
|
|
var archive2 = await TarArchive.OpenAsyncArchive(
|
2026-01-22 16:24:07 +00:00
|
|
|
new AsyncOnlyStream(File.OpenRead(unmodified)),
|
2026-04-21 10:55:59 +01:00
|
|
|
ReaderOptions.ForExternalStream.WithLeaveStreamOpen(false)
|
2026-01-16 09:50:36 +00:00
|
|
|
)
|
|
|
|
|
)
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-01-13 14:28:45 +00:00
|
|
|
Assert.Equal(1, await archive2.EntriesAsync.CountAsync());
|
2026-01-13 14:29:10 +00:00
|
|
|
Assert.Contains(
|
|
|
|
|
filename,
|
|
|
|
|
await archive2.EntriesAsync.Select(entry => entry.Key).ToListAsync()
|
|
|
|
|
);
|
2025-10-27 10:46:08 +00:00
|
|
|
|
2026-01-13 14:28:45 +00:00
|
|
|
await foreach (var entry in archive2.EntriesAsync)
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-02-13 14:26:54 +00:00
|
|
|
using (var sr = new StreamReader(await entry.OpenEntryStreamAsync()))
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal("dummy filecontent", await sr.ReadLineAsync());
|
|
|
|
|
}
|
2025-10-27 10:46:08 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Tar_VeryLongFilepathReadback_Async()
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
var archive = "Tar_VeryLongFilepathReadback.tar";
|
|
|
|
|
|
|
|
|
|
// create a very long filename
|
|
|
|
|
var longFilename = "";
|
|
|
|
|
for (var i = 0; i < 600; i = longFilename.Length)
|
|
|
|
|
{
|
|
|
|
|
longFilename += i.ToString("D10") + "-";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
longFilename += ".txt";
|
|
|
|
|
|
|
|
|
|
// Step 1: create a tar file containing a file with a long name
|
|
|
|
|
using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive)))
|
2026-04-28 10:24:36 +00:00
|
|
|
await using (
|
2026-02-12 11:53:19 +00:00
|
|
|
var writer = await WriterFactory.OpenAsyncWriter(
|
2026-01-16 09:50:36 +00:00
|
|
|
new AsyncOnlyStream(stream),
|
2026-01-15 15:20:52 +00:00
|
|
|
ArchiveType.Tar,
|
2026-01-22 16:24:07 +00:00
|
|
|
new WriterOptions(CompressionType.None) { LeaveStreamOpen = false }
|
2026-01-15 15:20:52 +00:00
|
|
|
)
|
|
|
|
|
)
|
2025-10-27 10:46:08 +00:00
|
|
|
using (Stream inputStream = new MemoryStream())
|
|
|
|
|
{
|
|
|
|
|
var sw = new StreamWriter(inputStream);
|
|
|
|
|
await sw.WriteAsync("dummy filecontent");
|
|
|
|
|
await sw.FlushAsync();
|
|
|
|
|
|
|
|
|
|
inputStream.Position = 0;
|
|
|
|
|
await writer.WriteAsync(longFilename, inputStream, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Step 2: check if the written tar file can be read correctly
|
|
|
|
|
var unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive);
|
2026-01-16 09:50:36 +00:00
|
|
|
await using (
|
2026-02-12 10:18:43 +00:00
|
|
|
var archive2 = await TarArchive.OpenAsyncArchive(
|
2026-01-22 16:24:07 +00:00
|
|
|
new AsyncOnlyStream(File.OpenRead(unmodified)),
|
2026-04-21 10:55:59 +01:00
|
|
|
ReaderOptions.ForExternalStream.WithLeaveStreamOpen(false)
|
2026-01-16 09:50:36 +00:00
|
|
|
)
|
|
|
|
|
)
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-01-13 14:28:45 +00:00
|
|
|
Assert.Equal(1, await archive2.EntriesAsync.CountAsync());
|
2026-01-13 14:29:10 +00:00
|
|
|
Assert.Contains(
|
|
|
|
|
longFilename,
|
|
|
|
|
await archive2.EntriesAsync.Select(entry => entry.Key).ToListAsync()
|
|
|
|
|
);
|
2025-10-27 10:46:08 +00:00
|
|
|
|
2026-01-13 14:28:45 +00:00
|
|
|
await foreach (var entry in archive2.EntriesAsync)
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-02-13 14:26:54 +00:00
|
|
|
using (var sr = new StreamReader(await entry.OpenEntryStreamAsync()))
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal("dummy filecontent", await sr.ReadLineAsync());
|
|
|
|
|
}
|
2025-10-27 10:46:08 +00:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-22 16:24:07 +00:00
|
|
|
#if LEGACY_DOTNET
|
|
|
|
|
//add a delay because old .net sucks on DisposeAsync
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(1));
|
|
|
|
|
#endif
|
2025-10-27 10:46:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Tar_Create_New_Async()
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.tar");
|
|
|
|
|
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar");
|
|
|
|
|
|
2026-02-12 10:18:43 +00:00
|
|
|
await using (var archive = await TarArchive.CreateAsyncArchive())
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-01-18 14:57:01 +00:00
|
|
|
await archive.AddAllFromDirectoryAsync(ORIGINAL_FILES_PATH);
|
2026-02-06 15:16:45 +00:00
|
|
|
var twopt = new TarWriterOptions(CompressionType.None, true)
|
|
|
|
|
{
|
|
|
|
|
ArchiveEncoding = new ArchiveEncoding { Default = Encoding.GetEncoding(866) },
|
|
|
|
|
};
|
2025-10-27 10:46:08 +00:00
|
|
|
await archive.SaveToAsync(scratchPath, twopt);
|
|
|
|
|
}
|
|
|
|
|
CompareArchivesByPath(unmodified, scratchPath);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 13:12:11 +00:00
|
|
|
[Fact]
|
|
|
|
|
public async ValueTask Tar_Async_Dispose_Closes_New_Entry_Stream()
|
|
|
|
|
{
|
|
|
|
|
var entryStream = new TestStream(new MemoryStream(Encoding.UTF8.GetBytes("test")));
|
|
|
|
|
|
|
|
|
|
await using (var archive = await TarArchive.CreateAsyncArchive())
|
|
|
|
|
{
|
|
|
|
|
await archive.AddEntryAsync(
|
|
|
|
|
"test.txt",
|
|
|
|
|
entryStream,
|
|
|
|
|
closeStream: true,
|
|
|
|
|
size: entryStream.Length
|
|
|
|
|
);
|
|
|
|
|
await archive.SaveToAsync(
|
|
|
|
|
new MemoryStream(),
|
|
|
|
|
new TarWriterOptions(CompressionType.None, true)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.True(entryStream.IsDisposed);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-27 10:46:08 +00:00
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Tar_Random_Write_Add_Async()
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
var jpg = Path.Combine(ORIGINAL_FILES_PATH, "jpg", "test.jpg");
|
|
|
|
|
var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.mod.tar");
|
|
|
|
|
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar");
|
|
|
|
|
var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar");
|
|
|
|
|
|
2026-02-12 10:18:43 +00:00
|
|
|
await using (var archive = await TarArchive.OpenAsyncArchive(unmodified))
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-01-18 14:57:01 +00:00
|
|
|
await archive.AddEntryAsync("jpg\\test.jpg", jpg);
|
2026-02-10 10:48:42 +00:00
|
|
|
await archive.SaveToAsync(
|
|
|
|
|
scratchPath,
|
|
|
|
|
new TarWriterOptions(CompressionType.None, true)
|
|
|
|
|
);
|
2025-10-27 10:46:08 +00:00
|
|
|
}
|
|
|
|
|
CompareArchivesByPath(modified, scratchPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Tar_Random_Write_Remove_Async()
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
var scratchPath = Path.Combine(SCRATCH_FILES_PATH, "Tar.mod.tar");
|
|
|
|
|
var modified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.mod.tar");
|
|
|
|
|
var unmodified = Path.Combine(TEST_ARCHIVES_PATH, "Tar.noEmptyDirs.tar");
|
|
|
|
|
|
2026-02-12 10:18:43 +00:00
|
|
|
await using (var archive = await TarArchive.OpenAsyncArchive(unmodified))
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-01-12 14:57:13 +00:00
|
|
|
var entry = await archive.EntriesAsync.SingleAsync(x =>
|
2025-10-27 10:46:08 +00:00
|
|
|
x.Key.NotNull().EndsWith("jpg", StringComparison.OrdinalIgnoreCase)
|
|
|
|
|
);
|
2026-01-18 15:07:02 +00:00
|
|
|
await archive.RemoveEntryAsync(entry);
|
2026-02-10 10:48:42 +00:00
|
|
|
await archive.SaveToAsync(
|
|
|
|
|
scratchPath,
|
|
|
|
|
new TarWriterOptions(CompressionType.None, true)
|
|
|
|
|
);
|
2025-10-27 10:46:08 +00:00
|
|
|
}
|
|
|
|
|
CompareArchivesByPath(modified, scratchPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
|
|
|
|
[InlineData(10)]
|
|
|
|
|
[InlineData(128)]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Tar_Japanese_Name_Async(int length)
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
using var mstm = new MemoryStream();
|
|
|
|
|
var enc = new ArchiveEncoding { Default = Encoding.UTF8 };
|
2026-02-06 15:16:45 +00:00
|
|
|
var twopt = new TarWriterOptions(CompressionType.None, true) { ArchiveEncoding = enc };
|
2025-10-27 10:46:08 +00:00
|
|
|
var fname = new string((char)0x3042, length);
|
|
|
|
|
using (var tw = new TarWriter(mstm, twopt))
|
|
|
|
|
using (var input = new MemoryStream(new byte[32]))
|
|
|
|
|
{
|
|
|
|
|
await tw.WriteAsync(fname, input, null);
|
|
|
|
|
}
|
|
|
|
|
using (var inputMemory = new MemoryStream(mstm.ToArray()))
|
|
|
|
|
{
|
2026-04-21 11:04:14 +01:00
|
|
|
var tropt = ReaderOptions.ForExternalStream.WithArchiveEncoding(enc);
|
2026-01-16 09:50:36 +00:00
|
|
|
await using (
|
|
|
|
|
var tr = await ReaderFactory.OpenAsyncReader(
|
|
|
|
|
new AsyncOnlyStream(inputMemory),
|
|
|
|
|
tropt
|
|
|
|
|
)
|
|
|
|
|
)
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-01-13 14:28:45 +00:00
|
|
|
while (await tr.MoveToNextEntryAsync())
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
Assert.Equal(fname, tr.Entry.Key);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-01-08 12:35:12 +00:00
|
|
|
public async ValueTask Tar_Read_One_At_A_Time_Async()
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
var archiveEncoding = new ArchiveEncoding { Default = Encoding.UTF8 };
|
|
|
|
|
var tarWriterOptions = new TarWriterOptions(CompressionType.None, true)
|
|
|
|
|
{
|
|
|
|
|
ArchiveEncoding = archiveEncoding,
|
|
|
|
|
};
|
|
|
|
|
var testBytes = Encoding.UTF8.GetBytes("This is a test.");
|
|
|
|
|
|
|
|
|
|
using var memoryStream = new MemoryStream();
|
|
|
|
|
using (var tarWriter = new TarWriter(memoryStream, tarWriterOptions))
|
|
|
|
|
using (var testFileStream = new MemoryStream(testBytes))
|
|
|
|
|
{
|
|
|
|
|
await tarWriter.WriteAsync("test1.txt", testFileStream, null);
|
|
|
|
|
testFileStream.Position = 0;
|
|
|
|
|
await tarWriter.WriteAsync("test2.txt", testFileStream, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memoryStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
var numberOfEntries = 0;
|
|
|
|
|
|
2026-01-16 09:50:36 +00:00
|
|
|
await using (
|
2026-02-12 09:48:06 +00:00
|
|
|
var archiveFactory = await ArchiveFactory.OpenAsyncArchive(
|
|
|
|
|
new AsyncOnlyStream(memoryStream)
|
|
|
|
|
)
|
2026-01-16 09:50:36 +00:00
|
|
|
)
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
2026-01-13 14:29:10 +00:00
|
|
|
await foreach (var entry in archiveFactory.EntriesAsync)
|
2025-10-27 10:46:08 +00:00
|
|
|
{
|
|
|
|
|
++numberOfEntries;
|
|
|
|
|
|
2026-02-04 13:25:05 +00:00
|
|
|
#if LEGACY_DOTNET
|
2026-01-08 16:24:11 +00:00
|
|
|
using var tarEntryStream = await entry.OpenEntryStreamAsync();
|
2026-02-04 13:25:05 +00:00
|
|
|
#else
|
|
|
|
|
await using var tarEntryStream = await entry.OpenEntryStreamAsync();
|
|
|
|
|
#endif
|
2025-10-27 10:46:08 +00:00
|
|
|
using var testFileStream = new MemoryStream();
|
|
|
|
|
await tarEntryStream.CopyToAsync(testFileStream);
|
|
|
|
|
Assert.Equal(testBytes.Length, testFileStream.Length);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.Equal(2, numberOfEntries);
|
|
|
|
|
}
|
2026-05-29 11:58:38 +00:00
|
|
|
|
|
|
|
|
[Fact]
|
2026-04-19 15:17:35 +01:00
|
|
|
public async ValueTask Tar_PaxLocalHeader_Archive_Async()
|
|
|
|
|
{
|
|
|
|
|
var archivePath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.PaxLocalHeader.tar");
|
|
|
|
|
await using var archive = await TarArchive.OpenAsyncArchive(
|
|
|
|
|
new AsyncOnlyStream(File.OpenRead(archivePath))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var firstEntry = (TarArchiveEntry)
|
|
|
|
|
await archive.EntriesAsync.SingleAsync(entry => entry.Key == "pax/overridden-name.txt");
|
|
|
|
|
Assert.Equal(10, firstEntry.Size);
|
|
|
|
|
Assert.Equal(1234, firstEntry.UserID);
|
|
|
|
|
Assert.Equal(2345, firstEntry.GroupId);
|
|
|
|
|
Assert.Equal(Convert.ToInt64("640", 8), firstEntry.Mode);
|
|
|
|
|
|
|
|
|
|
var expectedTime = DateTimeOffset.FromUnixTimeSeconds(1700000000).LocalDateTime;
|
|
|
|
|
Assert.Equal(expectedTime, firstEntry.LastModifiedTime);
|
|
|
|
|
|
|
|
|
|
var secondEntry = (TarArchiveEntry)
|
|
|
|
|
await archive.EntriesAsync.SingleAsync(entry => entry.Key == "second.txt");
|
|
|
|
|
Assert.Equal(2, secondEntry.Size);
|
|
|
|
|
Assert.Equal(11, secondEntry.UserID);
|
|
|
|
|
Assert.Equal(22, secondEntry.GroupId);
|
|
|
|
|
Assert.Equal(Convert.ToInt64("644", 8), secondEntry.Mode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async ValueTask Tar_PaxLocalHeader_Link_Archive_Async()
|
|
|
|
|
{
|
|
|
|
|
var archivePath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.PaxLocalHeader.Link.tar");
|
|
|
|
|
await using var archive = await TarArchive.OpenAsyncArchive(
|
|
|
|
|
new AsyncOnlyStream(File.OpenRead(archivePath))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var entry = (TarArchiveEntry)await archive.EntriesAsync.SingleAsync();
|
|
|
|
|
Assert.Equal("pax/link-entry", entry.Key);
|
|
|
|
|
Assert.Equal("pax/target-entry", entry.LinkTarget);
|
|
|
|
|
}
|
2026-04-20 08:26:11 +01:00
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async ValueTask Tar_PaxGlobalHeader_Archive_Async()
|
|
|
|
|
{
|
|
|
|
|
var archivePath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.PaxGlobalHeader.tar");
|
|
|
|
|
await using var archive = await TarArchive.OpenAsyncArchive(
|
|
|
|
|
new AsyncOnlyStream(File.OpenRead(archivePath))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var globalTime = DateTimeOffset.FromUnixTimeSeconds(1700000100).LocalDateTime;
|
|
|
|
|
var localOverrideTime = DateTimeOffset.FromUnixTimeSeconds(1700000200).LocalDateTime;
|
|
|
|
|
|
|
|
|
|
var firstEntry = (TarArchiveEntry)
|
|
|
|
|
await archive.EntriesAsync.SingleAsync(entry => entry.Key == "global-one.txt");
|
|
|
|
|
Assert.Equal(4000, firstEntry.UserID);
|
|
|
|
|
Assert.Equal(5000, firstEntry.GroupId);
|
|
|
|
|
Assert.Equal(Convert.ToInt64("640", 8), firstEntry.Mode);
|
|
|
|
|
Assert.Equal(globalTime, firstEntry.LastModifiedTime);
|
|
|
|
|
|
|
|
|
|
var secondEntry = (TarArchiveEntry)
|
|
|
|
|
await archive.EntriesAsync.SingleAsync(entry =>
|
|
|
|
|
entry.Key == "global-local-override.txt"
|
|
|
|
|
);
|
|
|
|
|
Assert.Equal(4010, secondEntry.UserID);
|
|
|
|
|
Assert.Equal(5010, secondEntry.GroupId);
|
|
|
|
|
Assert.Equal(Convert.ToInt64("600", 8), secondEntry.Mode);
|
|
|
|
|
Assert.Equal(localOverrideTime, secondEntry.LastModifiedTime);
|
|
|
|
|
|
|
|
|
|
var thirdEntry = (TarArchiveEntry)
|
|
|
|
|
await archive.EntriesAsync.SingleAsync(entry => entry.Key == "global-three.txt");
|
|
|
|
|
Assert.Equal(4000, thirdEntry.UserID);
|
|
|
|
|
Assert.Equal(5000, thirdEntry.GroupId);
|
|
|
|
|
Assert.Equal(Convert.ToInt64("640", 8), thirdEntry.Mode);
|
|
|
|
|
Assert.Equal(globalTime, thirdEntry.LastModifiedTime);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public async ValueTask Tar_PaxGlobalHeader_Link_Archive_Async()
|
|
|
|
|
{
|
|
|
|
|
var archivePath = Path.Combine(TEST_ARCHIVES_PATH, "Tar.PaxGlobalHeader.Link.tar");
|
|
|
|
|
await using var archive = await TarArchive.OpenAsyncArchive(
|
|
|
|
|
new AsyncOnlyStream(File.OpenRead(archivePath))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
var globalLink = (TarArchiveEntry)
|
|
|
|
|
await archive.EntriesAsync.SingleAsync(entry => entry.Key == "global-link");
|
|
|
|
|
Assert.Equal("global-target", globalLink.LinkTarget);
|
|
|
|
|
Assert.Equal(4100, globalLink.UserID);
|
|
|
|
|
Assert.Equal(5100, globalLink.GroupId);
|
|
|
|
|
Assert.Equal(Convert.ToInt64("777", 8), globalLink.Mode);
|
|
|
|
|
|
|
|
|
|
var localOverrideLink = (TarArchiveEntry)
|
|
|
|
|
await archive.EntriesAsync.SingleAsync(entry => entry.Key == "local-link-override");
|
|
|
|
|
Assert.Equal("local-target", localOverrideLink.LinkTarget);
|
|
|
|
|
Assert.Equal(4100, localOverrideLink.UserID);
|
|
|
|
|
Assert.Equal(5100, localOverrideLink.GroupId);
|
|
|
|
|
Assert.Equal(Convert.ToInt64("777", 8), localOverrideLink.Mode);
|
2026-05-29 15:11:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-05-29 11:58:38 +00:00
|
|
|
public async ValueTask Tar_Read_One_At_A_Time_Without_Disposing_Entry_Stream_Async()
|
|
|
|
|
{
|
|
|
|
|
var archiveEncoding = new ArchiveEncoding { Default = Encoding.UTF8 };
|
|
|
|
|
var tarWriterOptions = new TarWriterOptions(CompressionType.None, true)
|
|
|
|
|
{
|
|
|
|
|
ArchiveEncoding = archiveEncoding,
|
|
|
|
|
};
|
|
|
|
|
var testBytes = Encoding.UTF8.GetBytes("This is a test.");
|
|
|
|
|
|
|
|
|
|
using var memoryStream = new MemoryStream();
|
|
|
|
|
using (var tarWriter = new TarWriter(memoryStream, tarWriterOptions))
|
|
|
|
|
using (var testFileStream = new MemoryStream(testBytes))
|
|
|
|
|
{
|
|
|
|
|
await tarWriter.WriteAsync("file0.txt", testFileStream, null);
|
|
|
|
|
testFileStream.Position = 0;
|
|
|
|
|
await tarWriter.WriteAsync("file1.txt", testFileStream, null);
|
|
|
|
|
tarWriter.WriteDirectory("folder0", null);
|
|
|
|
|
testFileStream.Position = 0;
|
|
|
|
|
await tarWriter.WriteAsync("folder0/file_in_folder0.txt", testFileStream, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
memoryStream.Position = 0;
|
|
|
|
|
|
|
|
|
|
var entryKeys = new List<string?>();
|
|
|
|
|
var openEntryStreams = new List<Stream>();
|
|
|
|
|
|
|
|
|
|
await using (
|
|
|
|
|
var archive = await TarArchive.OpenAsyncArchive(
|
|
|
|
|
new AsyncOnlyStream(memoryStream),
|
|
|
|
|
ReaderOptions.ForExternalStream
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
await foreach (var entry in archive.EntriesAsync)
|
|
|
|
|
{
|
|
|
|
|
entryKeys.Add(entry.Key);
|
|
|
|
|
if (entry.IsDirectory)
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tarEntryStream = await entry.OpenEntryStreamAsync();
|
|
|
|
|
openEntryStreams.Add(tarEntryStream);
|
|
|
|
|
|
|
|
|
|
using var testFileStream = new MemoryStream();
|
|
|
|
|
await tarEntryStream.CopyToAsync(testFileStream);
|
|
|
|
|
Assert.Equal(testBytes.Length, testFileStream.Length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Assert.Equal(4, await archive.EntriesAsync.CountAsync());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
openEntryStreams.ForEach(stream => stream.Dispose());
|
|
|
|
|
|
|
|
|
|
Assert.Equal(
|
|
|
|
|
["file0.txt", "file1.txt", "folder0/", "folder0/file_in_folder0.txt"],
|
|
|
|
|
entryKeys
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-10-27 10:46:08 +00:00
|
|
|
}
|