public async Task Extract_UnseekableStream_BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize)
        {
            byte[] fileContents = new byte[contentSize];
            Array.Fill <byte>(fileContents, 0x1);

            using var archive = new MemoryStream();
            using (var compressor = new GZipStream(archive, CompressionMode.Compress, leaveOpen: true))
            {
                using var writer = new TarWriter(compressor);
                var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file");
                entry1.DataStream = new MemoryStream(fileContents);
                await writer.WriteEntryAsync(entry1);

                var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file");
                await writer.WriteEntryAsync(entry2);
            }

            archive.Position       = 0;
            using var decompressor = new GZipStream(archive, CompressionMode.Decompress);
            using var reader       = new TarReader(decompressor);

            using TempDirectory destination = new TempDirectory();
            await TarFile.ExtractToDirectoryAsync(decompressor, destination.Path, overwriteFiles : true);

            Assert.Equal(2, Directory.GetFileSystemEntries(destination.Path, "*", SearchOption.AllDirectories).Count());
        }
Esempio n. 2
0
        public async Task ReadAndWriteMultipleGlobalExtendedAttributesEntries_Async(TarEntryFormat format)
        {
            Dictionary <string, string> attrs = new Dictionary <string, string>()
            {
                { "hello", "world" },
                { "dotnet", "runtime" }
            };

            using MemoryStream archiveStream = new MemoryStream();
            await using (TarWriter writer = new TarWriter(archiveStream, leaveOpen: true))
            {
                PaxGlobalExtendedAttributesTarEntry gea1 = new PaxGlobalExtendedAttributesTarEntry(attrs);
                await writer.WriteEntryAsync(gea1);

                TarEntry entry1 = InvokeTarEntryCreationConstructor(format, TarEntryType.Directory, "dir1");
                await writer.WriteEntryAsync(entry1);

                PaxGlobalExtendedAttributesTarEntry gea2 = new PaxGlobalExtendedAttributesTarEntry(attrs);
                await writer.WriteEntryAsync(gea2);

                TarEntry entry2 = InvokeTarEntryCreationConstructor(format, TarEntryType.Directory, "dir2");
                await writer.WriteEntryAsync(entry2);
            }

            archiveStream.Position = 0;
            await using (TarReader reader = new TarReader(archiveStream, leaveOpen: false))
            {
                VerifyGlobalExtendedAttributesEntry(await reader.GetNextEntryAsync(), attrs);
                VerifyDirectory(await reader.GetNextEntryAsync(), format, "dir1");
                VerifyGlobalExtendedAttributesEntry(await reader.GetNextEntryAsync(), attrs);
                VerifyDirectory(await reader.GetNextEntryAsync(), format, "dir2");
                Assert.Null(await reader.GetNextEntryAsync());
            }
        }
Esempio n. 3
0
        public async Task FileName_NullOrEmpty_Async()
        {
            using MemoryStream archiveStream = new MemoryStream();
            await using (TarWriter writer = new TarWriter(archiveStream))
            {
                await Assert.ThrowsAsync <ArgumentNullException>(() => writer.WriteEntryAsync(null, "entryName"));

                await Assert.ThrowsAsync <ArgumentException>(() => writer.WriteEntryAsync(string.Empty, "entryName"));
            }
        }
        public async Task GetNextEntry_UnseekableArchive_ReplaceDataStream_ExcludeFromDisposing_Async(bool copyData)
        {
            await using (MemoryStream archive = new MemoryStream())
            {
                await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true))
                {
                    UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt");
                    entry1.DataStream = new MemoryStream();
                    using (StreamWriter streamWriter = new StreamWriter(entry1.DataStream, leaveOpen: true))
                    {
                        streamWriter.WriteLine("Hello world!");
                    }
                    entry1.DataStream.Seek(0, SeekOrigin.Begin); // Rewind to ensure it gets written from the beginning
                    await writer.WriteEntryAsync(entry1);

                    UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir");
                    await writer.WriteEntryAsync(entry2);
                }

                archive.Seek(0, SeekOrigin.Begin);
                await using (WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false))
                {
                    UstarTarEntry entry;
                    Stream        oldStream;
                    await using (TarReader reader = new TarReader(wrapped)) // Unseekable
                    {
                        entry = await reader.GetNextEntryAsync(copyData) as UstarTarEntry;

                        Assert.NotNull(entry);
                        Assert.Equal(TarEntryType.RegularFile, entry.EntryType);

                        oldStream = entry.DataStream;

                        entry.DataStream = new MemoryStream(); // Substitution, setter should dispose the previous stream
                        using (StreamWriter streamWriter = new StreamWriter(entry.DataStream, leaveOpen: true))
                        {
                            streamWriter.WriteLine("Substituted");
                        }
                    } // Disposing reader should not dispose the substituted DataStream

                    Assert.Throws <ObjectDisposedException>(() => oldStream.Read(new byte[1]));

                    entry.DataStream.Seek(0, SeekOrigin.Begin);
                    using (StreamReader streamReader = new StreamReader(entry.DataStream))
                    {
                        Assert.Equal("Substituted", streamReader.ReadLine());
                    }
                }
            }
        }
        public async Task GetNextEntry_CopyDataTrue_UnseekableArchive_Async()
        {
            string expectedText = "Hello world!";

            await using (MemoryStream archive = new MemoryStream())
            {
                await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true))
                {
                    UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt");
                    entry1.DataStream = new MemoryStream();
                    using (StreamWriter streamWriter = new StreamWriter(entry1.DataStream, leaveOpen: true))
                    {
                        streamWriter.WriteLine(expectedText);
                    }
                    entry1.DataStream.Seek(0, SeekOrigin.Begin);
                    await writer.WriteEntryAsync(entry1);

                    UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir");
                    await writer.WriteEntryAsync(entry2);
                }

                archive.Seek(0, SeekOrigin.Begin);
                await using (WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false))
                {
                    UstarTarEntry entry;
                    await using (TarReader reader = new TarReader(wrapped, leaveOpen: true)) // Unseekable
                    {
                        entry = await reader.GetNextEntryAsync(copyData : true) as UstarTarEntry;

                        Assert.NotNull(entry);
                        Assert.Equal(TarEntryType.RegularFile, entry.EntryType);

                        // Force reading the next entry to advance the underlying stream position
                        Assert.NotNull(await reader.GetNextEntryAsync());
                        Assert.Null(await reader.GetNextEntryAsync());

                        Assert.NotNull(entry.DataStream);
                        entry.DataStream.Seek(0, SeekOrigin.Begin); // Should not throw: This is a new stream, not the archive's disposed stream
                        using (StreamReader streamReader = new StreamReader(entry.DataStream))
                        {
                            string actualText = streamReader.ReadLine();
                            Assert.Equal(expectedText, actualText);
                        }
                    }
                    // The reader must stay alive because it's in charge of disposing all the entries it collected
                    Assert.Throws <ObjectDisposedException>(() => entry.DataStream.Read(new byte[1]));
                }
            }
        }
