ZStandard tar support

This commit is contained in:
Mitch Capper
2025-09-23 03:26:33 -07:00
parent 634e562b93
commit 553c533ada
10 changed files with 150 additions and 4 deletions

View File

@@ -15,6 +15,7 @@
| Tar | None | Both | TarArchive | TarReader | TarWriter (3) |
| Tar.GZip | DEFLATE | Both | TarArchive | TarReader | TarWriter (3) |
| Tar.BZip2 | BZip2 | Both | TarArchive | TarReader | TarWriter (3) |
| Tar.Zstandard | ZStandard | Decompress | TarArchive | TarReader | N/A |
| Tar.LZip | LZMA | Both | TarArchive | TarReader | TarWriter (3) |
| Tar.XZ | LZMA2 | Decompress | TarArchive | TarReader | TarWriter (3) |
| GZip (single file) | DEFLATE | Both | GZipArchive | GZipReader | GZipWriter |
@@ -41,6 +42,7 @@ For those who want to directly compress/decompress bits. The single file formats
| ADCStream | Decompress |
| LZipStream | Both |
| XZStream | Decompress |
| ZStandardStream | Decompress |
## Archive Formats vs Compression

View File

@@ -1,6 +1,6 @@
# SharpCompress
SharpCompress is a compression library in pure C# for .NET Framework 4.62, .NET Standard 2.1, .NET 6.0 and NET 8.0 that can unrar, un7zip, unzip, untar unbzip2, ungzip, unlzip with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip/lzip are implemented.
SharpCompress is a compression library in pure C# for .NET Framework 4.62, .NET Standard 2.1, .NET 6.0 and NET 8.0 that can unrar, un7zip, unzip, untar unbzip2, ungzip, unlzip, unzstd with forward-only reading and file random access APIs. Write support for zip/tar/bzip2/gzip/lzip are implemented.
The major feature is support for non-seekable streams so large files can be processed on the fly (i.e. download stream).
@@ -20,10 +20,12 @@ In general, I recommend GZip (Deflate)/BZip2 (BZip)/LZip (LZMA) as the simplicit
Zip is okay, but it's a very hap-hazard format and the variation in headers and implementations makes it hard to get correct. Uses Deflate by default but supports a lot of compression methods.
RAR is not recommended as it's a propriatory format and the compression is closed source. Use Tar/LZip for LZMA
RAR is not recommended as it's a proprietary format and the compression is closed source. Use Tar/LZip for LZMA
7Zip and XZ both are overly complicated. 7Zip does not support streamable formats. XZ has known holes explained here: (http://www.nongnu.org/lzip/xz_inadequate.html) Use Tar/LZip for LZMA compression instead.
ZStandard is an efficient format that works well for streaming with a flexible compression level to tweak the speed/performance trade off you are looking for. We currently only implement decompression for ZStandard but as we leverage the [ZstdSharp](https://github.com/oleg-st/ZstdSharp) library one could likely add compression support without much trouble (PRs are welcome!).
## A Simple Request
Hi everyone. I hope you're using SharpCompress and finding it useful. Please give me feedback on what you'd like to see changed especially as far as usability goes. New feature suggestions are always welcome as well. I would also like to know what projects SharpCompress is being used in. I like seeing how it is used to give me ideas for future versions. Thanks!
@@ -40,6 +42,7 @@ I'm always looking for help or ideas. Please submit code or email with ideas. Un
* 7Zip writing
* Zip64 (Need writing and extend Reading)
* Multi-volume Zip support.
* ZStandard writing
## Version Log

View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.IO;
namespace SharpCompress.Compressors.ZStandard;
internal class ZStandardStream : ZstdSharp.DecompressionStream, IStreamStack
{
#if DEBUG_STREAMS
long IStreamStack.InstanceId { get; set; }
#endif
public int DefaultBufferSize { get; set; }
int IStreamStack.BufferSize
{
get => 0;
set { }
}
int IStreamStack.BufferPosition
{
get => 0;
set { }
}
private readonly Stream stream;
Stream IStreamStack.BaseStream() => stream;
void IStreamStack.SetPosition(long position) { }
internal static bool IsZStandard(Stream stream)
{
var br = new BinaryReader(stream);
var magic = br.ReadUInt32();
if (ZstandardConstants.MAGIC != magic)
{
return false;
}
return true;
}
public ZStandardStream(Stream baseInputStream)
: base(baseInputStream)
{
this.stream = baseInputStream;
#if DEBUG_STREAMS
this.DebugConstruct(typeof(ZStandardStream));
#endif
}
/// <summary>
/// The current position within the stream.
/// Throws a NotSupportedException when attempting to set the position
/// </summary>
/// <exception cref="NotSupportedException">Attempting to set the position</exception>
public override long Position
{
get { return stream.Position; }
set { throw new NotSupportedException("InflaterInputStream Position not supported"); }
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpCompress.Compressors.ZStandard;
internal class ZstandardConstants
{
/// <summary>
/// Magic number found at start of ZStandard frame: 0xFD 0x2F 0xB5 0x28
/// </summary>
public const uint MAGIC = 0xFD2FB528;
}

View File

@@ -8,6 +8,7 @@ using SharpCompress.Compressors.BZip2;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.Lzw;
using SharpCompress.Compressors.Xz;
using SharpCompress.Compressors.ZStandard;
using SharpCompress.IO;
using SharpCompress.Readers;
using SharpCompress.Readers.Tar;
@@ -63,7 +64,7 @@ public class TarFactory
yield return "taZ";
// zstd
// yield return "tzst"; // unsupported
yield return "tzst";
}
/// <inheritdoc/>
@@ -135,6 +136,21 @@ public class TarFactory
}
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (ZStandardStream.IsZStandard(rewindableStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
var testStream = new ZStandardStream(
SharpCompressStream.Create(rewindableStream, leaveOpen: true)
);
if (TarArchive.IsTarFile(testStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
reader = new TarReader(rewindableStream, options, CompressionType.ZStandard);
return true;
}
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (LZipStream.IsLZipFile(rewindableStream))
{

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SharpCompress.Archives;
using SharpCompress.Compressors.ZStandard;
using SharpCompress.Readers;
namespace SharpCompress.Factories;
internal class ZStandardFactory : Factory
{
public override string Name => "ZStandard";
public override IEnumerable<string> GetSupportedExtensions()
{
yield return "zst";
yield return "zstd";
}
public override bool IsArchive(
Stream stream,
string? password = null,
int bufferSize = 65536
) => ZStandardStream.IsZStandard(stream);
}

View File

@@ -32,7 +32,7 @@ public static class ReaderFactory
}
throw new InvalidFormatException(
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Zip, GZip, BZip2, Tar, Rar, LZip, XZ"
"Cannot determine compressed stream type. Supported Reader Formats: Arc, Zip, GZip, BZip2, Tar, Rar, LZip, XZ, ZStandard"
);
}
}

View File

@@ -11,6 +11,7 @@ using SharpCompress.Compressors.Deflate;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.Lzw;
using SharpCompress.Compressors.Xz;
using SharpCompress.Compressors.ZStandard;
using SharpCompress.IO;
namespace SharpCompress.Readers.Tar;
@@ -35,6 +36,7 @@ public class TarReader : AbstractReader<TarEntry, TarVolume>
{
CompressionType.BZip2 => new BZip2Stream(stream, CompressionMode.Decompress, false),
CompressionType.GZip => new GZipStream(stream, CompressionMode.Decompress),
CompressionType.ZStandard => new ZStandardStream(stream),
CompressionType.LZip => new LZipStream(stream, CompressionMode.Decompress),
CompressionType.Xz => new XZStream(stream),
CompressionType.Lzw => new LzwStream(stream),
@@ -84,6 +86,18 @@ public class TarReader : AbstractReader<TarEntry, TarVolume>
throw new InvalidFormatException("Not a tar file.");
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (ZStandardStream.IsZStandard(rewindableStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
var testStream = new ZStandardStream(rewindableStream);
if (TarArchive.IsTarFile(testStream))
{
((IStreamStack)rewindableStream).StackSeek(pos);
return new TarReader(rewindableStream, options, CompressionType.ZStandard);
}
throw new InvalidFormatException("Not a tar file.");
}
((IStreamStack)rewindableStream).StackSeek(pos);
if (LZipStream.IsLZipFile(rewindableStream))
{

View File

@@ -49,6 +49,9 @@ public class TarReaderTests : ReaderTests
[Fact]
public void Tar_GZip_Reader() => Read("Tar.tar.gz", CompressionType.GZip);
[Fact]
public void Tar_ZStandard_Reader() => Read("Tar.tar.zst", CompressionType.ZStandard);
[Fact]
public void Tar_LZip_Reader() => Read("Tar.tar.lz", CompressionType.LZip);

Binary file not shown.