Esempio n. 6
0
        public async Task Write_LongName_And_LongLinkName_Async(TarEntryType entryType)
        {
            // Both the Name and LinkName fields in header only fit 100 bytes
            string longName     = new string('a', 101);
            string longLinkName = new string('a', 101);

            await using (MemoryStream archiveStream = new MemoryStream())
            {
                await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Gnu, leaveOpen: true))
                {
                    GnuTarEntry entry = new GnuTarEntry(entryType, longName);
                    entry.LinkName = longLinkName;
                    await writer.WriteEntryAsync(entry);
                }

                archiveStream.Position = 0;
                await using (TarReader reader = new TarReader(archiveStream))
                {
                    GnuTarEntry entry = await reader.GetNextEntryAsync() as GnuTarEntry;

                    Assert.Equal(entryType, entry.EntryType);
                    Assert.Equal(longName, entry.Name);
                    Assert.Equal(longLinkName, entry.LinkName);
                }
            }
        }
        public async Task ExtractEntry_ManySubfolderSegments_NoPrecedingDirectoryEntries_Async()
        {
            using (TempDirectory root = new TempDirectory())
            {
                string firstSegment        = "a";
                string secondSegment       = Path.Join(firstSegment, "b");
                string fileWithTwoSegments = Path.Join(secondSegment, "c.txt");

                await using (MemoryStream archive = new MemoryStream())
                {
                    await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true))
                    {
                        // No preceding directory entries for the segments
                        UstarTarEntry entry = new UstarTarEntry(TarEntryType.RegularFile, fileWithTwoSegments);

                        entry.DataStream = new MemoryStream();
                        entry.DataStream.Write(new byte[] { 0x1 });
                        entry.DataStream.Seek(0, SeekOrigin.Begin);

                        await writer.WriteEntryAsync(entry);
                    }

                    archive.Seek(0, SeekOrigin.Begin);
                    await TarFile.ExtractToDirectoryAsync(archive, root.Path, overwriteFiles : false);

                    Assert.True(Directory.Exists(Path.Join(root.Path, firstSegment)));
                    Assert.True(Directory.Exists(Path.Join(root.Path, secondSegment)));
                    Assert.True(File.Exists(Path.Join(root.Path, fileWithTwoSegments)));
                }
            }
        }
        // This test would not pass for the V7 and Ustar formats in some OSs like MacCatalyst, tvOSSimulator and OSX, because the TempDirectory gets created in
        // a folder with a path longer than 100 bytes, and those tar formats have no way of handling pathnames and linknames longer than that length.
        // The rest of the OSs create the TempDirectory in a path that does not surpass the 100 bytes, so the 'subfolder' parameter gives a chance to extend
        // the base directory past that length, to ensure this scenario is tested everywhere.
        private async Task Extract_LinkEntry_TargetInsideDirectory_Internal_Async(TarEntryType entryType, TarEntryFormat format, string subfolder)
        {
            using (TempDirectory root = new TempDirectory())
            {
                string baseDir = string.IsNullOrEmpty(subfolder) ? root.Path : Path.Join(root.Path, subfolder);
                Directory.CreateDirectory(baseDir);

                string linkName   = "link";
                string targetName = "target";
                string targetPath = Path.Join(baseDir, targetName);

                File.Create(targetPath).Dispose();

                await using (MemoryStream archive = new MemoryStream())
                {
                    await using (TarWriter writer = new TarWriter(archive, format, leaveOpen: true))
                    {
                        TarEntry entry = InvokeTarEntryCreationConstructor(format, entryType, linkName);
                        entry.LinkName = targetPath;
                        await writer.WriteEntryAsync(entry);
                    }

                    archive.Seek(0, SeekOrigin.Begin);

                    await TarFile.ExtractToDirectoryAsync(archive, baseDir, overwriteFiles : false);

                    Assert.Equal(2, Directory.GetFileSystemEntries(baseDir).Count());
                }
            }
        }
Esempio n. 9
0
        public async Task Write_V7RegularFileEntry_In_OtherFormatsWriter_Async(TarEntryFormat writerFormat)
        {
            using MemoryStream archive = new MemoryStream();
            TarWriter writer = new TarWriter(archive, format: writerFormat, leaveOpen: true);

            await using (writer)
            {
                V7TarEntry entry = new V7TarEntry(TarEntryType.V7RegularFile, InitialEntryName);

                // Should be written in the format of the entry
                await writer.WriteEntryAsync(entry);
            }

            archive.Seek(0, SeekOrigin.Begin);
            TarReader reader = new TarReader(archive);

            await using (reader)
            {
                TarEntry entry = await reader.GetNextEntryAsync();

                Assert.NotNull(entry);
                Assert.Equal(TarEntryFormat.V7, entry.Format);
                Assert.True(entry is V7TarEntry);

                Assert.Null(await reader.GetNextEntryAsync());
            }
        }
Esempio n. 10
0
        public async Task WriteEntryAsync_Cancel(TarEntryFormat format)
        {
            CancellationTokenSource cs = new CancellationTokenSource();

            cs.Cancel();
            await using (MemoryStream archiveStream = new MemoryStream())
            {
                await using (TarWriter writer = new TarWriter(archiveStream, leaveOpen: false))
                {
                    TarEntry entry = InvokeTarEntryCreationConstructor(format, TarEntryType.Directory, "dir");
                    await Assert.ThrowsAsync <TaskCanceledException>(() => writer.WriteEntryAsync(entry, cs.Token));

                    await Assert.ThrowsAsync <TaskCanceledException>(() => writer.WriteEntryAsync("file.txt", "file.txt", cs.Token));
                }
            }
        }
Esempio n. 11
0
        public async Task Add_Empty_GlobalExtendedAttributes_Async()
        {
            using MemoryStream archive = new MemoryStream();

            TarWriter writer = new TarWriter(archive, leaveOpen: true);

            await using (writer)
            {
                PaxGlobalExtendedAttributesTarEntry gea = new PaxGlobalExtendedAttributesTarEntry(new Dictionary <string, string>());
                await writer.WriteEntryAsync(gea);
            }

            archive.Seek(0, SeekOrigin.Begin);
            TarReader reader = new TarReader(archive);

            await using (reader)
            {
                PaxGlobalExtendedAttributesTarEntry gea = await reader.GetNextEntryAsync() as PaxGlobalExtendedAttributesTarEntry;

                Assert.NotNull(gea);
                Assert.Equal(TarEntryFormat.Pax, gea.Format);
                Assert.Equal(TarEntryType.GlobalExtendedAttributes, gea.EntryType);

                Assert.Equal(0, gea.GlobalExtendedAttributes.Count);

                Assert.Null(await reader.GetNextEntryAsync());
            }
        }
Esempio n. 12
0
        public async Task WriteEntry_RespectDefaultWriterFormat_Async(TarEntryFormat expectedFormat)
        {
            using (TempDirectory root = new TempDirectory())
            {
                string path = Path.Join(root.Path, "file.txt");
                File.Create(path).Dispose();

                await using (MemoryStream archiveStream = new MemoryStream())
                {
                    await using (TarWriter writer = new TarWriter(archiveStream, expectedFormat, leaveOpen: true))
                    {
                        await writer.WriteEntryAsync(path, "file.txt");
                    }

                    archiveStream.Position = 0;
                    await using (TarReader reader = new TarReader(archiveStream, leaveOpen: false))
                    {
                        TarEntry entry = await reader.GetNextEntryAsync();

                        Assert.Equal(expectedFormat, entry.Format);

                        Type expectedType = GetTypeForFormat(expectedFormat);

                        Assert.Equal(expectedType, entry.GetType());
                    }
                }
            }
        }
Esempio n. 13
0
        public async Task WritePaxAttributes_Timestamps_UserProvided_Async()
        {
            Dictionary <string, string> extendedAttributes = new();

            extendedAttributes.Add(PaxEaATime, GetTimestampStringFromDateTimeOffset(TestAccessTime));
            extendedAttributes.Add(PaxEaCTime, GetTimestampStringFromDateTimeOffset(TestChangeTime));

            await using (MemoryStream archiveStream = new MemoryStream())
            {
                await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true))
                {
                    PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName, extendedAttributes);
                    regularFile.ModificationTime = TestModificationTime;
                    await writer.WriteEntryAsync(regularFile);
                }

                archiveStream.Position = 0;
                await using (TarReader reader = new TarReader(archiveStream))
                {
                    PaxTarEntry regularFile = await reader.GetNextEntryAsync() as PaxTarEntry;

                    AssertExtensions.GreaterThanOrEqualTo(regularFile.ExtendedAttributes.Count, 4);
                    VerifyExtendedAttributeTimestamp(regularFile, PaxEaMTime, TestModificationTime);
                    VerifyExtendedAttributeTimestamp(regularFile, PaxEaATime, TestAccessTime);
                    VerifyExtendedAttributeTimestamp(regularFile, PaxEaCTime, TestChangeTime);
                }
            }
        }
Esempio n. 14
0
        public async void Write_To_UnseekableStream_Async()
        {
            await using (MemoryStream inner = new MemoryStream())
            {
                await using (WrappedStream wrapped = new WrappedStream(inner, canRead: true, canWrite: true, canSeek: false))
                {
                    await using (TarWriter writer = new TarWriter(wrapped, TarEntryFormat.Pax, leaveOpen: true))
                    {
                        PaxTarEntry paxEntry = new PaxTarEntry(TarEntryType.RegularFile, "file.txt");
                        await writer.WriteEntryAsync(paxEntry);
                    } // The final records should get written, and the length should not be set because position cannot be read

                    inner.Seek(0, SeekOrigin.Begin); // Rewind the base stream (wrapped cannot be rewound)

                    await using (TarReader reader = new TarReader(wrapped))
                    {
                        TarEntry entry = await reader.GetNextEntryAsync();

                        Assert.Equal(TarEntryFormat.Pax, entry.Format);
                        Assert.Equal(TarEntryType.RegularFile, entry.EntryType);
                        Assert.Null(await reader.GetNextEntryAsync());
                    }
                }
            }
        }
Esempio n. 15
0
        public async Task WriteEntry_FromUnseekableStream_AdvanceDataStream_WriteFromThatPosition_Async()
        {
            using MemoryStream source      = GetTarMemoryStream(CompressionMethod.Uncompressed, TestTarFormat.ustar, "file");
            using WrappedStream unseekable = new WrappedStream(source, canRead: true, canWrite: true, canSeek: false);

            using MemoryStream destination = new MemoryStream();
            await using (TarReader reader1 = new TarReader(unseekable))
            {
                TarEntry entry = await reader1.GetNextEntryAsync();

                Assert.NotNull(entry);
                Assert.NotNull(entry.DataStream);
                entry.DataStream.ReadByte(); // Advance one byte, now the expected string would be "ello file"

                await using (TarWriter writer = new TarWriter(destination, TarEntryFormat.Ustar, leaveOpen: true))
                {
                    await writer.WriteEntryAsync(entry);
                }
            }

            destination.Seek(0, SeekOrigin.Begin);
            await using (TarReader reader2 = new TarReader(destination))
            {
                TarEntry entry = await reader2.GetNextEntryAsync();

                Assert.NotNull(entry);
                Assert.NotNull(entry.DataStream);

                using (StreamReader streamReader = new StreamReader(entry.DataStream, leaveOpen: true))
                {
                    string contents = streamReader.ReadLine();
                    Assert.Equal("ello file", contents);
                }
            }
        }
        public async Task LongEndMarkers_DoNotAdvanceStream_Async()
        {
            await using (MemoryStream archive = new MemoryStream())
            {
                await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true))
                {
                    UstarTarEntry entry = new UstarTarEntry(TarEntryType.Directory, "dir");
                    await writer.WriteEntryAsync(entry);
                }

                byte[] buffer = new byte[2048]; // Four additional end markers (512 each)
                Array.Fill <byte>(buffer, 0x0);
                archive.Write(buffer);
                archive.Seek(0, SeekOrigin.Begin);

                await using (TarReader reader = new TarReader(archive))
                {
                    Assert.NotNull(await reader.GetNextEntryAsync());
                    Assert.Null(await reader.GetNextEntryAsync());
                    long expectedPosition = archive.Position; // After reading the first null entry, should not advance more
                    Assert.Null(await reader.GetNextEntryAsync());
                    Assert.Equal(expectedPosition, archive.Position);
                }
            }
        }
Esempio n. 17
0
        public async Task ThrowIf_AddFile_AfterDispose_Async()
        {
            using MemoryStream archiveStream = new MemoryStream();
            TarWriter writer = new TarWriter(archiveStream);
            await writer.DisposeAsync();

            await Assert.ThrowsAsync <ObjectDisposedException>(() => writer.WriteEntryAsync("fileName", "entryName"));
        }
Esempio n. 18
0
        public async Task WriteEntry_AfterDispose_Throws_Async()
        {
            using MemoryStream archiveStream = new MemoryStream();
            TarWriter writer = new TarWriter(archiveStream);
            await writer.DisposeAsync();

            PaxTarEntry entry = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName);
            await Assert.ThrowsAsync <ObjectDisposedException>(() => writer.WriteEntryAsync(entry));
        }
Esempio n. 19
0
        public async Task TarGz_TarWriter_TarReader_Async()
        {
            using (TempDirectory root = new TempDirectory())
            {
                string archivePath = Path.Join(root.Path, "compressed.tar.gz");

                string fileName = "file.txt";
                string filePath = Path.Join(root.Path, fileName);
                File.Create(filePath).Dispose();

                // Create tar.gz archive
                FileStreamOptions createOptions = new()
                {
                    Mode    = FileMode.CreateNew,
                    Access  = FileAccess.Write,
                    Options = FileOptions.Asynchronous
                };

                await using (FileStream streamToCompress = new FileStream(archivePath, createOptions))
                {
                    await using (GZipStream compressorStream = new GZipStream(streamToCompress, CompressionMode.Compress))
                    {
                        await using (TarWriter writer = new TarWriter(compressorStream))
                        {
                            await writer.WriteEntryAsync(fileName : filePath, entryName : fileName);
                        }
                    }
                }

                FileInfo fileInfo = new FileInfo(archivePath);
                Assert.True(fileInfo.Exists);
                Assert.True(fileInfo.Length > 0);

                // Verify tar.gz archive contents
                FileStreamOptions readOptions = new()
                {
                    Mode    = FileMode.Open,
                    Access  = FileAccess.Read,
                    Options = FileOptions.Asynchronous
                };

                await using (FileStream streamToDecompress = File.Open(archivePath, readOptions))
                {
                    await using (GZipStream decompressorStream = new GZipStream(streamToDecompress, CompressionMode.Decompress))
                    {
                        await using (TarReader reader = new TarReader(decompressorStream))
                        {
                            TarEntry entry = await reader.GetNextEntryAsync();

                            Assert.Equal(TarEntryFormat.Pax, entry.Format);
                            Assert.Equal(fileName, entry.Name);
                            Assert.Null(await reader.GetNextEntryAsync());
                        }
                    }
                }
            }
        }
        public async Task GetNextEntry_CopyDataFalse_UnseekableArchive_Exceptions_Async()
        {
            await using (MemoryStream archive = new MemoryStream())
            {
                await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.Ustar, leaveOpen: true))
                {
                    UstarTarEntry entry1 = new UstarTarEntry(TarEntryType.RegularFile, "file.txt");
                    entry1.DataStream = new MemoryStream();
                    using (StreamWriter streamWriter = new StreamWriter(entry1.DataStream, leaveOpen: true))
                    {
                        streamWriter.WriteLine("Hello world!");
                    }
                    entry1.DataStream.Seek(0, SeekOrigin.Begin); // Rewind to ensure it gets written from the beginning
                    await writer.WriteEntryAsync(entry1);

                    UstarTarEntry entry2 = new UstarTarEntry(TarEntryType.Directory, "dir");
                    await writer.WriteEntryAsync(entry2);
                }

                archive.Seek(0, SeekOrigin.Begin);
                await using (WrappedStream wrapped = new WrappedStream(archive, canRead: true, canWrite: false, canSeek: false))
                {
                    UstarTarEntry entry;
                    await using (TarReader reader = new TarReader(wrapped)) // Unseekable
                    {
                        entry = await reader.GetNextEntryAsync(copyData : false) as UstarTarEntry;

                        Assert.NotNull(entry);
                        Assert.Equal(TarEntryType.RegularFile, entry.EntryType);
                        entry.DataStream.ReadByte(); // Reading is possible as long as we don't move to the next entry

                        // Attempting to read the next entry should automatically move the position pointer to the beginning of the next header
                        Assert.NotNull(await reader.GetNextEntryAsync());
                        Assert.Null(await reader.GetNextEntryAsync());

                        // This is not possible because the position of the main stream is already past the data
                        Assert.Throws <EndOfStreamException>(() => entry.DataStream.Read(new byte[1]));
                    }

                    // The reader must stay alive because it's in charge of disposing all the entries it collected
                    Assert.Throws <ObjectDisposedException>(() => entry.DataStream.Read(new byte[1]));
                }
            }
        }
        public async Task Extract_AllSegmentsOfPath_Async()
        {
            using (TempDirectory source = new TempDirectory())
            {
                string archivePath = Path.Join(source.Path, "archive.tar");
                using (TempDirectory destination = new TempDirectory())
                {
                    FileStreamOptions fileOptions = new()
                    {
                        Access  = FileAccess.Write,
                        Mode    = FileMode.CreateNew,
                        Share   = FileShare.None,
                        Options = FileOptions.Asynchronous
                    };

                    await using (FileStream archiveStream = new FileStream(archivePath, fileOptions))
                    {
                        await using (TarWriter writer = new TarWriter(archiveStream))
                        {
                            PaxTarEntry segment1 = new PaxTarEntry(TarEntryType.Directory, "segment1");
                            await writer.WriteEntryAsync(segment1);

                            PaxTarEntry segment2 = new PaxTarEntry(TarEntryType.Directory, "segment1/segment2");
                            await writer.WriteEntryAsync(segment2);

                            PaxTarEntry file = new PaxTarEntry(TarEntryType.RegularFile, "segment1/segment2/file.txt");
                            await writer.WriteEntryAsync(file);
                        }
                    }

                    await TarFile.ExtractToDirectoryAsync(archivePath, destination.Path, overwriteFiles : false);

                    string segment1Path = Path.Join(destination.Path, "segment1");
                    Assert.True(Directory.Exists(segment1Path), $"{segment1Path}' does not exist.");

                    string segment2Path = Path.Join(segment1Path, "segment2");
                    Assert.True(Directory.Exists(segment2Path), $"{segment2Path}' does not exist.");

                    string filePath = Path.Join(segment2Path, "file.txt");
                    Assert.True(File.Exists(filePath), $"{filePath}' does not exist.");
                }
            }
        }
Esempio n. 22
0
        public async Task WritePaxAttributes_LongLinkName_AutomaticallyAdded_Async()
        {
            using MemoryStream archiveStream = new MemoryStream();

            string    longSymbolicLinkName = new string('a', 101);
            string    longHardLinkName     = new string('b', 101);
            TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true);

            await using (writer)
            {
                PaxTarEntry symlink = new PaxTarEntry(TarEntryType.SymbolicLink, "symlink");
                symlink.LinkName = longSymbolicLinkName;
                await writer.WriteEntryAsync(symlink);

                PaxTarEntry hardlink = new PaxTarEntry(TarEntryType.HardLink, "hardlink");
                hardlink.LinkName = longHardLinkName;
                await writer.WriteEntryAsync(hardlink);
            }

            archiveStream.Position = 0;
            TarReader reader = new TarReader(archiveStream);

            await using (reader)
            {
                PaxTarEntry symlink = await reader.GetNextEntryAsync() as PaxTarEntry;

                AssertExtensions.GreaterThanOrEqualTo(symlink.ExtendedAttributes.Count, 5);

                Assert.Contains(PaxEaName, symlink.ExtendedAttributes);
                Assert.Equal("symlink", symlink.ExtendedAttributes[PaxEaName]);
                Assert.Contains(PaxEaLinkName, symlink.ExtendedAttributes);
                Assert.Equal(longSymbolicLinkName, symlink.ExtendedAttributes[PaxEaLinkName]);

                PaxTarEntry hardlink = await reader.GetNextEntryAsync() as PaxTarEntry;

                AssertExtensions.GreaterThanOrEqualTo(hardlink.ExtendedAttributes.Count, 5);

                Assert.Contains(PaxEaName, hardlink.ExtendedAttributes);
                Assert.Equal("hardlink", hardlink.ExtendedAttributes[PaxEaName]);
                Assert.Contains(PaxEaLinkName, hardlink.ExtendedAttributes);
                Assert.Equal(longHardLinkName, hardlink.ExtendedAttributes[PaxEaLinkName]);
            }
        }
Esempio n. 23
0
 protected async Task WriteEntry_Null_Throws_Async_Internal(TarEntryFormat format)
 {
     await using (MemoryStream archiveStream = new MemoryStream())
     {
         await using (TarWriter writer = new TarWriter(archiveStream, format, leaveOpen: false))
         {
             await Assert.ThrowsAsync <ArgumentNullException>(() => writer.WriteEntryAsync(null));
         }
     }
 }
Esempio n. 24
0
        // The fixed size fields for mtime, atime and ctime can fit 12 ASCII characters, but the last character is reserved for an ASCII space.
        // We internally use long to represent the seconds since Unix epoch, not int.
        // If the max allowed value is 77,777,777,777 in octal, then the max allowed seconds since the Unix epoch are 8,589,934,591,
        // which represents the date "2242/03/16 12:56:32 +00:00".
        // Pax should survive after this date because it stores the timestamps in the extended attributes dictionary
        // without size restrictions.
        public async Task WriteTimestampsBeyondOctalLimitInPax_Async()
        {
            DateTimeOffset overLimitTimestamp = new DateTimeOffset(2242, 3, 16, 12, 56, 33, TimeSpan.Zero); // One second past the octal limit

            string strOverLimitTimestamp = GetTimestampStringFromDateTimeOffset(overLimitTimestamp);

            Dictionary <string, string> ea = new Dictionary <string, string>()
            {
                { PaxEaATime, strOverLimitTimestamp },
                { PaxEaCTime, strOverLimitTimestamp }
            };

            PaxTarEntry entry = new PaxTarEntry(TarEntryType.Directory, "dir", ea);

            entry.ModificationTime = overLimitTimestamp;
            Assert.Equal(overLimitTimestamp, entry.ModificationTime);

            Assert.Contains(PaxEaATime, entry.ExtendedAttributes);
            DateTimeOffset atime = GetDateTimeOffsetFromTimestampString(entry.ExtendedAttributes, PaxEaATime);

            Assert.Equal(overLimitTimestamp, atime);

            Assert.Contains(PaxEaCTime, entry.ExtendedAttributes);
            DateTimeOffset ctime = GetDateTimeOffsetFromTimestampString(entry.ExtendedAttributes, PaxEaCTime);

            Assert.Equal(overLimitTimestamp, ctime);

            using MemoryStream archiveStream = new MemoryStream();
            TarWriter writer = new TarWriter(archiveStream, leaveOpen: true);

            await using (writer)
            {
                await writer.WriteEntryAsync(entry);
            }

            archiveStream.Position = 0;
            TarReader reader = new TarReader(archiveStream);

            await using (reader)
            {
                PaxTarEntry readEntry = await reader.GetNextEntryAsync() as PaxTarEntry;

                Assert.NotNull(readEntry);

                Assert.Equal(overLimitTimestamp, readEntry.ModificationTime);

                Assert.Contains(PaxEaATime, readEntry.ExtendedAttributes);
                DateTimeOffset actualATime = GetDateTimeOffsetFromTimestampString(readEntry.ExtendedAttributes, PaxEaATime);
                Assert.Equal(overLimitTimestamp, actualATime);

                Assert.Contains(PaxEaCTime, readEntry.ExtendedAttributes);
                DateTimeOffset actualCTime = GetDateTimeOffsetFromTimestampString(readEntry.ExtendedAttributes, PaxEaCTime);
                Assert.Equal(overLimitTimestamp, actualCTime);
            }
        }
Esempio n. 25
0
        // Y2K38 will happen one second after "2038/19/01 03:14:07 +00:00". This timestamp represents the seconds since the Unix epoch with a
        // value of int.MaxValue: 2,147,483,647.
        // The fixed size fields for mtime, atime and ctime can fit 12 ASCII characters, but the last character is reserved for an ASCII space.
        // All our entry types should survive the Epochalypse because we internally use long to represent the seconds since Unix epoch, not int.
        // So if the max allowed value is 77,777,777,777 in octal, then the max allowed seconds since the Unix epoch are 8,589,934,591, which
        // is way past int MaxValue, but still within the long limits. That number represents the date "2242/16/03 12:56:32 +00:00".
        public async Task WriteTimestampsBeyondEpochalypseInPax_Async()
        {
            DateTimeOffset epochalypse    = new DateTimeOffset(2038, 1, 19, 3, 14, 8, TimeSpan.Zero);
            string         strEpochalypse = GetTimestampStringFromDateTimeOffset(epochalypse);

            Dictionary <string, string> ea = new Dictionary <string, string>()
            {
                { PaxEaATime, strEpochalypse },
                { PaxEaCTime, strEpochalypse }
            };

            PaxTarEntry entry = new PaxTarEntry(TarEntryType.Directory, "dir", ea);

            entry.ModificationTime = epochalypse;
            Assert.Equal(epochalypse, entry.ModificationTime);

            Assert.Contains(PaxEaATime, entry.ExtendedAttributes);
            DateTimeOffset atime = GetDateTimeOffsetFromTimestampString(entry.ExtendedAttributes, PaxEaATime);

            Assert.Equal(epochalypse, atime);

            Assert.Contains(PaxEaCTime, entry.ExtendedAttributes);
            DateTimeOffset ctime = GetDateTimeOffsetFromTimestampString(entry.ExtendedAttributes, PaxEaCTime);

            Assert.Equal(epochalypse, ctime);

            using MemoryStream archiveStream = new MemoryStream();
            TarWriter writer = new TarWriter(archiveStream, leaveOpen: true);

            await using (writer)
            {
                await writer.WriteEntryAsync(entry);
            }

            archiveStream.Position = 0;
            TarReader reader = new TarReader(archiveStream);

            await using (reader)
            {
                PaxTarEntry readEntry = await reader.GetNextEntryAsync() as PaxTarEntry;

                Assert.NotNull(readEntry);

                Assert.Equal(epochalypse, readEntry.ModificationTime);

                Assert.Contains(PaxEaATime, readEntry.ExtendedAttributes);
                DateTimeOffset actualATime = GetDateTimeOffsetFromTimestampString(readEntry.ExtendedAttributes, PaxEaATime);
                Assert.Equal(epochalypse, actualATime);

                Assert.Contains(PaxEaCTime, readEntry.ExtendedAttributes);
                DateTimeOffset actualCTime = GetDateTimeOffsetFromTimestampString(readEntry.ExtendedAttributes, PaxEaCTime);
                Assert.Equal(epochalypse, actualCTime);
            }
        }
Esempio n. 26
0
        public async Task EntryName_NullOrEmpty_Async()
        {
            using (TempDirectory root = new TempDirectory())
            {
                string file1Name = "file1.txt";
                string file2Name = "file2.txt";

                string file1Path = Path.Join(root.Path, file1Name);
                string file2Path = Path.Join(root.Path, file2Name);

                File.Create(file1Path).Dispose();
                File.Create(file2Path).Dispose();

                await using (MemoryStream archiveStream = new MemoryStream())
                {
                    await using (TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true))
                    {
                        await writer.WriteEntryAsync(file1Path, null);

                        await writer.WriteEntryAsync(file2Path, string.Empty);
                    }

                    archiveStream.Seek(0, SeekOrigin.Begin);
                    await using (TarReader reader = new TarReader(archiveStream))
                    {
                        TarEntry first = await reader.GetNextEntryAsync();

                        Assert.NotNull(first);
                        Assert.Equal(file1Name, first.Name);

                        TarEntry second = await reader.GetNextEntryAsync();

                        Assert.NotNull(second);
                        Assert.Equal(file2Name, second.Name);

                        Assert.Null(await reader.GetNextEntryAsync());
                    }
                }
            }
        }
        public async Task BlockAlignmentPadding_DoesNotAffectNextEntries_Async(int contentSize, bool copyData)
        {
            byte[] fileContents = new byte[contentSize];
            Array.Fill <byte>(fileContents, 0x1);

            using var archive = new MemoryStream();
            using (var writer = new TarWriter(archive, leaveOpen: true))
            {
                var entry1 = new PaxTarEntry(TarEntryType.RegularFile, "file");
                entry1.DataStream = new MemoryStream(fileContents);
                await writer.WriteEntryAsync(entry1);

                var entry2 = new PaxTarEntry(TarEntryType.RegularFile, "next-file");
                await writer.WriteEntryAsync(entry2);
            }

            archive.Position     = 0;
            using var unseekable = new WrappedStream(archive, archive.CanRead, archive.CanWrite, canSeek: false);
            using var reader     = new TarReader(unseekable);

            TarEntry e = await reader.GetNextEntryAsync(copyData);

            Assert.Equal(contentSize, e.Length);

            byte[] buffer = new byte[contentSize];
            while (e.DataStream.Read(buffer) > 0)
            {
                ;
            }
            AssertExtensions.SequenceEqual(fileContents, buffer);

            e = await reader.GetNextEntryAsync(copyData);

            Assert.Equal(0, e.Length);

            e = await reader.GetNextEntryAsync(copyData);

            Assert.Null(e);
        }
Esempio n. 28
0
        public async Task VerifyChecksumV7_Async()
        {
            await using (MemoryStream archive = new MemoryStream())
            {
                await using (TarWriter writer = new TarWriter(archive, TarEntryFormat.V7, leaveOpen: true))
                {
                    V7TarEntry entry = new V7TarEntry(
                        // '\0' = 0
                        TarEntryType.V7RegularFile,
                        // 'a.b' = 97 + 46 + 98 = 241
                        entryName: "a.b");

                    // '0000744\0' = 48 + 48 + 48 + 48 + 55 + 52 + 52 + 0 = 351
                    entry.Mode = AssetMode; // octal 744 = u+rxw, g+r, o+r

                    // '0017351\0' = 48 + 48 + 49 + 55 + 51 + 53 + 49 + 0 = 353
                    entry.Uid = AssetUid; // decimal 7913, octal 17351

                    // '0006773\0' = 48 + 48 + 48 + 54 + 55 + 55 + 51 + 0 = 359
                    entry.Gid = AssetGid; // decimal 3579, octal 6773

                    // '14164217674\0' = 49 + 52 + 49 + 54 + 52 + 50 + 49 + 55 + 54 + 55 + 52 + 0 = 571
                    DateTimeOffset mtime = new DateTimeOffset(2022, 1, 2, 3, 45, 00, TimeSpan.Zero); // ToUnixTimeSeconds() = decimal 1641095100, octal 14164217674
                    entry.ModificationTime = mtime;

                    entry.DataStream = new MemoryStream();
                    byte[] buffer = new byte[] { 72, 101, 108, 108, 111 };

                    // '0000000005\0' = 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 48 + 53 + 0 = 533
                    await entry.DataStream.WriteAsync(buffer);  // Data length: decimal 5

                    entry.DataStream.Seek(0, SeekOrigin.Begin); // Rewind to ensure it gets written from the beginning

                    // Sum so far: 0 + 241 + 351 + 353 + 359 + 571 + 533 = decimal 2408
                    // Add 8 spaces to the sum: 2408 + (8 x 32) = octal 5150, decimal 2664 (final)
                    // Checksum: '005150\0 '

                    await writer.WriteEntryAsync(entry);

                    Assert.Equal(2664, entry.Checksum);
                }

                archive.Seek(0, SeekOrigin.Begin);
                await using (TarReader reader = new TarReader(archive))
                {
                    TarEntry entry = await reader.GetNextEntryAsync();

                    Assert.Equal(2664, entry.Checksum);
                }
            }
        }
Esempio n. 29
0
        public async Task WritePaxAttributes_LongGroupName_LongUserName_Async()
        {
            string userName  = "******";
            string groupName = "IAmAGroupNameWhoseLengthIsWayBeyondTheThirtyTwoByteLimit";

            using MemoryStream archiveStream = new MemoryStream();
            TarWriter writer = new TarWriter(archiveStream, TarEntryFormat.Pax, leaveOpen: true);

            await using (writer)
            {
                PaxTarEntry regularFile = new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName);
                SetRegularFile(regularFile);
                VerifyRegularFile(regularFile, isWritable: true);
                regularFile.UserName  = userName;
                regularFile.GroupName = groupName;
                await writer.WriteEntryAsync(regularFile);
            }

            archiveStream.Position = 0;
            TarReader reader = new TarReader(archiveStream);

            await using (reader)
            {
                PaxTarEntry regularFile = await reader.GetNextEntryAsync() as PaxTarEntry;

                VerifyRegularFile(regularFile, isWritable: false);

                Assert.NotNull(regularFile.ExtendedAttributes);

                // path, mtime, atime and ctime are always collected by default
                AssertExtensions.GreaterThanOrEqualTo(regularFile.ExtendedAttributes.Count, 6);

                Assert.Contains(PaxEaName, regularFile.ExtendedAttributes);
                Assert.Contains(PaxEaMTime, regularFile.ExtendedAttributes);
                Assert.Contains(PaxEaATime, regularFile.ExtendedAttributes);
                Assert.Contains(PaxEaCTime, regularFile.ExtendedAttributes);

                Assert.Contains(PaxEaUName, regularFile.ExtendedAttributes);
                Assert.Equal(userName, regularFile.ExtendedAttributes[PaxEaUName]);

                Assert.Contains(PaxEaGName, regularFile.ExtendedAttributes);
                Assert.Equal(groupName, regularFile.ExtendedAttributes[PaxEaGName]);

                // They should also get exposed via the regular properties
                Assert.Equal(groupName, regularFile.GroupName);
                Assert.Equal(userName, regularFile.UserName);
            }
        }
Esempio n. 30
0
        public async Task Write_RegularFileEntry_In_V7Writer_Async(TarEntryFormat entryFormat)
        {
            using MemoryStream archive = new MemoryStream();
            TarWriter writer = new TarWriter(archive, format: TarEntryFormat.V7, leaveOpen: true);

            await using (writer)
            {
                TarEntry entry = entryFormat switch
                {
                    TarEntryFormat.Ustar => new UstarTarEntry(TarEntryType.RegularFile, InitialEntryName),
                    TarEntryFormat.Pax => new PaxTarEntry(TarEntryType.RegularFile, InitialEntryName),
                    TarEntryFormat.Gnu => new GnuTarEntry(TarEntryType.RegularFile, InitialEntryName),
                    _ => throw new FormatException($"Unexpected format: {entryFormat}")
                };

                // Should be written in the format of the entry
                await writer.WriteEntryAsync(entry);
            }

            archive.Seek(0, SeekOrigin.Begin);
            TarReader reader = new TarReader(archive);

            await using (reader)
            {
                TarEntry entry = await reader.GetNextEntryAsync();

                Assert.NotNull(entry);
                Assert.Equal(entryFormat, entry.Format);

                switch (entryFormat)
                {
                case TarEntryFormat.Ustar:
                    Assert.True(entry is UstarTarEntry);
                    break;

                case TarEntryFormat.Pax:
                    Assert.True(entry is PaxTarEntry);
                    break;

                case TarEntryFormat.Gnu:
                    Assert.True(entry is GnuTarEntry);
                    break;
                }

                Assert.Null(await reader.GetNextEntryAsync());
            }
        }