mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-06 05:27:05 +00:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83e8bf8462 | ||
|
|
65bcfadfde | ||
|
|
5acc195cf7 | ||
|
|
161f99bbad | ||
|
|
c012db0776 | ||
|
|
8ee257d299 | ||
|
|
f9522107c3 | ||
|
|
e07046a37a | ||
|
|
ad6d0d9ae8 | ||
|
|
fdc33e91bd | ||
|
|
a34f5a855c | ||
|
|
6474741af1 | ||
|
|
c10bd840c5 | ||
|
|
e6dded826b | ||
|
|
8a022c4b18 | ||
|
|
cfef228afc | ||
|
|
237ff9f055 | ||
|
|
020f862814 | ||
|
|
d5cbe71cae | ||
|
|
014ecd4fc1 | ||
|
|
9600709219 | ||
|
|
fa6107200d | ||
|
|
eb81f972c4 | ||
|
|
93c1ff396e | ||
|
|
d5e6c31a9f | ||
|
|
5faa603d59 |
@@ -19,7 +19,6 @@
|
||||
| Tar.XZ | LZMA2 | Decompress | TarArchive | TarReader | TarWriter (3) |
|
||||
| GZip (single file) | DEFLATE | Both | GZipArchive | GZipReader | GZipWriter |
|
||||
| 7Zip (4) | LZMA, LZMA2, BZip2, PPMd, BCJ, BCJ2, Deflate | Decompress | SevenZipArchive | N/A | N/A |
|
||||
| LZip (single file) (5) | LZip (LZMA) | Both | LZipArchive | LZipReader | LZipWriter |
|
||||
|
||||
1. SOLID Rars are only supported in the RarReader API.
|
||||
2. Zip format supports pkware and WinzipAES encryption. However, encrypted LZMA is not supported. Zip64 reading/writing is supported but only with seekable streams as the Zip spec doesn't support Zip64 data in post data descriptors. Deflate64 is only supported for reading.
|
||||
|
||||
@@ -2,25 +2,29 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives
|
||||
{
|
||||
public abstract class AbstractArchive<TEntry, TVolume> : IArchive
|
||||
public abstract class AbstractArchive<TEntry, TVolume> : IArchive, IArchiveExtractionListener
|
||||
where TEntry : IArchiveEntry
|
||||
where TVolume : IVolume
|
||||
{
|
||||
private readonly LazyReadOnlyCollection<TVolume> lazyVolumes;
|
||||
private readonly LazyReadOnlyCollection<TEntry> lazyEntries;
|
||||
|
||||
protected ReaderOptions ReaderOptions { get; } = new ();
|
||||
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionBegin;
|
||||
public event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>>? EntryExtractionEnd;
|
||||
|
||||
public event EventHandler<CompressedBytesReadEventArgs>? CompressedBytesRead;
|
||||
public event EventHandler<FilePartExtractionBeginEventArgs>? FilePartExtractionBegin;
|
||||
|
||||
protected ReaderOptions ReaderOptions { get; }
|
||||
|
||||
private bool disposed;
|
||||
|
||||
internal AbstractArchive(ArchiveType type, FileInfo fileInfo, ReaderOptions readerOptions, CancellationToken cancellationToken)
|
||||
internal AbstractArchive(ArchiveType type, FileInfo fileInfo, ReaderOptions readerOptions)
|
||||
{
|
||||
Type = type;
|
||||
if (!fileInfo.Exists)
|
||||
@@ -29,30 +33,42 @@ namespace SharpCompress.Archives
|
||||
}
|
||||
ReaderOptions = readerOptions;
|
||||
readerOptions.LeaveStreamOpen = false;
|
||||
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(fileInfo, cancellationToken));
|
||||
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes, cancellationToken));
|
||||
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(fileInfo));
|
||||
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
|
||||
}
|
||||
|
||||
|
||||
protected abstract IAsyncEnumerable<TVolume> LoadVolumes(FileInfo file, CancellationToken cancellationToken);
|
||||
protected abstract IEnumerable<TVolume> LoadVolumes(FileInfo file);
|
||||
|
||||
internal AbstractArchive(ArchiveType type, IAsyncEnumerable<Stream> streams, ReaderOptions readerOptions, CancellationToken cancellationToken)
|
||||
internal AbstractArchive(ArchiveType type, IEnumerable<Stream> streams, ReaderOptions readerOptions)
|
||||
{
|
||||
Type = type;
|
||||
ReaderOptions = readerOptions;
|
||||
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(streams.Select(CheckStreams), cancellationToken));
|
||||
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes, cancellationToken));
|
||||
lazyVolumes = new LazyReadOnlyCollection<TVolume>(LoadVolumes(streams.Select(CheckStreams)));
|
||||
lazyEntries = new LazyReadOnlyCollection<TEntry>(LoadEntries(Volumes));
|
||||
}
|
||||
|
||||
#nullable disable
|
||||
internal AbstractArchive(ArchiveType type)
|
||||
{
|
||||
Type = type;
|
||||
lazyVolumes = new LazyReadOnlyCollection<TVolume>( AsyncEnumerable.Empty<TVolume>());
|
||||
lazyEntries = new LazyReadOnlyCollection<TEntry>(AsyncEnumerable.Empty<TEntry>());
|
||||
lazyVolumes = new LazyReadOnlyCollection<TVolume>(Enumerable.Empty<TVolume>());
|
||||
lazyEntries = new LazyReadOnlyCollection<TEntry>(Enumerable.Empty<TEntry>());
|
||||
}
|
||||
#nullable enable
|
||||
|
||||
public ArchiveType Type { get; }
|
||||
|
||||
void IArchiveExtractionListener.FireEntryExtractionBegin(IArchiveEntry entry)
|
||||
{
|
||||
EntryExtractionBegin?.Invoke(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
|
||||
}
|
||||
|
||||
void IArchiveExtractionListener.FireEntryExtractionEnd(IArchiveEntry entry)
|
||||
{
|
||||
EntryExtractionEnd?.Invoke(this, new ArchiveExtractionEventArgs<IArchiveEntry>(entry));
|
||||
}
|
||||
|
||||
private static Stream CheckStreams(Stream stream)
|
||||
{
|
||||
if (!stream.CanSeek || !stream.CanRead)
|
||||
@@ -65,48 +81,63 @@ namespace SharpCompress.Archives
|
||||
/// <summary>
|
||||
/// Returns an ReadOnlyCollection of all the RarArchiveEntries across the one or many parts of the RarArchive.
|
||||
/// </summary>
|
||||
public virtual IAsyncEnumerable<TEntry> Entries => lazyEntries;
|
||||
public virtual ICollection<TEntry> Entries => lazyEntries;
|
||||
|
||||
/// <summary>
|
||||
/// Returns an ReadOnlyCollection of all the RarArchiveVolumes across the one or many parts of the RarArchive.
|
||||
/// </summary>
|
||||
public IAsyncEnumerable<TVolume> Volumes => lazyVolumes;
|
||||
public ICollection<TVolume> Volumes => lazyVolumes;
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the files compressed in the archive.
|
||||
/// </summary>
|
||||
public virtual async ValueTask<long> TotalSizeAsync()
|
||||
{
|
||||
await EnsureEntriesLoaded();
|
||||
return await Entries.AggregateAsync(0L, (total, cf) => total + cf.CompressedSize);
|
||||
}
|
||||
public virtual long TotalSize => Entries.Aggregate(0L, (total, cf) => total + cf.CompressedSize);
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the files as uncompressed in the archive.
|
||||
/// </summary>
|
||||
public virtual async ValueTask<long> TotalUncompressedSizeAsync()
|
||||
{
|
||||
await EnsureEntriesLoaded();
|
||||
return await Entries.AggregateAsync(0L, (total, cf) => total + cf.Size);
|
||||
}
|
||||
public virtual long TotalUncompressSize => Entries.Aggregate(0L, (total, cf) => total + cf.Size);
|
||||
|
||||
protected abstract IAsyncEnumerable<TVolume> LoadVolumes(IAsyncEnumerable<Stream> streams, CancellationToken cancellationToken);
|
||||
protected abstract IAsyncEnumerable<TEntry> LoadEntries(IAsyncEnumerable<TVolume> volumes, CancellationToken cancellationToken);
|
||||
protected abstract IEnumerable<TVolume> LoadVolumes(IEnumerable<Stream> streams);
|
||||
protected abstract IEnumerable<TEntry> LoadEntries(IEnumerable<TVolume> volumes);
|
||||
|
||||
IAsyncEnumerable<IArchiveEntry> IArchive.Entries => Entries.Select(x => (IArchiveEntry)x);
|
||||
IEnumerable<IArchiveEntry> IArchive.Entries => Entries.Cast<IArchiveEntry>();
|
||||
|
||||
IAsyncEnumerable<IVolume> IArchive.Volumes => lazyVolumes.Select(x => (IVolume)x);
|
||||
IEnumerable<IVolume> IArchive.Volumes => lazyVolumes.Cast<IVolume>();
|
||||
|
||||
public virtual async ValueTask DisposeAsync()
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (!disposed)
|
||||
{
|
||||
await lazyVolumes.ForEachAsync(async v => await v.DisposeAsync());
|
||||
await lazyEntries.GetLoaded().Cast<Entry>().ForEachAsync(async x => await x.CloseAsync());
|
||||
lazyVolumes.ForEach(v => v.Dispose());
|
||||
lazyEntries.GetLoaded().Cast<Entry>().ForEach(x => x.Close());
|
||||
disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IArchiveExtractionListener.EnsureEntriesLoaded()
|
||||
{
|
||||
lazyEntries.EnsureFullyLoaded();
|
||||
lazyVolumes.EnsureFullyLoaded();
|
||||
}
|
||||
|
||||
void IExtractionListener.FireCompressedBytesRead(long currentPartCompressedBytes, long compressedReadBytes)
|
||||
{
|
||||
CompressedBytesRead?.Invoke(this, new CompressedBytesReadEventArgs(
|
||||
currentFilePartCompressedBytesRead: currentPartCompressedBytes,
|
||||
compressedBytesRead: compressedReadBytes
|
||||
));
|
||||
}
|
||||
|
||||
void IExtractionListener.FireFilePartExtractionBegin(string name, long size, long compressedSize)
|
||||
{
|
||||
FilePartExtractionBegin?.Invoke(this, new FilePartExtractionBeginEventArgs(
|
||||
compressedSize: compressedSize,
|
||||
size: size,
|
||||
name: name
|
||||
));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this method to extract all entries in an archive in order.
|
||||
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
|
||||
@@ -118,32 +149,29 @@ namespace SharpCompress.Archives
|
||||
/// occur if this is used at the same time as other extraction methods on this instance.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async ValueTask<IReader> ExtractAllEntries()
|
||||
public IReader ExtractAllEntries()
|
||||
{
|
||||
await EnsureEntriesLoaded();
|
||||
return await CreateReaderForSolidExtraction();
|
||||
}
|
||||
|
||||
public async ValueTask EnsureEntriesLoaded()
|
||||
{
|
||||
await lazyEntries.EnsureFullyLoaded();
|
||||
await lazyVolumes.EnsureFullyLoaded();
|
||||
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
|
||||
return CreateReaderForSolidExtraction();
|
||||
}
|
||||
|
||||
protected abstract ValueTask<IReader> CreateReaderForSolidExtraction();
|
||||
protected abstract IReader CreateReaderForSolidExtraction();
|
||||
|
||||
/// <summary>
|
||||
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
|
||||
/// </summary>
|
||||
public virtual ValueTask<bool> IsSolidAsync() => new(false);
|
||||
public virtual bool IsSolid => false;
|
||||
|
||||
/// <summary>
|
||||
/// The archive can find all the parts of the archive needed to fully extract the archive. This forces the parsing of the entire archive.
|
||||
/// </summary>
|
||||
public async ValueTask<bool> IsCompleteAsync()
|
||||
public bool IsComplete
|
||||
{
|
||||
await EnsureEntriesLoaded();
|
||||
return await Entries.AllAsync(x => x.IsComplete);
|
||||
get
|
||||
{
|
||||
((IArchiveExtractionListener)this).EnsureEntriesLoaded();
|
||||
return Entries.All(x => x.IsComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Writers;
|
||||
@@ -14,7 +12,7 @@ namespace SharpCompress.Archives
|
||||
where TEntry : IArchiveEntry
|
||||
where TVolume : IVolume
|
||||
{
|
||||
private class RebuildPauseDisposable : IAsyncDisposable
|
||||
private class RebuildPauseDisposable : IDisposable
|
||||
{
|
||||
private readonly AbstractWritableArchive<TEntry, TVolume> archive;
|
||||
|
||||
@@ -24,16 +22,16 @@ namespace SharpCompress.Archives
|
||||
archive.pauseRebuilding = true;
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
public void Dispose()
|
||||
{
|
||||
archive.pauseRebuilding = false;
|
||||
await archive.RebuildModifiedCollection();
|
||||
archive.RebuildModifiedCollection();
|
||||
}
|
||||
}
|
||||
private readonly List<TEntry> newEntries = new();
|
||||
private readonly List<TEntry> removedEntries = new();
|
||||
private readonly List<TEntry> newEntries = new List<TEntry>();
|
||||
private readonly List<TEntry> removedEntries = new List<TEntry>();
|
||||
|
||||
private readonly List<TEntry> modifiedEntries = new();
|
||||
private readonly List<TEntry> modifiedEntries = new List<TEntry>();
|
||||
private bool hasModifications;
|
||||
private bool pauseRebuilding;
|
||||
|
||||
@@ -42,36 +40,34 @@ namespace SharpCompress.Archives
|
||||
{
|
||||
}
|
||||
|
||||
internal AbstractWritableArchive(ArchiveType type, Stream stream, ReaderOptions readerFactoryOptions,
|
||||
CancellationToken cancellationToken)
|
||||
: base(type, stream.AsAsyncEnumerable(), readerFactoryOptions, cancellationToken)
|
||||
internal AbstractWritableArchive(ArchiveType type, Stream stream, ReaderOptions readerFactoryOptions)
|
||||
: base(type, stream.AsEnumerable(), readerFactoryOptions)
|
||||
{
|
||||
}
|
||||
|
||||
internal AbstractWritableArchive(ArchiveType type, FileInfo fileInfo, ReaderOptions readerFactoryOptions,
|
||||
CancellationToken cancellationToken)
|
||||
: base(type, fileInfo, readerFactoryOptions, cancellationToken)
|
||||
internal AbstractWritableArchive(ArchiveType type, FileInfo fileInfo, ReaderOptions readerFactoryOptions)
|
||||
: base(type, fileInfo, readerFactoryOptions)
|
||||
{
|
||||
}
|
||||
|
||||
public override IAsyncEnumerable<TEntry> Entries
|
||||
public override ICollection<TEntry> Entries
|
||||
{
|
||||
get
|
||||
{
|
||||
if (hasModifications)
|
||||
{
|
||||
return modifiedEntries.ToAsyncEnumerable();
|
||||
return modifiedEntries;
|
||||
}
|
||||
return base.Entries;
|
||||
}
|
||||
}
|
||||
|
||||
public IAsyncDisposable PauseEntryRebuilding()
|
||||
public IDisposable PauseEntryRebuilding()
|
||||
{
|
||||
return new RebuildPauseDisposable(this);
|
||||
}
|
||||
|
||||
private async ValueTask RebuildModifiedCollection()
|
||||
private void RebuildModifiedCollection()
|
||||
{
|
||||
if (pauseRebuilding)
|
||||
{
|
||||
@@ -80,57 +76,56 @@ namespace SharpCompress.Archives
|
||||
hasModifications = true;
|
||||
newEntries.RemoveAll(v => removedEntries.Contains(v));
|
||||
modifiedEntries.Clear();
|
||||
modifiedEntries.AddRange(await OldEntries.Concat(newEntries.ToAsyncEnumerable()).ToListAsync());
|
||||
modifiedEntries.AddRange(OldEntries.Concat(newEntries));
|
||||
}
|
||||
|
||||
private IAsyncEnumerable<TEntry> OldEntries { get { return base.Entries.Where(x => !removedEntries.Contains(x)); } }
|
||||
private IEnumerable<TEntry> OldEntries { get { return base.Entries.Where(x => !removedEntries.Contains(x)); } }
|
||||
|
||||
public async ValueTask RemoveEntryAsync(TEntry entry)
|
||||
public void RemoveEntry(TEntry entry)
|
||||
{
|
||||
if (!removedEntries.Contains(entry))
|
||||
{
|
||||
removedEntries.Add(entry);
|
||||
await RebuildModifiedCollection();
|
||||
RebuildModifiedCollection();
|
||||
}
|
||||
}
|
||||
|
||||
ValueTask IWritableArchive.RemoveEntryAsync(IArchiveEntry entry, CancellationToken cancellationToken)
|
||||
void IWritableArchive.RemoveEntry(IArchiveEntry entry)
|
||||
{
|
||||
return RemoveEntryAsync((TEntry)entry);
|
||||
RemoveEntry((TEntry)entry);
|
||||
}
|
||||
|
||||
public ValueTask<TEntry> AddEntryAsync(string key, Stream source,
|
||||
long size = 0, DateTime? modified = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public TEntry AddEntry(string key, Stream source,
|
||||
long size = 0, DateTime? modified = null)
|
||||
{
|
||||
return AddEntryAsync(key, source, false, size, modified, cancellationToken);
|
||||
return AddEntry(key, source, false, size, modified);
|
||||
}
|
||||
|
||||
async ValueTask<IArchiveEntry> IWritableArchive.AddEntryAsync(string key, Stream source, bool closeStream, long size, DateTime? modified, CancellationToken cancellationToken)
|
||||
IArchiveEntry IWritableArchive.AddEntry(string key, Stream source, bool closeStream, long size, DateTime? modified)
|
||||
{
|
||||
return await AddEntryAsync(key, source, closeStream, size, modified, cancellationToken);
|
||||
return AddEntry(key, source, closeStream, size, modified);
|
||||
}
|
||||
|
||||
public async ValueTask<TEntry> AddEntryAsync(string key, Stream source, bool closeStream,
|
||||
long size = 0, DateTime? modified = null, CancellationToken cancellationToken = default)
|
||||
public TEntry AddEntry(string key, Stream source, bool closeStream,
|
||||
long size = 0, DateTime? modified = null)
|
||||
{
|
||||
if (key.Length > 0 && key[0] is '/' or '\\')
|
||||
{
|
||||
key = key.Substring(1);
|
||||
}
|
||||
if (await DoesKeyMatchExisting(key))
|
||||
if (DoesKeyMatchExisting(key))
|
||||
{
|
||||
throw new ArchiveException("Cannot add entry with duplicate key: " + key);
|
||||
}
|
||||
var entry = await CreateEntry(key, source, size, modified, closeStream, cancellationToken);
|
||||
var entry = CreateEntry(key, source, size, modified, closeStream);
|
||||
newEntries.Add(entry);
|
||||
await RebuildModifiedCollection();
|
||||
RebuildModifiedCollection();
|
||||
return entry;
|
||||
}
|
||||
|
||||
private async ValueTask<bool> DoesKeyMatchExisting(string key)
|
||||
private bool DoesKeyMatchExisting(string key)
|
||||
{
|
||||
await foreach (var path in Entries.Select(x => x.Key))
|
||||
foreach (var path in Entries.Select(x => x.Key))
|
||||
{
|
||||
var p = path.Replace('/', '\\');
|
||||
if (p.Length > 0 && p[0] == '\\')
|
||||
@@ -142,35 +137,34 @@ namespace SharpCompress.Archives
|
||||
return false;
|
||||
}
|
||||
|
||||
public async ValueTask SaveToAsync(Stream stream, WriterOptions options, CancellationToken cancellationToken = default)
|
||||
public void SaveTo(Stream stream, WriterOptions options)
|
||||
{
|
||||
//reset streams of new entries
|
||||
newEntries.Cast<IWritableArchiveEntry>().ForEach(x => x.Stream.Seek(0, SeekOrigin.Begin));
|
||||
await SaveToAsync(stream, options, OldEntries, newEntries.ToAsyncEnumerable(), cancellationToken);
|
||||
SaveTo(stream, options, OldEntries, newEntries);
|
||||
}
|
||||
|
||||
protected ValueTask<TEntry> CreateEntry(string key, Stream source, long size, DateTime? modified,
|
||||
bool closeStream, CancellationToken cancellationToken)
|
||||
protected TEntry CreateEntry(string key, Stream source, long size, DateTime? modified,
|
||||
bool closeStream)
|
||||
{
|
||||
if (!source.CanRead || !source.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("Streams must be readable and seekable to use the Writing Archive API");
|
||||
}
|
||||
return CreateEntryInternal(key, source, size, modified, closeStream, cancellationToken);
|
||||
return CreateEntryInternal(key, source, size, modified, closeStream);
|
||||
}
|
||||
|
||||
protected abstract ValueTask<TEntry> CreateEntryInternal(string key, Stream source, long size, DateTime? modified,
|
||||
bool closeStream, CancellationToken cancellationToken);
|
||||
protected abstract TEntry CreateEntryInternal(string key, Stream source, long size, DateTime? modified,
|
||||
bool closeStream);
|
||||
|
||||
protected abstract ValueTask SaveToAsync(Stream stream, WriterOptions options, IAsyncEnumerable<TEntry> oldEntries, IAsyncEnumerable<TEntry> newEntries,
|
||||
CancellationToken cancellationToken = default);
|
||||
protected abstract void SaveTo(Stream stream, WriterOptions options, IEnumerable<TEntry> oldEntries, IEnumerable<TEntry> newEntries);
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
public override void Dispose()
|
||||
{
|
||||
await base.DisposeAsync();
|
||||
await newEntries.Cast<Entry>().ForEachAsync(async x => await x.CloseAsync());
|
||||
await removedEntries.Cast<Entry>().ForEachAsync(async x => await x.CloseAsync());
|
||||
await modifiedEntries.Cast<Entry>().ForEachAsync(async x => await x.CloseAsync());
|
||||
base.Dispose();
|
||||
newEntries.Cast<Entry>().ForEach(x => x.Close());
|
||||
removedEntries.Cast<Entry>().ForEach(x => x.Close());
|
||||
modifiedEntries.Cast<Entry>().ForEach(x => x.Close());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Archives.Dmg;
|
||||
using SharpCompress.Archives.GZip;
|
||||
//using SharpCompress.Archives.Rar;
|
||||
//using SharpCompress.Archives.SevenZip;
|
||||
using SharpCompress.Archives.Rar;
|
||||
using SharpCompress.Archives.SevenZip;
|
||||
using SharpCompress.Archives.Tar;
|
||||
using SharpCompress.Archives.Zip;
|
||||
using SharpCompress.Common;
|
||||
@@ -20,7 +19,7 @@ namespace SharpCompress.Archives
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
/// <returns></returns>
|
||||
public static async ValueTask<IArchive> OpenAsync(Stream stream, ReaderOptions? readerOptions = null, CancellationToken cancellationToken = default)
|
||||
public static IArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
if (!stream.CanRead || !stream.CanSeek)
|
||||
@@ -28,36 +27,42 @@ namespace SharpCompress.Archives
|
||||
throw new ArgumentException("Stream should be readable and seekable");
|
||||
}
|
||||
readerOptions ??= new ReaderOptions();
|
||||
if (await ZipArchive.IsZipFileAsync(stream, null, cancellationToken))
|
||||
if (ZipArchive.IsZipFile(stream, null))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return ZipArchive.Open(stream, readerOptions);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
/*if (SevenZipArchive.IsSevenZipFile(stream))
|
||||
if (SevenZipArchive.IsSevenZipFile(stream))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return SevenZipArchive.Open(stream, readerOptions);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin); */
|
||||
if (await GZipArchive.IsGZipFileAsync(stream, cancellationToken))
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
if (GZipArchive.IsGZipFile(stream))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return GZipArchive.Open(stream, readerOptions);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
/* if (RarArchive.IsRarFile(stream, readerOptions))
|
||||
if (DmgArchive.IsDmgFile(stream))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return DmgArchive.Open(stream, readerOptions);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
if (RarArchive.IsRarFile(stream, readerOptions))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return RarArchive.Open(stream, readerOptions);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin); */
|
||||
if (await TarArchive.IsTarFileAsync(stream, cancellationToken))
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
if (TarArchive.IsTarFile(stream))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return TarArchive.Open(stream, readerOptions);
|
||||
}
|
||||
throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip, LZip");
|
||||
}
|
||||
throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip, LZip, Dmg");
|
||||
}
|
||||
|
||||
public static IWritableArchive Create(ArchiveType type)
|
||||
@@ -65,7 +70,7 @@ namespace SharpCompress.Archives
|
||||
return type switch
|
||||
{
|
||||
ArchiveType.Zip => ZipArchive.Create(),
|
||||
//ArchiveType.Tar => TarArchive.Create(),
|
||||
ArchiveType.Tar => TarArchive.Create(),
|
||||
ArchiveType.GZip => GZipArchive.Create(),
|
||||
_ => throw new NotSupportedException("Cannot create Archives of type: " + type)
|
||||
};
|
||||
@@ -76,10 +81,10 @@ namespace SharpCompress.Archives
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="options"></param>
|
||||
public static ValueTask<IArchive> OpenAsync(string filePath, ReaderOptions? options = null)
|
||||
public static IArchive Open(string filePath, ReaderOptions? options = null)
|
||||
{
|
||||
filePath.CheckNotNullOrEmpty(nameof(filePath));
|
||||
return OpenAsync(new FileInfo(filePath), options);
|
||||
return Open(new FileInfo(filePath), options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -87,28 +92,34 @@ namespace SharpCompress.Archives
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="options"></param>
|
||||
public static async ValueTask<IArchive> OpenAsync(FileInfo fileInfo, ReaderOptions? options = null, CancellationToken cancellationToken = default)
|
||||
public static IArchive Open(FileInfo fileInfo, ReaderOptions? options = null)
|
||||
{
|
||||
fileInfo.CheckNotNull(nameof(fileInfo));
|
||||
options ??= new ReaderOptions { LeaveStreamOpen = false };
|
||||
|
||||
await using var stream = fileInfo.OpenRead();
|
||||
if (await ZipArchive.IsZipFileAsync(stream, null, cancellationToken))
|
||||
using var stream = fileInfo.OpenRead();
|
||||
if (ZipArchive.IsZipFile(stream, null))
|
||||
{
|
||||
return ZipArchive.Open(fileInfo, options);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
/*if (SevenZipArchive.IsSevenZipFile(stream))
|
||||
if (SevenZipArchive.IsSevenZipFile(stream))
|
||||
{
|
||||
return SevenZipArchive.Open(fileInfo, options);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin); */
|
||||
if (await GZipArchive.IsGZipFileAsync(stream, cancellationToken))
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
if (GZipArchive.IsGZipFile(stream))
|
||||
{
|
||||
return GZipArchive.Open(fileInfo, options);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
/*if (RarArchive.IsRarFile(stream, options))
|
||||
if (DmgArchive.IsDmgFile(stream))
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return DmgArchive.Open(fileInfo, options);
|
||||
}
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
if (RarArchive.IsRarFile(stream, options))
|
||||
{
|
||||
return RarArchive.Open(fileInfo, options);
|
||||
}
|
||||
@@ -116,22 +127,20 @@ namespace SharpCompress.Archives
|
||||
if (TarArchive.IsTarFile(stream))
|
||||
{
|
||||
return TarArchive.Open(fileInfo, options);
|
||||
} */
|
||||
throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip");
|
||||
}
|
||||
throw new InvalidOperationException("Cannot determine compressed stream type. Supported Archive Formats: Zip, GZip, Tar, Rar, 7Zip, Dmg");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific directory, retaining filename
|
||||
/// </summary>
|
||||
public static async ValueTask WriteToDirectory(string sourceArchive,
|
||||
string destinationDirectory,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static void WriteToDirectory(string sourceArchive, string destinationDirectory,
|
||||
ExtractionOptions? options = null)
|
||||
{
|
||||
await using IArchive archive = await OpenAsync(sourceArchive);
|
||||
await foreach (IArchiveEntry entry in archive.Entries.WithCancellation(cancellationToken))
|
||||
using IArchive archive = Open(sourceArchive);
|
||||
foreach (IArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
await entry.WriteEntryToDirectoryAsync(destinationDirectory, options, cancellationToken);
|
||||
entry.WriteToDirectory(destinationDirectory, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
117
src/SharpCompress/Archives/Dmg/DmgArchive.cs
Normal file
117
src/SharpCompress/Archives/Dmg/DmgArchive.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Dmg;
|
||||
using SharpCompress.Common.Dmg.Headers;
|
||||
using SharpCompress.Common.Dmg.HFS;
|
||||
using SharpCompress.Readers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpCompress.Archives.Dmg
|
||||
{
|
||||
public class DmgArchive : AbstractArchive<DmgArchiveEntry, DmgVolume>
|
||||
{
|
||||
private readonly string _fileName;
|
||||
|
||||
internal DmgArchive(FileInfo fileInfo, ReaderOptions readerOptions)
|
||||
: base(ArchiveType.Dmg, fileInfo, readerOptions)
|
||||
{
|
||||
_fileName = fileInfo.FullName;
|
||||
}
|
||||
|
||||
internal DmgArchive(Stream stream, ReaderOptions readerOptions)
|
||||
: base(ArchiveType.Dmg, stream.AsEnumerable(), readerOptions)
|
||||
{
|
||||
_fileName = string.Empty;
|
||||
}
|
||||
|
||||
protected override IReader CreateReaderForSolidExtraction()
|
||||
=> new DmgReader(ReaderOptions, this, _fileName);
|
||||
|
||||
protected override IEnumerable<DmgArchiveEntry> LoadEntries(IEnumerable<DmgVolume> volumes)
|
||||
=> volumes.Single().LoadEntries();
|
||||
|
||||
protected override IEnumerable<DmgVolume> LoadVolumes(FileInfo file)
|
||||
=> new DmgVolume(this, file.OpenRead(), file.FullName, ReaderOptions).AsEnumerable();
|
||||
|
||||
protected override IEnumerable<DmgVolume> LoadVolumes(IEnumerable<Stream> streams)
|
||||
=> new DmgVolume(this, streams.Single(), string.Empty, ReaderOptions).AsEnumerable();
|
||||
|
||||
public static bool IsDmgFile(FileInfo fileInfo)
|
||||
{
|
||||
if (!fileInfo.Exists) return false;
|
||||
|
||||
using var stream = fileInfo.OpenRead();
|
||||
return IsDmgFile(stream);
|
||||
}
|
||||
|
||||
public static bool IsDmgFile(Stream stream)
|
||||
{
|
||||
long headerPos = stream.Length - DmgHeader.HeaderSize;
|
||||
if (headerPos < 0) return false;
|
||||
stream.Position = headerPos;
|
||||
|
||||
return DmgHeader.TryRead(stream, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor expects a filepath to an existing file.
|
||||
/// </summary>
|
||||
/// <param name="filePath"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static DmgArchive Open(string filePath, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
filePath.CheckNotNullOrEmpty(nameof(filePath));
|
||||
return Open(new FileInfo(filePath), readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with a FileInfo object to an existing file.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static DmgArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.CheckNotNull(nameof(fileInfo));
|
||||
return new DmgArchive(fileInfo, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes a seekable Stream as a source
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static DmgArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
return new DmgArchive(stream, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
private sealed class DmgReader : AbstractReader<DmgEntry, DmgVolume>
|
||||
{
|
||||
private readonly DmgArchive _archive;
|
||||
private readonly string _fileName;
|
||||
private readonly Stream? _partitionStream;
|
||||
|
||||
public override DmgVolume Volume { get; }
|
||||
|
||||
internal DmgReader(ReaderOptions readerOptions, DmgArchive archive, string fileName)
|
||||
: base(readerOptions, ArchiveType.Dmg)
|
||||
{
|
||||
_archive = archive;
|
||||
_fileName = fileName;
|
||||
Volume = archive.Volumes.Single();
|
||||
|
||||
using var compressedStream = DmgUtil.LoadHFSPartitionStream(Volume.Stream, Volume.Header);
|
||||
_partitionStream = compressedStream?.Decompress();
|
||||
}
|
||||
|
||||
protected override IEnumerable<DmgEntry> GetEntries(Stream stream)
|
||||
{
|
||||
if (_partitionStream is null) return Array.Empty<DmgArchiveEntry>();
|
||||
else return HFSUtil.LoadEntriesFromPartition(_partitionStream, _fileName, _archive);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/SharpCompress/Archives/Dmg/DmgArchiveEntry.cs
Normal file
32
src/SharpCompress/Archives/Dmg/DmgArchiveEntry.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using SharpCompress.Common.Dmg;
|
||||
using SharpCompress.Common.Dmg.HFS;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Archives.Dmg
|
||||
{
|
||||
public sealed class DmgArchiveEntry : DmgEntry, IArchiveEntry
|
||||
{
|
||||
private readonly Stream? _stream;
|
||||
|
||||
public bool IsComplete { get; } = true;
|
||||
|
||||
public IArchive Archive { get; }
|
||||
|
||||
internal DmgArchiveEntry(Stream? stream, DmgArchive archive, HFSCatalogRecord record, string path, DmgFilePart part)
|
||||
: base(record, path, stream?.Length ?? 0, part)
|
||||
{
|
||||
_stream = stream;
|
||||
Archive = archive;
|
||||
}
|
||||
|
||||
public Stream OpenEntryStream()
|
||||
{
|
||||
if (IsDirectory)
|
||||
throw new NotSupportedException("Directories cannot be opened as stream");
|
||||
|
||||
_stream!.Position = 0;
|
||||
return _stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.GZip;
|
||||
using SharpCompress.Readers;
|
||||
@@ -33,11 +29,10 @@ namespace SharpCompress.Archives.GZip
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static GZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.CheckNotNull(nameof(fileInfo));
|
||||
return new GZipArchive(fileInfo, readerOptions ?? new ReaderOptions(), cancellationToken);
|
||||
return new GZipArchive(fileInfo, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,11 +40,10 @@ namespace SharpCompress.Archives.GZip
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static GZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
return new GZipArchive(stream, readerOptions ?? new ReaderOptions(), cancellationToken);
|
||||
return new GZipArchive(stream, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
public static GZipArchive Create()
|
||||
@@ -62,58 +56,57 @@ namespace SharpCompress.Archives.GZip
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="options"></param>
|
||||
internal GZipArchive(FileInfo fileInfo, ReaderOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
: base(ArchiveType.GZip, fileInfo, options, cancellationToken)
|
||||
internal GZipArchive(FileInfo fileInfo, ReaderOptions options)
|
||||
: base(ArchiveType.GZip, fileInfo, options)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IAsyncEnumerable<GZipVolume> LoadVolumes(FileInfo file,
|
||||
CancellationToken cancellationToken)
|
||||
protected override IEnumerable<GZipVolume> LoadVolumes(FileInfo file)
|
||||
{
|
||||
return new GZipVolume(file, ReaderOptions).AsAsyncEnumerable();
|
||||
return new GZipVolume(file, ReaderOptions).AsEnumerable();
|
||||
}
|
||||
|
||||
public static ValueTask<bool> IsGZipFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
public static bool IsGZipFile(string filePath)
|
||||
{
|
||||
return IsGZipFileAsync(new FileInfo(filePath), cancellationToken);
|
||||
return IsGZipFile(new FileInfo(filePath));
|
||||
}
|
||||
|
||||
public static async ValueTask<bool> IsGZipFileAsync(FileInfo fileInfo, CancellationToken cancellationToken = default)
|
||||
public static bool IsGZipFile(FileInfo fileInfo)
|
||||
{
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await using Stream stream = fileInfo.OpenRead();
|
||||
return await IsGZipFileAsync(stream, cancellationToken);
|
||||
using Stream stream = fileInfo.OpenRead();
|
||||
return IsGZipFile(stream);
|
||||
}
|
||||
|
||||
public Task SaveToAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
public void SaveTo(string filePath)
|
||||
{
|
||||
return SaveToAsync(new FileInfo(filePath), cancellationToken);
|
||||
SaveTo(new FileInfo(filePath));
|
||||
}
|
||||
|
||||
public async Task SaveToAsync(FileInfo fileInfo, CancellationToken cancellationToken = default)
|
||||
public void SaveTo(FileInfo fileInfo)
|
||||
{
|
||||
await using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
|
||||
await SaveToAsync(stream, new WriterOptions(CompressionType.GZip), cancellationToken);
|
||||
using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
SaveTo(stream, new WriterOptions(CompressionType.GZip));
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<bool> IsGZipFileAsync(Stream stream, CancellationToken cancellationToken = default)
|
||||
public static bool IsGZipFile(Stream stream)
|
||||
{
|
||||
// read the header on the first read
|
||||
using var header = MemoryPool<byte>.Shared.Rent(10);
|
||||
var slice = header.Memory.Slice(0, 10);
|
||||
Span<byte> header = stackalloc byte[10];
|
||||
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (await stream.ReadAsync(slice, cancellationToken) != 10)
|
||||
if (!stream.ReadFully(header))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (slice.Span[0] != 0x1F || slice.Span[1] != 0x8B || slice.Span[2] != 8)
|
||||
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -126,9 +119,8 @@ namespace SharpCompress.Archives.GZip
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="options"></param>
|
||||
internal GZipArchive(Stream stream, ReaderOptions options,
|
||||
CancellationToken cancellationToken)
|
||||
: base(ArchiveType.GZip, stream, options, cancellationToken)
|
||||
internal GZipArchive(Stream stream, ReaderOptions options)
|
||||
: base(ArchiveType.GZip, stream, options)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -137,54 +129,51 @@ namespace SharpCompress.Archives.GZip
|
||||
{
|
||||
}
|
||||
|
||||
protected override async ValueTask<GZipArchiveEntry> CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified,
|
||||
bool closeStream, CancellationToken cancellationToken = default)
|
||||
protected override GZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified,
|
||||
bool closeStream)
|
||||
{
|
||||
if (await Entries.AnyAsync(cancellationToken: cancellationToken))
|
||||
if (Entries.Any())
|
||||
{
|
||||
throw new InvalidOperationException("Only one entry is allowed in a GZip Archive");
|
||||
}
|
||||
return new GZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
|
||||
}
|
||||
|
||||
protected override async ValueTask SaveToAsync(Stream stream, WriterOptions options,
|
||||
IAsyncEnumerable<GZipArchiveEntry> oldEntries,
|
||||
IAsyncEnumerable<GZipArchiveEntry> newEntries,
|
||||
CancellationToken cancellationToken = default)
|
||||
protected override void SaveTo(Stream stream, WriterOptions options,
|
||||
IEnumerable<GZipArchiveEntry> oldEntries,
|
||||
IEnumerable<GZipArchiveEntry> newEntries)
|
||||
{
|
||||
if (await Entries.CountAsync(cancellationToken: cancellationToken) > 1)
|
||||
if (Entries.Count > 1)
|
||||
{
|
||||
throw new InvalidOperationException("Only one entry is allowed in a GZip Archive");
|
||||
}
|
||||
|
||||
await using var writer = new GZipWriter(stream, new GZipWriterOptions(options));
|
||||
await foreach (var entry in oldEntries.Concat(newEntries)
|
||||
.Where(x => !x.IsDirectory)
|
||||
.WithCancellation(cancellationToken))
|
||||
using (var writer = new GZipWriter(stream, new GZipWriterOptions(options)))
|
||||
{
|
||||
await using var entryStream = await entry.OpenEntryStreamAsync(cancellationToken);
|
||||
await writer.WriteAsync(entry.Key, entryStream, entry.LastModifiedTime, cancellationToken);
|
||||
foreach (var entry in oldEntries.Concat(newEntries)
|
||||
.Where(x => !x.IsDirectory))
|
||||
{
|
||||
using (var entryStream = entry.OpenEntryStream())
|
||||
{
|
||||
writer.Write(entry.Key, entryStream, entry.LastModifiedTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<GZipVolume> LoadVolumes(IAsyncEnumerable<Stream> streams,
|
||||
[EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
protected override IEnumerable<GZipVolume> LoadVolumes(IEnumerable<Stream> streams)
|
||||
{
|
||||
yield return new GZipVolume(await streams.FirstAsync(cancellationToken: cancellationToken), ReaderOptions);
|
||||
return new GZipVolume(streams.First(), ReaderOptions).AsEnumerable();
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<GZipArchiveEntry> LoadEntries(IAsyncEnumerable<GZipVolume> volumes,
|
||||
[EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
protected override IEnumerable<GZipArchiveEntry> LoadEntries(IEnumerable<GZipVolume> volumes)
|
||||
{
|
||||
Stream stream = (await volumes.SingleAsync(cancellationToken: cancellationToken)).Stream;
|
||||
var part = new GZipFilePart(ReaderOptions.ArchiveEncoding);
|
||||
await part.Initialize(stream, cancellationToken);
|
||||
yield return new GZipArchiveEntry(this, part);
|
||||
Stream stream = volumes.Single().Stream;
|
||||
yield return new GZipArchiveEntry(this, new GZipFilePart(stream, ReaderOptions.ArchiveEncoding));
|
||||
}
|
||||
|
||||
protected override async ValueTask<IReader> CreateReaderForSolidExtraction()
|
||||
protected override IReader CreateReaderForSolidExtraction()
|
||||
{
|
||||
var stream = (await Volumes.SingleAsync()).Stream;
|
||||
var stream = Volumes.Single().Stream;
|
||||
stream.Position = 0;
|
||||
return GZipReader.Open(stream);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.GZip;
|
||||
|
||||
namespace SharpCompress.Archives.GZip
|
||||
@@ -14,7 +12,7 @@ namespace SharpCompress.Archives.GZip
|
||||
Archive = archive;
|
||||
}
|
||||
|
||||
public virtual async ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
|
||||
public virtual Stream OpenEntryStream()
|
||||
{
|
||||
//this is to reset the stream to be read multiple times
|
||||
var part = (GZipFilePart)Parts.Single();
|
||||
@@ -22,7 +20,7 @@ namespace SharpCompress.Archives.GZip
|
||||
{
|
||||
part.GetRawStream().Position = part.EntryStartPosition;
|
||||
}
|
||||
return await Parts.Single().GetCompressedStreamAsync(cancellationToken);
|
||||
return Parts.Single().GetCompressedStream();
|
||||
}
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -52,18 +50,18 @@ namespace SharpCompress.Archives.GZip
|
||||
|
||||
Stream IWritableArchiveEntry.Stream => stream;
|
||||
|
||||
public override ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
|
||||
public override Stream OpenEntryStream()
|
||||
{
|
||||
//ensure new stream is at the start, this could be reset
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return new(new NonDisposingStream(stream));
|
||||
return new NonDisposingStream(stream);
|
||||
}
|
||||
|
||||
internal override async ValueTask CloseAsync()
|
||||
internal override void Close()
|
||||
{
|
||||
if (closeStream)
|
||||
{
|
||||
await stream.DisposeAsync();
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Archives
|
||||
{
|
||||
public interface IArchive : IAsyncDisposable
|
||||
public interface IArchive : IDisposable
|
||||
{
|
||||
IAsyncEnumerable<IArchiveEntry> Entries { get; }
|
||||
IAsyncEnumerable<IVolume> Volumes { get; }
|
||||
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionBegin;
|
||||
event EventHandler<ArchiveExtractionEventArgs<IArchiveEntry>> EntryExtractionEnd;
|
||||
|
||||
event EventHandler<CompressedBytesReadEventArgs> CompressedBytesRead;
|
||||
event EventHandler<FilePartExtractionBeginEventArgs> FilePartExtractionBegin;
|
||||
|
||||
IEnumerable<IArchiveEntry> Entries { get; }
|
||||
IEnumerable<IVolume> Volumes { get; }
|
||||
|
||||
ArchiveType Type { get; }
|
||||
ValueTask EnsureEntriesLoaded();
|
||||
|
||||
/// <summary>
|
||||
/// Use this method to extract all entries in an archive in order.
|
||||
/// This is primarily for SOLID Rar Archives or 7Zip Archives as they need to be
|
||||
/// extracted sequentially for the best performance.
|
||||
/// </summary>
|
||||
ValueTask<IReader> ExtractAllEntries();
|
||||
IReader ExtractAllEntries();
|
||||
|
||||
/// <summary>
|
||||
/// Archive is SOLID (this means the Archive saved bytes by reusing information which helps for archives containing many small files).
|
||||
/// Rar Archives can be SOLID while all 7Zip archives are considered SOLID.
|
||||
/// </summary>
|
||||
ValueTask<bool> IsSolidAsync();
|
||||
bool IsSolid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// This checks to see if all the known entries have IsComplete = true
|
||||
/// </summary>
|
||||
ValueTask<bool> IsCompleteAsync();
|
||||
bool IsComplete { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the files compressed in the archive.
|
||||
/// </summary>
|
||||
ValueTask<long> TotalSizeAsync();
|
||||
long TotalSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The total size of the files as uncompressed in the archive.
|
||||
/// </summary>
|
||||
ValueTask<long> TotalUncompressedSizeAsync();
|
||||
long TotalUncompressSize { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Archives
|
||||
@@ -11,7 +9,7 @@ namespace SharpCompress.Archives
|
||||
/// Opens the current entry as a stream that will decompress as it is read.
|
||||
/// Read the entire stream or use SkipEntry on EntryStream.
|
||||
/// </summary>
|
||||
ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default);
|
||||
Stream OpenEntryStream();
|
||||
|
||||
/// <summary>
|
||||
/// The archive can find all the parts of the archive needed to extract this entry.
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -8,53 +6,58 @@ namespace SharpCompress.Archives
|
||||
{
|
||||
public static class IArchiveEntryExtensions
|
||||
{
|
||||
public static async ValueTask WriteToAsync(this IArchiveEntry archiveEntry, Stream streamToWriteTo, CancellationToken cancellationToken = default)
|
||||
public static void WriteTo(this IArchiveEntry archiveEntry, Stream streamToWriteTo)
|
||||
{
|
||||
if (archiveEntry.IsDirectory)
|
||||
{
|
||||
throw new ExtractionException("Entry is a file directory and cannot be extracted.");
|
||||
}
|
||||
|
||||
var archive = archiveEntry.Archive;
|
||||
await archive.EnsureEntriesLoaded();
|
||||
var entryStream = await archiveEntry.OpenEntryStreamAsync(cancellationToken);
|
||||
var streamListener = (IArchiveExtractionListener)archiveEntry.Archive;
|
||||
streamListener.EnsureEntriesLoaded();
|
||||
streamListener.FireEntryExtractionBegin(archiveEntry);
|
||||
streamListener.FireFilePartExtractionBegin(archiveEntry.Key, archiveEntry.Size, archiveEntry.CompressedSize);
|
||||
var entryStream = archiveEntry.OpenEntryStream();
|
||||
if (entryStream is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await using (entryStream)
|
||||
using (entryStream)
|
||||
{
|
||||
await entryStream.TransferToAsync(streamToWriteTo, cancellationToken);
|
||||
using (Stream s = new ListeningStream(streamListener, entryStream))
|
||||
{
|
||||
s.TransferTo(streamToWriteTo);
|
||||
}
|
||||
}
|
||||
streamListener.FireEntryExtractionEnd(archiveEntry);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific directory, retaining filename
|
||||
/// </summary>
|
||||
public static ValueTask WriteEntryToDirectoryAsync(this IArchiveEntry entry,
|
||||
string destinationDirectory,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static void WriteToDirectory(this IArchiveEntry entry, string destinationDirectory,
|
||||
ExtractionOptions? options = null)
|
||||
{
|
||||
return ExtractionMethods.WriteEntryToDirectoryAsync(entry, destinationDirectory, options,
|
||||
entry.WriteToFileAsync, cancellationToken);
|
||||
ExtractionMethods.WriteEntryToDirectory(entry, destinationDirectory, options,
|
||||
entry.WriteToFile);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extract to specific file
|
||||
/// </summary>
|
||||
public static ValueTask WriteToFileAsync(this IArchiveEntry entry,
|
||||
public static void WriteToFile(this IArchiveEntry entry,
|
||||
string destinationFileName,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
ExtractionOptions? options = null)
|
||||
{
|
||||
|
||||
return ExtractionMethods.WriteEntryToFileAsync(entry, destinationFileName, options,
|
||||
async (x, fm, ct) =>
|
||||
ExtractionMethods.WriteEntryToFile(entry, destinationFileName, options,
|
||||
(x, fm) =>
|
||||
{
|
||||
await using FileStream fs = File.Open(x, fm);
|
||||
await entry.WriteToAsync(fs, ct);
|
||||
}, cancellationToken);
|
||||
using (FileStream fs = File.Open(destinationFileName, fm))
|
||||
{
|
||||
entry.WriteTo(fs);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Archives
|
||||
@@ -10,14 +8,12 @@ namespace SharpCompress.Archives
|
||||
/// <summary>
|
||||
/// Extract to specific directory, retaining filename
|
||||
/// </summary>
|
||||
public static async ValueTask WriteToDirectoryAsync(this IArchive archive,
|
||||
string destinationDirectory,
|
||||
ExtractionOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static void WriteToDirectory(this IArchive archive, string destinationDirectory,
|
||||
ExtractionOptions? options = null)
|
||||
{
|
||||
await foreach (IArchiveEntry entry in archive.Entries.Where(x => !x.IsDirectory).WithCancellation(cancellationToken))
|
||||
foreach (IArchiveEntry entry in archive.Entries.Where(x => !x.IsDirectory))
|
||||
{
|
||||
await entry.WriteEntryToDirectoryAsync(destinationDirectory, options, cancellationToken);
|
||||
entry.WriteToDirectory(destinationDirectory, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/SharpCompress/Archives/IArchiveExtractionListener.cs
Normal file
11
src/SharpCompress/Archives/IArchiveExtractionListener.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Archives
|
||||
{
|
||||
internal interface IArchiveExtractionListener : IExtractionListener
|
||||
{
|
||||
void EnsureEntriesLoaded();
|
||||
void FireEntryExtractionBegin(IArchiveEntry entry);
|
||||
void FireEntryExtractionEnd(IArchiveEntry entry);
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Writers;
|
||||
|
||||
namespace SharpCompress.Archives
|
||||
{
|
||||
public interface IWritableArchive : IArchive
|
||||
{
|
||||
ValueTask RemoveEntryAsync(IArchiveEntry entry, CancellationToken cancellationToken = default);
|
||||
void RemoveEntry(IArchiveEntry entry);
|
||||
|
||||
ValueTask<IArchiveEntry> AddEntryAsync(string key, Stream source, bool closeStream, long size = 0, DateTime? modified = null, CancellationToken cancellationToken = default);
|
||||
IArchiveEntry AddEntry(string key, Stream source, bool closeStream, long size = 0, DateTime? modified = null);
|
||||
|
||||
ValueTask SaveToAsync(Stream stream, WriterOptions options, CancellationToken cancellationToken = default);
|
||||
void SaveTo(Stream stream, WriterOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Use this to pause entry rebuilding when adding large collections of entries. Dispose when complete. A using statement is recommended.
|
||||
/// </summary>
|
||||
/// <returns>IDisposeable to resume entry rebuilding</returns>
|
||||
IAsyncDisposable PauseEntryRebuilding();
|
||||
IDisposable PauseEntryRebuilding();
|
||||
}
|
||||
}
|
||||
@@ -1,62 +1,57 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Writers;
|
||||
|
||||
namespace SharpCompress.Archives
|
||||
{
|
||||
public static class IWritableArchiveExtensions
|
||||
{
|
||||
public static async ValueTask AddEntryAsync(this IWritableArchive writableArchive,
|
||||
string entryPath, string filePath,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static void AddEntry(this IWritableArchive writableArchive,
|
||||
string entryPath, string filePath)
|
||||
{
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
throw new FileNotFoundException("Could not AddEntry: " + filePath);
|
||||
}
|
||||
await writableArchive.AddEntryAsync(entryPath, new FileInfo(filePath).OpenRead(), true, fileInfo.Length,
|
||||
fileInfo.LastWriteTime, cancellationToken);
|
||||
writableArchive.AddEntry(entryPath, new FileInfo(filePath).OpenRead(), true, fileInfo.Length,
|
||||
fileInfo.LastWriteTime);
|
||||
}
|
||||
|
||||
public static Task SaveToAsync(this IWritableArchive writableArchive, string filePath, WriterOptions options, CancellationToken cancellationToken = default)
|
||||
public static void SaveTo(this IWritableArchive writableArchive, string filePath, WriterOptions options)
|
||||
{
|
||||
return writableArchive.SaveToAsync(new FileInfo(filePath), options, cancellationToken);
|
||||
writableArchive.SaveTo(new FileInfo(filePath), options);
|
||||
}
|
||||
|
||||
public static async Task SaveToAsync(this IWritableArchive writableArchive, FileInfo fileInfo, WriterOptions options, CancellationToken cancellationToken = default)
|
||||
public static void SaveTo(this IWritableArchive writableArchive, FileInfo fileInfo, WriterOptions options)
|
||||
{
|
||||
await using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
|
||||
await writableArchive.SaveToAsync(stream, options, cancellationToken);
|
||||
using (var stream = fileInfo.Open(FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
writableArchive.SaveTo(stream, options);
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask AddAllFromDirectoryAsync(
|
||||
public static void AddAllFromDirectory(
|
||||
this IWritableArchive writableArchive,
|
||||
string filePath, string searchPattern = "*.*",
|
||||
SearchOption searchOption = SearchOption.AllDirectories,
|
||||
CancellationToken cancellationToken = default)
|
||||
string filePath, string searchPattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories)
|
||||
{
|
||||
await using (writableArchive.PauseEntryRebuilding())
|
||||
using (writableArchive.PauseEntryRebuilding())
|
||||
{
|
||||
foreach (var path in Directory.EnumerateFiles(filePath, searchPattern, searchOption))
|
||||
{
|
||||
var fileInfo = new FileInfo(path);
|
||||
await writableArchive.AddEntryAsync(path.Substring(filePath.Length), fileInfo.OpenRead(), true, fileInfo.Length,
|
||||
fileInfo.LastWriteTime,
|
||||
cancellationToken);
|
||||
writableArchive.AddEntry(path.Substring(filePath.Length), fileInfo.OpenRead(), true, fileInfo.Length,
|
||||
fileInfo.LastWriteTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
public static ValueTask<IArchiveEntry> AddEntryAsync(this IWritableArchive writableArchive, string key, FileInfo fileInfo,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static IArchiveEntry AddEntry(this IWritableArchive writableArchive, string key, FileInfo fileInfo)
|
||||
{
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
throw new ArgumentException("FileInfo does not exist.");
|
||||
}
|
||||
return writableArchive.AddEntryAsync(key, fileInfo.OpenRead(), true, fileInfo.Length, fileInfo.LastWriteTime, cancellationToken);
|
||||
return writableArchive.AddEntry(key, fileInfo.OpenRead(), true, fileInfo.Length, fileInfo.LastWriteTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Tar;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
@@ -34,11 +31,10 @@ namespace SharpCompress.Archives.Tar
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static TarArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.CheckNotNull(nameof(fileInfo));
|
||||
return new TarArchive(fileInfo, readerOptions ?? new ReaderOptions(), cancellationToken);
|
||||
return new TarArchive(fileInfo, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -46,35 +42,35 @@ namespace SharpCompress.Archives.Tar
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static TarArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
return new TarArchive(stream, readerOptions ?? new ReaderOptions(), cancellationToken);
|
||||
return new TarArchive(stream, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
public static ValueTask<bool> IsTarFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
public static bool IsTarFile(string filePath)
|
||||
{
|
||||
return IsTarFileAsync(new FileInfo(filePath), cancellationToken);
|
||||
return IsTarFile(new FileInfo(filePath));
|
||||
}
|
||||
|
||||
public static async ValueTask<bool> IsTarFileAsync(FileInfo fileInfo, CancellationToken cancellationToken = default)
|
||||
public static bool IsTarFile(FileInfo fileInfo)
|
||||
{
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await using Stream stream = fileInfo.OpenRead();
|
||||
return await IsTarFileAsync(stream, cancellationToken);
|
||||
using (Stream stream = fileInfo.OpenRead())
|
||||
{
|
||||
return IsTarFile(stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<bool> IsTarFileAsync(Stream stream, CancellationToken cancellationToken = default)
|
||||
public static bool IsTarFile(Stream stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
TarHeader tarHeader = new(new ArchiveEncoding());
|
||||
bool readSucceeded = await tarHeader.Read(stream, cancellationToken);
|
||||
TarHeader tarHeader = new TarHeader(new ArchiveEncoding());
|
||||
bool readSucceeded = tarHeader.Read(new BinaryReader(stream));
|
||||
bool isEmptyArchive = tarHeader.Name.Length == 0 && tarHeader.Size == 0 && Enum.IsDefined(typeof(EntryType), tarHeader.EntryType);
|
||||
return readSucceeded || isEmptyArchive;
|
||||
}
|
||||
@@ -89,15 +85,14 @@ namespace SharpCompress.Archives.Tar
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
internal TarArchive(FileInfo fileInfo, ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken)
|
||||
: base(ArchiveType.Tar, fileInfo, readerOptions, cancellationToken)
|
||||
internal TarArchive(FileInfo fileInfo, ReaderOptions readerOptions)
|
||||
: base(ArchiveType.Tar, fileInfo, readerOptions)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IAsyncEnumerable<TarVolume> LoadVolumes(FileInfo file, CancellationToken cancellationToken)
|
||||
protected override IEnumerable<TarVolume> LoadVolumes(FileInfo file)
|
||||
{
|
||||
return new TarVolume(file.OpenRead(), ReaderOptions).AsAsyncEnumerable();
|
||||
return new TarVolume(file.OpenRead(), ReaderOptions).AsEnumerable();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,9 +100,8 @@ namespace SharpCompress.Archives.Tar
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
internal TarArchive(Stream stream, ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken)
|
||||
: base(ArchiveType.Tar, stream, readerOptions, cancellationToken)
|
||||
internal TarArchive(Stream stream, ReaderOptions readerOptions)
|
||||
: base(ArchiveType.Tar, stream, readerOptions)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -116,18 +110,16 @@ namespace SharpCompress.Archives.Tar
|
||||
{
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<TarVolume> LoadVolumes(IAsyncEnumerable<Stream> streams,
|
||||
[EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
protected override IEnumerable<TarVolume> LoadVolumes(IEnumerable<Stream> streams)
|
||||
{
|
||||
yield return new TarVolume(await streams.FirstAsync(cancellationToken: cancellationToken), ReaderOptions);
|
||||
return new TarVolume(streams.First(), ReaderOptions).AsEnumerable();
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<TarArchiveEntry> LoadEntries(IAsyncEnumerable<TarVolume> volumes,
|
||||
[EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
protected override IEnumerable<TarArchiveEntry> LoadEntries(IEnumerable<TarVolume> volumes)
|
||||
{
|
||||
Stream stream = (await volumes.SingleAsync(cancellationToken: cancellationToken)).Stream;
|
||||
Stream stream = volumes.Single().Stream;
|
||||
TarHeader? previousHeader = null;
|
||||
await foreach (TarHeader? header in TarHeaderFactory.ReadHeader(StreamingMode.Seekable, stream, ReaderOptions.ArchiveEncoding, cancellationToken))
|
||||
foreach (TarHeader? header in TarHeaderFactory.ReadHeader(StreamingMode.Seekable, stream, ReaderOptions.ArchiveEncoding))
|
||||
{
|
||||
if (header != null)
|
||||
{
|
||||
@@ -144,11 +136,11 @@ namespace SharpCompress.Archives.Tar
|
||||
|
||||
var oldStreamPos = stream.Position;
|
||||
|
||||
await using (var entryStream = await entry.OpenEntryStreamAsync(cancellationToken))
|
||||
using (var entryStream = entry.OpenEntryStream())
|
||||
{
|
||||
await using (var memoryStream = new MemoryStream())
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
await entryStream.TransferToAsync(memoryStream, cancellationToken);
|
||||
entryStream.TransferTo(memoryStream);
|
||||
memoryStream.Position = 0;
|
||||
var bytes = memoryStream.ToArray();
|
||||
|
||||
@@ -168,37 +160,38 @@ namespace SharpCompress.Archives.Tar
|
||||
|
||||
public static TarArchive Create()
|
||||
{
|
||||
return new();
|
||||
return new TarArchive();
|
||||
}
|
||||
|
||||
protected override ValueTask<TarArchiveEntry> CreateEntryInternal(string filePath, Stream source,
|
||||
long size, DateTime? modified, bool closeStream,
|
||||
CancellationToken cancellationToken)
|
||||
protected override TarArchiveEntry CreateEntryInternal(string filePath, Stream source,
|
||||
long size, DateTime? modified, bool closeStream)
|
||||
{
|
||||
return new (new TarWritableArchiveEntry(this, source, CompressionType.Unknown, filePath, size, modified,
|
||||
closeStream));
|
||||
return new TarWritableArchiveEntry(this, source, CompressionType.Unknown, filePath, size, modified,
|
||||
closeStream);
|
||||
}
|
||||
|
||||
protected override async ValueTask SaveToAsync(Stream stream, WriterOptions options,
|
||||
IAsyncEnumerable<TarArchiveEntry> oldEntries,
|
||||
IAsyncEnumerable<TarArchiveEntry> newEntries,
|
||||
CancellationToken cancellationToken = default)
|
||||
protected override void SaveTo(Stream stream, WriterOptions options,
|
||||
IEnumerable<TarArchiveEntry> oldEntries,
|
||||
IEnumerable<TarArchiveEntry> newEntries)
|
||||
{
|
||||
await using var writer = await TarWriter.CreateAsync(stream, new TarWriterOptions(options), cancellationToken);
|
||||
await foreach (var entry in oldEntries.Concat(newEntries)
|
||||
.Where(x => !x.IsDirectory)
|
||||
.WithCancellation(cancellationToken))
|
||||
using (var writer = new TarWriter(stream, new TarWriterOptions(options)))
|
||||
{
|
||||
await using var entryStream = await entry.OpenEntryStreamAsync(cancellationToken);
|
||||
await writer.WriteAsync(entry.Key, entryStream, entry.LastModifiedTime, entry.Size, cancellationToken);
|
||||
foreach (var entry in oldEntries.Concat(newEntries)
|
||||
.Where(x => !x.IsDirectory))
|
||||
{
|
||||
using (var entryStream = entry.OpenEntryStream())
|
||||
{
|
||||
writer.Write(entry.Key, entryStream, entry.LastModifiedTime, entry.Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async ValueTask<IReader> CreateReaderForSolidExtraction()
|
||||
protected override IReader CreateReaderForSolidExtraction()
|
||||
{
|
||||
var stream = (await Volumes.SingleAsync()).Stream;
|
||||
var stream = Volumes.Single().Stream;
|
||||
stream.Position = 0;
|
||||
return await TarReader.OpenAsync(stream);
|
||||
return TarReader.Open(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Tar;
|
||||
|
||||
@@ -15,9 +13,9 @@ namespace SharpCompress.Archives.Tar
|
||||
Archive = archive;
|
||||
}
|
||||
|
||||
public virtual async ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
|
||||
public virtual Stream OpenEntryStream()
|
||||
{
|
||||
return await Parts.Single().GetCompressedStreamAsync(cancellationToken);
|
||||
return Parts.Single().GetCompressedStream();
|
||||
}
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -51,18 +49,18 @@ namespace SharpCompress.Archives.Tar
|
||||
internal override IEnumerable<FilePart> Parts => throw new NotImplementedException();
|
||||
Stream IWritableArchiveEntry.Stream => stream;
|
||||
|
||||
public override ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
|
||||
public override Stream OpenEntryStream()
|
||||
{
|
||||
//ensure new stream is at the start, this could be reset
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return new(new NonDisposingStream(stream));
|
||||
return new NonDisposingStream(stream);
|
||||
}
|
||||
|
||||
internal override async ValueTask CloseAsync()
|
||||
internal override void Close()
|
||||
{
|
||||
if (closeStream)
|
||||
{
|
||||
await stream.DisposeAsync();
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Zip;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
using SharpCompress.Readers.Zip;
|
||||
using SharpCompress.Writers;
|
||||
@@ -45,11 +41,10 @@ namespace SharpCompress.Archives.Zip
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static ZipArchive Open(FileInfo fileInfo, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
fileInfo.CheckNotNull(nameof(fileInfo));
|
||||
return new ZipArchive(fileInfo, readerOptions ?? new ReaderOptions(), cancellationToken);
|
||||
return new ZipArchive(fileInfo, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,45 +52,35 @@ namespace SharpCompress.Archives.Zip
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
public static ZipArchive Open(Stream stream, ReaderOptions? readerOptions = null)
|
||||
{
|
||||
stream.CheckNotNull(nameof(stream));
|
||||
return new ZipArchive(stream, readerOptions ?? new ReaderOptions(), cancellationToken);
|
||||
return new ZipArchive(stream, readerOptions ?? new ReaderOptions());
|
||||
}
|
||||
|
||||
public static ValueTask<bool> IsZipFile(string filePath, string? password = null)
|
||||
public static bool IsZipFile(string filePath, string? password = null)
|
||||
{
|
||||
return IsZipFileAsync(new FileInfo(filePath), password);
|
||||
return IsZipFile(new FileInfo(filePath), password);
|
||||
}
|
||||
|
||||
public static async ValueTask<bool> IsZipFileAsync(FileInfo fileInfo, string? password = null)
|
||||
public static bool IsZipFile(FileInfo fileInfo, string? password = null)
|
||||
{
|
||||
if (!fileInfo.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
await using Stream stream = fileInfo.OpenRead();
|
||||
return await IsZipFileAsync(stream, password);
|
||||
using (Stream stream = fileInfo.OpenRead())
|
||||
{
|
||||
return IsZipFile(stream, password);
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask<bool> IsZipFileAsync(Stream stream, string? password = null, CancellationToken cancellationToken = default)
|
||||
public static bool IsZipFile(Stream stream, string? password = null)
|
||||
{
|
||||
StreamingZipHeaderFactory headerFactory = new(password, new ArchiveEncoding());
|
||||
StreamingZipHeaderFactory headerFactory = new StreamingZipHeaderFactory(password, new ArchiveEncoding());
|
||||
try
|
||||
{
|
||||
RewindableStream rewindableStream;
|
||||
if (stream is RewindableStream rs)
|
||||
{
|
||||
rewindableStream = rs;
|
||||
}
|
||||
else
|
||||
{
|
||||
rewindableStream = new RewindableStream(stream);
|
||||
}
|
||||
ZipHeader? header = await headerFactory.ReadStreamHeader(rewindableStream, cancellationToken)
|
||||
.FirstOrDefaultAsync(x => x.ZipHeaderType != ZipHeaderType.Split, cancellationToken: cancellationToken);
|
||||
ZipHeader? header = headerFactory.ReadStreamHeader(stream).FirstOrDefault(x => x.ZipHeaderType != ZipHeaderType.Split);
|
||||
if (header is null)
|
||||
{
|
||||
return false;
|
||||
@@ -117,17 +102,15 @@ namespace SharpCompress.Archives.Zip
|
||||
/// </summary>
|
||||
/// <param name="fileInfo"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
internal ZipArchive(FileInfo fileInfo, ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken)
|
||||
: base(ArchiveType.Zip, fileInfo, readerOptions, cancellationToken)
|
||||
internal ZipArchive(FileInfo fileInfo, ReaderOptions readerOptions)
|
||||
: base(ArchiveType.Zip, fileInfo, readerOptions)
|
||||
{
|
||||
headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding);
|
||||
}
|
||||
|
||||
protected override IAsyncEnumerable<ZipVolume> LoadVolumes(FileInfo file,
|
||||
CancellationToken cancellationToken)
|
||||
protected override IEnumerable<ZipVolume> LoadVolumes(FileInfo file)
|
||||
{
|
||||
return new ZipVolume(file.OpenRead(), ReaderOptions).AsAsyncEnumerable();
|
||||
return new ZipVolume(file.OpenRead(), ReaderOptions).AsEnumerable();
|
||||
}
|
||||
|
||||
internal ZipArchive()
|
||||
@@ -140,86 +123,82 @@ namespace SharpCompress.Archives.Zip
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <param name="readerOptions"></param>
|
||||
internal ZipArchive(Stream stream, ReaderOptions readerOptions,
|
||||
CancellationToken cancellationToken)
|
||||
: base(ArchiveType.Zip, stream, readerOptions, cancellationToken)
|
||||
internal ZipArchive(Stream stream, ReaderOptions readerOptions)
|
||||
: base(ArchiveType.Zip, stream, readerOptions)
|
||||
{
|
||||
headerFactory = new SeekableZipHeaderFactory(readerOptions.Password, readerOptions.ArchiveEncoding);
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<ZipVolume> LoadVolumes(IAsyncEnumerable<Stream> streams,
|
||||
[EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
protected override IEnumerable<ZipVolume> LoadVolumes(IEnumerable<Stream> streams)
|
||||
{
|
||||
yield return new ZipVolume(await streams.FirstAsync(cancellationToken: cancellationToken), ReaderOptions);
|
||||
return new ZipVolume(streams.First(), ReaderOptions).AsEnumerable();
|
||||
}
|
||||
|
||||
protected override async IAsyncEnumerable<ZipArchiveEntry> LoadEntries(IAsyncEnumerable<ZipVolume> volumes,
|
||||
[EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
protected override IEnumerable<ZipArchiveEntry> LoadEntries(IEnumerable<ZipVolume> volumes)
|
||||
{
|
||||
await Task.CompletedTask;
|
||||
var volume = await volumes.SingleAsync(cancellationToken: cancellationToken);
|
||||
var volume = volumes.Single();
|
||||
Stream stream = volume.Stream;
|
||||
await foreach (ZipHeader h in headerFactory.ReadSeekableHeader(stream, cancellationToken))
|
||||
foreach (ZipHeader h in headerFactory.ReadSeekableHeader(stream))
|
||||
{
|
||||
if (h != null)
|
||||
{
|
||||
switch (h.ZipHeaderType)
|
||||
{
|
||||
case ZipHeaderType.DirectoryEntry:
|
||||
{
|
||||
yield return new ZipArchiveEntry(this,
|
||||
new SeekableZipFilePart(headerFactory,
|
||||
(DirectoryEntryHeader)h,
|
||||
stream));
|
||||
}
|
||||
{
|
||||
yield return new ZipArchiveEntry(this,
|
||||
new SeekableZipFilePart(headerFactory,
|
||||
(DirectoryEntryHeader)h,
|
||||
stream));
|
||||
}
|
||||
break;
|
||||
case ZipHeaderType.DirectoryEnd:
|
||||
{
|
||||
byte[] bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty<byte>();
|
||||
volume.Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
|
||||
yield break;
|
||||
}
|
||||
{
|
||||
byte[] bytes = ((DirectoryEndHeader)h).Comment ?? Array.Empty<byte>();
|
||||
volume.Comment = ReaderOptions.ArchiveEncoding.Decode(bytes);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask SaveToAsync(Stream stream, CancellationToken cancellationToken = default)
|
||||
public void SaveTo(Stream stream)
|
||||
{
|
||||
return SaveToAsync(stream, new WriterOptions(CompressionType.Deflate), cancellationToken);
|
||||
SaveTo(stream, new WriterOptions(CompressionType.Deflate));
|
||||
}
|
||||
|
||||
protected override async ValueTask SaveToAsync(Stream stream, WriterOptions options,
|
||||
IAsyncEnumerable<ZipArchiveEntry> oldEntries,
|
||||
IAsyncEnumerable<ZipArchiveEntry> newEntries,
|
||||
CancellationToken cancellationToken = default)
|
||||
protected override void SaveTo(Stream stream, WriterOptions options,
|
||||
IEnumerable<ZipArchiveEntry> oldEntries,
|
||||
IEnumerable<ZipArchiveEntry> newEntries)
|
||||
{
|
||||
await using var writer = new ZipWriter(stream, new ZipWriterOptions(options));
|
||||
await foreach (var entry in oldEntries.Concat(newEntries)
|
||||
.Where(x => !x.IsDirectory)
|
||||
.WithCancellation(cancellationToken))
|
||||
using (var writer = new ZipWriter(stream, new ZipWriterOptions(options)))
|
||||
{
|
||||
await using (var entryStream = await entry.OpenEntryStreamAsync(cancellationToken))
|
||||
foreach (var entry in oldEntries.Concat(newEntries)
|
||||
.Where(x => !x.IsDirectory))
|
||||
{
|
||||
await writer.WriteAsync(entry.Key, entryStream, entry.LastModifiedTime, cancellationToken);
|
||||
using (var entryStream = entry.OpenEntryStream())
|
||||
{
|
||||
writer.Write(entry.Key, entryStream, entry.LastModifiedTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override ValueTask<ZipArchiveEntry> CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified,
|
||||
bool closeStream, CancellationToken cancellationToken = default)
|
||||
protected override ZipArchiveEntry CreateEntryInternal(string filePath, Stream source, long size, DateTime? modified,
|
||||
bool closeStream)
|
||||
{
|
||||
return new(new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream));
|
||||
return new ZipWritableArchiveEntry(this, source, filePath, size, modified, closeStream);
|
||||
}
|
||||
|
||||
public static ZipArchive Create()
|
||||
{
|
||||
return new();
|
||||
return new ZipArchive();
|
||||
}
|
||||
|
||||
protected override async ValueTask<IReader> CreateReaderForSolidExtraction()
|
||||
protected override IReader CreateReaderForSolidExtraction()
|
||||
{
|
||||
var stream = (await Volumes.SingleAsync()).Stream;
|
||||
var stream = Volumes.Single().Stream;
|
||||
stream.Position = 0;
|
||||
return ZipReader.Open(stream, ReaderOptions);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Zip;
|
||||
|
||||
namespace SharpCompress.Archives.Zip
|
||||
@@ -14,9 +12,9 @@ namespace SharpCompress.Archives.Zip
|
||||
Archive = archive;
|
||||
}
|
||||
|
||||
public virtual ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
|
||||
public virtual Stream OpenEntryStream()
|
||||
{
|
||||
return Parts.Single().GetCompressedStreamAsync(cancellationToken);
|
||||
return Parts.Single().GetCompressedStream();
|
||||
}
|
||||
|
||||
#region IArchiveEntry Members
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -51,18 +49,18 @@ namespace SharpCompress.Archives.Zip
|
||||
|
||||
Stream IWritableArchiveEntry.Stream => stream;
|
||||
|
||||
public override ValueTask<Stream> OpenEntryStreamAsync(CancellationToken cancellationToken = default)
|
||||
public override Stream OpenEntryStream()
|
||||
{
|
||||
//ensure new stream is at the start, this could be reset
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
return new(new NonDisposingStream(stream));
|
||||
return new NonDisposingStream(stream);
|
||||
}
|
||||
|
||||
internal override async ValueTask CloseAsync()
|
||||
internal override void Close()
|
||||
{
|
||||
if (closeStream && !isDisposed)
|
||||
{
|
||||
await stream.DisposeAsync();
|
||||
stream.Dispose();
|
||||
isDisposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress
|
||||
{
|
||||
public static class AsyncEnumerable
|
||||
{
|
||||
public static IAsyncEnumerable<T> Empty<T>() => EmptyAsyncEnumerable<T>.Instance;
|
||||
|
||||
|
||||
private class EmptyAsyncEnumerable<T> : IAsyncEnumerator<T>, IAsyncEnumerable<T>
|
||||
{
|
||||
public static readonly EmptyAsyncEnumerable<T> Instance =
|
||||
new();
|
||||
public T Current => default!;
|
||||
public ValueTask DisposeAsync() => default;
|
||||
public ValueTask<bool> MoveNextAsync() => new(false);
|
||||
public IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace SharpCompress.Common
|
||||
/// Set this when you want to use a custom method for all decoding operations.
|
||||
/// </summary>
|
||||
/// <returns>string Func(bytes, index, length)</returns>
|
||||
//public Func<byte[], int, int, string>? CustomDecoder { get; set; }
|
||||
public Func<byte[], int, int, string>? CustomDecoder { get; set; }
|
||||
|
||||
public ArchiveEncoding()
|
||||
: this(Encoding.Default, Encoding.Default)
|
||||
@@ -50,12 +50,7 @@ namespace SharpCompress.Common
|
||||
|
||||
public string Decode(byte[] bytes, int start, int length)
|
||||
{
|
||||
return GetEncoding().GetString(bytes, start, length);
|
||||
}
|
||||
|
||||
public string Decode(ReadOnlySpan<byte> span)
|
||||
{
|
||||
return GetEncoding().GetString(span);
|
||||
return GetDecoder().Invoke(bytes, start, length);
|
||||
}
|
||||
|
||||
public string DecodeUTF8(byte[] bytes)
|
||||
@@ -72,5 +67,10 @@ namespace SharpCompress.Common
|
||||
{
|
||||
return Forced ?? Default ?? Encoding.UTF8;
|
||||
}
|
||||
|
||||
public Func<byte[], int, int, string> GetDecoder()
|
||||
{
|
||||
return CustomDecoder ?? ((bytes, index, count) => GetEncoding().GetString(bytes, index, count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,5 +8,10 @@ namespace SharpCompress.Common
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public ArchiveException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
Zip,
|
||||
Tar,
|
||||
SevenZip,
|
||||
GZip
|
||||
GZip,
|
||||
Dmg
|
||||
}
|
||||
}
|
||||
323
src/SharpCompress/Common/Dmg/DmgBlockDataStream.cs
Normal file
323
src/SharpCompress/Common/Dmg/DmgBlockDataStream.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using SharpCompress.Common.Dmg.Headers;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Compressors.ADC;
|
||||
using SharpCompress.Compressors.BZip2;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Dmg
|
||||
{
|
||||
internal sealed class DmgBlockDataStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
private readonly DmgHeader _header;
|
||||
private readonly BlkxTable _table;
|
||||
private long _position;
|
||||
private bool _isEnded;
|
||||
private int _chunkIndex;
|
||||
private Stream? _chunkStream;
|
||||
private long _chunkPos;
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanWrite => false;
|
||||
public override bool CanSeek => true;
|
||||
public override long Length { get; }
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if ((value < 0) || (value > Length)) throw new ArgumentOutOfRangeException(nameof(value));
|
||||
|
||||
if (value == Length)
|
||||
{
|
||||
// End of the stream
|
||||
|
||||
_position = Length;
|
||||
_isEnded = true;
|
||||
_chunkIndex = -1;
|
||||
_chunkStream = null;
|
||||
}
|
||||
else if (value != _position)
|
||||
{
|
||||
_position = value;
|
||||
|
||||
// We can only seek over entire chunks at a time because some chunks may be compressed.
|
||||
// So we first find the chunk that we are now in, then we read to the exact position inside that chunk.
|
||||
|
||||
for (int i = 0; i < _table.Chunks.Count; i++)
|
||||
{
|
||||
var chunk = _table.Chunks[i];
|
||||
if (IsChunkValid(chunk) && (chunk.UncompressedOffset <= (ulong)_position)
|
||||
&& ((chunk.UncompressedOffset + chunk.UncompressedLength) > (ulong)_position))
|
||||
{
|
||||
if (i == _chunkIndex)
|
||||
{
|
||||
// We are still in the same chunk, so if the new position is
|
||||
// behind the previous one we can just read to the new position.
|
||||
|
||||
long offset = (long)chunk.UncompressedOffset + _chunkPos;
|
||||
if (offset <= _position)
|
||||
{
|
||||
long skip = _position - offset;
|
||||
_chunkStream!.Skip(skip);
|
||||
_chunkPos += skip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_chunkIndex = i;
|
||||
_chunkStream = GetChunkStream();
|
||||
_chunkPos = 0;
|
||||
|
||||
// If the chunk happens to not be compressed this read will still result in a fast seek
|
||||
if ((ulong)_position != chunk.UncompressedOffset)
|
||||
{
|
||||
long skip = _position - (long)chunk.UncompressedOffset;
|
||||
_chunkStream.Skip(skip);
|
||||
_chunkPos = skip;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DmgBlockDataStream(Stream baseStream, DmgHeader header, BlkxTable table)
|
||||
{
|
||||
if (!baseStream.CanRead) throw new ArgumentException("Requires a readable stream", nameof(baseStream));
|
||||
if (!baseStream.CanSeek) throw new ArgumentException("Requires a seekable stream", nameof(baseStream));
|
||||
|
||||
_baseStream = baseStream;
|
||||
_header = header;
|
||||
_table = table;
|
||||
|
||||
Length = 0;
|
||||
foreach (var chunk in table.Chunks)
|
||||
{
|
||||
if (IsChunkValid(chunk))
|
||||
Length += (long)chunk.UncompressedLength;
|
||||
}
|
||||
|
||||
_position = 0;
|
||||
_chunkIndex = -1;
|
||||
_chunkIndex = GetNextChunk();
|
||||
_isEnded = _chunkIndex < 0;
|
||||
if (!_isEnded) _chunkStream = GetChunkStream();
|
||||
_chunkPos = 0;
|
||||
}
|
||||
|
||||
private static bool IsChunkValid(BlkxChunk chunk)
|
||||
{
|
||||
return chunk.Type switch
|
||||
{
|
||||
BlkxChunkType.Zero => true,
|
||||
BlkxChunkType.Uncompressed => true,
|
||||
BlkxChunkType.Ignore => true,
|
||||
BlkxChunkType.AdcCompressed => true,
|
||||
BlkxChunkType.ZlibCompressed => true,
|
||||
BlkxChunkType.Bz2Compressed => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private int GetNextChunk()
|
||||
{
|
||||
int index = _chunkIndex;
|
||||
bool isValid = false;
|
||||
while (!isValid)
|
||||
{
|
||||
index++;
|
||||
if (index >= _table.Chunks.Count) return -1;
|
||||
|
||||
var chunk = _table.Chunks[index];
|
||||
if (chunk.Type == BlkxChunkType.Last) return -1;
|
||||
|
||||
isValid = IsChunkValid(chunk);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private Stream GetChunkStream()
|
||||
{
|
||||
if (_chunkIndex < 0)
|
||||
throw new InvalidOperationException("Invalid chunk index");
|
||||
|
||||
var chunk = _table.Chunks[_chunkIndex];
|
||||
|
||||
// For our purposes, ignore behaves the same as zero
|
||||
if ((chunk.Type == BlkxChunkType.Zero) || (chunk.Type == BlkxChunkType.Ignore))
|
||||
return new ConstantStream(0, (long)chunk.UncompressedLength);
|
||||
|
||||
// We first create a sub-stream on the region of the base stream where the
|
||||
// (possibly compressed) data is physically located at.
|
||||
var subStream = new SeekableSubStream(_baseStream,
|
||||
(long)(_header.DataForkOffset + _table.DataOffset + chunk.CompressedOffset),
|
||||
(long)chunk.CompressedLength);
|
||||
|
||||
// Then we nest that sub-stream into the apropriate compressed stream.
|
||||
return chunk.Type switch
|
||||
{
|
||||
BlkxChunkType.Uncompressed => subStream,
|
||||
BlkxChunkType.AdcCompressed => new ADCStream(subStream, CompressionMode.Decompress),
|
||||
BlkxChunkType.ZlibCompressed => new ZlibStream(subStream, CompressionMode.Decompress),
|
||||
BlkxChunkType.Bz2Compressed => new BZip2Stream(subStream, CompressionMode.Decompress, false),
|
||||
_ => throw new InvalidOperationException("Invalid chunk type")
|
||||
};
|
||||
}
|
||||
|
||||
// Decompresses the entire stream in memory for faster extraction.
|
||||
// This is about two orders of magnitude faster than decompressing
|
||||
// on-the-fly while extracting, but also eats RAM for breakfest.
|
||||
public Stream Decompress()
|
||||
{
|
||||
// We have to load all the chunks into separate memory streams first
|
||||
// because otherwise the decompression threads would block each other
|
||||
// and actually be slower than just a single decompression thread.
|
||||
|
||||
var rawStreams = new Stream?[_table.Chunks.Count];
|
||||
for (int i = 0; i < rawStreams.Length; i++)
|
||||
{
|
||||
var chunk = _table.Chunks[i];
|
||||
if (IsChunkValid(chunk))
|
||||
{
|
||||
if ((chunk.Type == BlkxChunkType.Zero) || (chunk.Type == BlkxChunkType.Ignore))
|
||||
{
|
||||
rawStreams[i] = new ConstantStream(0, (long)chunk.UncompressedLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
var subStream = new SeekableSubStream(_baseStream,
|
||||
(long)(_header.DataForkOffset + _table.DataOffset + chunk.CompressedOffset),
|
||||
(long)chunk.CompressedLength);
|
||||
|
||||
var memStream = new MemoryStream();
|
||||
subStream.CopyTo(memStream);
|
||||
memStream.Position = 0;
|
||||
rawStreams[i] = memStream;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rawStreams[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we can decompress the chunks multithreaded
|
||||
|
||||
var streams = new Stream?[_table.Chunks.Count];
|
||||
Parallel.For(0, streams.Length, i =>
|
||||
{
|
||||
var rawStream = rawStreams[i];
|
||||
if (rawStream is not null)
|
||||
{
|
||||
var chunk = _table.Chunks[i];
|
||||
if ((chunk.Type == BlkxChunkType.Zero)
|
||||
|| (chunk.Type == BlkxChunkType.Ignore)
|
||||
|| (chunk.Type == BlkxChunkType.Uncompressed))
|
||||
{
|
||||
streams[i] = rawStream;
|
||||
}
|
||||
else
|
||||
{
|
||||
Stream compStream = chunk.Type switch
|
||||
{
|
||||
BlkxChunkType.AdcCompressed => new ADCStream(rawStream, CompressionMode.Decompress),
|
||||
BlkxChunkType.ZlibCompressed => new ZlibStream(rawStream, CompressionMode.Decompress),
|
||||
BlkxChunkType.Bz2Compressed => new BZip2Stream(rawStream, CompressionMode.Decompress, false),
|
||||
_ => throw new InvalidOperationException("Invalid chunk type")
|
||||
};
|
||||
|
||||
var memStream = new MemoryStream();
|
||||
compStream.CopyTo(memStream);
|
||||
compStream.Dispose();
|
||||
|
||||
memStream.Position = 0;
|
||||
streams[i] = memStream;
|
||||
}
|
||||
|
||||
rawStream.Dispose();
|
||||
rawStreams[i] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
streams[i] = null;
|
||||
}
|
||||
});
|
||||
|
||||
return new CompositeStream((IEnumerable<Stream>)streams.Where(s => s is not null));
|
||||
}
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_isEnded) return 0;
|
||||
|
||||
int readCount = _chunkStream!.Read(buffer, offset, count);
|
||||
_chunkPos += readCount;
|
||||
|
||||
while (readCount < count)
|
||||
{
|
||||
// Current chunk has ended, so we have to continue reading from the next chunk.
|
||||
|
||||
_chunkIndex = GetNextChunk();
|
||||
if (_chunkIndex < 0)
|
||||
{
|
||||
// We have reached the last chunk
|
||||
|
||||
_isEnded = true;
|
||||
_chunkPos = 0;
|
||||
_position += readCount;
|
||||
return readCount;
|
||||
}
|
||||
|
||||
_chunkStream = GetChunkStream();
|
||||
int rc = _chunkStream.Read(buffer, offset + readCount, count - readCount);
|
||||
_chunkPos = rc;
|
||||
readCount += rc;
|
||||
}
|
||||
|
||||
_position += readCount;
|
||||
return readCount;
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{ }
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
Position = offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
Position += offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
Position = Length - offset;
|
||||
break;
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
52
src/SharpCompress/Common/Dmg/DmgEntry.cs
Normal file
52
src/SharpCompress/Common/Dmg/DmgEntry.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using SharpCompress.Common.Dmg.HFS;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharpCompress.Common.Dmg
|
||||
{
|
||||
public abstract class DmgEntry : Entry
|
||||
{
|
||||
public override string Key { get; }
|
||||
public override bool IsDirectory { get; }
|
||||
public override long Size { get; }
|
||||
public override long CompressedSize { get; }
|
||||
public override CompressionType CompressionType { get; }
|
||||
public override DateTime? LastModifiedTime { get; }
|
||||
public override DateTime? CreatedTime { get; }
|
||||
public override DateTime? LastAccessedTime { get; }
|
||||
public override DateTime? ArchivedTime { get; }
|
||||
|
||||
public override long Crc { get; } = 0; // Not stored
|
||||
public override string? LinkTarget { get; } = null;
|
||||
public override bool IsEncrypted { get; } = false;
|
||||
public override bool IsSplitAfter { get; } = false;
|
||||
|
||||
internal override IEnumerable<FilePart> Parts { get; }
|
||||
|
||||
internal DmgEntry(HFSCatalogRecord record, string path, long size, DmgFilePart part)
|
||||
{
|
||||
Key = path;
|
||||
IsDirectory = record.Type == HFSCatalogRecordType.Folder;
|
||||
Size = CompressedSize = size; // There is no way to get the actual compressed size or the compression type of
|
||||
CompressionType = CompressionType.Unknown; // a file in a DMG archive since the files are nested inside the HFS partition.
|
||||
Parts = part.AsEnumerable();
|
||||
|
||||
if (IsDirectory)
|
||||
{
|
||||
var folder = (HFSCatalogFolder)record;
|
||||
LastModifiedTime = (folder.AttributeModDate > folder.ContentModDate) ? folder.AttributeModDate : folder.ContentModDate;
|
||||
CreatedTime = folder.CreateDate;
|
||||
LastAccessedTime = folder.AccessDate;
|
||||
ArchivedTime = folder.BackupDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
var file = (HFSCatalogFile)record;
|
||||
LastModifiedTime = (file.AttributeModDate > file.ContentModDate) ? file.AttributeModDate : file.ContentModDate;
|
||||
CreatedTime = file.CreateDate;
|
||||
LastAccessedTime = file.AccessDate;
|
||||
ArchivedTime = file.BackupDate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/SharpCompress/Common/Dmg/DmgFilePart.cs
Normal file
21
src/SharpCompress/Common/Dmg/DmgFilePart.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg
|
||||
{
|
||||
internal sealed class DmgFilePart : FilePart
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
|
||||
internal override string FilePartName { get; }
|
||||
|
||||
public DmgFilePart(Stream stream, string fileName)
|
||||
: base(new ArchiveEncoding())
|
||||
{
|
||||
_stream = stream;
|
||||
FilePartName = fileName;
|
||||
}
|
||||
|
||||
internal override Stream GetCompressedStream() => _stream;
|
||||
internal override Stream? GetRawStream() => null;
|
||||
}
|
||||
}
|
||||
183
src/SharpCompress/Common/Dmg/DmgUtil.cs
Normal file
183
src/SharpCompress/Common/Dmg/DmgUtil.cs
Normal file
@@ -0,0 +1,183 @@
|
||||
using SharpCompress.Common.Dmg.Headers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace SharpCompress.Common.Dmg
|
||||
{
|
||||
internal static class DmgUtil
|
||||
{
|
||||
private const string MalformedXmlMessage = "Malformed XML block";
|
||||
|
||||
private static T[] ParseArray<T>(in XElement parent, in Func<XElement, T> parseElement)
|
||||
{
|
||||
var list = new List<T>();
|
||||
|
||||
foreach (var node in parent.Elements())
|
||||
list.Add(parseElement(node));
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private static Dictionary<string, T> ParseDict<T>(in XElement parent, in Func<XElement, T> parseValue)
|
||||
{
|
||||
var dict = new Dictionary<string, T>();
|
||||
|
||||
string? key = null;
|
||||
foreach (var node in parent.Elements())
|
||||
{
|
||||
if (string.Equals(node.Name.LocalName, "key", StringComparison.Ordinal))
|
||||
{
|
||||
key = node.Value;
|
||||
}
|
||||
else if (key is not null)
|
||||
{
|
||||
var value = parseValue(node);
|
||||
dict.Add(key, value);
|
||||
key = null;
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
private static Dictionary<string, Dictionary<string, Dictionary<string, string>[]>> ParsePList(in XDocument doc)
|
||||
{
|
||||
var dictNode = doc.Root?.Element("dict");
|
||||
if (dictNode is null) throw new InvalidFormatException(MalformedXmlMessage);
|
||||
|
||||
static Dictionary<string, string> ParseObject(XElement parent)
|
||||
=> ParseDict(parent, node => node.Value);
|
||||
|
||||
static Dictionary<string, string>[] ParseObjectArray(XElement parent)
|
||||
=> ParseArray(parent, ParseObject);
|
||||
|
||||
static Dictionary<string, Dictionary<string, string>[]> ParseSubDict(XElement parent)
|
||||
=> ParseDict(parent, ParseObjectArray);
|
||||
|
||||
return ParseDict(dictNode, ParseSubDict);
|
||||
}
|
||||
|
||||
private static BlkxData CreateDataFromDict(in Dictionary<string, string> dict)
|
||||
{
|
||||
static bool TryParseHex(string? s, out uint value)
|
||||
{
|
||||
value = 0;
|
||||
if (string.IsNullOrEmpty(s)) return false;
|
||||
|
||||
if (s!.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
s = s.Substring(2);
|
||||
|
||||
return uint.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value);
|
||||
}
|
||||
|
||||
if (!dict.TryGetValue("ID", out string? idStr) || !int.TryParse(idStr, out int id))
|
||||
throw new InvalidFormatException(MalformedXmlMessage);
|
||||
if (!dict.TryGetValue("Name", out string? name))
|
||||
throw new InvalidFormatException(MalformedXmlMessage);
|
||||
if (!dict.TryGetValue("Attributes", out string? attribStr) || !TryParseHex(attribStr, out uint attribs))
|
||||
throw new InvalidFormatException(MalformedXmlMessage);
|
||||
if (!dict.TryGetValue("Data", out string? base64Data) || string.IsNullOrEmpty(base64Data))
|
||||
throw new InvalidFormatException(MalformedXmlMessage);
|
||||
|
||||
try
|
||||
{
|
||||
var data = Convert.FromBase64String(base64Data);
|
||||
if (!BlkxTable.TryRead(data, out var table))
|
||||
throw new InvalidFormatException("Invalid BLKX table");
|
||||
|
||||
return new BlkxData(id, name, attribs, table!);
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
throw new InvalidFormatException(MalformedXmlMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static DmgBlockDataStream? LoadHFSPartitionStream(Stream baseStream, DmgHeader header)
|
||||
{
|
||||
if ((header.XMLOffset + header.XMLLength) >= (ulong)baseStream.Length)
|
||||
throw new IncompleteArchiveException("XML block incomplete");
|
||||
if ((header.DataForkOffset + header.DataForkLength) >= (ulong)baseStream.Length)
|
||||
throw new IncompleteArchiveException("Data block incomplete");
|
||||
|
||||
baseStream.Position = (long)header.XMLOffset;
|
||||
var xmlBuffer = new byte[header.XMLLength];
|
||||
baseStream.Read(xmlBuffer, 0, (int)header.XMLLength);
|
||||
var xml = Encoding.ASCII.GetString(xmlBuffer);
|
||||
|
||||
var doc = XDocument.Parse(xml);
|
||||
var pList = ParsePList(doc);
|
||||
if (!pList.TryGetValue("resource-fork", out var resDict) || !resDict.TryGetValue("blkx", out var blkxDicts))
|
||||
throw new InvalidFormatException(MalformedXmlMessage);
|
||||
|
||||
var objs = new BlkxData[blkxDicts.Length];
|
||||
for (int i = 0; i < objs.Length; i++)
|
||||
objs[i] = CreateDataFromDict(blkxDicts[i]);
|
||||
|
||||
// Index 0 is the protective MBR partition
|
||||
// Index 1 is the GPT header
|
||||
// Index 2 is the GPT partition table
|
||||
|
||||
try
|
||||
{
|
||||
var headerData = objs[1];
|
||||
using var headerStream = new DmgBlockDataStream(baseStream, header, headerData.Table);
|
||||
if (!GptHeader.TryRead(headerStream, out var gptHeader))
|
||||
throw new InvalidFormatException("Invalid GPT header");
|
||||
|
||||
var tableData = objs[2];
|
||||
using var tableStream = new DmgBlockDataStream(baseStream, header, tableData.Table);
|
||||
var gptTable = new GptPartitionEntry[gptHeader!.EntriesCount];
|
||||
for (int i = 0; i < gptHeader.EntriesCount; i++)
|
||||
gptTable[i] = GptPartitionEntry.Read(tableStream);
|
||||
|
||||
foreach (var entry in gptTable)
|
||||
{
|
||||
if (entry.TypeGuid == PartitionFormat.AppleHFS)
|
||||
{
|
||||
BlkxData? partitionData = null;
|
||||
for (int i = 3; i < objs.Length; i++)
|
||||
{
|
||||
if (objs[i].Name.StartsWith(entry.Name, StringComparison.Ordinal))
|
||||
{
|
||||
partitionData = objs[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (partitionData is null)
|
||||
throw new InvalidFormatException($"Missing partition {entry.Name}");
|
||||
|
||||
return new DmgBlockDataStream(baseStream, header, partitionData.Table);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (EndOfStreamException ex)
|
||||
{
|
||||
throw new IncompleteArchiveException("Partition incomplete", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class BlkxData
|
||||
{
|
||||
public int Id { get; }
|
||||
public string Name { get; }
|
||||
public uint Attributes { get; }
|
||||
public BlkxTable Table { get; }
|
||||
|
||||
public BlkxData(int id, string name, uint attributes, BlkxTable table)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Attributes = attributes;
|
||||
Table = table;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/SharpCompress/Common/Dmg/DmgVolume.cs
Normal file
38
src/SharpCompress/Common/Dmg/DmgVolume.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using SharpCompress.Archives.Dmg;
|
||||
using SharpCompress.Common.Dmg.Headers;
|
||||
using SharpCompress.Common.Dmg.HFS;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg
|
||||
{
|
||||
public class DmgVolume : Volume
|
||||
{
|
||||
private readonly DmgArchive _archive;
|
||||
private readonly string _fileName;
|
||||
|
||||
internal DmgHeader Header { get; }
|
||||
|
||||
public DmgVolume(DmgArchive archive, Stream stream, string fileName, Readers.ReaderOptions readerOptions)
|
||||
: base(stream, readerOptions)
|
||||
{
|
||||
_archive = archive;
|
||||
_fileName = fileName;
|
||||
|
||||
long pos = stream.Length - DmgHeader.HeaderSize;
|
||||
if (pos < 0) throw new InvalidFormatException("Invalid DMG volume");
|
||||
stream.Position = pos;
|
||||
|
||||
if (DmgHeader.TryRead(stream, out var header)) Header = header!;
|
||||
else throw new InvalidFormatException("Invalid DMG volume");
|
||||
}
|
||||
|
||||
internal IEnumerable<DmgArchiveEntry> LoadEntries()
|
||||
{
|
||||
var partitionStream = DmgUtil.LoadHFSPartitionStream(Stream, Header);
|
||||
if (partitionStream is null) return Array.Empty<DmgArchiveEntry>();
|
||||
else return HFSUtil.LoadEntriesFromPartition(partitionStream, _fileName, _archive);
|
||||
}
|
||||
}
|
||||
}
|
||||
336
src/SharpCompress/Common/Dmg/HFS/HFSCatalogRecord.cs
Normal file
336
src/SharpCompress/Common/Dmg/HFS/HFSCatalogRecord.cs
Normal file
@@ -0,0 +1,336 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal sealed class HFSCatalogKey : HFSStructBase, IEquatable<HFSCatalogKey>, IComparable<HFSCatalogKey>, IComparable
|
||||
{
|
||||
private readonly StringComparer _comparer;
|
||||
|
||||
public uint ParentId { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
private static StringComparer GetComparer(HFSKeyCompareType compareType, bool isHFSX)
|
||||
{
|
||||
if (isHFSX)
|
||||
{
|
||||
return compareType switch
|
||||
{
|
||||
HFSKeyCompareType.CaseFolding => StringComparer.InvariantCultureIgnoreCase,
|
||||
HFSKeyCompareType.BinaryCompare => StringComparer.Ordinal,
|
||||
_ => StringComparer.InvariantCultureIgnoreCase
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return StringComparer.InvariantCultureIgnoreCase;
|
||||
}
|
||||
}
|
||||
|
||||
public HFSCatalogKey(uint parentId, string name, HFSKeyCompareType compareType, bool isHFSX)
|
||||
{
|
||||
ParentId = parentId;
|
||||
Name = name;
|
||||
_comparer = GetComparer(compareType, isHFSX);
|
||||
}
|
||||
|
||||
public HFSCatalogKey(byte[] key, HFSKeyCompareType compareType, bool isHFSX)
|
||||
{
|
||||
ReadOnlySpan<byte> data = key.AsSpan();
|
||||
ParentId = ReadUInt32(ref data);
|
||||
Name = ReadString(ref data, true);
|
||||
_comparer = GetComparer(compareType, isHFSX);
|
||||
}
|
||||
|
||||
public bool Equals(HFSCatalogKey? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
else return (ParentId == other.ParentId) && _comparer.Equals(Name, other.Name);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is HFSCatalogKey other) return Equals(other);
|
||||
else return false;
|
||||
}
|
||||
|
||||
public int CompareTo(HFSCatalogKey? other)
|
||||
{
|
||||
if (other is null) return 1;
|
||||
|
||||
int result = ParentId.CompareTo(other.ParentId);
|
||||
if (result == 0) result = _comparer.Compare(Name, other.Name);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
else if (obj is HFSCatalogKey other) return CompareTo(other);
|
||||
else throw new ArgumentException("Object is not of type CatalogKey", nameof(obj));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
=> ParentId.GetHashCode() ^ _comparer.GetHashCode(Name);
|
||||
|
||||
public static bool operator ==(HFSCatalogKey? left, HFSCatalogKey? right)
|
||||
{
|
||||
if (left is null) return right is null;
|
||||
else return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(HFSCatalogKey? left, HFSCatalogKey? right)
|
||||
{
|
||||
if (left is null) return right is not null;
|
||||
else return !left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator <(HFSCatalogKey? left, HFSCatalogKey? right)
|
||||
{
|
||||
if (left is null) return right is not null;
|
||||
else return left.CompareTo(right) < 0;
|
||||
}
|
||||
|
||||
public static bool operator >(HFSCatalogKey? left, HFSCatalogKey? right)
|
||||
{
|
||||
if (left is null) return false;
|
||||
else return left.CompareTo(right) > 0;
|
||||
}
|
||||
|
||||
public static bool operator <=(HFSCatalogKey? left, HFSCatalogKey? right)
|
||||
{
|
||||
if (left is null) return true;
|
||||
else return left.CompareTo(right) <= 0;
|
||||
}
|
||||
|
||||
public static bool operator >=(HFSCatalogKey? left, HFSCatalogKey? right)
|
||||
{
|
||||
if (left is null) return right is null;
|
||||
else return left.CompareTo(right) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal enum HFSCatalogRecordType : ushort
|
||||
{
|
||||
Folder = 0x0001,
|
||||
File = 0x0002,
|
||||
FolderThread = 0x0003,
|
||||
FileThread = 0x0004
|
||||
}
|
||||
|
||||
internal abstract class HFSCatalogRecord : HFSStructBase
|
||||
{
|
||||
public HFSCatalogRecordType Type { get; }
|
||||
|
||||
protected HFSCatalogRecord(HFSCatalogRecordType type)
|
||||
=> Type = type;
|
||||
|
||||
public static bool TryRead(ref ReadOnlySpan<byte> data, HFSKeyCompareType compareType, bool isHFSX, out HFSCatalogRecord? record)
|
||||
{
|
||||
record = null;
|
||||
|
||||
ushort rawType = ReadUInt16(ref data);
|
||||
if (!Enum.IsDefined(typeof(HFSCatalogRecordType), rawType)) return false;
|
||||
|
||||
var type = (HFSCatalogRecordType)rawType;
|
||||
switch (type)
|
||||
{
|
||||
case HFSCatalogRecordType.Folder:
|
||||
record = HFSCatalogFolder.Read(ref data);
|
||||
return true;
|
||||
|
||||
case HFSCatalogRecordType.File:
|
||||
record = HFSCatalogFile.Read(ref data);
|
||||
return true;
|
||||
|
||||
case HFSCatalogRecordType.FolderThread:
|
||||
record = HFSCatalogThread.Read(ref data, false, compareType, isHFSX);
|
||||
return true;
|
||||
|
||||
case HFSCatalogRecordType.FileThread:
|
||||
record = HFSCatalogThread.Read(ref data, true, compareType, isHFSX);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSCatalogFolder : HFSCatalogRecord
|
||||
{
|
||||
public uint Valence { get; }
|
||||
public uint FolderId { get; }
|
||||
public DateTime CreateDate { get; }
|
||||
public DateTime ContentModDate { get; }
|
||||
public DateTime AttributeModDate { get; }
|
||||
public DateTime AccessDate { get; }
|
||||
public DateTime BackupDate { get; }
|
||||
public HFSPermissions Permissions { get; }
|
||||
public HFSFolderInfo Info { get; }
|
||||
public uint TextEncoding { get; }
|
||||
|
||||
private HFSCatalogFolder(
|
||||
uint valence,
|
||||
uint folderId,
|
||||
DateTime createDate,
|
||||
DateTime contentModDate,
|
||||
DateTime attributeModDate,
|
||||
DateTime accessDate,
|
||||
DateTime backupDate,
|
||||
HFSPermissions permissions,
|
||||
HFSFolderInfo info,
|
||||
uint textEncoding)
|
||||
: base(HFSCatalogRecordType.Folder)
|
||||
{
|
||||
Valence = valence;
|
||||
FolderId = folderId;
|
||||
CreateDate = createDate;
|
||||
ContentModDate = contentModDate;
|
||||
AttributeModDate = attributeModDate;
|
||||
AccessDate = accessDate;
|
||||
BackupDate = backupDate;
|
||||
Permissions = permissions;
|
||||
Info = info;
|
||||
TextEncoding = textEncoding;
|
||||
}
|
||||
|
||||
public static HFSCatalogFolder Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
_ = ReadUInt16(ref data); // reserved
|
||||
uint valence = ReadUInt32(ref data);
|
||||
uint folderId = ReadUInt32(ref data);
|
||||
var createDate = ReadDate(ref data);
|
||||
var contentModDate = ReadDate(ref data);
|
||||
var attributeModDate = ReadDate(ref data);
|
||||
var accessDate = ReadDate(ref data);
|
||||
var backupDate = ReadDate(ref data);
|
||||
var permissions = HFSPermissions.Read(ref data);
|
||||
var info = HFSFolderInfo.Read(ref data);
|
||||
uint textEncoding = ReadUInt32(ref data);
|
||||
_ = ReadUInt32(ref data); // reserved
|
||||
|
||||
return new HFSCatalogFolder(
|
||||
valence,
|
||||
folderId,
|
||||
createDate,
|
||||
contentModDate,
|
||||
attributeModDate,
|
||||
accessDate,
|
||||
backupDate,
|
||||
permissions,
|
||||
info,
|
||||
textEncoding);
|
||||
}
|
||||
}
|
||||
|
||||
internal enum HFSFileFlags : ushort
|
||||
{
|
||||
LockedBit = 0x0000,
|
||||
LockedMask = 0x0001,
|
||||
ThreadExistsBit = 0x0001,
|
||||
ThreadExistsMask = 0x0002
|
||||
}
|
||||
|
||||
internal sealed class HFSCatalogFile : HFSCatalogRecord
|
||||
{
|
||||
public HFSFileFlags Flags { get; }
|
||||
public uint FileId { get; }
|
||||
public DateTime CreateDate { get; }
|
||||
public DateTime ContentModDate { get; }
|
||||
public DateTime AttributeModDate { get; }
|
||||
public DateTime AccessDate { get; }
|
||||
public DateTime BackupDate { get; }
|
||||
public HFSPermissions Permissions { get; }
|
||||
public HFSFileInfo Info { get; }
|
||||
public uint TextEncoding { get; }
|
||||
|
||||
public HFSForkData DataFork { get; }
|
||||
public HFSForkData ResourceFork { get; }
|
||||
|
||||
private HFSCatalogFile(
|
||||
HFSFileFlags flags,
|
||||
uint fileId,
|
||||
DateTime createDate,
|
||||
DateTime contentModDate,
|
||||
DateTime attributeModDate,
|
||||
DateTime accessDate,
|
||||
DateTime backupDate,
|
||||
HFSPermissions permissions,
|
||||
HFSFileInfo info,
|
||||
uint textEncoding,
|
||||
HFSForkData dataFork,
|
||||
HFSForkData resourceFork)
|
||||
:base(HFSCatalogRecordType.File)
|
||||
{
|
||||
Flags = flags;
|
||||
FileId = fileId;
|
||||
CreateDate = createDate;
|
||||
ContentModDate = contentModDate;
|
||||
AttributeModDate = attributeModDate;
|
||||
AccessDate = accessDate;
|
||||
BackupDate = backupDate;
|
||||
Permissions = permissions;
|
||||
Info = info;
|
||||
TextEncoding = textEncoding;
|
||||
DataFork = dataFork;
|
||||
ResourceFork = resourceFork;
|
||||
}
|
||||
|
||||
public static HFSCatalogFile Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
var flags = (HFSFileFlags)ReadUInt16(ref data);
|
||||
_ = ReadUInt32(ref data); // reserved
|
||||
uint fileId = ReadUInt32(ref data);
|
||||
var createDate = ReadDate(ref data);
|
||||
var contentModDate = ReadDate(ref data);
|
||||
var attributeModDate = ReadDate(ref data);
|
||||
var accessDate = ReadDate(ref data);
|
||||
var backupDate = ReadDate(ref data);
|
||||
var permissions = HFSPermissions.Read(ref data);
|
||||
var info = HFSFileInfo.Read(ref data);
|
||||
uint textEncoding = ReadUInt32(ref data);
|
||||
_ = ReadUInt32(ref data); // reserved
|
||||
|
||||
var dataFork = HFSForkData.Read(ref data);
|
||||
var resourceFork = HFSForkData.Read(ref data);
|
||||
|
||||
return new HFSCatalogFile(
|
||||
flags,
|
||||
fileId,
|
||||
createDate,
|
||||
contentModDate,
|
||||
attributeModDate,
|
||||
accessDate,
|
||||
backupDate,
|
||||
permissions,
|
||||
info,
|
||||
textEncoding,
|
||||
dataFork,
|
||||
resourceFork);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSCatalogThread : HFSCatalogRecord
|
||||
{
|
||||
public uint ParentId { get; }
|
||||
public string NodeName { get; }
|
||||
public HFSCatalogKey CatalogKey { get; }
|
||||
|
||||
private HFSCatalogThread(uint parentId, string nodeName, bool isFile, HFSKeyCompareType compareType, bool isHFSX)
|
||||
: base(isFile ? HFSCatalogRecordType.FileThread : HFSCatalogRecordType.FolderThread)
|
||||
{
|
||||
ParentId = parentId;
|
||||
NodeName = nodeName;
|
||||
CatalogKey = new HFSCatalogKey(ParentId, NodeName, compareType, isHFSX);
|
||||
}
|
||||
|
||||
public static HFSCatalogThread Read(ref ReadOnlySpan<byte> data, bool isFile, HFSKeyCompareType compareType, bool isHFSX)
|
||||
{
|
||||
_ = ReadInt16(ref data); // reserved
|
||||
uint parentId = ReadUInt32(ref data);
|
||||
string nodeName = ReadString(ref data, true);
|
||||
|
||||
return new HFSCatalogThread(parentId, nodeName, isFile, compareType, isHFSX);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/SharpCompress/Common/Dmg/HFS/HFSExtentDescriptor.cs
Normal file
31
src/SharpCompress/Common/Dmg/HFS/HFSExtentDescriptor.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal sealed class HFSExtentDescriptor : HFSStructBase
|
||||
{
|
||||
public uint StartBlock { get; }
|
||||
public uint BlockCount { get; }
|
||||
|
||||
private HFSExtentDescriptor(uint startBlock, uint blockCount)
|
||||
{
|
||||
StartBlock = startBlock;
|
||||
BlockCount = blockCount;
|
||||
}
|
||||
|
||||
public static HFSExtentDescriptor Read(Stream stream)
|
||||
{
|
||||
return new HFSExtentDescriptor(
|
||||
ReadUInt32(stream),
|
||||
ReadUInt32(stream));
|
||||
}
|
||||
|
||||
public static HFSExtentDescriptor Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
return new HFSExtentDescriptor(
|
||||
ReadUInt32(ref data),
|
||||
ReadUInt32(ref data));
|
||||
}
|
||||
}
|
||||
}
|
||||
115
src/SharpCompress/Common/Dmg/HFS/HFSExtentRecord.cs
Normal file
115
src/SharpCompress/Common/Dmg/HFS/HFSExtentRecord.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal sealed class HFSExtentKey : HFSStructBase, IEquatable<HFSExtentKey>, IComparable<HFSExtentKey>, IComparable
|
||||
{
|
||||
public byte ForkType { get; }
|
||||
public uint FileId { get; }
|
||||
public uint StartBlock { get; }
|
||||
|
||||
public HFSExtentKey(byte forkType, uint fileId, uint startBlock)
|
||||
{
|
||||
ForkType = forkType;
|
||||
FileId = fileId;
|
||||
StartBlock = startBlock;
|
||||
}
|
||||
|
||||
public HFSExtentKey(byte[] key)
|
||||
{
|
||||
ReadOnlySpan<byte> data = key.AsSpan();
|
||||
ForkType = ReadUInt8(ref data);
|
||||
_ = ReadUInt8(ref data); // padding
|
||||
FileId = ReadUInt32(ref data);
|
||||
StartBlock = ReadUInt32(ref data);
|
||||
}
|
||||
|
||||
public bool Equals(HFSExtentKey? other)
|
||||
{
|
||||
if (other is null) return false;
|
||||
else return (ForkType == other.ForkType) && (FileId == other.FileId) && (StartBlock == other.StartBlock);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is HFSExtentKey other) return Equals(other);
|
||||
else return false;
|
||||
}
|
||||
|
||||
public int CompareTo(HFSExtentKey? other)
|
||||
{
|
||||
if (other is null) return 1;
|
||||
|
||||
int result = FileId.CompareTo(other.FileId);
|
||||
if (result == 0) result = ForkType.CompareTo(other.ForkType);
|
||||
if (result == 0) result = StartBlock.CompareTo(other.StartBlock);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int CompareTo(object? obj)
|
||||
{
|
||||
if (obj is null) return 1;
|
||||
else if (obj is HFSExtentKey other) return CompareTo(other);
|
||||
else throw new ArgumentException("Object is not of type ExtentKey", nameof(obj));
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
=> ForkType.GetHashCode() ^ FileId.GetHashCode() ^ StartBlock.GetHashCode();
|
||||
|
||||
public static bool operator ==(HFSExtentKey? left, HFSExtentKey? right)
|
||||
{
|
||||
if (left is null) return right is null;
|
||||
else return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(HFSExtentKey? left, HFSExtentKey? right)
|
||||
{
|
||||
if (left is null) return right is not null;
|
||||
else return !left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator <(HFSExtentKey? left, HFSExtentKey? right)
|
||||
{
|
||||
if (left is null) return right is not null;
|
||||
else return left.CompareTo(right) < 0;
|
||||
}
|
||||
|
||||
public static bool operator >(HFSExtentKey? left, HFSExtentKey? right)
|
||||
{
|
||||
if (left is null) return false;
|
||||
else return left.CompareTo(right) > 0;
|
||||
}
|
||||
|
||||
public static bool operator <=(HFSExtentKey? left, HFSExtentKey? right)
|
||||
{
|
||||
if (left is null) return true;
|
||||
else return left.CompareTo(right) <= 0;
|
||||
}
|
||||
|
||||
public static bool operator >=(HFSExtentKey? left, HFSExtentKey? right)
|
||||
{
|
||||
if (left is null) return right is null;
|
||||
else return left.CompareTo(right) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSExtentRecord : HFSStructBase
|
||||
{
|
||||
private const int ExtentCount = 8;
|
||||
|
||||
public IReadOnlyList<HFSExtentDescriptor> Extents { get; }
|
||||
|
||||
private HFSExtentRecord(IReadOnlyList<HFSExtentDescriptor> extents)
|
||||
=> Extents = extents;
|
||||
|
||||
public static HFSExtentRecord Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
var extents = new HFSExtentDescriptor[ExtentCount];
|
||||
for (int i = 0; i < ExtentCount; i++)
|
||||
extents[i] = HFSExtentDescriptor.Read(ref data);
|
||||
|
||||
return new HFSExtentRecord(extents);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
src/SharpCompress/Common/Dmg/HFS/HFSFinderInfo.cs
Normal file
145
src/SharpCompress/Common/Dmg/HFS/HFSFinderInfo.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal struct HFSPoint
|
||||
{
|
||||
public short V;
|
||||
public short H;
|
||||
}
|
||||
|
||||
internal struct HFSRect
|
||||
{
|
||||
public short Top;
|
||||
public short Left;
|
||||
public short Bottom;
|
||||
public short Right;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum HFSFinderFlags : ushort
|
||||
{
|
||||
None = 0x0000,
|
||||
|
||||
IsOnDesk = 0x0001, /* Files and folders (System 6) */
|
||||
Color = 0x000E, /* Files and folders */
|
||||
IsShared = 0x0040, /* Files only (Applications only) If */
|
||||
/* clear, the application needs */
|
||||
/* to write to its resource fork, */
|
||||
/* and therefore cannot be shared */
|
||||
/* on a server */
|
||||
HasNoINITs = 0x0080, /* Files only (Extensions/Control */
|
||||
/* Panels only) */
|
||||
/* This file contains no INIT resource */
|
||||
HasBeenInited = 0x0100, /* Files only. Clear if the file */
|
||||
/* contains desktop database resources */
|
||||
/* ('BNDL', 'FREF', 'open', 'kind'...) */
|
||||
/* that have not been added yet. Set */
|
||||
/* only by the Finder. */
|
||||
/* Reserved for folders */
|
||||
HasCustomIcon = 0x0400, /* Files and folders */
|
||||
IsStationery = 0x0800, /* Files only */
|
||||
NameLocked = 0x1000, /* Files and folders */
|
||||
HasBundle = 0x2000, /* Files only */
|
||||
IsInvisible = 0x4000, /* Files and folders */
|
||||
IsAlias = 0x8000 /* Files only */
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum HFSExtendedFinderFlags : ushort
|
||||
{
|
||||
None = 0x0000,
|
||||
|
||||
ExtendedFlagsAreInvalid = 0x8000, /* The other extended flags */
|
||||
/* should be ignored */
|
||||
HasCustomBadge = 0x0100, /* The file or folder has a */
|
||||
/* badge resource */
|
||||
HasRoutingInfo = 0x0004 /* The file contains routing */
|
||||
/* info resource */
|
||||
}
|
||||
|
||||
internal sealed class HFSFileInfo : HFSStructBase
|
||||
{
|
||||
public string FileType { get; } /* The type of the file */
|
||||
public string FileCreator { get; } /* The file's creator */
|
||||
public HFSFinderFlags FinderFlags { get; }
|
||||
public HFSPoint Location { get; } /* File's location in the folder. */
|
||||
public HFSExtendedFinderFlags ExtendedFinderFlags { get; }
|
||||
public int PutAwayFolderId { get; }
|
||||
|
||||
private HFSFileInfo(
|
||||
string fileType,
|
||||
string fileCreator,
|
||||
HFSFinderFlags finderFlags,
|
||||
HFSPoint location,
|
||||
HFSExtendedFinderFlags extendedFinderFlags,
|
||||
int putAwayFolderId)
|
||||
{
|
||||
FileType = fileType;
|
||||
FileCreator = fileCreator;
|
||||
FinderFlags = finderFlags;
|
||||
Location = location;
|
||||
ExtendedFinderFlags = extendedFinderFlags;
|
||||
PutAwayFolderId = putAwayFolderId;
|
||||
}
|
||||
|
||||
public static HFSFileInfo Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
string fileType = ReadOSType(ref data);
|
||||
string fileCreator = ReadOSType(ref data);
|
||||
var finderFlags = (HFSFinderFlags)ReadUInt16(ref data);
|
||||
var location = ReadPoint(ref data);
|
||||
_ = ReadUInt16(ref data); // reserved
|
||||
data = data.Slice(4 * sizeof(short)); // reserved
|
||||
var extendedFinderFlags = (HFSExtendedFinderFlags)ReadUInt16(ref data);
|
||||
_ = ReadInt16(ref data); // reserved
|
||||
int putAwayFolderId = ReadInt32(ref data);
|
||||
|
||||
return new HFSFileInfo(fileType, fileCreator, finderFlags, location, extendedFinderFlags, putAwayFolderId);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSFolderInfo : HFSStructBase
|
||||
{
|
||||
public HFSRect WindowBounds { get; } /* The position and dimension of the */
|
||||
/* folder's window */
|
||||
public HFSFinderFlags FinderFlags { get; }
|
||||
public HFSPoint Location { get; } /* Folder's location in the parent */
|
||||
/* folder. If set to {0, 0}, the Finder */
|
||||
/* will place the item automatically */
|
||||
public HFSPoint ScrollPosition { get; } /* Scroll position (for icon views) */
|
||||
public HFSExtendedFinderFlags ExtendedFinderFlags { get; }
|
||||
public int PutAwayFolderId { get; }
|
||||
|
||||
private HFSFolderInfo(
|
||||
HFSRect windowBounds,
|
||||
HFSFinderFlags finderFlags,
|
||||
HFSPoint location,
|
||||
HFSPoint scrollPosition,
|
||||
HFSExtendedFinderFlags extendedFinderFlags,
|
||||
int putAwayFolderId)
|
||||
{
|
||||
WindowBounds = windowBounds;
|
||||
FinderFlags = finderFlags;
|
||||
Location = location;
|
||||
ScrollPosition = scrollPosition;
|
||||
ExtendedFinderFlags = extendedFinderFlags;
|
||||
PutAwayFolderId = putAwayFolderId;
|
||||
}
|
||||
|
||||
public static HFSFolderInfo Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
var windowBounds = ReadRect(ref data);
|
||||
var finderFlags = (HFSFinderFlags)ReadUInt16(ref data);
|
||||
var location = ReadPoint(ref data);
|
||||
_ = ReadUInt16(ref data); // reserved
|
||||
var scrollPosition = ReadPoint(ref data);
|
||||
_ = ReadInt32(ref data); // reserved
|
||||
var extendedFinderFlags = (HFSExtendedFinderFlags)ReadUInt16(ref data);
|
||||
_ = ReadInt16(ref data); // reserved
|
||||
int putAwayFolderId = ReadInt32(ref data);
|
||||
|
||||
return new HFSFolderInfo(windowBounds, finderFlags, location, scrollPosition, extendedFinderFlags, putAwayFolderId);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/SharpCompress/Common/Dmg/HFS/HFSForkData.cs
Normal file
50
src/SharpCompress/Common/Dmg/HFS/HFSForkData.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal sealed class HFSForkData : HFSStructBase
|
||||
{
|
||||
private const int ExtentCount = 8;
|
||||
|
||||
public ulong LogicalSize { get; }
|
||||
public uint ClumpSize { get; }
|
||||
public uint TotalBlocks { get; }
|
||||
public IReadOnlyList<HFSExtentDescriptor> Extents { get; }
|
||||
|
||||
private HFSForkData(ulong logicalSize, uint clumpSize, uint totalBlocks, IReadOnlyList<HFSExtentDescriptor> extents)
|
||||
{
|
||||
LogicalSize = logicalSize;
|
||||
ClumpSize = clumpSize;
|
||||
TotalBlocks = totalBlocks;
|
||||
Extents = extents;
|
||||
}
|
||||
|
||||
public static HFSForkData Read(Stream stream)
|
||||
{
|
||||
ulong logicalSize = ReadUInt64(stream);
|
||||
uint clumpSize = ReadUInt32(stream);
|
||||
uint totalBlocks = ReadUInt32(stream);
|
||||
|
||||
var extents = new HFSExtentDescriptor[ExtentCount];
|
||||
for (int i = 0; i < ExtentCount; i++)
|
||||
extents[i] = HFSExtentDescriptor.Read(stream);
|
||||
|
||||
return new HFSForkData(logicalSize, clumpSize, totalBlocks, extents);
|
||||
}
|
||||
|
||||
public static HFSForkData Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
ulong logicalSize = ReadUInt64(ref data);
|
||||
uint clumpSize = ReadUInt32(ref data);
|
||||
uint totalBlocks = ReadUInt32(ref data);
|
||||
|
||||
var extents = new HFSExtentDescriptor[ExtentCount];
|
||||
for (int i = 0; i < ExtentCount; i++)
|
||||
extents[i] = HFSExtentDescriptor.Read(ref data);
|
||||
|
||||
return new HFSForkData(logicalSize, clumpSize, totalBlocks, extents);
|
||||
}
|
||||
}
|
||||
}
|
||||
196
src/SharpCompress/Common/Dmg/HFS/HFSForkStream.cs
Normal file
196
src/SharpCompress/Common/Dmg/HFS/HFSForkStream.cs
Normal file
@@ -0,0 +1,196 @@
|
||||
using SharpCompress.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal sealed class HFSForkStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
private readonly HFSVolumeHeader _volumeHeader;
|
||||
private readonly IReadOnlyList<HFSExtentDescriptor> _extents;
|
||||
private long _position;
|
||||
private bool _isEnded;
|
||||
private int _extentIndex;
|
||||
private Stream? _extentStream;
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanWrite => false;
|
||||
public override bool CanSeek => true;
|
||||
public override long Length { get; }
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _position;
|
||||
set
|
||||
{
|
||||
if ((value < 0) || (value > Length)) throw new ArgumentOutOfRangeException(nameof(value));
|
||||
|
||||
if (value == Length)
|
||||
{
|
||||
// End of the stream
|
||||
|
||||
_position = Length;
|
||||
_isEnded = true;
|
||||
_extentIndex = -1;
|
||||
_extentStream = null;
|
||||
}
|
||||
else if (value != _position)
|
||||
{
|
||||
_position = value;
|
||||
|
||||
// We first have to determine in which extent we are now, then we seek to the exact position in that extent.
|
||||
|
||||
long offsetInExtent = _position;
|
||||
for (int i = 0; i < _extents.Count; i++)
|
||||
{
|
||||
var extent = _extents[i];
|
||||
long extentSize = extent.BlockCount * _volumeHeader.BlockSize;
|
||||
if (extentSize < offsetInExtent)
|
||||
{
|
||||
if (i == _extentIndex)
|
||||
{
|
||||
// We are in the same extent so just seek to the correct position
|
||||
_extentStream!.Position = offsetInExtent;
|
||||
}
|
||||
else
|
||||
{
|
||||
_extentIndex = i;
|
||||
_extentStream = GetExtentStream();
|
||||
_extentStream.Position = offsetInExtent;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
offsetInExtent -= extentSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HFSForkStream(Stream baseStream, HFSVolumeHeader volumeHeader, HFSForkData forkData)
|
||||
{
|
||||
_baseStream = baseStream;
|
||||
_volumeHeader = volumeHeader;
|
||||
_extents = forkData.Extents;
|
||||
Length = (long)forkData.LogicalSize;
|
||||
|
||||
_position = 0;
|
||||
_extentIndex = -1;
|
||||
_extentIndex = GetNextExtent();
|
||||
_isEnded = _extentIndex < 0;
|
||||
if (!_isEnded) _extentStream = GetExtentStream();
|
||||
}
|
||||
|
||||
public HFSForkStream(
|
||||
Stream baseStream, HFSVolumeHeader volumeHeader, HFSForkData forkData, uint fileId,
|
||||
IReadOnlyDictionary<HFSExtentKey, HFSExtentRecord> extents)
|
||||
{
|
||||
_baseStream = baseStream;
|
||||
_volumeHeader = volumeHeader;
|
||||
Length = (long)forkData.LogicalSize;
|
||||
|
||||
uint blocks = (uint)forkData.Extents.Sum(e => e.BlockCount);
|
||||
var totalExtents = new List<HFSExtentDescriptor>(forkData.Extents);
|
||||
_extents = totalExtents;
|
||||
|
||||
var nextKey = new HFSExtentKey(0, fileId, blocks);
|
||||
while (extents.TryGetValue(nextKey, out var record))
|
||||
{
|
||||
blocks += (uint)record.Extents.Sum(e => e.BlockCount);
|
||||
totalExtents.AddRange(record.Extents);
|
||||
|
||||
nextKey = new HFSExtentKey(0, fileId, blocks);
|
||||
}
|
||||
|
||||
_position = 0;
|
||||
_extentIndex = -1;
|
||||
_extentIndex = GetNextExtent();
|
||||
_isEnded = _extentIndex < 0;
|
||||
if (!_isEnded) _extentStream = GetExtentStream();
|
||||
}
|
||||
|
||||
private int GetNextExtent()
|
||||
{
|
||||
int index = _extentIndex + 1;
|
||||
if (index >= _extents.Count) return -1;
|
||||
|
||||
var extent = _extents[index];
|
||||
if ((extent.StartBlock == 0) && (extent.BlockCount == 0)) return -1;
|
||||
return index;
|
||||
}
|
||||
|
||||
private Stream GetExtentStream()
|
||||
{
|
||||
if (_extentIndex < 0)
|
||||
throw new InvalidOperationException("Invalid extent index");
|
||||
|
||||
var extent = _extents[_extentIndex];
|
||||
return new HFSExtentStream(_baseStream, _volumeHeader, extent);
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{ }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_isEnded) return 0;
|
||||
|
||||
count = (int)Math.Min(count, Length - Position);
|
||||
int readCount = _extentStream!.Read(buffer, offset, count);
|
||||
while (readCount < count)
|
||||
{
|
||||
_extentIndex = GetNextExtent();
|
||||
if (_extentIndex < 0)
|
||||
{
|
||||
_isEnded = true;
|
||||
return readCount;
|
||||
}
|
||||
|
||||
_extentStream = GetExtentStream();
|
||||
readCount += _extentStream.Read(buffer, offset + readCount, count - readCount);
|
||||
}
|
||||
|
||||
_position += readCount;
|
||||
return readCount;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
Position = offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.Current:
|
||||
Position += offset;
|
||||
break;
|
||||
|
||||
case SeekOrigin.End:
|
||||
Position = Length - offset;
|
||||
break;
|
||||
}
|
||||
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
private sealed class HFSExtentStream : SeekableSubStream
|
||||
{
|
||||
public HFSExtentStream(Stream stream, HFSVolumeHeader volumeHeader, HFSExtentDescriptor extent)
|
||||
: base(stream, (long)extent.StartBlock * volumeHeader.BlockSize, (long)extent.BlockCount * volumeHeader.BlockSize)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/SharpCompress/Common/Dmg/HFS/HFSKeyedRecord.cs
Normal file
91
src/SharpCompress/Common/Dmg/HFS/HFSKeyedRecord.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal abstract class HFSKeyedRecord : HFSStructBase
|
||||
{
|
||||
private readonly HFSKeyCompareType _compareType;
|
||||
private readonly bool _isHFSX;
|
||||
private HFSCatalogKey? _catalogKey;
|
||||
private HFSExtentKey? _extentKey;
|
||||
|
||||
public byte[] Key { get; }
|
||||
|
||||
public HFSCatalogKey GetCatalogKey() => _catalogKey ??= new HFSCatalogKey(Key, _compareType, _isHFSX);
|
||||
|
||||
public HFSExtentKey GetExtentKey() => _extentKey ??= new HFSExtentKey(Key);
|
||||
|
||||
protected HFSKeyedRecord(byte[] key, HFSKeyCompareType compareType, bool isHFSX)
|
||||
{
|
||||
Key = key;
|
||||
_compareType = compareType;
|
||||
_isHFSX = isHFSX;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSPointerRecord : HFSKeyedRecord
|
||||
{
|
||||
public uint NodeNumber { get; }
|
||||
|
||||
private HFSPointerRecord(byte[] key, uint nodeNumber, HFSKeyCompareType compareType, bool isHFSX)
|
||||
: base(key, compareType, isHFSX)
|
||||
{
|
||||
NodeNumber = nodeNumber;
|
||||
}
|
||||
|
||||
public static HFSPointerRecord Read(ref ReadOnlySpan<byte> data, HFSTreeHeaderRecord headerRecord, bool isHFSX)
|
||||
{
|
||||
bool isBigKey = headerRecord.Attributes.HasFlag(HFSTreeAttributes.BigKeys);
|
||||
ushort keyLength = isBigKey ? ReadUInt16(ref data) : ReadUInt8(ref data);
|
||||
if (!headerRecord.Attributes.HasFlag(HFSTreeAttributes.VariableIndexKeys)) keyLength = headerRecord.MaxKeyLength;
|
||||
int keySize = (isBigKey ? 2 : 1) + keyLength;
|
||||
|
||||
var key = new byte[keyLength];
|
||||
data.Slice(0, keyLength).CopyTo(key);
|
||||
data = data.Slice(keyLength);
|
||||
|
||||
// data is always aligned to 2 bytes
|
||||
if (keySize % 2 == 1) data = data.Slice(1);
|
||||
|
||||
uint nodeNumber = ReadUInt32(ref data);
|
||||
|
||||
return new HFSPointerRecord(key, nodeNumber, headerRecord.KeyCompareType, isHFSX);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSDataRecord : HFSKeyedRecord
|
||||
{
|
||||
public byte[] Data { get; }
|
||||
|
||||
private HFSDataRecord(byte[] key, byte[] data, HFSKeyCompareType compareType, bool isHFSX)
|
||||
: base(key, compareType, isHFSX)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public static HFSDataRecord Read(ref ReadOnlySpan<byte> data, int size, HFSTreeHeaderRecord headerRecord, bool isHFSX)
|
||||
{
|
||||
bool isBigKey = headerRecord.Attributes.HasFlag(HFSTreeAttributes.BigKeys);
|
||||
ushort keyLength = isBigKey ? ReadUInt16(ref data) : ReadUInt8(ref data);
|
||||
int keySize = (isBigKey ? 2 : 1) + keyLength;
|
||||
size -= keySize;
|
||||
|
||||
var key = new byte[keyLength];
|
||||
data.Slice(0, keyLength).CopyTo(key);
|
||||
data = data.Slice(keyLength);
|
||||
|
||||
// data is always aligned to 2 bytes
|
||||
if (keySize % 2 == 1)
|
||||
{
|
||||
data = data.Slice(1);
|
||||
size--;
|
||||
}
|
||||
|
||||
var structData = new byte[size];
|
||||
data.Slice(0, size).CopyTo(structData);
|
||||
data = data.Slice(size);
|
||||
|
||||
return new HFSDataRecord(key, structData, headerRecord.KeyCompareType, isHFSX);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/SharpCompress/Common/Dmg/HFS/HFSPermissions.cs
Normal file
35
src/SharpCompress/Common/Dmg/HFS/HFSPermissions.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal sealed class HFSPermissions : HFSStructBase
|
||||
{
|
||||
public uint OwnerID { get; }
|
||||
public uint GroupID { get; }
|
||||
public byte AdminFlags { get; }
|
||||
public byte OwnerFlags { get; }
|
||||
public ushort FileMode { get; }
|
||||
public uint Special { get; }
|
||||
|
||||
private HFSPermissions(uint ownerID, uint groupID, byte adminFlags, byte ownerFlags, ushort fileMode, uint special)
|
||||
{
|
||||
OwnerID = ownerID;
|
||||
GroupID = groupID;
|
||||
AdminFlags = adminFlags;
|
||||
OwnerFlags = ownerFlags;
|
||||
FileMode = fileMode;
|
||||
Special = special;
|
||||
}
|
||||
|
||||
public static HFSPermissions Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
return new HFSPermissions(
|
||||
ReadUInt32(ref data),
|
||||
ReadUInt32(ref data),
|
||||
ReadUInt8(ref data),
|
||||
ReadUInt8(ref data),
|
||||
ReadUInt16(ref data),
|
||||
ReadUInt32(ref data));
|
||||
}
|
||||
}
|
||||
}
|
||||
187
src/SharpCompress/Common/Dmg/HFS/HFSStructBase.cs
Normal file
187
src/SharpCompress/Common/Dmg/HFS/HFSStructBase.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal abstract class HFSStructBase
|
||||
{
|
||||
private const int StringSize = 510;
|
||||
private const int OSTypeSize = 4;
|
||||
private static readonly DateTime Epoch = new DateTime(1904, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
private static readonly byte[] _buffer = new byte[StringSize];
|
||||
|
||||
protected static byte ReadUInt8(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(byte)) != sizeof(byte))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return _buffer[0];
|
||||
}
|
||||
|
||||
protected static ushort ReadUInt16(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(ushort)) != sizeof(ushort))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadUInt16BigEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static short ReadInt16(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(short)) != sizeof(short))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadInt16BigEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static uint ReadUInt32(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(uint)) != sizeof(uint))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static int ReadInt32(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(int)) != sizeof(int))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadInt32BigEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static ulong ReadUInt64(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(ulong)) != sizeof(ulong))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadUInt64BigEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static long ReadInt64(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(long)) != sizeof(long))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadInt64BigEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static string ReadString(Stream stream)
|
||||
{
|
||||
ushort length = ReadUInt16(stream);
|
||||
if (stream.Read(_buffer, 0, StringSize) != StringSize)
|
||||
throw new EndOfStreamException();
|
||||
return Encoding.Unicode.GetString(_buffer, 0, Math.Min(length * 2, StringSize));
|
||||
}
|
||||
|
||||
protected static DateTime ReadDate(Stream stream)
|
||||
{
|
||||
uint seconds = ReadUInt32(stream);
|
||||
var span = TimeSpan.FromSeconds(seconds);
|
||||
return Epoch + span;
|
||||
}
|
||||
|
||||
protected static byte ReadUInt8(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
byte val = data[0];
|
||||
data = data.Slice(sizeof(byte));
|
||||
return val;
|
||||
}
|
||||
|
||||
protected static ushort ReadUInt16(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
ushort val = BinaryPrimitives.ReadUInt16BigEndian(data);
|
||||
data = data.Slice(sizeof(ushort));
|
||||
return val;
|
||||
}
|
||||
|
||||
protected static short ReadInt16(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
short val = BinaryPrimitives.ReadInt16BigEndian(data);
|
||||
data = data.Slice(sizeof(short));
|
||||
return val;
|
||||
}
|
||||
|
||||
protected static uint ReadUInt32(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
uint val = BinaryPrimitives.ReadUInt32BigEndian(data);
|
||||
data = data.Slice(sizeof(uint));
|
||||
return val;
|
||||
}
|
||||
|
||||
protected static int ReadInt32(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
int val = BinaryPrimitives.ReadInt32BigEndian(data);
|
||||
data = data.Slice(sizeof(int));
|
||||
return val;
|
||||
}
|
||||
|
||||
protected static ulong ReadUInt64(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
ulong val = BinaryPrimitives.ReadUInt64BigEndian(data);
|
||||
data = data.Slice(sizeof(ulong));
|
||||
return val;
|
||||
}
|
||||
|
||||
protected static long ReadInt64(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
long val = BinaryPrimitives.ReadInt64BigEndian(data);
|
||||
data = data.Slice(sizeof(long));
|
||||
return val;
|
||||
}
|
||||
|
||||
protected static string ReadString(ref ReadOnlySpan<byte> data, bool truncate)
|
||||
{
|
||||
int length = ReadUInt16(ref data);
|
||||
if (truncate)
|
||||
{
|
||||
length = Math.Min(length * 2, StringSize);
|
||||
data.Slice(0, length).CopyTo(_buffer);
|
||||
data = data.Slice(length);
|
||||
return Encoding.BigEndianUnicode.GetString(_buffer, 0, length);
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Slice(0, StringSize).CopyTo(_buffer);
|
||||
data = data.Slice(StringSize);
|
||||
return Encoding.BigEndianUnicode.GetString(_buffer, 0, Math.Min(length * 2, StringSize));
|
||||
}
|
||||
}
|
||||
|
||||
protected static DateTime ReadDate(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
uint seconds = ReadUInt32(ref data);
|
||||
var span = TimeSpan.FromSeconds(seconds);
|
||||
return Epoch + span;
|
||||
}
|
||||
|
||||
protected static string ReadOSType(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
data.Slice(0, OSTypeSize).CopyTo(_buffer);
|
||||
data = data.Slice(OSTypeSize);
|
||||
return Encoding.ASCII.GetString(_buffer, 0, OSTypeSize).NullTerminate();
|
||||
}
|
||||
|
||||
protected static HFSPoint ReadPoint(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
return new HFSPoint()
|
||||
{
|
||||
V = ReadInt16(ref data),
|
||||
H = ReadInt16(ref data)
|
||||
};
|
||||
}
|
||||
|
||||
protected static HFSRect ReadRect(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
return new HFSRect()
|
||||
{
|
||||
Top = ReadInt16(ref data),
|
||||
Left = ReadInt16(ref data),
|
||||
Bottom = ReadInt16(ref data),
|
||||
Right = ReadInt16(ref data)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
108
src/SharpCompress/Common/Dmg/HFS/HFSTreeHeaderRecord.cs
Normal file
108
src/SharpCompress/Common/Dmg/HFS/HFSTreeHeaderRecord.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal enum HFSTreeType : byte
|
||||
{
|
||||
HFS = 0, // control file
|
||||
User = 128, // user btree type starts from 128
|
||||
Reserved = 255
|
||||
}
|
||||
|
||||
internal enum HFSKeyCompareType : byte
|
||||
{
|
||||
CaseFolding = 0xCF, // case-insensitive
|
||||
BinaryCompare = 0xBC // case-sensitive
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum HFSTreeAttributes : uint
|
||||
{
|
||||
None = 0x00000000,
|
||||
BadClose = 0x00000001,
|
||||
BigKeys = 0x00000002,
|
||||
VariableIndexKeys = 0x00000004
|
||||
}
|
||||
|
||||
internal sealed class HFSTreeHeaderRecord : HFSStructBase
|
||||
{
|
||||
public ushort TreeDepth;
|
||||
public uint RootNode;
|
||||
public uint LeafRecords;
|
||||
public uint FirstLeafNode;
|
||||
public uint LastLeafNode;
|
||||
public ushort NodeSize;
|
||||
public ushort MaxKeyLength;
|
||||
public uint TotalNodes;
|
||||
public uint FreeNodes;
|
||||
public uint ClumpSize;
|
||||
public HFSTreeType TreeType;
|
||||
public HFSKeyCompareType KeyCompareType;
|
||||
public HFSTreeAttributes Attributes;
|
||||
|
||||
private HFSTreeHeaderRecord(
|
||||
ushort treeDepth,
|
||||
uint rootNode,
|
||||
uint leafRecords,
|
||||
uint firstLeafNode,
|
||||
uint lastLeafNode,
|
||||
ushort nodeSize,
|
||||
ushort maxKeyLength,
|
||||
uint totalNodes,
|
||||
uint freeNodes,
|
||||
uint clumpSize,
|
||||
HFSTreeType treeType,
|
||||
HFSKeyCompareType keyCompareType,
|
||||
HFSTreeAttributes attributes)
|
||||
{
|
||||
TreeDepth = treeDepth;
|
||||
RootNode = rootNode;
|
||||
LeafRecords = leafRecords;
|
||||
FirstLeafNode = firstLeafNode;
|
||||
LastLeafNode = lastLeafNode;
|
||||
NodeSize = nodeSize;
|
||||
MaxKeyLength = maxKeyLength;
|
||||
TotalNodes = totalNodes;
|
||||
FreeNodes = freeNodes;
|
||||
ClumpSize = clumpSize;
|
||||
TreeType = treeType;
|
||||
KeyCompareType = keyCompareType;
|
||||
Attributes = attributes;
|
||||
}
|
||||
|
||||
public static HFSTreeHeaderRecord Read(Stream stream)
|
||||
{
|
||||
ushort treeDepth = ReadUInt16(stream);
|
||||
uint rootNode = ReadUInt32(stream);
|
||||
uint leafRecords = ReadUInt32(stream);
|
||||
uint firstLeafNode = ReadUInt32(stream);
|
||||
uint lastLeafNode = ReadUInt32(stream);
|
||||
ushort nodeSize = ReadUInt16(stream);
|
||||
ushort maxKeyLength = ReadUInt16(stream);
|
||||
uint totalNodes = ReadUInt32(stream);
|
||||
uint freeNodes = ReadUInt32(stream);
|
||||
_ = ReadUInt16(stream); // reserved
|
||||
uint clumpSize = ReadUInt32(stream);
|
||||
var treeType = (HFSTreeType)ReadUInt8(stream);
|
||||
var keyCompareType = (HFSKeyCompareType)ReadUInt8(stream);
|
||||
var attributes = (HFSTreeAttributes)ReadUInt32(stream);
|
||||
for (int i = 0; i < 16; i++) _ = ReadUInt32(stream); // reserved
|
||||
|
||||
return new HFSTreeHeaderRecord(
|
||||
treeDepth,
|
||||
rootNode,
|
||||
leafRecords,
|
||||
firstLeafNode,
|
||||
lastLeafNode,
|
||||
nodeSize,
|
||||
maxKeyLength,
|
||||
totalNodes,
|
||||
freeNodes,
|
||||
clumpSize,
|
||||
treeType,
|
||||
keyCompareType,
|
||||
attributes);
|
||||
}
|
||||
}
|
||||
}
|
||||
167
src/SharpCompress/Common/Dmg/HFS/HFSTreeNode.cs
Normal file
167
src/SharpCompress/Common/Dmg/HFS/HFSTreeNode.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal abstract class HFSTreeNode : HFSStructBase
|
||||
{
|
||||
private static byte[]? _buffer = null;
|
||||
|
||||
public HFSTreeNodeDescriptor Descriptor { get; }
|
||||
|
||||
protected HFSTreeNode(HFSTreeNodeDescriptor descriptor)
|
||||
=> Descriptor = descriptor;
|
||||
|
||||
public static bool TryRead(Stream stream, HFSTreeHeaderRecord headerRecord, bool isHFSX, out HFSTreeNode? node)
|
||||
{
|
||||
node = null;
|
||||
|
||||
if (!HFSTreeNodeDescriptor.TryRead(stream, out var descriptor)) return false;
|
||||
|
||||
int size = (int)headerRecord.NodeSize - HFSTreeNodeDescriptor.Size;
|
||||
if ((_buffer is null) || (_buffer.Length < size))
|
||||
_buffer = new byte[size * 2];
|
||||
|
||||
if (stream.Read(_buffer, 0, size) != size)
|
||||
throw new EndOfStreamException();
|
||||
ReadOnlySpan<byte> data = _buffer.AsSpan(0, size);
|
||||
|
||||
switch (descriptor!.Kind)
|
||||
{
|
||||
case HFSTreeNodeKind.Leaf:
|
||||
node = HFSLeafTreeNode.Read(descriptor, data, headerRecord, isHFSX);
|
||||
return true;
|
||||
|
||||
case HFSTreeNodeKind.Index:
|
||||
node = HFSIndexTreeNode.Read(descriptor, data, headerRecord, isHFSX);
|
||||
return true;
|
||||
|
||||
case HFSTreeNodeKind.Map:
|
||||
node = HFSMapTreeNode.Read(descriptor, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSHeaderTreeNode : HFSTreeNode
|
||||
{
|
||||
private const int UserDataSize = 128;
|
||||
|
||||
public HFSTreeHeaderRecord HeaderRecord { get; }
|
||||
|
||||
public IReadOnlyList<byte> UserData { get; }
|
||||
|
||||
public IReadOnlyList<byte> Map { get; }
|
||||
|
||||
private HFSHeaderTreeNode(
|
||||
HFSTreeNodeDescriptor descriptor,
|
||||
HFSTreeHeaderRecord headerRecord,
|
||||
IReadOnlyList<byte> userData,
|
||||
IReadOnlyList<byte> map)
|
||||
: base(descriptor)
|
||||
{
|
||||
HeaderRecord = headerRecord;
|
||||
UserData = userData;
|
||||
Map = map;
|
||||
}
|
||||
|
||||
public static HFSHeaderTreeNode Read(HFSTreeNodeDescriptor descriptor, Stream stream)
|
||||
{
|
||||
if (descriptor.Kind != HFSTreeNodeKind.Header)
|
||||
throw new ArgumentException("Descriptor does not define a header node");
|
||||
|
||||
var headerRecord = HFSTreeHeaderRecord.Read(stream);
|
||||
var userData = new byte[UserDataSize];
|
||||
if (stream.Read(userData, 0, UserDataSize) != UserDataSize)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
int mapSize = (int)(headerRecord.NodeSize - 256);
|
||||
var map = new byte[mapSize];
|
||||
if (stream.Read(map, 0, mapSize) != mapSize)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
// offset values (not required for header node)
|
||||
_ = ReadUInt16(stream);
|
||||
_ = ReadUInt16(stream);
|
||||
_ = ReadUInt16(stream);
|
||||
_ = ReadUInt16(stream);
|
||||
|
||||
return new HFSHeaderTreeNode(descriptor, headerRecord, userData, map);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSMapTreeNode : HFSTreeNode
|
||||
{
|
||||
public IReadOnlyList<byte> Map { get; }
|
||||
|
||||
private HFSMapTreeNode(HFSTreeNodeDescriptor descriptor, IReadOnlyList<byte> map)
|
||||
: base(descriptor)
|
||||
{
|
||||
Map = map;
|
||||
}
|
||||
|
||||
public static HFSMapTreeNode Read(HFSTreeNodeDescriptor descriptor, ReadOnlySpan<byte> data)
|
||||
{
|
||||
int mapSize = data.Length - 6;
|
||||
var map = new byte[mapSize];
|
||||
data.Slice(0, mapSize).CopyTo(map);
|
||||
|
||||
return new HFSMapTreeNode(descriptor, map);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSIndexTreeNode : HFSTreeNode
|
||||
{
|
||||
public IReadOnlyList<HFSPointerRecord> Records { get; }
|
||||
|
||||
private HFSIndexTreeNode(HFSTreeNodeDescriptor descriptor, IReadOnlyList<HFSPointerRecord> records)
|
||||
: base(descriptor)
|
||||
{
|
||||
Records = records;
|
||||
}
|
||||
|
||||
public static HFSIndexTreeNode Read(HFSTreeNodeDescriptor descriptor, ReadOnlySpan<byte> data, HFSTreeHeaderRecord headerRecord, bool isHFSX)
|
||||
{
|
||||
int recordCount = descriptor.NumRecords;
|
||||
var records = new HFSPointerRecord[recordCount];
|
||||
for (int i = 0; i < recordCount; i++)
|
||||
records[i] = HFSPointerRecord.Read(ref data, headerRecord, isHFSX);
|
||||
return new HFSIndexTreeNode(descriptor, records);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HFSLeafTreeNode : HFSTreeNode
|
||||
{
|
||||
public IReadOnlyList<HFSDataRecord> Records { get; }
|
||||
|
||||
private HFSLeafTreeNode(HFSTreeNodeDescriptor descriptor, IReadOnlyList<HFSDataRecord> records)
|
||||
: base(descriptor)
|
||||
{
|
||||
Records = records;
|
||||
}
|
||||
|
||||
public static HFSLeafTreeNode Read(HFSTreeNodeDescriptor descriptor, ReadOnlySpan<byte> data, HFSTreeHeaderRecord headerRecord, bool isHFSX)
|
||||
{
|
||||
int recordCount = descriptor.NumRecords;
|
||||
var recordOffsets = new int[recordCount + 1];
|
||||
for (int i = 0; i < recordOffsets.Length; i++)
|
||||
{
|
||||
var offsetData = data.Slice(data.Length - (2 * i) - 2);
|
||||
ushort offset = ReadUInt16(ref offsetData);
|
||||
recordOffsets[i] = offset;
|
||||
}
|
||||
|
||||
var records = new HFSDataRecord[recordCount];
|
||||
for (int i = 0; i < recordCount; i++)
|
||||
{
|
||||
int size = recordOffsets[i + 1] - recordOffsets[i];
|
||||
records[i] = HFSDataRecord.Read(ref data, size, headerRecord, isHFSX);
|
||||
}
|
||||
|
||||
return new HFSLeafTreeNode(descriptor, records);
|
||||
}
|
||||
}
|
||||
}
|
||||
55
src/SharpCompress/Common/Dmg/HFS/HFSTreeNodeDescriptor.cs
Normal file
55
src/SharpCompress/Common/Dmg/HFS/HFSTreeNodeDescriptor.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal enum HFSTreeNodeKind : sbyte
|
||||
{
|
||||
Leaf = -1,
|
||||
Index = 0,
|
||||
Header = 1,
|
||||
Map = 2
|
||||
}
|
||||
|
||||
internal sealed class HFSTreeNodeDescriptor : HFSStructBase
|
||||
{
|
||||
public const int Size = 14;
|
||||
|
||||
public uint FLink { get; }
|
||||
public uint BLink { get; }
|
||||
public HFSTreeNodeKind Kind { get; }
|
||||
public byte Height { get; }
|
||||
public ushort NumRecords { get; }
|
||||
|
||||
private HFSTreeNodeDescriptor(uint fLink, uint bLink, HFSTreeNodeKind kind, byte height, ushort numRecords)
|
||||
{
|
||||
FLink = fLink;
|
||||
BLink = bLink;
|
||||
Kind = kind;
|
||||
Height = height;
|
||||
NumRecords = numRecords;
|
||||
}
|
||||
|
||||
public static bool TryRead(Stream stream, out HFSTreeNodeDescriptor? descriptor)
|
||||
{
|
||||
descriptor = null;
|
||||
|
||||
uint fLink = ReadUInt32(stream);
|
||||
uint bLink = ReadUInt32(stream);
|
||||
|
||||
sbyte rawKind = (sbyte)ReadUInt8(stream);
|
||||
if (!Enum.IsDefined(typeof(HFSTreeNodeKind), rawKind)) return false;
|
||||
var kind = (HFSTreeNodeKind)rawKind;
|
||||
|
||||
byte height = ReadUInt8(stream);
|
||||
if (((kind == HFSTreeNodeKind.Header) || (kind == HFSTreeNodeKind.Map)) && (height != 0)) return false;
|
||||
if ((kind == HFSTreeNodeKind.Leaf) && (height != 1)) return false;
|
||||
|
||||
ushort numRecords = ReadUInt16(stream);
|
||||
_ = ReadUInt16(stream); // reserved
|
||||
|
||||
descriptor = new HFSTreeNodeDescriptor(fLink, bLink, kind, height, numRecords);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
206
src/SharpCompress/Common/Dmg/HFS/HFSUtil.cs
Normal file
206
src/SharpCompress/Common/Dmg/HFS/HFSUtil.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using SharpCompress.Archives.Dmg;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal static class HFSUtil
|
||||
{
|
||||
private const string CorruptHFSMessage = "Corrupt HFS volume";
|
||||
|
||||
private static (HFSHeaderTreeNode, IReadOnlyList<HFSTreeNode>) ReadTree(Stream stream, bool isHFSX)
|
||||
{
|
||||
if (!HFSTreeNodeDescriptor.TryRead(stream, out var headerDesc))
|
||||
throw new InvalidFormatException(CorruptHFSMessage);
|
||||
var header = HFSHeaderTreeNode.Read(headerDesc!, stream);
|
||||
|
||||
var nodes = new HFSTreeNode[header.HeaderRecord.TotalNodes];
|
||||
nodes[0] = header;
|
||||
|
||||
for (int i = 1; i < nodes.Length; i++)
|
||||
{
|
||||
if (!HFSTreeNode.TryRead(stream, header.HeaderRecord, isHFSX, out var node))
|
||||
throw new InvalidFormatException(CorruptHFSMessage);
|
||||
|
||||
nodes[i] = node!;
|
||||
}
|
||||
|
||||
return (header, nodes);
|
||||
}
|
||||
|
||||
private static void EnumerateExtentsTree(
|
||||
IReadOnlyList<HFSTreeNode> extentsTree,
|
||||
IDictionary<HFSExtentKey, HFSExtentRecord> records,
|
||||
int parentIndex)
|
||||
{
|
||||
var parent = extentsTree[parentIndex];
|
||||
if (parent is HFSLeafTreeNode leafNode)
|
||||
{
|
||||
foreach (var record in leafNode.Records)
|
||||
{
|
||||
ReadOnlySpan<byte> data = record.Data.AsSpan();
|
||||
var recordData = HFSExtentRecord.Read(ref data);
|
||||
var key = record.GetExtentKey();
|
||||
records.Add(key, recordData);
|
||||
}
|
||||
}
|
||||
else if (parent is HFSIndexTreeNode indexNode)
|
||||
{
|
||||
foreach (var record in indexNode.Records)
|
||||
EnumerateExtentsTree(extentsTree, records, (int)record.NodeNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidFormatException(CorruptHFSMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<HFSExtentKey, HFSExtentRecord> LoadExtents(IReadOnlyList<HFSTreeNode> extentsTree, int rootIndex)
|
||||
{
|
||||
var records = new Dictionary<HFSExtentKey, HFSExtentRecord>();
|
||||
if (rootIndex == 0) return records;
|
||||
|
||||
EnumerateExtentsTree(extentsTree, records, rootIndex);
|
||||
return records;
|
||||
}
|
||||
|
||||
private static void EnumerateCatalogTree(
|
||||
HFSHeaderTreeNode catalogHeader,
|
||||
IReadOnlyList<HFSTreeNode> catalogTree,
|
||||
IDictionary<HFSCatalogKey, HFSCatalogRecord> records,
|
||||
IDictionary<uint, HFSCatalogThread> threads,
|
||||
int parentIndex,
|
||||
bool isHFSX)
|
||||
{
|
||||
var parent = catalogTree[parentIndex];
|
||||
if (parent is HFSLeafTreeNode leafNode)
|
||||
{
|
||||
foreach (var record in leafNode.Records)
|
||||
{
|
||||
ReadOnlySpan<byte> data = record.Data.AsSpan();
|
||||
if (HFSCatalogRecord.TryRead(ref data, catalogHeader.HeaderRecord.KeyCompareType, isHFSX, out var recordData))
|
||||
{
|
||||
var key = record.GetCatalogKey();
|
||||
if ((recordData!.Type == HFSCatalogRecordType.FileThread) || (recordData!.Type == HFSCatalogRecordType.FolderThread))
|
||||
{
|
||||
threads.Add(key.ParentId, (HFSCatalogThread)recordData);
|
||||
}
|
||||
else
|
||||
{
|
||||
records.Add(key, recordData);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidFormatException(CorruptHFSMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (parent is HFSIndexTreeNode indexNode)
|
||||
{
|
||||
foreach (var record in indexNode.Records)
|
||||
EnumerateCatalogTree(catalogHeader, catalogTree, records, threads, (int)record.NodeNumber, isHFSX);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidFormatException(CorruptHFSMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static (HFSCatalogKey, HFSCatalogRecord) GetRecord(uint id, IDictionary<HFSCatalogKey, HFSCatalogRecord> records, IDictionary<uint, HFSCatalogThread> threads)
|
||||
{
|
||||
if (threads.TryGetValue(id, out var thread))
|
||||
{
|
||||
if (records.TryGetValue(thread.CatalogKey, out var record))
|
||||
return (thread.CatalogKey, record!);
|
||||
}
|
||||
|
||||
throw new InvalidFormatException(CorruptHFSMessage);
|
||||
}
|
||||
|
||||
private static string SanitizePath(string path)
|
||||
{
|
||||
var sb = new StringBuilder(path.Length);
|
||||
foreach (char c in path)
|
||||
{
|
||||
if (!char.IsControl(c))
|
||||
sb.Append(c);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static string GetPath(HFSCatalogKey key, IDictionary<HFSCatalogKey, HFSCatalogRecord> records, IDictionary<uint, HFSCatalogThread> threads)
|
||||
{
|
||||
if (key.ParentId == 1)
|
||||
{
|
||||
return key.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
var (parentKey, _) = GetRecord(key.ParentId, records, threads);
|
||||
var path = Path.Combine(GetPath(parentKey, records, threads), key.Name);
|
||||
return SanitizePath(path);
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<DmgArchiveEntry> LoadEntriesFromCatalogTree(
|
||||
Stream partitionStream,
|
||||
DmgFilePart filePart,
|
||||
HFSVolumeHeader volumeHeader,
|
||||
HFSHeaderTreeNode catalogHeader,
|
||||
IReadOnlyList<HFSTreeNode> catalogTree,
|
||||
IReadOnlyDictionary<HFSExtentKey, HFSExtentRecord> extents,
|
||||
DmgArchive archive,
|
||||
int rootIndex)
|
||||
{
|
||||
if (rootIndex == 0) return Array.Empty<DmgArchiveEntry>();
|
||||
|
||||
var records = new Dictionary<HFSCatalogKey, HFSCatalogRecord>();
|
||||
var threads = new Dictionary<uint, HFSCatalogThread>();
|
||||
EnumerateCatalogTree(catalogHeader, catalogTree, records, threads, rootIndex, volumeHeader.IsHFSX);
|
||||
|
||||
var entries = new List<DmgArchiveEntry>();
|
||||
foreach (var kvp in records)
|
||||
{
|
||||
var key = kvp.Key;
|
||||
var record = kvp.Value;
|
||||
|
||||
string path = GetPath(key, records, threads);
|
||||
var stream = (record is HFSCatalogFile file) ? new HFSForkStream(partitionStream, volumeHeader, file.DataFork, file.FileId, extents) : null;
|
||||
var entry = new DmgArchiveEntry(stream, archive, record, path, filePart);
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
public static IEnumerable<DmgArchiveEntry> LoadEntriesFromPartition(Stream partitionStream, string fileName, DmgArchive archive)
|
||||
{
|
||||
if (!HFSVolumeHeader.TryRead(partitionStream, out var volumeHeader))
|
||||
throw new InvalidFormatException(CorruptHFSMessage);
|
||||
var filePart = new DmgFilePart(partitionStream, fileName);
|
||||
|
||||
var extentsFile = volumeHeader!.ExtentsFile;
|
||||
var extentsStream = new HFSForkStream(partitionStream, volumeHeader, extentsFile);
|
||||
var (extentsHeader, extentsTree) = ReadTree(extentsStream, volumeHeader.IsHFSX);
|
||||
|
||||
var extents = LoadExtents(extentsTree, (int)extentsHeader.HeaderRecord.RootNode);
|
||||
|
||||
var catalogFile = volumeHeader!.CatalogFile;
|
||||
var catalogStream = new HFSForkStream(partitionStream, volumeHeader, catalogFile);
|
||||
var (catalogHeader, catalogTree) = ReadTree(catalogStream, volumeHeader.IsHFSX);
|
||||
|
||||
return LoadEntriesFromCatalogTree(
|
||||
partitionStream,
|
||||
filePart,
|
||||
volumeHeader,
|
||||
catalogHeader,
|
||||
catalogTree,
|
||||
extents,
|
||||
archive,
|
||||
(int)catalogHeader.HeaderRecord.RootNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
179
src/SharpCompress/Common/Dmg/HFS/HFSVolumeHeader.cs
Normal file
179
src/SharpCompress/Common/Dmg/HFS/HFSVolumeHeader.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.HFS
|
||||
{
|
||||
internal sealed class HFSVolumeHeader : HFSStructBase
|
||||
{
|
||||
private const ushort SignaturePlus = 0x482B;
|
||||
private const ushort SignatureX = 0x4858;
|
||||
private const int FinderInfoCount = 8;
|
||||
|
||||
public bool IsHFSX { get; }
|
||||
public ushort Version { get; }
|
||||
public uint Attributes { get; }
|
||||
public uint LastMountedVersion { get; }
|
||||
public uint JournalInfoBlock { get; }
|
||||
|
||||
public DateTime CreateDate { get; }
|
||||
public DateTime ModifyDate { get; }
|
||||
public DateTime BackupDate { get; }
|
||||
public DateTime CheckedDate { get; }
|
||||
|
||||
public uint FileCount { get; }
|
||||
public uint FolderCount { get; }
|
||||
|
||||
public uint BlockSize { get; }
|
||||
public uint TotalBlocks { get; }
|
||||
public uint FreeBlocks { get; }
|
||||
|
||||
public uint NextAllocation { get; }
|
||||
public uint RsrcClumpSize { get; }
|
||||
public uint DataClumpSize { get; }
|
||||
public uint NextCatalogID { get; }
|
||||
|
||||
public uint WriteCount { get; }
|
||||
public ulong EncodingsBitmap { get; }
|
||||
|
||||
public IReadOnlyList<uint> FinderInfo { get; }
|
||||
|
||||
public HFSForkData AllocationFile { get; }
|
||||
public HFSForkData ExtentsFile { get; }
|
||||
public HFSForkData CatalogFile { get; }
|
||||
public HFSForkData AttributesFile { get; }
|
||||
public HFSForkData StartupFile { get; }
|
||||
|
||||
public HFSVolumeHeader(
|
||||
bool isHFSX,
|
||||
ushort version,
|
||||
uint attributes,
|
||||
uint lastMountedVersion,
|
||||
uint journalInfoBlock,
|
||||
DateTime createDate,
|
||||
DateTime modifyDate,
|
||||
DateTime backupDate,
|
||||
DateTime checkedDate,
|
||||
uint fileCount,
|
||||
uint folderCount,
|
||||
uint blockSize,
|
||||
uint totalBlocks,
|
||||
uint freeBlocks,
|
||||
uint nextAllocation,
|
||||
uint rsrcClumpSize,
|
||||
uint dataClumpSize,
|
||||
uint nextCatalogID,
|
||||
uint writeCount,
|
||||
ulong encodingsBitmap,
|
||||
IReadOnlyList<uint> finderInfo,
|
||||
HFSForkData allocationFile,
|
||||
HFSForkData extentsFile,
|
||||
HFSForkData catalogFile,
|
||||
HFSForkData attributesFile,
|
||||
HFSForkData startupFile)
|
||||
{
|
||||
IsHFSX = isHFSX;
|
||||
Version = version;
|
||||
Attributes = attributes;
|
||||
LastMountedVersion = lastMountedVersion;
|
||||
JournalInfoBlock = journalInfoBlock;
|
||||
CreateDate = createDate;
|
||||
ModifyDate = modifyDate;
|
||||
BackupDate = backupDate;
|
||||
CheckedDate = checkedDate;
|
||||
FileCount = fileCount;
|
||||
FolderCount = folderCount;
|
||||
BlockSize = blockSize;
|
||||
TotalBlocks = totalBlocks;
|
||||
FreeBlocks = freeBlocks;
|
||||
NextAllocation = nextAllocation;
|
||||
RsrcClumpSize = rsrcClumpSize;
|
||||
DataClumpSize = dataClumpSize;
|
||||
NextCatalogID = nextCatalogID;
|
||||
WriteCount = writeCount;
|
||||
EncodingsBitmap = encodingsBitmap;
|
||||
FinderInfo = finderInfo;
|
||||
AllocationFile = allocationFile;
|
||||
ExtentsFile = extentsFile;
|
||||
CatalogFile = catalogFile;
|
||||
AttributesFile = attributesFile;
|
||||
StartupFile = startupFile;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<uint> ReadFinderInfo(Stream stream)
|
||||
{
|
||||
var finderInfo = new uint[FinderInfoCount];
|
||||
for (int i = 0; i < FinderInfoCount; i++)
|
||||
finderInfo[i] = ReadUInt32(stream);
|
||||
return finderInfo;
|
||||
}
|
||||
|
||||
public static bool TryRead(Stream stream, out HFSVolumeHeader? header)
|
||||
{
|
||||
header = null;
|
||||
stream.Skip(1024); // reserved bytes
|
||||
|
||||
bool isHFSX;
|
||||
ushort sig = ReadUInt16(stream);
|
||||
if (sig == SignaturePlus) isHFSX = false;
|
||||
else if (sig == SignatureX) isHFSX = true;
|
||||
else return false;
|
||||
|
||||
ushort version = ReadUInt16(stream);
|
||||
uint attributes = ReadUInt32(stream);
|
||||
uint lastMountedVersion = ReadUInt32(stream);
|
||||
uint journalInfoBlock = ReadUInt32(stream);
|
||||
DateTime createDate = ReadDate(stream);
|
||||
DateTime modifyDate = ReadDate(stream);
|
||||
DateTime backupDate = ReadDate(stream);
|
||||
DateTime checkedDate = ReadDate(stream);
|
||||
uint fileCount = ReadUInt32(stream);
|
||||
uint folderCount = ReadUInt32(stream);
|
||||
uint blockSize = ReadUInt32(stream);
|
||||
uint totalBlocks = ReadUInt32(stream);
|
||||
uint freeBlocks = ReadUInt32(stream);
|
||||
uint nextAllocation = ReadUInt32(stream);
|
||||
uint rsrcClumpSize = ReadUInt32(stream);
|
||||
uint dataClumpSize = ReadUInt32(stream);
|
||||
uint nextCatalogID = ReadUInt32(stream);
|
||||
uint writeCount = ReadUInt32(stream);
|
||||
ulong encodingsBitmap = ReadUInt64(stream);
|
||||
IReadOnlyList<uint> finderInfo = ReadFinderInfo(stream);
|
||||
HFSForkData allocationFile = HFSForkData.Read(stream);
|
||||
HFSForkData extentsFile = HFSForkData.Read(stream);
|
||||
HFSForkData catalogFile = HFSForkData.Read(stream);
|
||||
HFSForkData attributesFile = HFSForkData.Read(stream);
|
||||
HFSForkData startupFile = HFSForkData.Read(stream);
|
||||
|
||||
header = new HFSVolumeHeader(
|
||||
isHFSX,
|
||||
version,
|
||||
attributes,
|
||||
lastMountedVersion,
|
||||
journalInfoBlock,
|
||||
createDate,
|
||||
modifyDate,
|
||||
backupDate,
|
||||
checkedDate,
|
||||
fileCount,
|
||||
folderCount,
|
||||
blockSize,
|
||||
totalBlocks,
|
||||
freeBlocks,
|
||||
nextAllocation,
|
||||
rsrcClumpSize,
|
||||
dataClumpSize,
|
||||
nextCatalogID,
|
||||
writeCount,
|
||||
encodingsBitmap,
|
||||
finderInfo,
|
||||
allocationFile,
|
||||
extentsFile,
|
||||
catalogFile,
|
||||
attributesFile,
|
||||
startupFile);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/SharpCompress/Common/Dmg/Headers/BlkxChunk.cs
Normal file
49
src/SharpCompress/Common/Dmg/Headers/BlkxChunk.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.Headers
|
||||
{
|
||||
internal enum BlkxChunkType : uint
|
||||
{
|
||||
Zero = 0x00000000u,
|
||||
Uncompressed = 0x00000001u,
|
||||
Ignore = 0x00000002u,
|
||||
AdcCompressed = 0x80000004u,
|
||||
ZlibCompressed = 0x80000005u,
|
||||
Bz2Compressed = 0x80000006u,
|
||||
Comment = 0x7FFFFFFEu,
|
||||
Last = 0xFFFFFFFFu,
|
||||
}
|
||||
|
||||
internal sealed class BlkxChunk : DmgStructBase
|
||||
{
|
||||
private const int SectorSize = 512;
|
||||
|
||||
public BlkxChunkType Type { get; } // Compression type used or chunk type
|
||||
public uint Comment { get; } // "+beg" or "+end", if EntryType is comment (0x7FFFFFFE). Else reserved.
|
||||
public ulong UncompressedOffset { get; } // Start sector of this chunk
|
||||
public ulong UncompressedLength { get; } // Number of sectors in this chunk
|
||||
public ulong CompressedOffset { get; } // Start of chunk in data fork
|
||||
public ulong CompressedLength { get; } // Count of bytes of chunk, in data fork
|
||||
|
||||
private BlkxChunk(BlkxChunkType type, uint comment, ulong sectorNumber, ulong sectorCount, ulong compressedOffset, ulong compressedLength)
|
||||
{
|
||||
Type = type;
|
||||
Comment = comment;
|
||||
UncompressedOffset = sectorNumber * SectorSize;
|
||||
UncompressedLength = sectorCount * SectorSize;
|
||||
CompressedOffset = compressedOffset;
|
||||
CompressedLength = compressedLength;
|
||||
}
|
||||
|
||||
public static bool TryRead(ref ReadOnlySpan<byte> data, out BlkxChunk? chunk)
|
||||
{
|
||||
chunk = null;
|
||||
|
||||
var type = (BlkxChunkType)ReadUInt32(ref data);
|
||||
if (!Enum.IsDefined(typeof(BlkxChunkType), type)) return false;
|
||||
|
||||
chunk = new BlkxChunk(type, ReadUInt32(ref data), ReadUInt64(ref data), ReadUInt64(ref data), ReadUInt64(ref data), ReadUInt64(ref data));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
75
src/SharpCompress/Common/Dmg/Headers/BlkxTable.cs
Normal file
75
src/SharpCompress/Common/Dmg/Headers/BlkxTable.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.Headers
|
||||
{
|
||||
internal sealed class BlkxTable : DmgStructBase
|
||||
{
|
||||
private const uint Signature = 0x6d697368u;
|
||||
|
||||
public uint Version { get; } // Current version is 1
|
||||
public ulong SectorNumber { get; } // Starting disk sector in this blkx descriptor
|
||||
public ulong SectorCount { get; } // Number of disk sectors in this blkx descriptor
|
||||
|
||||
public ulong DataOffset { get; }
|
||||
public uint BuffersNeeded { get; }
|
||||
public uint BlockDescriptors { get; } // Number of descriptors
|
||||
|
||||
public UdifChecksum Checksum { get; }
|
||||
|
||||
public IReadOnlyList<BlkxChunk> Chunks { get; }
|
||||
|
||||
private BlkxTable(
|
||||
uint version,
|
||||
ulong sectorNumber,
|
||||
ulong sectorCount,
|
||||
ulong dataOffset,
|
||||
uint buffersNeeded,
|
||||
uint blockDescriptors,
|
||||
UdifChecksum checksum,
|
||||
IReadOnlyList<BlkxChunk> chunks)
|
||||
{
|
||||
Version = version;
|
||||
SectorNumber = sectorNumber;
|
||||
SectorCount = sectorCount;
|
||||
DataOffset = dataOffset;
|
||||
BuffersNeeded = buffersNeeded;
|
||||
BlockDescriptors = blockDescriptors;
|
||||
Checksum = checksum;
|
||||
Chunks = chunks;
|
||||
}
|
||||
|
||||
public static bool TryRead(in byte[] buffer, out BlkxTable? header)
|
||||
{
|
||||
header = null;
|
||||
|
||||
ReadOnlySpan<byte> data = buffer.AsSpan();
|
||||
|
||||
uint sig = ReadUInt32(ref data);
|
||||
if (sig != Signature) return false;
|
||||
|
||||
uint version = ReadUInt32(ref data);
|
||||
ulong sectorNumber = ReadUInt64(ref data);
|
||||
ulong sectorCount = ReadUInt64(ref data);
|
||||
|
||||
ulong dataOffset = ReadUInt64(ref data);
|
||||
uint buffersNeeded = ReadUInt32(ref data);
|
||||
uint blockDescriptors = ReadUInt32(ref data);
|
||||
|
||||
data = data.Slice(6 * sizeof(uint)); // reserved
|
||||
|
||||
var checksum = UdifChecksum.Read(ref data);
|
||||
|
||||
uint chunkCount = ReadUInt32(ref data);
|
||||
var chunks = new BlkxChunk[chunkCount];
|
||||
for (int i = 0; i < chunkCount; i++)
|
||||
{
|
||||
if (!BlkxChunk.TryRead(ref data, out var chunk)) return false;
|
||||
chunks[i] = chunk!;
|
||||
}
|
||||
|
||||
header = new BlkxTable(version, sectorNumber, sectorCount, dataOffset, buffersNeeded, blockDescriptors, checksum, chunks);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
138
src/SharpCompress/Common/Dmg/Headers/DmgHeader.cs
Normal file
138
src/SharpCompress/Common/Dmg/Headers/DmgHeader.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.Headers
|
||||
{
|
||||
internal sealed class DmgHeader : DmgStructBase
|
||||
{
|
||||
public const int HeaderSize = 512;
|
||||
private const uint Signature = 0x6B6F6C79u;
|
||||
private const int UuidSize = 16; // 128 bit
|
||||
|
||||
public uint Version { get; } // Current version is 4
|
||||
public uint Flags { get; } // Flags
|
||||
public ulong RunningDataForkOffset { get; } //
|
||||
public ulong DataForkOffset { get; } // Data fork offset (usually 0, beginning of file)
|
||||
public ulong DataForkLength { get; } // Size of data fork (usually up to the XMLOffset, below)
|
||||
public ulong RsrcForkOffset { get; } // Resource fork offset, if any
|
||||
public ulong RsrcForkLength { get; } // Resource fork length, if any
|
||||
public uint SegmentNumber { get; } // Usually 1, may be 0
|
||||
public uint SegmentCount { get; } // Usually 1, may be 0
|
||||
public IReadOnlyList<byte> SegmentID { get; } // 128-bit GUID identifier of segment (if SegmentNumber !=0)
|
||||
|
||||
public UdifChecksum DataChecksum { get; }
|
||||
|
||||
public ulong XMLOffset { get; } // Offset of property list in DMG, from beginning
|
||||
public ulong XMLLength { get; } // Length of property list
|
||||
|
||||
public UdifChecksum Checksum { get; }
|
||||
|
||||
public uint ImageVariant { get; } // Commonly 1
|
||||
public ulong SectorCount { get; } // Size of DMG when expanded, in sectors
|
||||
|
||||
private DmgHeader(
|
||||
uint version,
|
||||
uint flags,
|
||||
ulong runningDataForkOffset,
|
||||
ulong dataForkOffset,
|
||||
ulong dataForkLength,
|
||||
ulong rsrcForkOffset,
|
||||
ulong rsrcForkLength,
|
||||
uint segmentNumber,
|
||||
uint segmentCount,
|
||||
IReadOnlyList<byte> segmentID,
|
||||
UdifChecksum dataChecksum,
|
||||
ulong xMLOffset,
|
||||
ulong xMLLength,
|
||||
UdifChecksum checksum,
|
||||
uint imageVariant,
|
||||
ulong sectorCount)
|
||||
{
|
||||
Version = version;
|
||||
Flags = flags;
|
||||
RunningDataForkOffset = runningDataForkOffset;
|
||||
DataForkOffset = dataForkOffset;
|
||||
DataForkLength = dataForkLength;
|
||||
RsrcForkOffset = rsrcForkOffset;
|
||||
RsrcForkLength = rsrcForkLength;
|
||||
SegmentNumber = segmentNumber;
|
||||
SegmentCount = segmentCount;
|
||||
SegmentID = segmentID;
|
||||
DataChecksum = dataChecksum;
|
||||
XMLOffset = xMLOffset;
|
||||
XMLLength = xMLLength;
|
||||
Checksum = checksum;
|
||||
ImageVariant = imageVariant;
|
||||
SectorCount = sectorCount;
|
||||
}
|
||||
|
||||
private static void ReadUuid(ref ReadOnlySpan<byte> data, byte[] buffer)
|
||||
{
|
||||
data.Slice(0, UuidSize).CopyTo(buffer);
|
||||
data = data.Slice(UuidSize);
|
||||
}
|
||||
|
||||
internal static bool TryRead(Stream input, out DmgHeader? header)
|
||||
{
|
||||
header = null;
|
||||
|
||||
var buffer = new byte[HeaderSize];
|
||||
int count = input.Read(buffer, 0, HeaderSize);
|
||||
if (count != HeaderSize) return false;
|
||||
ReadOnlySpan<byte> data = buffer.AsSpan();
|
||||
|
||||
uint sig = ReadUInt32(ref data);
|
||||
if (sig != Signature) return false;
|
||||
|
||||
uint version = ReadUInt32(ref data);
|
||||
|
||||
uint size = ReadUInt32(ref data);
|
||||
if (size != (uint)HeaderSize) return false;
|
||||
|
||||
uint flags = ReadUInt32(ref data);
|
||||
ulong runningDataForkOffset = ReadUInt64(ref data);
|
||||
ulong dataForkOffset = ReadUInt64(ref data);
|
||||
ulong dataForkLength = ReadUInt64(ref data);
|
||||
ulong rsrcForkOffset = ReadUInt64(ref data);
|
||||
ulong rsrcForkLength = ReadUInt64(ref data);
|
||||
uint segmentNumber = ReadUInt32(ref data);
|
||||
uint segmentCount = ReadUInt32(ref data);
|
||||
|
||||
var segmentID = new byte[UuidSize];
|
||||
ReadUuid(ref data, segmentID);
|
||||
|
||||
var dataChecksum = UdifChecksum.Read(ref data);
|
||||
|
||||
ulong xmlOffset = ReadUInt64(ref data);
|
||||
ulong xmlLength = ReadUInt64(ref data);
|
||||
|
||||
data = data.Slice(120); // Reserved bytes
|
||||
|
||||
var checksum = UdifChecksum.Read(ref data);
|
||||
|
||||
uint imageVariant = ReadUInt32(ref data);
|
||||
ulong sectorCount = ReadUInt64(ref data);
|
||||
|
||||
header = new DmgHeader(
|
||||
version,
|
||||
flags,
|
||||
runningDataForkOffset,
|
||||
dataForkOffset,
|
||||
dataForkLength,
|
||||
rsrcForkOffset,
|
||||
rsrcForkLength,
|
||||
segmentNumber,
|
||||
segmentCount,
|
||||
segmentID,
|
||||
dataChecksum,
|
||||
xmlOffset,
|
||||
xmlLength,
|
||||
checksum,
|
||||
imageVariant,
|
||||
sectorCount);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/SharpCompress/Common/Dmg/Headers/DmgStructBase.cs
Normal file
22
src/SharpCompress/Common/Dmg/Headers/DmgStructBase.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.Headers
|
||||
{
|
||||
internal abstract class DmgStructBase
|
||||
{
|
||||
protected static uint ReadUInt32(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
uint val = BinaryPrimitives.ReadUInt32BigEndian(data);
|
||||
data = data.Slice(sizeof(uint));
|
||||
return val;
|
||||
}
|
||||
|
||||
protected static ulong ReadUInt64(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
ulong val = BinaryPrimitives.ReadUInt64BigEndian(data);
|
||||
data = data.Slice(sizeof(ulong));
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
90
src/SharpCompress/Common/Dmg/Headers/GptHeader.cs
Normal file
90
src/SharpCompress/Common/Dmg/Headers/GptHeader.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.Headers
|
||||
{
|
||||
internal sealed class GptHeader : GptStructBase
|
||||
{
|
||||
private const int HeaderSize = 92;
|
||||
private static readonly ulong Signature = BinaryPrimitives.ReadUInt64LittleEndian(new byte[] { 69, 70, 73, 32, 80, 65, 82, 84 });
|
||||
|
||||
public uint Revision { get; }
|
||||
public uint Crc32Header { get; }
|
||||
public ulong CurrentLba { get; }
|
||||
public ulong BackupLba { get; }
|
||||
public ulong FirstUsableLba { get; }
|
||||
public ulong LastUsableLba { get; }
|
||||
public Guid DiskGuid { get; }
|
||||
public ulong EntriesStart { get; }
|
||||
public uint EntriesCount { get; }
|
||||
public uint EntriesSize { get; }
|
||||
public uint Crc32Array { get; }
|
||||
|
||||
private GptHeader(
|
||||
uint revision,
|
||||
uint crc32Header,
|
||||
ulong currentLba,
|
||||
ulong backupLba,
|
||||
ulong firstUsableLba,
|
||||
ulong lastUsableLba,
|
||||
Guid diskGuid,
|
||||
ulong entriesStart,
|
||||
uint entriesCount,
|
||||
uint entriesSize,
|
||||
uint crc32Array)
|
||||
{
|
||||
Revision = revision;
|
||||
Crc32Header = crc32Header;
|
||||
CurrentLba = currentLba;
|
||||
BackupLba = backupLba;
|
||||
FirstUsableLba = firstUsableLba;
|
||||
LastUsableLba = lastUsableLba;
|
||||
DiskGuid = diskGuid;
|
||||
EntriesStart = entriesStart;
|
||||
EntriesCount = entriesCount;
|
||||
EntriesSize = entriesSize;
|
||||
Crc32Array = crc32Array;
|
||||
}
|
||||
|
||||
public static bool TryRead(Stream stream, out GptHeader? header)
|
||||
{
|
||||
header = null;
|
||||
|
||||
ulong sig = ReadUInt64(stream);
|
||||
if (sig != Signature) return false;
|
||||
|
||||
uint revision = ReadUInt32(stream);
|
||||
|
||||
uint headerSize = ReadUInt32(stream);
|
||||
if (headerSize != HeaderSize) return false;
|
||||
|
||||
uint crc32Header = ReadUInt32(stream);
|
||||
_ = ReadUInt32(stream); // reserved
|
||||
ulong currentLba = ReadUInt64(stream);
|
||||
ulong backupLba = ReadUInt64(stream);
|
||||
ulong firstUsableLba = ReadUInt64(stream);
|
||||
ulong lastUsableLba = ReadUInt64(stream);
|
||||
Guid diskGuid = ReadGuid(stream);
|
||||
ulong entriesStart = ReadUInt64(stream);
|
||||
uint entriesCount = ReadUInt32(stream);
|
||||
uint entriesSize = ReadUInt32(stream);
|
||||
uint crc32Array = ReadUInt32(stream);
|
||||
|
||||
header = new GptHeader(
|
||||
revision,
|
||||
crc32Header,
|
||||
currentLba,
|
||||
backupLba,
|
||||
firstUsableLba,
|
||||
lastUsableLba,
|
||||
diskGuid,
|
||||
entriesStart,
|
||||
entriesCount,
|
||||
entriesSize,
|
||||
crc32Array);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
src/SharpCompress/Common/Dmg/Headers/GptPartitionEntry.cs
Normal file
36
src/SharpCompress/Common/Dmg/Headers/GptPartitionEntry.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.Headers
|
||||
{
|
||||
internal sealed class GptPartitionEntry : GptStructBase
|
||||
{
|
||||
public Guid TypeGuid { get; }
|
||||
public Guid Guid { get; }
|
||||
public ulong FirstLba { get; }
|
||||
public ulong LastLba { get; }
|
||||
public ulong Attributes { get; }
|
||||
public string Name { get; }
|
||||
|
||||
private GptPartitionEntry(Guid typeGuid, Guid guid, ulong firstLba, ulong lastLba, ulong attributes, string name)
|
||||
{
|
||||
TypeGuid = typeGuid;
|
||||
Guid = guid;
|
||||
FirstLba = firstLba;
|
||||
LastLba = lastLba;
|
||||
Attributes = attributes;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public static GptPartitionEntry Read(Stream stream)
|
||||
{
|
||||
return new GptPartitionEntry(
|
||||
ReadGuid(stream),
|
||||
ReadGuid(stream),
|
||||
ReadUInt64(stream),
|
||||
ReadUInt64(stream),
|
||||
ReadUInt64(stream),
|
||||
ReadString(stream, 72));
|
||||
}
|
||||
}
|
||||
}
|
||||
56
src/SharpCompress/Common/Dmg/Headers/GptStructBase.cs
Normal file
56
src/SharpCompress/Common/Dmg/Headers/GptStructBase.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.Headers
|
||||
{
|
||||
internal abstract class GptStructBase
|
||||
{
|
||||
private static readonly byte[] _buffer = new byte[8];
|
||||
|
||||
protected static ushort ReadUInt16(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(ushort)) != sizeof(ushort))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadUInt16LittleEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static uint ReadUInt32(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(uint)) != sizeof(uint))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadUInt32LittleEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static ulong ReadUInt64(Stream stream)
|
||||
{
|
||||
if (stream.Read(_buffer, 0, sizeof(ulong)) != sizeof(ulong))
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return BinaryPrimitives.ReadUInt64LittleEndian(_buffer);
|
||||
}
|
||||
|
||||
protected static Guid ReadGuid(Stream stream)
|
||||
{
|
||||
int a = (int)ReadUInt32(stream);
|
||||
short b = (short)ReadUInt16(stream);
|
||||
short c = (short)ReadUInt16(stream);
|
||||
|
||||
if (stream.Read(_buffer, 0, 8) != 8)
|
||||
throw new EndOfStreamException();
|
||||
|
||||
return new Guid(a, b, c, _buffer);
|
||||
}
|
||||
|
||||
protected static string ReadString(Stream stream, int byteSize)
|
||||
{
|
||||
var buffer = new byte[byteSize];
|
||||
if (stream.Read(buffer, 0, byteSize) != byteSize)
|
||||
throw new EndOfStreamException();
|
||||
return Encoding.Unicode.GetString(buffer).NullTerminate();
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/SharpCompress/Common/Dmg/Headers/UdifChecksum.cs
Normal file
33
src/SharpCompress/Common/Dmg/Headers/UdifChecksum.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SharpCompress.Common.Dmg.Headers
|
||||
{
|
||||
internal sealed class UdifChecksum : DmgStructBase
|
||||
{
|
||||
private const int MaxSize = 32; // * 4 to get byte size
|
||||
|
||||
public uint Type { get; }
|
||||
public uint Size { get; } // in bits
|
||||
public IReadOnlyList<uint> Bits { get; }
|
||||
|
||||
private UdifChecksum(uint type, uint size, IReadOnlyList<uint> bits)
|
||||
{
|
||||
Type = type;
|
||||
Size = size;
|
||||
Bits = bits;
|
||||
}
|
||||
|
||||
public static UdifChecksum Read(ref ReadOnlySpan<byte> data)
|
||||
{
|
||||
uint type = ReadUInt32(ref data);
|
||||
uint size = ReadUInt32(ref data);
|
||||
|
||||
var bits = new uint[MaxSize];
|
||||
for (int i = 0; i < MaxSize; i++)
|
||||
bits[i] = ReadUInt32(ref data);
|
||||
|
||||
return new UdifChecksum(type, size, bits);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
src/SharpCompress/Common/Dmg/PartitionFormat.cs
Normal file
14
src/SharpCompress/Common/Dmg/PartitionFormat.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common.Dmg
|
||||
{
|
||||
internal static class PartitionFormat
|
||||
{
|
||||
public static readonly Guid AppleHFS = new Guid("48465300-0000-11AA-AA11-00306543ECAC");
|
||||
public static readonly Guid AppleUFS = new Guid("55465300-0000-11AA-AA11-00306543ECAC");
|
||||
public static readonly Guid AppleBoot = new Guid("426F6F74-0000-11AA-AA11-00306543ECAC");
|
||||
public static readonly Guid AppleRaid = new Guid("52414944-0000-11AA-AA11-00306543ECAC");
|
||||
public static readonly Guid AppleRaidOffline = new Guid("52414944-5F4F-11AA-AA11-00306543ECAC");
|
||||
public static readonly Guid AppleLabel = new Guid("4C616265-6C00-11AA-AA11-00306543ECAC");
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common
|
||||
{
|
||||
@@ -78,9 +77,8 @@ namespace SharpCompress.Common
|
||||
|
||||
internal bool IsSolid { get; set; }
|
||||
|
||||
internal virtual ValueTask CloseAsync()
|
||||
internal virtual void Close()
|
||||
{
|
||||
return new ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
namespace SharpCompress.Common
|
||||
{
|
||||
public class EntryStream : AsyncStream
|
||||
public class EntryStream : Stream
|
||||
{
|
||||
private readonly IReader _reader;
|
||||
private readonly Stream _stream;
|
||||
@@ -23,24 +20,25 @@ namespace SharpCompress.Common
|
||||
/// <summary>
|
||||
/// When reading a stream from OpenEntryStream, the stream must be completed so use this to finish reading the entire entry.
|
||||
/// </summary>
|
||||
public async ValueTask SkipEntryAsync(CancellationToken cancellationToken = default)
|
||||
public void SkipEntry()
|
||||
{
|
||||
await this.SkipAsync(cancellationToken);
|
||||
this.Skip();
|
||||
_completed = true;
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!(_completed || _reader.Cancelled))
|
||||
{
|
||||
await SkipEntryAsync();
|
||||
SkipEntry();
|
||||
}
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
await _stream.DisposeAsync();
|
||||
base.Dispose(disposing);
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
@@ -48,13 +46,18 @@ namespace SharpCompress.Common
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override long Length => _stream.Length;
|
||||
|
||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
||||
|
||||
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int read = await _stream.ReadAsync(buffer, cancellationToken);
|
||||
int read = _stream.Read(buffer, offset, count);
|
||||
if (read <= 0)
|
||||
{
|
||||
_completed = true;
|
||||
@@ -62,14 +65,14 @@ namespace SharpCompress.Common
|
||||
return read;
|
||||
}
|
||||
|
||||
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
public override int ReadByte()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
int value = _stream.ReadByte();
|
||||
if (value == -1)
|
||||
{
|
||||
_completed = true;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
@@ -81,5 +84,10 @@ namespace SharpCompress.Common
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common
|
||||
{
|
||||
@@ -10,11 +8,10 @@ namespace SharpCompress.Common
|
||||
/// <summary>
|
||||
/// Extract to specific directory, retaining filename
|
||||
/// </summary>
|
||||
public static async ValueTask WriteEntryToDirectoryAsync(IEntry entry,
|
||||
public static void WriteEntryToDirectory(IEntry entry,
|
||||
string destinationDirectory,
|
||||
ExtractionOptions? options,
|
||||
Func<string, ExtractionOptions?, CancellationToken, ValueTask> write,
|
||||
CancellationToken cancellationToken = default)
|
||||
Action<string, ExtractionOptions?> write)
|
||||
{
|
||||
string destinationFileName;
|
||||
string file = Path.GetFileName(entry.Key);
|
||||
@@ -55,7 +52,7 @@ namespace SharpCompress.Common
|
||||
{
|
||||
throw new ExtractionException("Entry is trying to write a file outside of the destination directory.");
|
||||
}
|
||||
await write(destinationFileName, options, cancellationToken);
|
||||
write(destinationFileName, options);
|
||||
}
|
||||
else if (options.ExtractFullPath && !Directory.Exists(destinationFileName))
|
||||
{
|
||||
@@ -63,12 +60,11 @@ namespace SharpCompress.Common
|
||||
}
|
||||
}
|
||||
|
||||
public static async ValueTask WriteEntryToFileAsync(IEntry entry, string destinationFileName,
|
||||
public static void WriteEntryToFile(IEntry entry, string destinationFileName,
|
||||
ExtractionOptions? options,
|
||||
Func<string, FileMode, CancellationToken, ValueTask> openAndWrite,
|
||||
CancellationToken cancellationToken = default)
|
||||
Action<string, FileMode> openAndWrite)
|
||||
{
|
||||
if (entry.LinkTarget is not null)
|
||||
if (entry.LinkTarget != null)
|
||||
{
|
||||
if (options?.WriteSymbolicLink is null)
|
||||
{
|
||||
@@ -89,7 +85,7 @@ namespace SharpCompress.Common
|
||||
fm = FileMode.CreateNew;
|
||||
}
|
||||
|
||||
await openAndWrite(destinationFileName, fm, cancellationToken);
|
||||
openAndWrite(destinationFileName, fm);
|
||||
entry.PreserveExtractionOptions(destinationFileName, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common
|
||||
{
|
||||
@@ -13,9 +11,9 @@ namespace SharpCompress.Common
|
||||
|
||||
internal ArchiveEncoding ArchiveEncoding { get; }
|
||||
|
||||
internal abstract string? FilePartName { get; }
|
||||
internal abstract string FilePartName { get; }
|
||||
|
||||
internal abstract ValueTask<Stream> GetCompressedStreamAsync(CancellationToken cancellationToken);
|
||||
internal abstract Stream GetCompressedStream();
|
||||
internal abstract Stream? GetRawStream();
|
||||
internal bool Skipped { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace SharpCompress.Common.GZip
|
||||
{
|
||||
@@ -19,7 +17,7 @@ namespace SharpCompress.Common.GZip
|
||||
|
||||
public override long Crc => _filePart.Crc ?? 0;
|
||||
|
||||
public override string Key => _filePart.FilePartName ?? string.Empty;
|
||||
public override string Key => _filePart.FilePartName;
|
||||
|
||||
public override string? LinkTarget => null;
|
||||
|
||||
@@ -43,12 +41,9 @@ namespace SharpCompress.Common.GZip
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.AsEnumerable<FilePart>();
|
||||
|
||||
internal static async IAsyncEnumerable<GZipEntry> GetEntries(Stream stream, OptionsBase options,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
internal static IEnumerable<GZipEntry> GetEntries(Stream stream, OptionsBase options)
|
||||
{
|
||||
var part = new GZipFilePart(options.ArchiveEncoding);
|
||||
await part.Initialize(stream, cancellationToken);
|
||||
yield return new GZipEntry(part);
|
||||
yield return new GZipEntry(new GZipFilePart(stream, options.ArchiveEncoding));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
@@ -14,44 +11,34 @@ namespace SharpCompress.Common.GZip
|
||||
internal sealed class GZipFilePart : FilePart
|
||||
{
|
||||
private string? _name;
|
||||
//init only
|
||||
#nullable disable
|
||||
private Stream _stream;
|
||||
#nullable enable
|
||||
private readonly Stream _stream;
|
||||
|
||||
internal GZipFilePart(ArchiveEncoding archiveEncoding)
|
||||
internal GZipFilePart(Stream stream, ArchiveEncoding archiveEncoding)
|
||||
: base(archiveEncoding)
|
||||
{
|
||||
}
|
||||
|
||||
internal async ValueTask Initialize(Stream stream, CancellationToken cancellationToken)
|
||||
{
|
||||
_stream = stream;
|
||||
ReadAndValidateGzipHeader();
|
||||
if (stream.CanSeek)
|
||||
{
|
||||
long position = stream.Position;
|
||||
stream.Position = stream.Length - 8;
|
||||
await ReadTrailerAsync(cancellationToken);
|
||||
ReadTrailer();
|
||||
stream.Position = position;
|
||||
}
|
||||
EntryStartPosition = stream.Position;
|
||||
}
|
||||
|
||||
internal long EntryStartPosition { get; private set; }
|
||||
internal long EntryStartPosition { get; }
|
||||
|
||||
internal DateTime? DateModified { get; private set; }
|
||||
internal int? Crc { get; private set; }
|
||||
internal int? UncompressedSize { get; private set; }
|
||||
|
||||
internal override string? FilePartName => _name;
|
||||
internal override string FilePartName => _name!;
|
||||
|
||||
internal override async ValueTask<Stream> GetCompressedStreamAsync(CancellationToken cancellationToken)
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
var stream = new GZipStream(_stream, CompressionMode.Decompress, CompressionLevel.Default);
|
||||
await stream.ReadAsync(Array.Empty<byte>(), 0, 0, cancellationToken);
|
||||
_name = stream.FileName;
|
||||
DateModified = stream.LastModified;
|
||||
return stream;
|
||||
return new DeflateStream(_stream, CompressionMode.Decompress, CompressionLevel.Default);
|
||||
}
|
||||
|
||||
internal override Stream GetRawStream()
|
||||
@@ -59,12 +46,93 @@ namespace SharpCompress.Common.GZip
|
||||
return _stream;
|
||||
}
|
||||
|
||||
private async ValueTask ReadTrailerAsync(CancellationToken cancellationToken)
|
||||
private void ReadTrailer()
|
||||
{
|
||||
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
|
||||
Span<byte> trailer = stackalloc byte[8];
|
||||
int n = _stream.Read(trailer);
|
||||
|
||||
Crc = await _stream.ReadInt32(cancellationToken);
|
||||
UncompressedSize = await _stream.ReadInt32(cancellationToken);
|
||||
Crc = BinaryPrimitives.ReadInt32LittleEndian(trailer);
|
||||
UncompressedSize = BinaryPrimitives.ReadInt32LittleEndian(trailer.Slice(4));
|
||||
}
|
||||
|
||||
private void ReadAndValidateGzipHeader()
|
||||
{
|
||||
// read the header on the first read
|
||||
Span<byte> header = stackalloc byte[10];
|
||||
int n = _stream.Read(header);
|
||||
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (n == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (n != 10)
|
||||
{
|
||||
throw new ZlibException("Not a valid GZIP stream.");
|
||||
}
|
||||
|
||||
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
|
||||
{
|
||||
throw new ZlibException("Bad GZIP header.");
|
||||
}
|
||||
|
||||
int timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4));
|
||||
DateModified = TarHeader.EPOCH.AddSeconds(timet);
|
||||
if ((header[3] & 0x04) == 0x04)
|
||||
{
|
||||
// read and discard extra field
|
||||
n = _stream.Read(header.Slice(0, 2)); // 2-byte length field
|
||||
|
||||
short extraLength = (short)(header[0] + header[1] * 256);
|
||||
byte[] extra = new byte[extraLength];
|
||||
|
||||
if (!_stream.ReadFully(extra))
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
n = extraLength;
|
||||
}
|
||||
if ((header[3] & 0x08) == 0x08)
|
||||
{
|
||||
_name = ReadZeroTerminatedString(_stream);
|
||||
}
|
||||
if ((header[3] & 0x10) == 0x010)
|
||||
{
|
||||
ReadZeroTerminatedString(_stream);
|
||||
}
|
||||
if ((header[3] & 0x02) == 0x02)
|
||||
{
|
||||
_stream.ReadByte(); // CRC16, ignore
|
||||
}
|
||||
}
|
||||
|
||||
private string ReadZeroTerminatedString(Stream stream)
|
||||
{
|
||||
Span<byte> buf1 = stackalloc byte[1];
|
||||
var list = new List<byte>();
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
// workitem 7740
|
||||
int n = stream.Read(buf1);
|
||||
if (n != 1)
|
||||
{
|
||||
throw new ZlibException("Unexpected EOF reading GZIP header.");
|
||||
}
|
||||
if (buf1[0] == 0)
|
||||
{
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(buf1[0]);
|
||||
}
|
||||
}
|
||||
while (!done);
|
||||
byte[] buffer = list.ToArray();
|
||||
return ArchiveEncoding.Decode(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace SharpCompress.Common
|
||||
{
|
||||
public interface IVolume : IAsyncDisposable
|
||||
public interface IVolume : IDisposable
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace SharpCompress.Common
|
||||
using System;
|
||||
|
||||
namespace SharpCompress.Common
|
||||
{
|
||||
public class IncompleteArchiveException : ArchiveException
|
||||
{
|
||||
@@ -6,5 +8,10 @@
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public IncompleteArchiveException(string message, Exception inner)
|
||||
: base(message, inner)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
using SharpCompress.Compressors.LZMA.Utilites;
|
||||
using SharpCompress.IO;
|
||||
@@ -785,7 +783,7 @@ namespace SharpCompress.Common.SevenZip
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<List<byte[]>> ReadAndDecodePackedStreams(long baseOffset, IPasswordProvider pass, CancellationToken cancellationToken)
|
||||
private List<byte[]> ReadAndDecodePackedStreams(long baseOffset, IPasswordProvider pass)
|
||||
{
|
||||
#if DEBUG
|
||||
Log.WriteLine("-- ReadAndDecodePackedStreams --");
|
||||
@@ -817,8 +815,8 @@ namespace SharpCompress.Common.SevenZip
|
||||
dataStartPos += packSize;
|
||||
}
|
||||
|
||||
var outStream = await DecoderStreamHelper.CreateDecoderStream(_stream, oldDataStartPos, myPackSizes,
|
||||
folder, pass, cancellationToken);
|
||||
var outStream = DecoderStreamHelper.CreateDecoderStream(_stream, oldDataStartPos, myPackSizes,
|
||||
folder, pass);
|
||||
|
||||
int unpackSize = checked((int)folder.GetUnpackSize());
|
||||
byte[] data = new byte[unpackSize];
|
||||
@@ -847,7 +845,7 @@ namespace SharpCompress.Common.SevenZip
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask ReadHeader(ArchiveDatabase db, IPasswordProvider getTextPassword, CancellationToken cancellationToken)
|
||||
private void ReadHeader(ArchiveDatabase db, IPasswordProvider getTextPassword)
|
||||
{
|
||||
#if DEBUG
|
||||
Log.WriteLine("-- ReadHeader --");
|
||||
@@ -866,7 +864,7 @@ namespace SharpCompress.Common.SevenZip
|
||||
List<byte[]> dataVector = null;
|
||||
if (type == BlockType.AdditionalStreamsInfo)
|
||||
{
|
||||
dataVector = await ReadAndDecodePackedStreams(db._startPositionAfterHeader, getTextPassword, cancellationToken);
|
||||
dataVector = ReadAndDecodePackedStreams(db._startPositionAfterHeader, getTextPassword);
|
||||
type = ReadId();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace SharpCompress.Common.SevenZip
|
||||
|
||||
public override DateTime? ArchivedTime => null;
|
||||
|
||||
public override bool IsEncrypted => false;
|
||||
public override bool IsEncrypted => FilePart.IsEncrypted;
|
||||
|
||||
public override bool IsDirectory => FilePart.Header.IsDir;
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.SevenZip
|
||||
@@ -37,11 +35,11 @@ namespace SharpCompress.Common.SevenZip
|
||||
return null;
|
||||
}
|
||||
|
||||
internal override async ValueTask<Stream> GetCompressedStreamAsync(CancellationToken cancellationToken)
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (!Header.HasStream)
|
||||
{
|
||||
return Stream.Null;
|
||||
return null!;
|
||||
}
|
||||
var folderStream = _database.GetFolderStream(_stream, Folder!, _database.PasswordProvider);
|
||||
|
||||
@@ -54,7 +52,7 @@ namespace SharpCompress.Common.SevenZip
|
||||
}
|
||||
if (skipSize > 0)
|
||||
{
|
||||
await folderStream.SkipAsync(skipSize, cancellationToken);
|
||||
folderStream.Skip(skipSize);
|
||||
}
|
||||
return new ReadOnlySubStream(folderStream, Header.Size);
|
||||
}
|
||||
@@ -104,5 +102,7 @@ namespace SharpCompress.Common.SevenZip
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsEncrypted => Folder!._coders.FindIndex(c => c._methodId._id == CMethodId.K_AES_ID) != -1;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Tar.Headers
|
||||
{
|
||||
@@ -35,48 +32,48 @@ namespace SharpCompress.Common.Tar.Headers
|
||||
|
||||
internal const int BLOCK_SIZE = 512;
|
||||
|
||||
internal async Task WriteAsync(Stream output)
|
||||
internal void Write(Stream output)
|
||||
{
|
||||
using var buffer = MemoryPool<byte>.Shared.Rent(BLOCK_SIZE);
|
||||
byte[] buffer = new byte[BLOCK_SIZE];
|
||||
|
||||
WriteOctalBytes(511, buffer.Memory.Span, 100, 8); // file mode
|
||||
WriteOctalBytes(0, buffer.Memory.Span, 108, 8); // owner ID
|
||||
WriteOctalBytes(0, buffer.Memory.Span, 116, 8); // group ID
|
||||
WriteOctalBytes(511, buffer, 100, 8); // file mode
|
||||
WriteOctalBytes(0, buffer, 108, 8); // owner ID
|
||||
WriteOctalBytes(0, buffer, 116, 8); // group ID
|
||||
|
||||
//ArchiveEncoding.UTF8.GetBytes("magic").CopyTo(buffer, 257);
|
||||
var nameByteCount = ArchiveEncoding.GetEncoding().GetByteCount(Name);
|
||||
if (nameByteCount > 100)
|
||||
{
|
||||
// Set mock filename and filetype to indicate the next block is the actual name of the file
|
||||
WriteStringBytes("././@LongLink", buffer.Memory.Span, 0, 100);
|
||||
buffer.Memory.Span[156] = (byte)EntryType.LongName;
|
||||
WriteOctalBytes(nameByteCount + 1, buffer.Memory.Span, 124, 12);
|
||||
WriteStringBytes("././@LongLink", buffer, 0, 100);
|
||||
buffer[156] = (byte)EntryType.LongName;
|
||||
WriteOctalBytes(nameByteCount + 1, buffer, 124, 12);
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteStringBytes(ArchiveEncoding.Encode(Name), buffer.Memory, 100);
|
||||
WriteOctalBytes(Size, buffer.Memory.Span, 124, 12);
|
||||
WriteStringBytes(ArchiveEncoding.Encode(Name), buffer, 100);
|
||||
WriteOctalBytes(Size, buffer, 124, 12);
|
||||
var time = (long)(LastModifiedTime.ToUniversalTime() - EPOCH).TotalSeconds;
|
||||
WriteOctalBytes(time, buffer.Memory.Span, 136, 12);
|
||||
buffer.Memory.Span[156] = (byte)EntryType;
|
||||
WriteOctalBytes(time, buffer, 136, 12);
|
||||
buffer[156] = (byte)EntryType;
|
||||
|
||||
if (Size >= 0x1FFFFFFFF)
|
||||
{
|
||||
using var bytes12 = MemoryPool<byte>.Shared.Rent(12);
|
||||
BinaryPrimitives.WriteInt64BigEndian(bytes12.Memory.Span.Slice(4), Size);
|
||||
bytes12.Memory.Span[0] |= 0x80;
|
||||
bytes12.Memory.CopyTo(buffer.Memory.Slice(124));
|
||||
Span<byte> bytes12 = stackalloc byte[12];
|
||||
BinaryPrimitives.WriteInt64BigEndian(bytes12.Slice(4), Size);
|
||||
bytes12[0] |= 0x80;
|
||||
bytes12.CopyTo(buffer.AsSpan(124));
|
||||
}
|
||||
}
|
||||
|
||||
int crc = RecalculateChecksum(buffer.Memory);
|
||||
WriteOctalBytes(crc, buffer.Memory.Span, 148, 8);
|
||||
int crc = RecalculateChecksum(buffer);
|
||||
WriteOctalBytes(crc, buffer, 148, 8);
|
||||
|
||||
await output.WriteAsync(buffer.Memory.Slice(0, BLOCK_SIZE));
|
||||
output.Write(buffer, 0, buffer.Length);
|
||||
|
||||
if (nameByteCount > 100)
|
||||
{
|
||||
await WriteLongFilenameHeaderAsync(output);
|
||||
WriteLongFilenameHeader(output);
|
||||
// update to short name lower than 100 - [max bytes of one character].
|
||||
// subtracting bytes is needed because preventing infinite loop(example code is here).
|
||||
//
|
||||
@@ -85,14 +82,14 @@ namespace SharpCompress.Common.Tar.Headers
|
||||
//
|
||||
// and then infinite recursion is occured in WriteLongFilenameHeader because truncated.Length is 102.
|
||||
Name = ArchiveEncoding.Decode(ArchiveEncoding.Encode(Name), 0, 100 - ArchiveEncoding.GetEncoding().GetMaxByteCount(1));
|
||||
await WriteAsync(output);
|
||||
Write(output);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteLongFilenameHeaderAsync(Stream output)
|
||||
private void WriteLongFilenameHeader(Stream output)
|
||||
{
|
||||
byte[] nameBytes = ArchiveEncoding.Encode(Name);
|
||||
await output.WriteAsync(nameBytes.AsMemory());
|
||||
output.Write(nameBytes, 0, nameBytes.Length);
|
||||
|
||||
// pad to multiple of BlockSize bytes, and make sure a terminating null is added
|
||||
int numPaddingBytes = BLOCK_SIZE - (nameBytes.Length % BLOCK_SIZE);
|
||||
@@ -100,56 +97,48 @@ namespace SharpCompress.Common.Tar.Headers
|
||||
{
|
||||
numPaddingBytes = BLOCK_SIZE;
|
||||
}
|
||||
|
||||
using var padding = MemoryPool<byte>.Shared.Rent(numPaddingBytes);
|
||||
padding.Memory.Span.Clear();
|
||||
await output.WriteAsync(padding.Memory.Slice(0, numPaddingBytes));
|
||||
output.Write(stackalloc byte[numPaddingBytes]);
|
||||
}
|
||||
|
||||
internal async ValueTask<bool> Read(Stream stream, CancellationToken cancellationToken)
|
||||
internal bool Read(BinaryReader reader)
|
||||
{
|
||||
var block = MemoryPool<byte>.Shared.Rent(BLOCK_SIZE);
|
||||
bool readFullyAsync = await stream.ReadAsync(block.Memory.Slice(0, BLOCK_SIZE), cancellationToken) == BLOCK_SIZE;
|
||||
if (readFullyAsync is false)
|
||||
var buffer = ReadBlock(reader);
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// for symlinks, additionally read the linkname
|
||||
if (ReadEntryType(block.Memory.Span) == EntryType.SymLink)
|
||||
if (ReadEntryType(buffer) == EntryType.SymLink)
|
||||
{
|
||||
LinkName = ArchiveEncoding.Decode(block.Memory.Span.Slice(157, 100)).TrimNulls();
|
||||
LinkName = ArchiveEncoding.Decode(buffer, 157, 100).TrimNulls();
|
||||
}
|
||||
|
||||
if (ReadEntryType(block.Memory.Span) == EntryType.LongName)
|
||||
if (ReadEntryType(buffer) == EntryType.LongName)
|
||||
{
|
||||
Name = await ReadLongName(stream, block.Memory.Slice(0,BLOCK_SIZE), cancellationToken);
|
||||
readFullyAsync = await stream.ReadAsync(block.Memory.Slice(0, BLOCK_SIZE), cancellationToken) == BLOCK_SIZE;
|
||||
if (readFullyAsync is false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Name = ReadLongName(reader, buffer);
|
||||
buffer = ReadBlock(reader);
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = ArchiveEncoding.Decode(block.Memory.Span.Slice( 0, 100)).TrimNulls();
|
||||
Name = ArchiveEncoding.Decode(buffer, 0, 100).TrimNulls();
|
||||
}
|
||||
|
||||
EntryType = ReadEntryType(block.Memory.Span);
|
||||
Size = ReadSize(block.Memory.Slice(0, BLOCK_SIZE));
|
||||
EntryType = ReadEntryType(buffer);
|
||||
Size = ReadSize(buffer);
|
||||
|
||||
//Mode = ReadASCIIInt32Base8(buffer, 100, 7);
|
||||
//UserId = ReadASCIIInt32Base8(buffer, 108, 7);
|
||||
//GroupId = ReadASCIIInt32Base8(buffer, 116, 7);
|
||||
long unixTimeStamp = ReadAsciiInt64Base8(block.Memory.Span.Slice(136, 11));
|
||||
long unixTimeStamp = ReadAsciiInt64Base8(buffer, 136, 11);
|
||||
LastModifiedTime = EPOCH.AddSeconds(unixTimeStamp).ToLocalTime();
|
||||
|
||||
Magic = ArchiveEncoding.Decode(block.Memory.Span.Slice( 257, 6)).TrimNulls();
|
||||
Magic = ArchiveEncoding.Decode(buffer, 257, 6).TrimNulls();
|
||||
|
||||
if (!string.IsNullOrEmpty(Magic)
|
||||
&& "ustar".Equals(Magic))
|
||||
{
|
||||
string namePrefix = ArchiveEncoding.Decode(block.Memory.Span.Slice( 345, 157));
|
||||
string namePrefix = ArchiveEncoding.Decode(buffer, 345, 157);
|
||||
namePrefix = namePrefix.TrimNulls();
|
||||
if (!string.IsNullOrEmpty(namePrefix))
|
||||
{
|
||||
@@ -164,46 +153,55 @@ namespace SharpCompress.Common.Tar.Headers
|
||||
return true;
|
||||
}
|
||||
|
||||
private async ValueTask<string> ReadLongName(Stream reader, ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken)
|
||||
private string ReadLongName(BinaryReader reader, byte[] buffer)
|
||||
{
|
||||
var size = ReadSize(buffer);
|
||||
var nameLength = (int)size;
|
||||
using var rented = MemoryPool<byte>.Shared.Rent(nameLength);
|
||||
var nameBytes = rented.Memory.Slice(0, nameLength);
|
||||
await reader.ReadAsync(nameBytes, cancellationToken);
|
||||
var nameBytes = reader.ReadBytes(nameLength);
|
||||
var remainingBytesToRead = BLOCK_SIZE - (nameLength % BLOCK_SIZE);
|
||||
|
||||
// Read the rest of the block and discard the data
|
||||
if (remainingBytesToRead < BLOCK_SIZE)
|
||||
{
|
||||
using var remaining = MemoryPool<byte>.Shared.Rent(remainingBytesToRead);
|
||||
await reader.ReadAsync(remaining.Memory.Slice(0, remainingBytesToRead), cancellationToken);
|
||||
reader.ReadBytes(remainingBytesToRead);
|
||||
}
|
||||
return ArchiveEncoding.Decode(nameBytes.Span).TrimNulls();
|
||||
return ArchiveEncoding.Decode(nameBytes, 0, nameBytes.Length).TrimNulls();
|
||||
}
|
||||
|
||||
private static EntryType ReadEntryType(Span<byte> buffer)
|
||||
private static EntryType ReadEntryType(byte[] buffer)
|
||||
{
|
||||
return (EntryType)buffer[156];
|
||||
}
|
||||
|
||||
private long ReadSize(ReadOnlyMemory<byte> buffer)
|
||||
private long ReadSize(byte[] buffer)
|
||||
{
|
||||
if ((buffer.Span[124] & 0x80) == 0x80) // if size in binary
|
||||
if ((buffer[124] & 0x80) == 0x80) // if size in binary
|
||||
{
|
||||
return BinaryPrimitives.ReadInt64BigEndian(buffer.Span.Slice(0x80));
|
||||
return BinaryPrimitives.ReadInt64BigEndian(buffer.AsSpan(0x80));
|
||||
}
|
||||
|
||||
return ReadAsciiInt64Base8(buffer.Span.Slice(124, 11));
|
||||
}
|
||||
private static void WriteStringBytes(ReadOnlySpan<byte> name, Memory<byte> buffer, int length)
|
||||
{
|
||||
name.CopyTo(buffer.Span.Slice(0));
|
||||
int i = Math.Min(length, name.Length);
|
||||
buffer.Slice(i, length - i).Span.Clear();
|
||||
return ReadAsciiInt64Base8(buffer, 124, 11);
|
||||
}
|
||||
|
||||
private static void WriteStringBytes(string name, Span<byte> buffer, int offset, int length)
|
||||
private static byte[] ReadBlock(BinaryReader reader)
|
||||
{
|
||||
byte[] buffer = reader.ReadBytes(BLOCK_SIZE);
|
||||
|
||||
if (buffer.Length != 0 && buffer.Length < BLOCK_SIZE)
|
||||
{
|
||||
throw new InvalidOperationException("Buffer is invalid size");
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static void WriteStringBytes(ReadOnlySpan<byte> name, Span<byte> buffer, int length)
|
||||
{
|
||||
name.CopyTo(buffer);
|
||||
int i = Math.Min(length, name.Length);
|
||||
buffer.Slice(i, length - i).Clear();
|
||||
}
|
||||
|
||||
private static void WriteStringBytes(string name, byte[] buffer, int offset, int length)
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -218,7 +216,7 @@ namespace SharpCompress.Common.Tar.Headers
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteOctalBytes(long value, Span<byte> buffer, int offset, int length)
|
||||
private static void WriteOctalBytes(long value, byte[] buffer, int offset, int length)
|
||||
{
|
||||
string val = Convert.ToString(value, 8);
|
||||
int shift = length - val.Length - 1;
|
||||
@@ -232,9 +230,19 @@ namespace SharpCompress.Common.Tar.Headers
|
||||
}
|
||||
}
|
||||
|
||||
private static long ReadAsciiInt64Base8(ReadOnlySpan<byte> buffer)
|
||||
private static int ReadAsciiInt32Base8(byte[] buffer, int offset, int count)
|
||||
{
|
||||
string s = Encoding.UTF8.GetString(buffer).TrimNulls();
|
||||
string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return Convert.ToInt32(s, 8);
|
||||
}
|
||||
|
||||
private static long ReadAsciiInt64Base8(byte[] buffer, int offset, int count)
|
||||
{
|
||||
string s = Encoding.UTF8.GetString(buffer, offset, count).TrimNulls();
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
return 0;
|
||||
@@ -258,20 +266,38 @@ namespace SharpCompress.Common.Tar.Headers
|
||||
(byte)' ', (byte)' ', (byte)' ', (byte)' '
|
||||
};
|
||||
|
||||
private static int RecalculateChecksum(Memory<byte> buf)
|
||||
internal static int RecalculateChecksum(byte[] buf)
|
||||
{
|
||||
// Set default value for checksum. That is 8 spaces.
|
||||
eightSpaces.CopyTo(buf.Slice(148));
|
||||
eightSpaces.CopyTo(buf, 148);
|
||||
|
||||
// Calculate checksum
|
||||
int headerChecksum = 0;
|
||||
foreach (byte b in buf.Span)
|
||||
foreach (byte b in buf)
|
||||
{
|
||||
headerChecksum += b;
|
||||
}
|
||||
return headerChecksum;
|
||||
}
|
||||
|
||||
internal static int RecalculateAltChecksum(byte[] buf)
|
||||
{
|
||||
eightSpaces.CopyTo(buf, 148);
|
||||
int headerChecksum = 0;
|
||||
foreach (byte b in buf)
|
||||
{
|
||||
if ((b & 0x80) == 0x80)
|
||||
{
|
||||
headerChecksum -= b ^ 0x80;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerChecksum += b;
|
||||
}
|
||||
}
|
||||
return headerChecksum;
|
||||
}
|
||||
|
||||
public long? DataStartPosition { get; set; }
|
||||
|
||||
public string Magic { get; set; }
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -48,11 +46,10 @@ namespace SharpCompress.Common.Tar
|
||||
|
||||
internal override IEnumerable<FilePart> Parts => _filePart.AsEnumerable<FilePart>();
|
||||
|
||||
internal static async IAsyncEnumerable<TarEntry> GetEntries(StreamingMode mode, Stream stream,
|
||||
CompressionType compressionType, ArchiveEncoding archiveEncoding,
|
||||
[EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
internal static IEnumerable<TarEntry> GetEntries(StreamingMode mode, Stream stream,
|
||||
CompressionType compressionType, ArchiveEncoding archiveEncoding)
|
||||
{
|
||||
await foreach (TarHeader h in TarHeaderFactory.ReadHeader(mode, stream, archiveEncoding, cancellationToken))
|
||||
foreach (TarHeader h in TarHeaderFactory.ReadHeader(mode, stream, archiveEncoding))
|
||||
{
|
||||
if (h != null)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Tar
|
||||
{
|
||||
@@ -20,14 +19,14 @@ namespace SharpCompress.Common.Tar
|
||||
|
||||
internal override string FilePartName => Header.Name;
|
||||
|
||||
internal override ValueTask<Stream> GetCompressedStreamAsync(CancellationToken cancellationToken)
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (_seekableStream != null)
|
||||
{
|
||||
_seekableStream.Position = Header.DataStartPosition!.Value;
|
||||
return new(new TarReadOnlySubStream(_seekableStream, Header.Size));
|
||||
return new TarReadOnlySubStream(_seekableStream, Header.Size);
|
||||
}
|
||||
return new(Header.PackedStream);
|
||||
return Header.PackedStream;
|
||||
}
|
||||
|
||||
internal override Stream? GetRawStream()
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -9,17 +7,17 @@ namespace SharpCompress.Common.Tar
|
||||
{
|
||||
internal static class TarHeaderFactory
|
||||
{
|
||||
internal static async IAsyncEnumerable<TarHeader?> ReadHeader(StreamingMode mode, Stream stream, ArchiveEncoding archiveEncoding,
|
||||
[EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
internal static IEnumerable<TarHeader?> ReadHeader(StreamingMode mode, Stream stream, ArchiveEncoding archiveEncoding)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
TarHeader? header = null;
|
||||
try
|
||||
{
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
header = new TarHeader(archiveEncoding);
|
||||
|
||||
if (!await header.Read(stream, cancellationToken))
|
||||
if (!header.Read(reader))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
@@ -27,10 +25,10 @@ namespace SharpCompress.Common.Tar
|
||||
{
|
||||
case StreamingMode.Seekable:
|
||||
{
|
||||
header.DataStartPosition = stream.Position;
|
||||
header.DataStartPosition = reader.BaseStream.Position;
|
||||
|
||||
//skip to nearest 512
|
||||
stream.Position += PadTo512(header.Size);
|
||||
reader.BaseStream.Position += PadTo512(header.Size);
|
||||
}
|
||||
break;
|
||||
case StreamingMode.Streaming:
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using SharpCompress.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Tar
|
||||
{
|
||||
@@ -16,7 +14,7 @@ namespace SharpCompress.Common.Tar
|
||||
BytesLeftToRead = bytesToRead;
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
@@ -25,17 +23,22 @@ namespace SharpCompress.Common.Tar
|
||||
|
||||
_isDisposed = true;
|
||||
|
||||
// Ensure we read all remaining blocks for this entry.
|
||||
await Stream.SkipAsync(BytesLeftToRead);
|
||||
_amountRead += BytesLeftToRead;
|
||||
|
||||
// If the last block wasn't a full 512 bytes, skip the remaining padding bytes.
|
||||
var bytesInLastBlock = _amountRead % 512;
|
||||
|
||||
if (bytesInLastBlock != 0)
|
||||
if (disposing)
|
||||
{
|
||||
await Stream.SkipAsync(512 - bytesInLastBlock);
|
||||
// Ensure we read all remaining blocks for this entry.
|
||||
Stream.Skip(BytesLeftToRead);
|
||||
_amountRead += BytesLeftToRead;
|
||||
|
||||
// If the last block wasn't a full 512 bytes, skip the remaining padding bytes.
|
||||
var bytesInLastBlock = _amountRead % 512;
|
||||
|
||||
if (bytesInLastBlock != 0)
|
||||
{
|
||||
Stream.Skip(512 - bytesInLastBlock);
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private long BytesLeftToRead { get; set; }
|
||||
@@ -46,18 +49,22 @@ namespace SharpCompress.Common.Tar
|
||||
|
||||
public override bool CanWrite => false;
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
|
||||
|
||||
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var count = buffer.Length;
|
||||
if (BytesLeftToRead < buffer.Length)
|
||||
if (BytesLeftToRead < count)
|
||||
{
|
||||
count = (int)BytesLeftToRead;
|
||||
}
|
||||
int read = await Stream.ReadAsync(buffer.Slice(0, count), cancellationToken);
|
||||
int read = Stream.Read(buffer, offset, count);
|
||||
if (read > 0)
|
||||
{
|
||||
BytesLeftToRead -= read;
|
||||
@@ -66,9 +73,20 @@ namespace SharpCompress.Common.Tar
|
||||
return read;
|
||||
}
|
||||
|
||||
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override int ReadByte()
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
if (BytesLeftToRead <= 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
int value = Stream.ReadByte();
|
||||
if (value != -1)
|
||||
{
|
||||
--BytesLeftToRead;
|
||||
++_amountRead;
|
||||
}
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
@@ -80,5 +98,10 @@ namespace SharpCompress.Common.Tar
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
using System.IO;
|
||||
using SharpCompress.IO;
|
||||
using SharpCompress.Readers;
|
||||
|
||||
@@ -33,10 +33,19 @@ namespace SharpCompress.Common
|
||||
/// RarArchive is part of a multi-part archive.
|
||||
/// </summary>
|
||||
public virtual bool IsMultiVolume => true;
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
return _actualStream.DisposeAsync();
|
||||
if (disposing)
|
||||
{
|
||||
_actualStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
@@ -11,29 +9,29 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
}
|
||||
|
||||
internal override async ValueTask Read(Stream stream, CancellationToken cancellationToken)
|
||||
internal override void Read(BinaryReader reader)
|
||||
{
|
||||
VolumeNumber = await stream.ReadUInt16(cancellationToken);
|
||||
FirstVolumeWithDirectory = await stream.ReadUInt16(cancellationToken);
|
||||
TotalNumberOfEntriesInDisk = await stream.ReadUInt16(cancellationToken);
|
||||
TotalNumberOfEntries = await stream.ReadUInt16(cancellationToken);
|
||||
DirectorySize = await stream.ReadUInt32(cancellationToken);
|
||||
DirectoryStartOffsetRelativeToDisk = await stream.ReadUInt32(cancellationToken);
|
||||
CommentLength = await stream.ReadUInt16(cancellationToken);
|
||||
Comment = await stream.ReadBytes(CommentLength ?? 0, cancellationToken);
|
||||
VolumeNumber = reader.ReadUInt16();
|
||||
FirstVolumeWithDirectory = reader.ReadUInt16();
|
||||
TotalNumberOfEntriesInDisk = reader.ReadUInt16();
|
||||
TotalNumberOfEntries = reader.ReadUInt16();
|
||||
DirectorySize = reader.ReadUInt32();
|
||||
DirectoryStartOffsetRelativeToDisk = reader.ReadUInt32();
|
||||
CommentLength = reader.ReadUInt16();
|
||||
Comment = reader.ReadBytes(CommentLength);
|
||||
}
|
||||
|
||||
public ushort? VolumeNumber { get; private set; }
|
||||
public ushort VolumeNumber { get; private set; }
|
||||
|
||||
public ushort? FirstVolumeWithDirectory { get; private set; }
|
||||
public ushort FirstVolumeWithDirectory { get; private set; }
|
||||
|
||||
public ushort? TotalNumberOfEntriesInDisk { get; private set; }
|
||||
public ushort TotalNumberOfEntriesInDisk { get; private set; }
|
||||
|
||||
public uint? DirectorySize { get; private set; }
|
||||
public uint DirectorySize { get; private set; }
|
||||
|
||||
public uint? DirectoryStartOffsetRelativeToDisk { get; private set; }
|
||||
public uint DirectoryStartOffsetRelativeToDisk { get; private set; }
|
||||
|
||||
public ushort? CommentLength { get; private set; }
|
||||
public ushort CommentLength { get; private set; }
|
||||
|
||||
public byte[]? Comment { get; private set; }
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
@@ -12,28 +10,28 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
}
|
||||
|
||||
internal override async ValueTask Read(Stream stream, CancellationToken cancellationToken)
|
||||
internal override void Read(BinaryReader reader)
|
||||
{
|
||||
Version = await stream.ReadUInt16(cancellationToken);
|
||||
VersionNeededToExtract = await stream.ReadUInt16(cancellationToken);
|
||||
Flags = (HeaderFlags)await stream.ReadUInt16(cancellationToken);
|
||||
CompressionMethod = (ZipCompressionMethod)await stream.ReadUInt16(cancellationToken);
|
||||
LastModifiedTime = await stream.ReadUInt16(cancellationToken);
|
||||
LastModifiedDate = await stream.ReadUInt16(cancellationToken);
|
||||
Crc = await stream.ReadUInt32(cancellationToken);
|
||||
CompressedSize = await stream.ReadUInt32(cancellationToken);
|
||||
UncompressedSize = await stream.ReadUInt32(cancellationToken);
|
||||
ushort nameLength = await stream.ReadUInt16(cancellationToken);
|
||||
ushort extraLength = await stream.ReadUInt16(cancellationToken);
|
||||
ushort commentLength = await stream.ReadUInt16(cancellationToken);
|
||||
DiskNumberStart = await stream.ReadUInt16(cancellationToken);
|
||||
InternalFileAttributes = await stream.ReadUInt16(cancellationToken);
|
||||
ExternalFileAttributes = await stream.ReadUInt32(cancellationToken);
|
||||
RelativeOffsetOfEntryHeader = await stream.ReadUInt32(cancellationToken);
|
||||
Version = reader.ReadUInt16();
|
||||
VersionNeededToExtract = reader.ReadUInt16();
|
||||
Flags = (HeaderFlags)reader.ReadUInt16();
|
||||
CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16();
|
||||
LastModifiedTime = reader.ReadUInt16();
|
||||
LastModifiedDate = reader.ReadUInt16();
|
||||
Crc = reader.ReadUInt32();
|
||||
CompressedSize = reader.ReadUInt32();
|
||||
UncompressedSize = reader.ReadUInt32();
|
||||
ushort nameLength = reader.ReadUInt16();
|
||||
ushort extraLength = reader.ReadUInt16();
|
||||
ushort commentLength = reader.ReadUInt16();
|
||||
DiskNumberStart = reader.ReadUInt16();
|
||||
InternalFileAttributes = reader.ReadUInt16();
|
||||
ExternalFileAttributes = reader.ReadUInt32();
|
||||
RelativeOffsetOfEntryHeader = reader.ReadUInt32();
|
||||
|
||||
byte[] name = await stream.ReadBytes(nameLength, cancellationToken);
|
||||
byte[] extra = await stream.ReadBytes(extraLength, cancellationToken);
|
||||
byte[] comment = await stream.ReadBytes(commentLength, cancellationToken);
|
||||
byte[] name = reader.ReadBytes(nameLength);
|
||||
byte[] extra = reader.ReadBytes(extraLength);
|
||||
byte[] comment = reader.ReadBytes(commentLength);
|
||||
|
||||
// According to .ZIP File Format Specification
|
||||
//
|
||||
@@ -65,6 +63,8 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
var zip64ExtraData = Extra.OfType<Zip64ExtendedInformationExtraField>().FirstOrDefault();
|
||||
if (zip64ExtraData != null)
|
||||
{
|
||||
zip64ExtraData.Process(UncompressedSize, CompressedSize, RelativeOffsetOfEntryHeader, DiskNumberStart);
|
||||
|
||||
if (CompressedSize == uint.MaxValue)
|
||||
{
|
||||
CompressedSize = zip64ExtraData.CompressedSize;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
@@ -11,9 +9,8 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
}
|
||||
|
||||
internal override ValueTask Read(Stream stream, CancellationToken cancellationToken)
|
||||
internal override void Read(BinaryReader reader)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
@@ -12,20 +10,20 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
}
|
||||
|
||||
internal override async ValueTask Read(Stream stream, CancellationToken cancellationToken)
|
||||
internal override void Read(BinaryReader reader)
|
||||
{
|
||||
Version = await stream.ReadUInt16(cancellationToken);
|
||||
Flags = (HeaderFlags)await stream.ReadUInt16(cancellationToken);
|
||||
CompressionMethod = (ZipCompressionMethod)await stream.ReadUInt16(cancellationToken);
|
||||
LastModifiedTime = await stream.ReadUInt16(cancellationToken);
|
||||
LastModifiedDate = await stream.ReadUInt16(cancellationToken);
|
||||
Crc = await stream.ReadUInt32(cancellationToken);
|
||||
CompressedSize = await stream.ReadUInt32(cancellationToken);
|
||||
UncompressedSize = await stream.ReadUInt32(cancellationToken);
|
||||
ushort nameLength = await stream.ReadUInt16(cancellationToken);
|
||||
ushort extraLength = await stream.ReadUInt16(cancellationToken);
|
||||
byte[] name = await stream.ReadBytes(nameLength, cancellationToken);
|
||||
byte[] extra = await stream.ReadBytes(extraLength, cancellationToken);
|
||||
Version = reader.ReadUInt16();
|
||||
Flags = (HeaderFlags)reader.ReadUInt16();
|
||||
CompressionMethod = (ZipCompressionMethod)reader.ReadUInt16();
|
||||
LastModifiedTime = reader.ReadUInt16();
|
||||
LastModifiedDate = reader.ReadUInt16();
|
||||
Crc = reader.ReadUInt32();
|
||||
CompressedSize = reader.ReadUInt32();
|
||||
UncompressedSize = reader.ReadUInt32();
|
||||
ushort nameLength = reader.ReadUInt16();
|
||||
ushort extraLength = reader.ReadUInt16();
|
||||
byte[] name = reader.ReadBytes(nameLength);
|
||||
byte[] extra = reader.ReadBytes(extraLength);
|
||||
|
||||
// According to .ZIP File Format Specification
|
||||
//
|
||||
@@ -55,6 +53,8 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
var zip64ExtraData = Extra.OfType<Zip64ExtendedInformationExtraField>().FirstOrDefault();
|
||||
if (zip64ExtraData != null)
|
||||
{
|
||||
zip64ExtraData.Process(UncompressedSize, CompressedSize, 0, 0);
|
||||
|
||||
if (CompressedSize == uint.MaxValue)
|
||||
{
|
||||
CompressedSize = zip64ExtraData.CompressedSize;
|
||||
|
||||
@@ -66,46 +66,74 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
public Zip64ExtendedInformationExtraField(ExtraDataType type, ushort length, byte[] dataBytes)
|
||||
: base(type, length, dataBytes)
|
||||
{
|
||||
Process();
|
||||
}
|
||||
|
||||
private void Process()
|
||||
// From the spec, values are only in the extradata if the standard
|
||||
// value is set to 0xFFFFFFFF (or 0xFFFF for the Disk Start Number).
|
||||
// Values, if present, must appear in the following order:
|
||||
// - Original Size
|
||||
// - Compressed Size
|
||||
// - Relative Header Offset
|
||||
// - Disk Start Number
|
||||
public void Process(long uncompressedFileSize, long compressedFileSize, long relativeHeaderOffset, ushort diskNumber)
|
||||
{
|
||||
if (DataBytes.Length >= 8)
|
||||
var bytesRequired = ((uncompressedFileSize == uint.MaxValue) ? 8 : 0)
|
||||
+ ((compressedFileSize == uint.MaxValue) ? 8 : 0)
|
||||
+ ((relativeHeaderOffset == uint.MaxValue) ? 8 : 0)
|
||||
+ ((diskNumber == ushort.MaxValue) ? 4 : 0);
|
||||
var currentIndex = 0;
|
||||
|
||||
if (bytesRequired > DataBytes.Length)
|
||||
{
|
||||
UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes);
|
||||
throw new ArchiveException("Zip64 extended information extra field is not large enough for the required information");
|
||||
}
|
||||
|
||||
if (DataBytes.Length >= 16)
|
||||
if (uncompressedFileSize == uint.MaxValue)
|
||||
{
|
||||
CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(8));
|
||||
UncompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex));
|
||||
currentIndex += 8;
|
||||
}
|
||||
|
||||
if (DataBytes.Length >= 24)
|
||||
if (compressedFileSize == uint.MaxValue)
|
||||
{
|
||||
RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(16));
|
||||
CompressedSize = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex));
|
||||
currentIndex += 8;
|
||||
}
|
||||
|
||||
if (DataBytes.Length >= 28)
|
||||
if (relativeHeaderOffset == uint.MaxValue)
|
||||
{
|
||||
VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(24));
|
||||
RelativeOffsetOfEntryHeader = BinaryPrimitives.ReadInt64LittleEndian(DataBytes.AsSpan(currentIndex));
|
||||
currentIndex += 8;
|
||||
}
|
||||
|
||||
switch (DataBytes.Length)
|
||||
if (diskNumber == ushort.MaxValue)
|
||||
{
|
||||
case 8:
|
||||
case 16:
|
||||
case 24:
|
||||
case 28:
|
||||
break;
|
||||
default:
|
||||
throw new ArchiveException($"Unexpected size of of Zip64 extended information extra field: {DataBytes.Length}");
|
||||
VolumeNumber = BinaryPrimitives.ReadUInt32LittleEndian(DataBytes.AsSpan(currentIndex));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uncompressed file size. Only valid after <see cref="Process(long, long, long, ushort)"/> has been called and if the
|
||||
/// original entry header had a corresponding 0xFFFFFFFF value.
|
||||
/// </summary>
|
||||
public long UncompressedSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Compressed file size. Only valid after <see cref="Process(long, long, long, ushort)"/> has been called and if the
|
||||
/// original entry header had a corresponding 0xFFFFFFFF value.
|
||||
/// </summary>
|
||||
public long CompressedSize { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Relative offset of the entry header. Only valid after <see cref="Process(long, long, long, ushort)"/> has been called and if the
|
||||
/// original entry header had a corresponding 0xFFFFFFFF value.
|
||||
/// </summary>
|
||||
public long RelativeOffsetOfEntryHeader { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Volume number. Only valid after <see cref="Process(long, long, long, ushort)"/> has been called and if the
|
||||
/// original entry header had a corresponding 0xFFFF value.
|
||||
/// </summary>
|
||||
public uint VolumeNumber { get; private set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
@@ -12,7 +10,7 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
}
|
||||
|
||||
internal override ValueTask Read(Stream stream, CancellationToken cancellationToken)
|
||||
internal override void Read(BinaryReader reader)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
@@ -11,18 +9,18 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
}
|
||||
|
||||
internal override async ValueTask Read(Stream stream, CancellationToken cancellationToken)
|
||||
internal override void Read(BinaryReader reader)
|
||||
{
|
||||
SizeOfDirectoryEndRecord = (long)await stream.ReadUInt64(cancellationToken);
|
||||
VersionMadeBy = await stream.ReadUInt16(cancellationToken);
|
||||
VersionNeededToExtract = await stream.ReadUInt16(cancellationToken);
|
||||
VolumeNumber = await stream.ReadUInt32(cancellationToken);
|
||||
FirstVolumeWithDirectory = await stream.ReadUInt32(cancellationToken);
|
||||
TotalNumberOfEntriesInDisk = (long)await stream.ReadUInt64(cancellationToken);
|
||||
TotalNumberOfEntries = (long)await stream.ReadUInt64(cancellationToken);
|
||||
DirectorySize = (long)await stream.ReadUInt64(cancellationToken);
|
||||
DirectoryStartOffsetRelativeToDisk = (long)await stream.ReadUInt64(cancellationToken);
|
||||
DataSector = await stream.ReadBytes((int)(SizeOfDirectoryEndRecord - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS), cancellationToken);
|
||||
SizeOfDirectoryEndRecord = (long)reader.ReadUInt64();
|
||||
VersionMadeBy = reader.ReadUInt16();
|
||||
VersionNeededToExtract = reader.ReadUInt16();
|
||||
VolumeNumber = reader.ReadUInt32();
|
||||
FirstVolumeWithDirectory = reader.ReadUInt32();
|
||||
TotalNumberOfEntriesInDisk = (long)reader.ReadUInt64();
|
||||
TotalNumberOfEntries = (long)reader.ReadUInt64();
|
||||
DirectorySize = (long)reader.ReadUInt64();
|
||||
DirectoryStartOffsetRelativeToDisk = (long)reader.ReadUInt64();
|
||||
DataSector = reader.ReadBytes((int)(SizeOfDirectoryEndRecord - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS));
|
||||
}
|
||||
|
||||
private const int SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS = 44;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
@@ -11,11 +9,11 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
}
|
||||
|
||||
internal override async ValueTask Read(Stream stream, CancellationToken cancellationToken)
|
||||
internal override void Read(BinaryReader reader)
|
||||
{
|
||||
FirstVolumeWithDirectory = await stream.ReadUInt32(cancellationToken);
|
||||
RelativeOffsetOfTheEndOfDirectoryRecord = (long)await stream.ReadUInt64(cancellationToken);
|
||||
TotalNumberOfVolumes = await stream.ReadUInt32(cancellationToken);
|
||||
FirstVolumeWithDirectory = reader.ReadUInt32();
|
||||
RelativeOffsetOfTheEndOfDirectoryRecord = (long)reader.ReadUInt64();
|
||||
TotalNumberOfVolumes = reader.ReadUInt32();
|
||||
}
|
||||
|
||||
public uint FirstVolumeWithDirectory { get; private set; }
|
||||
|
||||
@@ -105,6 +105,6 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
|
||||
internal ZipFilePart Part { get; set; }
|
||||
|
||||
internal bool IsZip64 => CompressedSize == uint.MaxValue;
|
||||
internal bool IsZip64 => CompressedSize >= uint.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers
|
||||
{
|
||||
@@ -14,7 +12,7 @@ namespace SharpCompress.Common.Zip.Headers
|
||||
|
||||
internal ZipHeaderType ZipHeaderType { get; }
|
||||
|
||||
internal abstract ValueTask Read(Stream stream, CancellationToken cancellationToken);
|
||||
internal abstract void Read(BinaryReader reader);
|
||||
|
||||
internal bool HasData { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -19,22 +17,22 @@ namespace SharpCompress.Common.Zip
|
||||
_directoryEntryHeader = header;
|
||||
}
|
||||
|
||||
internal override async ValueTask<Stream> GetCompressedStreamAsync(CancellationToken cancellationToken)
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (!_isLocalHeaderLoaded)
|
||||
{
|
||||
await LoadLocalHeader(cancellationToken);
|
||||
LoadLocalHeader();
|
||||
_isLocalHeaderLoaded = true;
|
||||
}
|
||||
return await base.GetCompressedStreamAsync(cancellationToken);
|
||||
return base.GetCompressedStream();
|
||||
}
|
||||
|
||||
internal string? Comment => ((DirectoryEntryHeader)Header).Comment;
|
||||
|
||||
private async ValueTask LoadLocalHeader(CancellationToken cancellationToken)
|
||||
private void LoadLocalHeader()
|
||||
{
|
||||
bool hasData = Header.HasData;
|
||||
Header = await _headerFactory.GetLocalHeader(BaseStream, (DirectoryEntryHeader)Header, cancellationToken);
|
||||
Header = _headerFactory.GetLocalHeader(BaseStream, ((DirectoryEntryHeader)Header));
|
||||
Header.HasData = hasData;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Zip
|
||||
@@ -24,13 +19,15 @@ namespace SharpCompress.Common.Zip
|
||||
{
|
||||
}
|
||||
|
||||
internal async IAsyncEnumerable<ZipHeader> ReadSeekableHeader(Stream stream, [EnumeratorCancellation]CancellationToken cancellationToken)
|
||||
internal IEnumerable<ZipHeader> ReadSeekableHeader(Stream stream)
|
||||
{
|
||||
await SeekBackToHeaderAsync(stream);
|
||||
var reader = new BinaryReader(stream);
|
||||
|
||||
SeekBackToHeader(stream, reader);
|
||||
|
||||
var eocd_location = stream.Position;
|
||||
var entry = new DirectoryEndHeader();
|
||||
await entry.Read(stream, cancellationToken);
|
||||
entry.Read(reader);
|
||||
|
||||
if (entry.IsZip64)
|
||||
{
|
||||
@@ -38,37 +35,37 @@ namespace SharpCompress.Common.Zip
|
||||
|
||||
// ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR should be before the EOCD
|
||||
stream.Seek(eocd_location - ZIP64_EOCD_LENGTH - 4, SeekOrigin.Begin);
|
||||
uint zip64_locator = await stream.ReadUInt32(cancellationToken);
|
||||
uint zip64_locator = reader.ReadUInt32();
|
||||
if( zip64_locator != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR )
|
||||
{
|
||||
throw new ArchiveException("Failed to locate the Zip64 Directory Locator");
|
||||
}
|
||||
|
||||
var zip64Locator = new Zip64DirectoryEndLocatorHeader();
|
||||
await zip64Locator.Read(stream, cancellationToken);
|
||||
zip64Locator.Read(reader);
|
||||
|
||||
stream.Seek(zip64Locator.RelativeOffsetOfTheEndOfDirectoryRecord, SeekOrigin.Begin);
|
||||
uint zip64Signature = await stream.ReadUInt32(cancellationToken);
|
||||
uint zip64Signature = reader.ReadUInt32();
|
||||
if (zip64Signature != ZIP64_END_OF_CENTRAL_DIRECTORY)
|
||||
{
|
||||
throw new ArchiveException("Failed to locate the Zip64 Header");
|
||||
}
|
||||
|
||||
var zip64Entry = new Zip64DirectoryEndHeader();
|
||||
await zip64Entry.Read(stream, cancellationToken);
|
||||
zip64Entry.Read(reader);
|
||||
stream.Seek(zip64Entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Seek(entry.DirectoryStartOffsetRelativeToDisk ?? 0, SeekOrigin.Begin);
|
||||
stream.Seek(entry.DirectoryStartOffsetRelativeToDisk, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
long position = stream.Position;
|
||||
while (true)
|
||||
{
|
||||
stream.Position = position;
|
||||
uint signature = await stream.ReadUInt32(cancellationToken);
|
||||
var nextHeader = await ReadHeader(signature, stream, cancellationToken, _zip64);
|
||||
uint signature = reader.ReadUInt32();
|
||||
var nextHeader = ReadHeader(signature, reader, _zip64);
|
||||
position = stream.Position;
|
||||
|
||||
if (nextHeader is null)
|
||||
@@ -89,7 +86,7 @@ namespace SharpCompress.Common.Zip
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsMatch (Span<byte> haystack, int position, byte[] needle)
|
||||
private static bool IsMatch( byte[] haystack, int position, byte[] needle)
|
||||
{
|
||||
for( int i = 0; i < needle.Length; i++ )
|
||||
{
|
||||
@@ -101,7 +98,7 @@ namespace SharpCompress.Common.Zip
|
||||
|
||||
return true;
|
||||
}
|
||||
private static async ValueTask SeekBackToHeaderAsync(Stream stream)
|
||||
private static void SeekBackToHeader(Stream stream, BinaryReader reader)
|
||||
{
|
||||
// Minimum EOCD length
|
||||
if (stream.Length < MINIMUM_EOCD_LENGTH)
|
||||
@@ -115,18 +112,16 @@ namespace SharpCompress.Common.Zip
|
||||
|
||||
stream.Seek(-len, SeekOrigin.End);
|
||||
|
||||
using var rented = MemoryPool<byte>.Shared.Rent(len);
|
||||
var buffer = rented.Memory.Slice(0, len);
|
||||
await stream.ReadAsync(buffer);
|
||||
byte[] seek = reader.ReadBytes(len);
|
||||
|
||||
// Search in reverse
|
||||
buffer.Span.Reverse();
|
||||
Array.Reverse(seek);
|
||||
|
||||
var max_search_area = len - MINIMUM_EOCD_LENGTH;
|
||||
|
||||
for( int pos_from_end = 0; pos_from_end < max_search_area; ++pos_from_end)
|
||||
{
|
||||
if( IsMatch( buffer.Span, pos_from_end, needle) )
|
||||
if( IsMatch(seek, pos_from_end, needle) )
|
||||
{
|
||||
stream.Seek(-pos_from_end, SeekOrigin.End);
|
||||
return;
|
||||
@@ -136,11 +131,12 @@ namespace SharpCompress.Common.Zip
|
||||
throw new ArchiveException("Failed to locate the Zip Header");
|
||||
}
|
||||
|
||||
internal async ValueTask<LocalEntryHeader> GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader, CancellationToken cancellationToken)
|
||||
internal LocalEntryHeader GetLocalHeader(Stream stream, DirectoryEntryHeader directoryEntryHeader)
|
||||
{
|
||||
stream.Seek(directoryEntryHeader.RelativeOffsetOfEntryHeader, SeekOrigin.Begin);
|
||||
uint signature = await stream.ReadUInt32(cancellationToken);
|
||||
var localEntryHeader = await ReadHeader(signature, stream, cancellationToken, _zip64) as LocalEntryHeader;
|
||||
BinaryReader reader = new BinaryReader(stream);
|
||||
uint signature = reader.ReadUInt32();
|
||||
var localEntryHeader = ReadHeader(signature, reader, _zip64) as LocalEntryHeader;
|
||||
if (localEntryHeader is null)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.IO;
|
||||
@@ -21,13 +19,13 @@ namespace SharpCompress.Common.Zip
|
||||
return Header.PackedStream;
|
||||
}
|
||||
|
||||
internal override async ValueTask<Stream> GetCompressedStreamAsync(CancellationToken cancellationToken)
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (!Header.HasData)
|
||||
{
|
||||
return Stream.Null;
|
||||
}
|
||||
_decompressionStream = await CreateDecompressionStream(GetCryptoStream(CreateBaseStream()), Header.CompressionMethod, cancellationToken);
|
||||
_decompressionStream = CreateDecompressionStream(GetCryptoStream(CreateBaseStream()), Header.CompressionMethod);
|
||||
if (LeaveStreamOpen)
|
||||
{
|
||||
return new NonDisposingStream(_decompressionStream);
|
||||
@@ -35,17 +33,17 @@ namespace SharpCompress.Common.Zip
|
||||
return _decompressionStream;
|
||||
}
|
||||
|
||||
internal async ValueTask FixStreamedFileLocation(RewindableStream rewindableStream, CancellationToken cancellationToken)
|
||||
internal BinaryReader FixStreamedFileLocation(ref RewindableStream rewindableStream)
|
||||
{
|
||||
if (Header.IsDirectory)
|
||||
{
|
||||
return;
|
||||
return new BinaryReader(rewindableStream);
|
||||
}
|
||||
if (Header.HasData && !Skipped)
|
||||
{
|
||||
_decompressionStream ??= await GetCompressedStreamAsync(cancellationToken);
|
||||
_decompressionStream ??= GetCompressedStream();
|
||||
|
||||
await _decompressionStream.SkipAsync(cancellationToken);
|
||||
_decompressionStream.Skip();
|
||||
|
||||
if (_decompressionStream is DeflateStream deflateStream)
|
||||
{
|
||||
@@ -53,7 +51,9 @@ namespace SharpCompress.Common.Zip
|
||||
}
|
||||
Skipped = true;
|
||||
}
|
||||
var reader = new BinaryReader(rewindableStream);
|
||||
_decompressionStream = null;
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.IO;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -13,36 +12,43 @@ namespace SharpCompress.Common.Zip
|
||||
{
|
||||
}
|
||||
|
||||
internal async IAsyncEnumerable<ZipHeader> ReadStreamHeader(RewindableStream rewindableStream, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
internal IEnumerable<ZipHeader> ReadStreamHeader(Stream stream)
|
||||
{
|
||||
RewindableStream rewindableStream;
|
||||
|
||||
if (stream is RewindableStream rs)
|
||||
{
|
||||
rewindableStream = rs;
|
||||
}
|
||||
else
|
||||
{
|
||||
rewindableStream = new RewindableStream(stream);
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
ZipHeader? header;
|
||||
BinaryReader reader = new BinaryReader(rewindableStream);
|
||||
if (_lastEntryHeader != null &&
|
||||
(FlagUtility.HasFlag(_lastEntryHeader.Flags, HeaderFlags.UsePostDataDescriptor) || _lastEntryHeader.IsZip64))
|
||||
{
|
||||
await ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation(rewindableStream, cancellationToken);
|
||||
reader = ((StreamingZipFilePart)_lastEntryHeader.Part).FixStreamedFileLocation(ref rewindableStream);
|
||||
long? pos = rewindableStream.CanSeek ? (long?)rewindableStream.Position : null;
|
||||
uint crc = await rewindableStream.ReadUInt32(cancellationToken);
|
||||
uint crc = reader.ReadUInt32();
|
||||
if (crc == POST_DATA_DESCRIPTOR)
|
||||
{
|
||||
crc = await rewindableStream.ReadUInt32(cancellationToken);
|
||||
crc = reader.ReadUInt32();
|
||||
}
|
||||
_lastEntryHeader.Crc = crc;
|
||||
_lastEntryHeader.CompressedSize = await rewindableStream.ReadUInt32(cancellationToken);
|
||||
_lastEntryHeader.UncompressedSize = await rewindableStream.ReadUInt32(cancellationToken);
|
||||
_lastEntryHeader.CompressedSize = reader.ReadUInt32();
|
||||
_lastEntryHeader.UncompressedSize = reader.ReadUInt32();
|
||||
if (pos.HasValue)
|
||||
{
|
||||
_lastEntryHeader.DataStartPosition = pos - _lastEntryHeader.CompressedSize;
|
||||
}
|
||||
}
|
||||
_lastEntryHeader = null;
|
||||
var headerBytes = await rewindableStream.ReadUInt32OrNull(cancellationToken);
|
||||
if (headerBytes is null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
header = await ReadHeader(headerBytes.Value, rewindableStream, cancellationToken);
|
||||
uint headerBytes = reader.ReadUInt32();
|
||||
header = ReadHeader(headerBytes, reader);
|
||||
if (header is null)
|
||||
{
|
||||
yield break;
|
||||
@@ -65,10 +71,10 @@ namespace SharpCompress.Common.Zip
|
||||
{
|
||||
rewindableStream.StartRecording();
|
||||
}
|
||||
uint nextHeaderBytes = await rewindableStream.ReadUInt32(cancellationToken);
|
||||
uint nextHeaderBytes = reader.ReadUInt32();
|
||||
|
||||
// Check if next data is PostDataDescriptor, streamed file with 0 length
|
||||
header.HasData = nextHeaderBytes != POST_DATA_DESCRIPTOR;
|
||||
header.HasData = !IsHeader(nextHeaderBytes);
|
||||
rewindableStream.Rewind(!isRecording);
|
||||
}
|
||||
else // We are not streaming and compressed size is 0, we have no data
|
||||
|
||||
@@ -2,15 +2,13 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Compressors.BZip2;
|
||||
using SharpCompress.Compressors.Deflate;
|
||||
using SharpCompress.Compressors.Deflate64;
|
||||
using SharpCompress.Compressors.LZMA;
|
||||
//using SharpCompress.Compressors.PPMd;
|
||||
using SharpCompress.Compressors.PPMd;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Common.Zip
|
||||
@@ -30,13 +28,13 @@ namespace SharpCompress.Common.Zip
|
||||
|
||||
internal override string FilePartName => Header.Name;
|
||||
|
||||
internal override async ValueTask<Stream> GetCompressedStreamAsync(CancellationToken cancellationToken)
|
||||
internal override Stream GetCompressedStream()
|
||||
{
|
||||
if (!Header.HasData)
|
||||
{
|
||||
return Stream.Null;
|
||||
}
|
||||
Stream decompressionStream = await CreateDecompressionStream(GetCryptoStream(CreateBaseStream()), Header.CompressionMethod, cancellationToken);
|
||||
Stream decompressionStream = CreateDecompressionStream(GetCryptoStream(CreateBaseStream()), Header.CompressionMethod);
|
||||
if (LeaveStreamOpen)
|
||||
{
|
||||
return new NonDisposingStream(decompressionStream);
|
||||
@@ -57,7 +55,7 @@ namespace SharpCompress.Common.Zip
|
||||
|
||||
protected bool LeaveStreamOpen => FlagUtility.HasFlag(Header.Flags, HeaderFlags.UsePostDataDescriptor) || Header.IsZip64;
|
||||
|
||||
protected async ValueTask<Stream> CreateDecompressionStream(Stream stream, ZipCompressionMethod method, CancellationToken cancellationToken)
|
||||
protected Stream CreateDecompressionStream(Stream stream, ZipCompressionMethod method)
|
||||
{
|
||||
switch (method)
|
||||
{
|
||||
@@ -75,29 +73,30 @@ namespace SharpCompress.Common.Zip
|
||||
}
|
||||
case ZipCompressionMethod.BZip2:
|
||||
{
|
||||
return await BZip2Stream.CreateAsync(stream, CompressionMode.Decompress, false, cancellationToken);
|
||||
}
|
||||
return new BZip2Stream(stream, CompressionMode.Decompress, false);
|
||||
}
|
||||
case ZipCompressionMethod.LZMA:
|
||||
{
|
||||
if (FlagUtility.HasFlag(Header.Flags, HeaderFlags.Encrypted))
|
||||
{
|
||||
throw new NotSupportedException("LZMA with pkware encryption.");
|
||||
}
|
||||
await stream.ReadUInt16(cancellationToken); //LZMA version
|
||||
var props = new byte[await stream.ReadUInt16(cancellationToken)];
|
||||
await stream.ReadAsync(props, 0, props.Length, cancellationToken);
|
||||
return await LzmaStream.CreateAsync(props, stream,
|
||||
var reader = new BinaryReader(stream);
|
||||
reader.ReadUInt16(); //LZMA version
|
||||
var props = new byte[reader.ReadUInt16()];
|
||||
reader.Read(props, 0, props.Length);
|
||||
return new LzmaStream(props, stream,
|
||||
Header.CompressedSize > 0 ? Header.CompressedSize - 4 - props.Length : -1,
|
||||
FlagUtility.HasFlag(Header.Flags, HeaderFlags.Bit1)
|
||||
? -1
|
||||
: (long)Header.UncompressedSize,
|
||||
cancellationToken: cancellationToken);
|
||||
: (long)Header.UncompressedSize);
|
||||
}
|
||||
/* case ZipCompressionMethod.PPMd:
|
||||
{
|
||||
var props = await stream.ReadBytes(2, cancellationToken);
|
||||
case ZipCompressionMethod.PPMd:
|
||||
{
|
||||
Span<byte> props = stackalloc byte[2];
|
||||
stream.ReadFully(props);
|
||||
return new PpmdStream(new PpmdProperties(props), stream, false);
|
||||
} */
|
||||
}
|
||||
case ZipCompressionMethod.WinzipAes:
|
||||
{
|
||||
ExtraData? data = Header.Extra.SingleOrDefault(x => x.Type == ExtraDataType.WinZipAes);
|
||||
@@ -121,7 +120,7 @@ namespace SharpCompress.Common.Zip
|
||||
{
|
||||
throw new InvalidFormatException("Unexpected vendor ID for WinZip AES metadata");
|
||||
}
|
||||
return await CreateDecompressionStream(stream, (ZipCompressionMethod)BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5)), cancellationToken);
|
||||
return CreateDecompressionStream(stream, (ZipCompressionMethod)BinaryPrimitives.ReadUInt16LittleEndian(data.DataBytes.AsSpan(5)));
|
||||
}
|
||||
default:
|
||||
{
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -32,15 +30,15 @@ namespace SharpCompress.Common.Zip
|
||||
this._archiveEncoding = archiveEncoding;
|
||||
}
|
||||
|
||||
protected async ValueTask<ZipHeader?> ReadHeader(uint headerBytes, Stream stream, CancellationToken cancellationToken, bool zip64 = false)
|
||||
protected ZipHeader? ReadHeader(uint headerBytes, BinaryReader reader, bool zip64 = false)
|
||||
{
|
||||
switch (headerBytes)
|
||||
{
|
||||
case ENTRY_HEADER_BYTES:
|
||||
{
|
||||
var entryHeader = new LocalEntryHeader(_archiveEncoding);
|
||||
await entryHeader.Read(stream, cancellationToken);
|
||||
await LoadHeader(entryHeader, stream, cancellationToken);
|
||||
entryHeader.Read(reader);
|
||||
LoadHeader(entryHeader, reader.BaseStream);
|
||||
|
||||
_lastEntryHeader = entryHeader;
|
||||
return entryHeader;
|
||||
@@ -48,20 +46,20 @@ namespace SharpCompress.Common.Zip
|
||||
case DIRECTORY_START_HEADER_BYTES:
|
||||
{
|
||||
var entry = new DirectoryEntryHeader(_archiveEncoding);
|
||||
await entry.Read(stream, cancellationToken);
|
||||
entry.Read(reader);
|
||||
return entry;
|
||||
}
|
||||
case POST_DATA_DESCRIPTOR:
|
||||
{
|
||||
if (FlagUtility.HasFlag(_lastEntryHeader!.Flags, HeaderFlags.UsePostDataDescriptor))
|
||||
{
|
||||
_lastEntryHeader.Crc = await stream.ReadUInt32(cancellationToken);
|
||||
_lastEntryHeader.CompressedSize = zip64 ? (long)await stream.ReadUInt64(cancellationToken) : await stream.ReadUInt32(cancellationToken);
|
||||
_lastEntryHeader.UncompressedSize = zip64 ? (long)await stream.ReadUInt64(cancellationToken) : await stream.ReadUInt32(cancellationToken);
|
||||
_lastEntryHeader.Crc = reader.ReadUInt32();
|
||||
_lastEntryHeader.CompressedSize = zip64 ? (long)reader.ReadUInt64() : reader.ReadUInt32();
|
||||
_lastEntryHeader.UncompressedSize = zip64 ? (long)reader.ReadUInt64() : reader.ReadUInt32();
|
||||
}
|
||||
else
|
||||
{
|
||||
await stream.ReadBytes(zip64 ? 20 : 12, cancellationToken);
|
||||
reader.ReadBytes(zip64 ? 20 : 12);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -70,7 +68,7 @@ namespace SharpCompress.Common.Zip
|
||||
case DIRECTORY_END_HEADER_BYTES:
|
||||
{
|
||||
var entry = new DirectoryEndHeader();
|
||||
await entry.Read(stream, cancellationToken);
|
||||
entry.Read(reader);
|
||||
return entry;
|
||||
}
|
||||
case SPLIT_ARCHIVE_HEADER_BYTES:
|
||||
@@ -80,13 +78,13 @@ namespace SharpCompress.Common.Zip
|
||||
case ZIP64_END_OF_CENTRAL_DIRECTORY:
|
||||
{
|
||||
var entry = new Zip64DirectoryEndHeader();
|
||||
await entry.Read(stream, cancellationToken);
|
||||
entry.Read(reader);
|
||||
return entry;
|
||||
}
|
||||
case ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR:
|
||||
{
|
||||
var entry = new Zip64DirectoryEndLocatorHeader();
|
||||
await entry.Read(stream, cancellationToken);
|
||||
entry.Read(reader);
|
||||
return entry;
|
||||
}
|
||||
default:
|
||||
@@ -112,7 +110,7 @@ namespace SharpCompress.Common.Zip
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask LoadHeader(ZipFileEntry entryHeader, Stream stream, CancellationToken cancellationToken)
|
||||
private void LoadHeader(ZipFileEntry entryHeader, Stream stream)
|
||||
{
|
||||
if (FlagUtility.HasFlag(entryHeader.Flags, HeaderFlags.Encrypted))
|
||||
{
|
||||
@@ -136,8 +134,10 @@ namespace SharpCompress.Common.Zip
|
||||
{
|
||||
var keySize = (WinzipAesKeySize)data.DataBytes[4];
|
||||
|
||||
var salt = await stream.ReadBytes(WinzipAesEncryptionData.KeyLengthInBytes(keySize) / 2, cancellationToken);
|
||||
var passwordVerifyValue = await stream.ReadBytes(2, cancellationToken);
|
||||
var salt = new byte[WinzipAesEncryptionData.KeyLengthInBytes(keySize) / 2];
|
||||
var passwordVerifyValue = new byte[2];
|
||||
stream.Read(salt, 0, salt.Length);
|
||||
stream.Read(passwordVerifyValue, 0, 2);
|
||||
entryHeader.WinzipAesEncryptionData =
|
||||
new WinzipAesEncryptionData(keySize, salt, passwordVerifyValue, _password);
|
||||
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.BZip2
|
||||
{
|
||||
public sealed class BZip2Stream : AsyncStream
|
||||
public sealed class BZip2Stream : Stream
|
||||
{
|
||||
private readonly Stream stream;
|
||||
private bool isDisposed;
|
||||
@@ -37,14 +33,17 @@ namespace SharpCompress.Compressors.BZip2
|
||||
(stream as CBZip2OutputStream)?.Finish();
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
isDisposed = true;
|
||||
await stream.DisposeAsync();
|
||||
if (disposing)
|
||||
{
|
||||
stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public CompressionMode Mode { get; }
|
||||
@@ -55,18 +54,23 @@ namespace SharpCompress.Compressors.BZip2
|
||||
|
||||
public override bool CanWrite => stream.CanWrite;
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
public override void Flush()
|
||||
{
|
||||
return stream.FlushAsync(cancellationToken);
|
||||
stream.Flush();
|
||||
}
|
||||
|
||||
public override long Length => stream.Length;
|
||||
|
||||
public override long Position { get => stream.Position; set => stream.Position = value; }
|
||||
|
||||
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return stream.ReadAsync(buffer, cancellationToken);
|
||||
return stream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
return stream.ReadByte();
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
@@ -79,14 +83,28 @@ namespace SharpCompress.Compressors.BZip2
|
||||
stream.SetLength(value);
|
||||
}
|
||||
|
||||
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
#if !NET461 && !NETSTANDARD2_0
|
||||
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
return stream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
return stream.Read(buffer);
|
||||
}
|
||||
|
||||
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
return stream.WriteAsync(buffer, cancellationToken);
|
||||
stream.Write(buffer);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
stream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
stream.WriteByte(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -94,12 +112,11 @@ namespace SharpCompress.Compressors.BZip2
|
||||
/// </summary>
|
||||
/// <param name="stream"></param>
|
||||
/// <returns></returns>
|
||||
public static async ValueTask<bool> IsBZip2Async(Stream stream, CancellationToken cancellationToken)
|
||||
public static bool IsBZip2(Stream stream)
|
||||
{
|
||||
using var rented = MemoryPool<byte>.Shared.Rent(2);
|
||||
var chars = rented.Memory.Slice(0, 2);
|
||||
await stream.ReadAsync(chars, cancellationToken);
|
||||
if (chars.Length < 2 || chars.Span[0] != 'B' || chars.Span[1] != 'Z')
|
||||
BinaryReader br = new BinaryReader(stream);
|
||||
byte[] chars = br.ReadBytes(2);
|
||||
if (chars.Length < 2 || chars[0] != 'B' || chars[1] != 'Z')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -27,13 +27,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Deflate
|
||||
{
|
||||
public class DeflateStream : AsyncStream
|
||||
public class DeflateStream : Stream
|
||||
{
|
||||
private readonly ZlibBaseStream _baseStream;
|
||||
private bool _disposed;
|
||||
@@ -219,25 +216,35 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <remarks>
|
||||
/// This may or may not result in a <c>Close()</c> call on the captive stream.
|
||||
/// </remarks>
|
||||
public override async ValueTask DisposeAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
try
|
||||
{
|
||||
await _baseStream.DisposeAsync();
|
||||
_disposed = true;
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_baseStream?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the stream.
|
||||
/// </summary>
|
||||
public override async Task FlushAsync(CancellationToken cancellationToken)
|
||||
public override void Flush()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("DeflateStream");
|
||||
}
|
||||
await _baseStream.FlushAsync(cancellationToken);
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -266,14 +273,24 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <param name="offset">the offset within that data array to put the first byte read.</param>
|
||||
/// <param name="count">the number of bytes to read.</param>
|
||||
/// <returns>the number of bytes actually read</returns>
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("DeflateStream");
|
||||
}
|
||||
return await _baseStream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
return _baseStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("DeflateStream");
|
||||
}
|
||||
return _baseStream.ReadByte();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calling this method always throws a <see cref="NotImplementedException"/>.
|
||||
/// </summary>
|
||||
@@ -323,13 +340,22 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <param name="buffer">The buffer holding data to write to the stream.</param>
|
||||
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
|
||||
/// <param name="count">the number of bytes to write.</param>
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("DeflateStream");
|
||||
}
|
||||
await _baseStream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("DeflateStream");
|
||||
}
|
||||
_baseStream.WriteByte(value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -27,25 +27,21 @@
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Deflate
|
||||
{
|
||||
public class GZipStream : AsyncStream
|
||||
public class GZipStream : Stream
|
||||
{
|
||||
private static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
internal static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
private string? _comment;
|
||||
private string? _fileName;
|
||||
private DateTime? _lastModified;
|
||||
|
||||
private readonly ZlibBaseStream _baseStream;
|
||||
internal ZlibBaseStream BaseStream;
|
||||
private bool _disposed;
|
||||
private bool _firstReadDone;
|
||||
private int _headerByteCount;
|
||||
@@ -64,7 +60,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
|
||||
public GZipStream(Stream stream, CompressionMode mode, CompressionLevel level, Encoding encoding)
|
||||
{
|
||||
_baseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding);
|
||||
BaseStream = new ZlibBaseStream(stream, mode, level, ZlibStreamFlavor.GZIP, encoding);
|
||||
_encoding = encoding;
|
||||
}
|
||||
|
||||
@@ -72,27 +68,27 @@ namespace SharpCompress.Compressors.Deflate
|
||||
|
||||
public virtual FlushType FlushMode
|
||||
{
|
||||
get => (_baseStream._flushMode);
|
||||
get => (BaseStream._flushMode);
|
||||
set
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
_baseStream._flushMode = value;
|
||||
BaseStream._flushMode = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int BufferSize
|
||||
{
|
||||
get => _baseStream._bufferSize;
|
||||
get => BaseStream._bufferSize;
|
||||
set
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
if (_baseStream._workingBuffer != null)
|
||||
if (BaseStream._workingBuffer != null)
|
||||
{
|
||||
throw new ZlibException("The working buffer is already set.");
|
||||
}
|
||||
@@ -102,13 +98,13 @@ namespace SharpCompress.Compressors.Deflate
|
||||
String.Format("Don't be silly. {0} bytes?? Use a bigger buffer, at least {1}.", value,
|
||||
ZlibConstants.WorkingBufferSizeMin));
|
||||
}
|
||||
_baseStream._bufferSize = value;
|
||||
BaseStream._bufferSize = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual long TotalIn => _baseStream._z.TotalBytesIn;
|
||||
internal virtual long TotalIn => BaseStream._z.TotalBytesIn;
|
||||
|
||||
internal virtual long TotalOut => _baseStream._z.TotalBytesOut;
|
||||
internal virtual long TotalOut => BaseStream._z.TotalBytesOut;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -128,7 +124,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
return _baseStream._stream.CanRead;
|
||||
return BaseStream._stream.CanRead;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +150,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
return _baseStream._stream.CanWrite;
|
||||
return BaseStream._stream.CanWrite;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,13 +174,13 @@ namespace SharpCompress.Compressors.Deflate
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Writer)
|
||||
if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Writer)
|
||||
{
|
||||
return _baseStream._z.TotalBytesOut + _headerByteCount;
|
||||
return BaseStream._z.TotalBytesOut + _headerByteCount;
|
||||
}
|
||||
if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Reader)
|
||||
if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Reader)
|
||||
{
|
||||
return _baseStream._z.TotalBytesIn + _baseStream._gzipHeaderByteCount;
|
||||
return BaseStream._z.TotalBytesIn + BaseStream._gzipHeaderByteCount;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -198,29 +194,36 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <remarks>
|
||||
/// This may or may not result in a <c>Close()</c> call on the captive stream.
|
||||
/// </remarks>
|
||||
public override async ValueTask DisposeAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (_baseStream is not null)
|
||||
if (disposing && (BaseStream != null))
|
||||
{
|
||||
await _baseStream.DisposeAsync();
|
||||
Crc32 = _baseStream.Crc32;
|
||||
BaseStream.Dispose();
|
||||
Crc32 = BaseStream.Crc32;
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the stream.
|
||||
/// </summary>
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
public override void Flush()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
return _baseStream.FlushAsync(cancellationToken);
|
||||
BaseStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -254,13 +257,13 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <param name="offset">the offset within that data array to put the first byte read.</param>
|
||||
/// <param name="count">the number of bytes to read.</param>
|
||||
/// <returns>the number of bytes actually read</returns>
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
int n = await _baseStream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
int n = BaseStream.Read(buffer, offset, count);
|
||||
|
||||
// Console.WriteLine("GZipStream::Read(buffer, off({0}), c({1}) = {2}", offset, count, n);
|
||||
// Console.WriteLine( Util.FormatByteArray(buffer, offset, n) );
|
||||
@@ -268,9 +271,9 @@ namespace SharpCompress.Compressors.Deflate
|
||||
if (!_firstReadDone)
|
||||
{
|
||||
_firstReadDone = true;
|
||||
FileName = _baseStream._GzipFileName;
|
||||
Comment = _baseStream._GzipComment;
|
||||
LastModified = _baseStream._GzipMtime;
|
||||
FileName = BaseStream._GzipFileName;
|
||||
Comment = BaseStream._GzipComment;
|
||||
LastModified = BaseStream._GzipMtime;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -317,19 +320,19 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <param name="buffer">The buffer holding data to write to the stream.</param>
|
||||
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
|
||||
/// <param name="count">the number of bytes to write.</param>
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("GZipStream");
|
||||
}
|
||||
if (_baseStream._streamMode == ZlibBaseStream.StreamMode.Undefined)
|
||||
if (BaseStream._streamMode == ZlibBaseStream.StreamMode.Undefined)
|
||||
{
|
||||
//Console.WriteLine("GZipStream: First write");
|
||||
if (_baseStream._wantCompress)
|
||||
if (BaseStream._wantCompress)
|
||||
{
|
||||
// first write in compression, therefore, emit the GZIP header
|
||||
_headerByteCount = await EmitHeaderAsync();
|
||||
_headerByteCount = EmitHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -337,7 +340,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
}
|
||||
}
|
||||
|
||||
await _baseStream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
BaseStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
#endregion Stream methods
|
||||
@@ -402,7 +405,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
|
||||
public int Crc32 { get; private set; }
|
||||
|
||||
private async ValueTask<int> EmitHeaderAsync()
|
||||
private int EmitHeader()
|
||||
{
|
||||
byte[]? commentBytes = (Comment is null) ? null
|
||||
: _encoding.GetBytes(Comment);
|
||||
@@ -471,7 +474,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
header[i++] = 0; // terminate
|
||||
}
|
||||
|
||||
await _baseStream._stream.WriteAsync(header, 0, header.Length);
|
||||
BaseStream._stream.Write(header, 0, header.Length);
|
||||
|
||||
return header.Length; // bytes written
|
||||
}
|
||||
|
||||
@@ -27,15 +27,11 @@
|
||||
// ------------------------------------------------------------------
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SharpCompress.Common.Tar.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Deflate
|
||||
{
|
||||
@@ -46,18 +42,18 @@ namespace SharpCompress.Compressors.Deflate
|
||||
GZIP = 1952
|
||||
}
|
||||
|
||||
internal class ZlibBaseStream : AsyncStream
|
||||
internal class ZlibBaseStream : Stream
|
||||
{
|
||||
protected internal ZlibCodec _z; // deferred init... new ZlibCodec();
|
||||
|
||||
protected internal StreamMode _streamMode = StreamMode.Undefined;
|
||||
protected internal FlushType _flushMode;
|
||||
private readonly ZlibStreamFlavor _flavor;
|
||||
private readonly CompressionMode _compressionMode;
|
||||
private readonly CompressionLevel _level;
|
||||
protected internal ZlibStreamFlavor _flavor;
|
||||
protected internal CompressionMode _compressionMode;
|
||||
protected internal CompressionLevel _level;
|
||||
protected internal byte[] _workingBuffer;
|
||||
protected internal int _bufferSize = ZlibConstants.WorkingBufferSizeDefault;
|
||||
private readonly byte[] _buf1 = new byte[1];
|
||||
protected internal byte[] _buf1 = new byte[1];
|
||||
|
||||
protected internal Stream _stream;
|
||||
protected internal CompressionStrategy Strategy = CompressionStrategy.Default;
|
||||
@@ -120,13 +116,19 @@ namespace SharpCompress.Compressors.Deflate
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] workingBuffer => _workingBuffer ??= new byte[_bufferSize];
|
||||
private byte[] workingBuffer
|
||||
{
|
||||
get => _workingBuffer ??= new byte[_bufferSize];
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
// workitem 7159
|
||||
// calculate the CRC on the unccompressed data (before writing)
|
||||
crc?.SlurpBlock(buffer, offset, count);
|
||||
if (crc != null)
|
||||
{
|
||||
crc.SlurpBlock(buffer, offset, count);
|
||||
}
|
||||
|
||||
if (_streamMode == StreamMode.Undefined)
|
||||
{
|
||||
@@ -146,7 +148,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
z.InputBuffer = buffer;
|
||||
_z.NextIn = offset;
|
||||
_z.AvailableBytesIn = count;
|
||||
var done = false;
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
_z.OutputBuffer = workingBuffer;
|
||||
@@ -161,7 +163,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
}
|
||||
|
||||
//if (_workingBuffer.Length - _z.AvailableBytesOut > 0)
|
||||
await _stream.WriteAsync(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut, cancellationToken);
|
||||
_stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
|
||||
|
||||
done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0;
|
||||
|
||||
@@ -174,7 +176,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
while (!done);
|
||||
}
|
||||
|
||||
private async Task FinishAsync()
|
||||
private void finish()
|
||||
{
|
||||
if (_z is null)
|
||||
{
|
||||
@@ -183,7 +185,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
|
||||
if (_streamMode == StreamMode.Writer)
|
||||
{
|
||||
var done = false;
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
_z.OutputBuffer = workingBuffer;
|
||||
@@ -198,14 +200,14 @@ namespace SharpCompress.Compressors.Deflate
|
||||
string verb = (_wantCompress ? "de" : "in") + "flating";
|
||||
if (_z.Message is null)
|
||||
{
|
||||
throw new ZlibException($"{verb}: (rc = {rc})");
|
||||
throw new ZlibException(String.Format("{0}: (rc = {1})", verb, rc));
|
||||
}
|
||||
throw new ZlibException(verb + ": " + _z.Message);
|
||||
}
|
||||
|
||||
if (_workingBuffer.Length - _z.AvailableBytesOut > 0)
|
||||
{
|
||||
await _stream.WriteAsync(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
|
||||
_stream.Write(_workingBuffer, 0, _workingBuffer.Length - _z.AvailableBytesOut);
|
||||
}
|
||||
|
||||
done = _z.AvailableBytesIn == 0 && _z.AvailableBytesOut != 0;
|
||||
@@ -218,7 +220,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
}
|
||||
while (!done);
|
||||
|
||||
await FlushAsync();
|
||||
Flush();
|
||||
|
||||
// workitem 7159
|
||||
if (_flavor == ZlibStreamFlavor.GZIP)
|
||||
@@ -226,13 +228,12 @@ namespace SharpCompress.Compressors.Deflate
|
||||
if (_wantCompress)
|
||||
{
|
||||
// Emit the GZIP trailer: CRC32 and size mod 2^32
|
||||
using var rented = MemoryPool<byte>.Shared.Rent(4);
|
||||
var intBuf = rented.Memory.Slice(0, 4);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(intBuf.Span, crc.Crc32Result);
|
||||
await _stream.WriteAsync(intBuf, CancellationToken.None);
|
||||
Span<byte> intBuf = stackalloc byte[4];
|
||||
BinaryPrimitives.WriteInt32LittleEndian(intBuf, crc.Crc32Result);
|
||||
_stream.Write(intBuf);
|
||||
int c2 = (int)(crc.TotalBytesRead & 0x00000000FFFFFFFF);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(intBuf.Span, c2);
|
||||
await _stream.WriteAsync(intBuf, CancellationToken.None);
|
||||
BinaryPrimitives.WriteInt32LittleEndian(intBuf, c2);
|
||||
_stream.Write(intBuf);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -255,41 +256,44 @@ namespace SharpCompress.Compressors.Deflate
|
||||
}
|
||||
|
||||
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
|
||||
using var rented = MemoryPool<byte>.Shared.Rent(8);
|
||||
var trailer = rented.Memory.Slice(0, 8);
|
||||
Span<byte> trailer = stackalloc byte[8];
|
||||
|
||||
// workitem 8679
|
||||
if (_z.AvailableBytesIn != 8)
|
||||
{
|
||||
// Make sure we have read to the end of the stream
|
||||
_z.InputBuffer.AsSpan(_z.NextIn, _z.AvailableBytesIn).CopyTo(trailer.Span);
|
||||
_z.InputBuffer.AsSpan(_z.NextIn, _z.AvailableBytesIn).CopyTo(trailer);
|
||||
int bytesNeeded = 8 - _z.AvailableBytesIn;
|
||||
int bytesRead = await _stream.ReadAsync(trailer.Slice(_z.AvailableBytesIn, bytesNeeded));
|
||||
int bytesRead = _stream.Read(trailer.Slice(_z.AvailableBytesIn, bytesNeeded));
|
||||
if (bytesNeeded != bytesRead)
|
||||
{
|
||||
throw new ZlibException($"Protocol error. AvailableBytesIn={_z.AvailableBytesIn + bytesRead}, expected 8");
|
||||
throw new ZlibException(String.Format(
|
||||
"Protocol error. AvailableBytesIn={0}, expected 8",
|
||||
_z.AvailableBytesIn + bytesRead));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_z.InputBuffer.AsSpan(_z.NextIn, trailer.Length).CopyTo(trailer.Span);
|
||||
_z.InputBuffer.AsSpan(_z.NextIn, trailer.Length).CopyTo(trailer);
|
||||
}
|
||||
|
||||
Int32 crc32_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer.Span);
|
||||
Int32 crc32_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer);
|
||||
Int32 crc32_actual = crc.Crc32Result;
|
||||
Int32 isize_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer.Span.Slice(4));
|
||||
Int32 isize_expected = BinaryPrimitives.ReadInt32LittleEndian(trailer.Slice(4));
|
||||
Int32 isize_actual = (Int32)(_z.TotalBytesOut & 0x00000000FFFFFFFF);
|
||||
|
||||
if (crc32_actual != crc32_expected)
|
||||
{
|
||||
throw new ZlibException(
|
||||
$"Bad CRC32 in GZIP stream. (actual({crc32_actual:X8})!=expected({crc32_expected:X8}))");
|
||||
String.Format("Bad CRC32 in GZIP stream. (actual({0:X8})!=expected({1:X8}))",
|
||||
crc32_actual, crc32_expected));
|
||||
}
|
||||
|
||||
if (isize_actual != isize_expected)
|
||||
{
|
||||
throw new ZlibException(
|
||||
$"Bad size in GZIP stream. (actual({isize_actual})!=expected({isize_expected}))");
|
||||
String.Format("Bad size in GZIP stream. (actual({0})!=expected({1}))", isize_actual,
|
||||
isize_expected));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -300,7 +304,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
}
|
||||
}
|
||||
|
||||
private void End()
|
||||
private void end()
|
||||
{
|
||||
if (z is null)
|
||||
{
|
||||
@@ -317,32 +321,36 @@ namespace SharpCompress.Compressors.Deflate
|
||||
_z = null;
|
||||
}
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
if (isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
isDisposed = true;
|
||||
base.Dispose(disposing);
|
||||
if (disposing)
|
||||
{
|
||||
if (_stream is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
await FinishAsync();
|
||||
finish();
|
||||
}
|
||||
finally
|
||||
{
|
||||
End();
|
||||
_stream?.DisposeAsync();
|
||||
end();
|
||||
_stream?.Dispose();
|
||||
_stream = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
public override void Flush()
|
||||
{
|
||||
return _stream.FlushAsync(cancellationToken);
|
||||
_stream.Flush();
|
||||
}
|
||||
|
||||
public override Int64 Seek(Int64 offset, SeekOrigin origin)
|
||||
@@ -357,7 +365,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
_stream.SetLength(value);
|
||||
}
|
||||
|
||||
/*
|
||||
#if NOT
|
||||
public int Read()
|
||||
{
|
||||
if (Read(_buf1, 0, 1) == 0)
|
||||
@@ -367,19 +375,19 @@ namespace SharpCompress.Compressors.Deflate
|
||||
crc.SlurpBlock(_buf1,0,1);
|
||||
return (_buf1[0] & 0xFF);
|
||||
}
|
||||
*/
|
||||
#endif
|
||||
|
||||
private bool _nomoreinput;
|
||||
private bool _isDisposed;
|
||||
private bool nomoreinput;
|
||||
private bool isDisposed;
|
||||
|
||||
private async Task<string> ReadZeroTerminatedStringAsync()
|
||||
private string ReadZeroTerminatedString()
|
||||
{
|
||||
var list = new List<byte>();
|
||||
var done = false;
|
||||
bool done = false;
|
||||
do
|
||||
{
|
||||
// workitem 7740
|
||||
int n = await _stream.ReadAsync(_buf1, 0, 1);
|
||||
int n = _stream.Read(_buf1, 0, 1);
|
||||
if (n != 1)
|
||||
{
|
||||
throw new ZlibException("Unexpected EOF reading GZIP header.");
|
||||
@@ -398,14 +406,13 @@ namespace SharpCompress.Compressors.Deflate
|
||||
return _encoding.GetString(buffer, 0, buffer.Length);
|
||||
}
|
||||
|
||||
private async Task<int> ReadAndValidateGzipHeaderAsync(CancellationToken cancellationToken)
|
||||
private int _ReadAndValidateGzipHeader()
|
||||
{
|
||||
var totalBytesRead = 0;
|
||||
int totalBytesRead = 0;
|
||||
|
||||
// read the header on the first read
|
||||
using var rented = MemoryPool<byte>.Shared.Rent(10);
|
||||
var header = rented.Memory.Slice(0, 10);
|
||||
int n = await _stream.ReadAsync(header, cancellationToken);
|
||||
Span<byte> header = stackalloc byte[10];
|
||||
int n = _stream.Read(header);
|
||||
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (n == 0)
|
||||
@@ -418,46 +425,46 @@ namespace SharpCompress.Compressors.Deflate
|
||||
throw new ZlibException("Not a valid GZIP stream.");
|
||||
}
|
||||
|
||||
if (header.Span[0] != 0x1F || header.Span[1] != 0x8B || header.Span[2] != 8)
|
||||
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
|
||||
{
|
||||
throw new ZlibException("Bad GZIP header.");
|
||||
}
|
||||
|
||||
int timet = BinaryPrimitives.ReadInt32LittleEndian(header.Span.Slice(4));
|
||||
int timet = BinaryPrimitives.ReadInt32LittleEndian(header.Slice(4));
|
||||
_GzipMtime = TarHeader.EPOCH.AddSeconds(timet);
|
||||
totalBytesRead += n;
|
||||
if ((header.Span[3] & 0x04) == 0x04)
|
||||
if ((header[3] & 0x04) == 0x04)
|
||||
{
|
||||
// read and discard extra field
|
||||
n = await _stream.ReadAsync(header.Slice(0, 2), cancellationToken); // 2-byte length field
|
||||
n = _stream.Read(header.Slice(0, 2)); // 2-byte length field
|
||||
totalBytesRead += n;
|
||||
|
||||
short extraLength = (short)(header.Span[0] + header.Span[1] * 256);
|
||||
short extraLength = (short)(header[0] + header[1] * 256);
|
||||
byte[] extra = new byte[extraLength];
|
||||
n = await _stream.ReadAsync(extra, 0, extra.Length, cancellationToken);
|
||||
n = _stream.Read(extra, 0, extra.Length);
|
||||
if (n != extraLength)
|
||||
{
|
||||
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
||||
}
|
||||
totalBytesRead += n;
|
||||
}
|
||||
if ((header.Span[3] & 0x08) == 0x08)
|
||||
if ((header[3] & 0x08) == 0x08)
|
||||
{
|
||||
_GzipFileName = await ReadZeroTerminatedStringAsync();
|
||||
_GzipFileName = ReadZeroTerminatedString();
|
||||
}
|
||||
if ((header.Span[3] & 0x10) == 0x010)
|
||||
if ((header[3] & 0x10) == 0x010)
|
||||
{
|
||||
_GzipComment = await ReadZeroTerminatedStringAsync();
|
||||
_GzipComment = ReadZeroTerminatedString();
|
||||
}
|
||||
if ((header.Span[3] & 0x02) == 0x02)
|
||||
if ((header[3] & 0x02) == 0x02)
|
||||
{
|
||||
await ReadAsync(_buf1, 0, 1, cancellationToken); // CRC16, ignore
|
||||
Read(_buf1, 0, 1); // CRC16, ignore
|
||||
}
|
||||
|
||||
return totalBytesRead;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override Int32 Read(Byte[] buffer, Int32 offset, Int32 count)
|
||||
{
|
||||
// According to MS documentation, any implementation of the IO.Stream.Read function must:
|
||||
// (a) throw an exception if offset & count reference an invalid part of the buffer,
|
||||
@@ -480,7 +487,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
z.AvailableBytesIn = 0;
|
||||
if (_flavor == ZlibStreamFlavor.GZIP)
|
||||
{
|
||||
_gzipHeaderByteCount = await ReadAndValidateGzipHeaderAsync(cancellationToken);
|
||||
_gzipHeaderByteCount = _ReadAndValidateGzipHeader();
|
||||
|
||||
// workitem 8501: handle edge case (decompress empty stream)
|
||||
if (_gzipHeaderByteCount == 0)
|
||||
@@ -499,7 +506,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (_nomoreinput && _wantCompress)
|
||||
if (nomoreinput && _wantCompress)
|
||||
{
|
||||
return 0; // workitem 8557
|
||||
}
|
||||
@@ -520,7 +527,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
throw new ArgumentOutOfRangeException(nameof(count));
|
||||
}
|
||||
|
||||
var rc = 0;
|
||||
int rc = 0;
|
||||
|
||||
// set up the output of the deflate/inflate codec:
|
||||
_z.OutputBuffer = buffer;
|
||||
@@ -535,14 +542,14 @@ namespace SharpCompress.Compressors.Deflate
|
||||
do
|
||||
{
|
||||
// need data in _workingBuffer in order to deflate/inflate. Here, we check if we have any.
|
||||
if ((_z.AvailableBytesIn == 0) && (!_nomoreinput))
|
||||
if ((_z.AvailableBytesIn == 0) && (!nomoreinput))
|
||||
{
|
||||
// No data available, so try to Read data from the captive stream.
|
||||
_z.NextIn = 0;
|
||||
_z.AvailableBytesIn = await _stream.ReadAsync(_workingBuffer, 0, _workingBuffer.Length, cancellationToken);
|
||||
_z.AvailableBytesIn = _stream.Read(_workingBuffer, 0, _workingBuffer.Length);
|
||||
if (_z.AvailableBytesIn == 0)
|
||||
{
|
||||
_nomoreinput = true;
|
||||
nomoreinput = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,22 +558,23 @@ namespace SharpCompress.Compressors.Deflate
|
||||
? _z.Deflate(_flushMode)
|
||||
: _z.Inflate(_flushMode);
|
||||
|
||||
if (_nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR))
|
||||
if (nomoreinput && (rc == ZlibConstants.Z_BUF_ERROR))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
|
||||
{
|
||||
throw new ZlibException($"{(_wantCompress ? "de" : "in")}flating: rc={rc} msg={_z.Message}");
|
||||
throw new ZlibException(String.Format("{0}flating: rc={1} msg={2}", (_wantCompress ? "de" : "in"),
|
||||
rc, _z.Message));
|
||||
}
|
||||
|
||||
if ((_nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count))
|
||||
if ((nomoreinput || rc == ZlibConstants.Z_STREAM_END) && (_z.AvailableBytesOut == count))
|
||||
{
|
||||
break; // nothing more to read
|
||||
}
|
||||
} //while (_z.AvailableBytesOut == count && rc == ZlibConstants.Z_OK);
|
||||
while (_z.AvailableBytesOut > 0 && !_nomoreinput && rc == ZlibConstants.Z_OK);
|
||||
while (_z.AvailableBytesOut > 0 && !nomoreinput && rc == ZlibConstants.Z_OK);
|
||||
|
||||
// workitem 8557
|
||||
// is there more room in output?
|
||||
@@ -578,7 +586,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
}
|
||||
|
||||
// are we completely done reading?
|
||||
if (_nomoreinput)
|
||||
if (nomoreinput)
|
||||
{
|
||||
// and in compression?
|
||||
if (_wantCompress)
|
||||
@@ -589,7 +597,7 @@ namespace SharpCompress.Compressors.Deflate
|
||||
|
||||
if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END)
|
||||
{
|
||||
throw new ZlibException($"Deflating: rc={rc} msg={_z.Message}");
|
||||
throw new ZlibException(String.Format("Deflating: rc={0} msg={1}", rc, _z.Message));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -598,7 +606,10 @@ namespace SharpCompress.Compressors.Deflate
|
||||
rc = (count - _z.AvailableBytesOut);
|
||||
|
||||
// calculate CRC after reading
|
||||
crc?.SlurpBlock(buffer, offset, rc);
|
||||
if (crc != null)
|
||||
{
|
||||
crc.SlurpBlock(buffer, offset, rc);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -28,13 +28,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Deflate
|
||||
{
|
||||
public class ZlibStream : AsyncStream
|
||||
public class ZlibStream : Stream
|
||||
{
|
||||
private readonly ZlibBaseStream _baseStream;
|
||||
private bool _disposed;
|
||||
@@ -207,25 +204,35 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <remarks>
|
||||
/// This may or may not result in a <c>Close()</c> call on the captive stream.
|
||||
/// </remarks>
|
||||
public override async ValueTask DisposeAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
try
|
||||
{
|
||||
await _baseStream.DisposeAsync();
|
||||
_disposed = true;
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_baseStream?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the stream.
|
||||
/// </summary>
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
public override void Flush()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("ZlibStream");
|
||||
}
|
||||
return _baseStream.FlushAsync(cancellationToken);
|
||||
_baseStream.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -254,13 +261,22 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <param name="buffer">The buffer into which the read data should be placed.</param>
|
||||
/// <param name="offset">the offset within that data array to put the first byte read.</param>
|
||||
/// <param name="count">the number of bytes to read.</param>
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("ZlibStream");
|
||||
}
|
||||
return await _baseStream.ReadAsync(buffer, offset, count, cancellationToken);
|
||||
return _baseStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override int ReadByte()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("ZlibStream");
|
||||
}
|
||||
return _baseStream.ReadByte();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -305,14 +321,24 @@ namespace SharpCompress.Compressors.Deflate
|
||||
/// <param name="buffer">The buffer holding data to write to the stream.</param>
|
||||
/// <param name="offset">the offset within that data array to find the first byte to write.</param>
|
||||
/// <param name="count">the number of bytes to write.</param>
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("ZlibStream");
|
||||
}
|
||||
await _baseStream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
_baseStream.Write(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException("ZlibStream");
|
||||
}
|
||||
_baseStream.WriteByte(value);
|
||||
}
|
||||
|
||||
#endregion System.IO.Stream methods
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common.SevenZip;
|
||||
using SharpCompress.Compressors.LZMA.Utilites;
|
||||
using SharpCompress.IO;
|
||||
@@ -93,9 +91,8 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
}
|
||||
|
||||
private static async ValueTask<Stream> CreateDecoderStream(Stream[] packStreams, long[] packSizes, Stream[] outStreams,
|
||||
CFolder folderInfo, int coderIndex, IPasswordProvider pass,
|
||||
CancellationToken cancellationToken)
|
||||
private static Stream CreateDecoderStream(Stream[] packStreams, long[] packSizes, Stream[] outStreams,
|
||||
CFolder folderInfo, int coderIndex, IPasswordProvider pass)
|
||||
{
|
||||
var coderInfo = folderInfo._coders[coderIndex];
|
||||
if (coderInfo._numOutStreams != 1)
|
||||
@@ -130,8 +127,8 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
|
||||
int otherCoderIndex = FindCoderIndexForOutStreamIndex(folderInfo, pairedOutIndex);
|
||||
inStreams[i] = await CreateDecoderStream(packStreams, packSizes, outStreams, folderInfo, otherCoderIndex,
|
||||
pass, cancellationToken);
|
||||
inStreams[i] = CreateDecoderStream(packStreams, packSizes, outStreams, folderInfo, otherCoderIndex,
|
||||
pass);
|
||||
|
||||
//inStreamSizes[i] = folderInfo.UnpackSizes[pairedOutIndex];
|
||||
|
||||
@@ -157,11 +154,11 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
|
||||
long unpackSize = folderInfo._unpackSizes[outStreamId];
|
||||
return await DecoderRegistry.CreateDecoderStream(coderInfo._methodId, inStreams, coderInfo._props, pass, unpackSize, cancellationToken);
|
||||
return DecoderRegistry.CreateDecoderStream(coderInfo._methodId, inStreams, coderInfo._props, pass, unpackSize);
|
||||
}
|
||||
|
||||
internal static async ValueTask<Stream> CreateDecoderStream(Stream inStream, long startPos, long[] packSizes, CFolder folderInfo,
|
||||
IPasswordProvider pass, CancellationToken cancellationToken)
|
||||
internal static Stream CreateDecoderStream(Stream inStream, long startPos, long[] packSizes, CFolder folderInfo,
|
||||
IPasswordProvider pass)
|
||||
{
|
||||
if (!folderInfo.CheckStructure())
|
||||
{
|
||||
@@ -179,7 +176,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
|
||||
int primaryCoderIndex, primaryOutStreamIndex;
|
||||
FindPrimaryOutStreamIndex(folderInfo, out primaryCoderIndex, out primaryOutStreamIndex);
|
||||
return await CreateDecoderStream(inStreams, packSizes, outStreams, folderInfo, primaryCoderIndex, pass, cancellationToken);
|
||||
return CreateDecoderStream(inStreams, packSizes, outStreams, folderInfo, primaryCoderIndex, pass);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA
|
||||
{
|
||||
@@ -61,8 +59,8 @@ namespace SharpCompress.Compressors.LZMA
|
||||
/// <param name="progress">
|
||||
/// callback progress reference.
|
||||
/// </param>
|
||||
ValueTask CodeAsync(Stream inStream, Stream outStream,
|
||||
Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken cancellationToken);
|
||||
void Code(Stream inStream, Stream outStream,
|
||||
Int64 inSize, Int64 outSize, ICodeProgress progress);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Crypto;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -17,70 +14,61 @@ namespace SharpCompress.Compressors.LZMA
|
||||
/// <summary>
|
||||
/// Stream supporting the LZIP format, as documented at http://www.nongnu.org/lzip/manual/lzip_manual.html
|
||||
/// </summary>
|
||||
public sealed class LZipStream : AsyncStream
|
||||
public sealed class LZipStream : Stream
|
||||
{
|
||||
#nullable disable
|
||||
private Stream _stream;
|
||||
#nullable enable
|
||||
private CountingWritableSubStream? _countingWritableSubStream;
|
||||
private readonly Stream _stream;
|
||||
private readonly CountingWritableSubStream? _countingWritableSubStream;
|
||||
private bool _disposed;
|
||||
private bool _finished;
|
||||
|
||||
private long _writeCount;
|
||||
|
||||
private LZipStream()
|
||||
public LZipStream(Stream stream, CompressionMode mode)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public static async ValueTask<LZipStream> CreateAsync(Stream stream, CompressionMode mode)
|
||||
{
|
||||
var lzip = new LZipStream();
|
||||
lzip.Mode = mode;
|
||||
Mode = mode;
|
||||
|
||||
if (mode == CompressionMode.Decompress)
|
||||
{
|
||||
int dSize = await ValidateAndReadSize(stream);
|
||||
int dSize = ValidateAndReadSize(stream);
|
||||
if (dSize == 0)
|
||||
{
|
||||
throw new IOException("Not an LZip stream");
|
||||
}
|
||||
byte[] properties = GetProperties(dSize);
|
||||
lzip._stream = await LzmaStream.CreateAsync(properties, stream);
|
||||
_stream = new LzmaStream(properties, stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
//default
|
||||
int dSize = 104 * 1024;
|
||||
await WriteHeaderSizeAsync(stream);
|
||||
WriteHeaderSize(stream);
|
||||
|
||||
lzip._countingWritableSubStream = new CountingWritableSubStream(stream);
|
||||
lzip._stream = new Crc32Stream(new LzmaStream(new LzmaEncoderProperties(true, dSize), false, lzip._countingWritableSubStream));
|
||||
_countingWritableSubStream = new CountingWritableSubStream(stream);
|
||||
_stream = new Crc32Stream(new LzmaStream(new LzmaEncoderProperties(true, dSize), false, _countingWritableSubStream));
|
||||
}
|
||||
return lzip;
|
||||
}
|
||||
|
||||
public async ValueTask FinishAsync()
|
||||
public void Finish()
|
||||
{
|
||||
if (!_finished)
|
||||
{
|
||||
if (Mode == CompressionMode.Compress)
|
||||
{
|
||||
var crc32Stream = (Crc32Stream)_stream;
|
||||
await crc32Stream.WrappedStream.DisposeAsync();
|
||||
await crc32Stream.DisposeAsync();
|
||||
crc32Stream.WrappedStream.Dispose();
|
||||
crc32Stream.Dispose();
|
||||
var compressedCount = _countingWritableSubStream!.Count;
|
||||
|
||||
byte[] intBuf = new byte[8];
|
||||
Span<byte> intBuf = stackalloc byte[8];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(intBuf, crc32Stream.Crc);
|
||||
await _countingWritableSubStream.WriteAsync(intBuf, 0, 4);
|
||||
_countingWritableSubStream.Write(intBuf.Slice(0, 4));
|
||||
|
||||
BinaryPrimitives.WriteInt64LittleEndian(intBuf, _writeCount);
|
||||
await _countingWritableSubStream.WriteAsync(intBuf, 0, 8);
|
||||
_countingWritableSubStream.Write(intBuf);
|
||||
|
||||
//total with headers
|
||||
BinaryPrimitives.WriteUInt64LittleEndian(intBuf, compressedCount + 6 + 20);
|
||||
await _countingWritableSubStream.WriteAsync(intBuf, 0, 8);
|
||||
_countingWritableSubStream.Write(intBuf);
|
||||
}
|
||||
_finished = true;
|
||||
}
|
||||
@@ -88,18 +76,21 @@ namespace SharpCompress.Compressors.LZMA
|
||||
|
||||
#region Stream methods
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_disposed = true;
|
||||
await FinishAsync();
|
||||
await _stream.DisposeAsync();
|
||||
if (disposing)
|
||||
{
|
||||
Finish();
|
||||
_stream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public CompressionMode Mode { get; private set; }
|
||||
public CompressionMode Mode { get; }
|
||||
|
||||
public override bool CanRead => Mode == CompressionMode.Decompress;
|
||||
|
||||
@@ -107,38 +98,54 @@ namespace SharpCompress.Compressors.LZMA
|
||||
|
||||
public override bool CanWrite => Mode == CompressionMode.Compress;
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken)
|
||||
public override void Flush()
|
||||
{
|
||||
return _stream.FlushAsync(cancellationToken);
|
||||
_stream.Flush();
|
||||
}
|
||||
|
||||
// TODO: Both Length and Position are sometimes feasible, but would require
|
||||
// reading the output length when we initialize.
|
||||
public override long Length => throw new NotSupportedException();
|
||||
public override long Length => throw new NotImplementedException();
|
||||
|
||||
public override long Position { get => throw new NotImplementedException(); set => throw new NotSupportedException(); }
|
||||
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
|
||||
|
||||
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
return _stream.ReadAsync(buffer, cancellationToken);
|
||||
}
|
||||
public override int Read(byte[] buffer, int offset, int count) => _stream.Read(buffer, offset, count);
|
||||
|
||||
public override int ReadByte() => _stream.ReadByte();
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
||||
|
||||
public override void SetLength(long value) => throw new NotSupportedException();
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
|
||||
|
||||
#if !NET461 && !NETSTANDARD2_0
|
||||
|
||||
public override int Read(Span<byte> buffer)
|
||||
{
|
||||
await _stream.WriteAsync(buffer, cancellationToken);
|
||||
return _stream.Read(buffer);
|
||||
}
|
||||
|
||||
public override void Write(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
_stream.Write(buffer);
|
||||
|
||||
_writeCount += buffer.Length;
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
#endif
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
await _stream.WriteAsync(buffer, offset, count, cancellationToken);
|
||||
_stream.Write(buffer, offset, count);
|
||||
_writeCount += count;
|
||||
}
|
||||
|
||||
public override void WriteByte(byte value)
|
||||
{
|
||||
_stream.WriteByte(value);
|
||||
++_writeCount;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
@@ -148,14 +155,14 @@ namespace SharpCompress.Compressors.LZMA
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to read from. Must not be null.</param>
|
||||
/// <returns><c>true</c> if the given stream is an LZip file, <c>false</c> otherwise.</returns>
|
||||
public static async ValueTask<bool> IsLZipFileAsync(Stream stream) => await ValidateAndReadSize(stream) != 0;
|
||||
public static bool IsLZipFile(Stream stream) => ValidateAndReadSize(stream) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Reads the 6-byte header of the stream, and returns 0 if either the header
|
||||
/// couldn't be read or it isn't a validate LZIP header, or the dictionary
|
||||
/// size if it *is* a valid LZIP file.
|
||||
/// </summary>
|
||||
private static async ValueTask<int> ValidateAndReadSize(Stream stream)
|
||||
public static int ValidateAndReadSize(Stream stream)
|
||||
{
|
||||
if (stream is null)
|
||||
{
|
||||
@@ -163,9 +170,8 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
|
||||
// Read the header
|
||||
using var buffer = MemoryPool<byte>.Shared.Rent(6);
|
||||
var header = buffer.Memory.Slice(0,6);
|
||||
int n = await stream.ReadAsync(header);
|
||||
Span<byte> header = stackalloc byte[6];
|
||||
int n = stream.Read(header);
|
||||
|
||||
// TODO: Handle reading only part of the header?
|
||||
|
||||
@@ -174,18 +180,18 @@ namespace SharpCompress.Compressors.LZMA
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (header.Span[0] != 'L' || header.Span[1] != 'Z' || header.Span[2] != 'I' || header.Span[3] != 'P' || header.Span[4] != 1 /* version 1 */)
|
||||
if (header[0] != 'L' || header[1] != 'Z' || header[2] != 'I' || header[3] != 'P' || header[4] != 1 /* version 1 */)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int basePower = header.Span[5] & 0x1F;
|
||||
int subtractionNumerator = (header.Span[5] & 0xE0) >> 5;
|
||||
int basePower = header[5] & 0x1F;
|
||||
int subtractionNumerator = (header[5] & 0xE0) >> 5;
|
||||
return (1 << basePower) - subtractionNumerator * (1 << (basePower - 4));
|
||||
}
|
||||
|
||||
private static readonly byte[] headerBytes = new byte[6] { (byte)'L', (byte)'Z', (byte)'I', (byte)'P', 1, 113 };
|
||||
|
||||
public static async ValueTask WriteHeaderSizeAsync(Stream stream)
|
||||
public static void WriteHeaderSize(Stream stream)
|
||||
{
|
||||
if (stream is null)
|
||||
{
|
||||
@@ -193,7 +199,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
|
||||
// hard coding the dictionary size encoding
|
||||
await stream.WriteAsync(headerBytes, 0, 6);
|
||||
stream.Write(headerBytes, 0, 6);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.LZMA.LZ;
|
||||
using SharpCompress.Compressors.LZMA.RangeCoder;
|
||||
|
||||
@@ -11,11 +11,11 @@ namespace SharpCompress.Compressors.LZMA
|
||||
{
|
||||
private class LenDecoder
|
||||
{
|
||||
private BitDecoder _choice = new();
|
||||
private BitDecoder _choice2 = new();
|
||||
private BitDecoder _choice = new BitDecoder();
|
||||
private BitDecoder _choice2 = new BitDecoder();
|
||||
private readonly BitTreeDecoder[] _lowCoder = new BitTreeDecoder[Base.K_NUM_POS_STATES_MAX];
|
||||
private readonly BitTreeDecoder[] _midCoder = new BitTreeDecoder[Base.K_NUM_POS_STATES_MAX];
|
||||
private BitTreeDecoder _highCoder = new(Base.K_NUM_HIGH_LEN_BITS);
|
||||
private BitTreeDecoder _highCoder = new BitTreeDecoder(Base.K_NUM_HIGH_LEN_BITS);
|
||||
private uint _numPosStates;
|
||||
|
||||
public void Create(uint numPosStates)
|
||||
@@ -40,21 +40,21 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_highCoder.Init();
|
||||
}
|
||||
|
||||
public async ValueTask<uint> DecodeAsync(RangeCoder.Decoder rangeDecoder, uint posState, CancellationToken cancellationToken)
|
||||
public uint Decode(RangeCoder.Decoder rangeDecoder, uint posState)
|
||||
{
|
||||
if (await _choice.DecodeAsync(rangeDecoder, cancellationToken) == 0)
|
||||
if (_choice.Decode(rangeDecoder) == 0)
|
||||
{
|
||||
return await _lowCoder[posState].DecodeAsync(rangeDecoder, cancellationToken);
|
||||
return _lowCoder[posState].Decode(rangeDecoder);
|
||||
}
|
||||
uint symbol = Base.K_NUM_LOW_LEN_SYMBOLS;
|
||||
if (await _choice2.DecodeAsync(rangeDecoder, cancellationToken) == 0)
|
||||
if (_choice2.Decode(rangeDecoder) == 0)
|
||||
{
|
||||
symbol += await _midCoder[posState].DecodeAsync(rangeDecoder, cancellationToken);
|
||||
symbol += _midCoder[posState].Decode(rangeDecoder);
|
||||
}
|
||||
else
|
||||
{
|
||||
symbol += Base.K_NUM_MID_LEN_SYMBOLS;
|
||||
symbol += await _highCoder.DecodeAsync(rangeDecoder, cancellationToken);
|
||||
symbol += _highCoder.Decode(rangeDecoder);
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
@@ -79,31 +79,31 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<byte> DecodeNormalAsync(RangeCoder.Decoder rangeDecoder, CancellationToken cancellationToken)
|
||||
public byte DecodeNormal(RangeCoder.Decoder rangeDecoder)
|
||||
{
|
||||
uint symbol = 1;
|
||||
do
|
||||
{
|
||||
symbol = (symbol << 1) | await _decoders[symbol].DecodeAsync(rangeDecoder, cancellationToken);
|
||||
symbol = (symbol << 1) | _decoders[symbol].Decode(rangeDecoder);
|
||||
}
|
||||
while (symbol < 0x100);
|
||||
return (byte)symbol;
|
||||
}
|
||||
|
||||
public async ValueTask<byte> DecodeWithMatchByteAsync(RangeCoder.Decoder rangeDecoder, byte matchByte, CancellationToken cancellationToken)
|
||||
public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, byte matchByte)
|
||||
{
|
||||
uint symbol = 1;
|
||||
do
|
||||
{
|
||||
uint matchBit = (uint)(matchByte >> 7) & 1;
|
||||
matchByte <<= 1;
|
||||
uint bit = await _decoders[((1 + matchBit) << 8) + symbol].DecodeAsync(rangeDecoder, cancellationToken);
|
||||
uint bit = _decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder);
|
||||
symbol = (symbol << 1) | bit;
|
||||
if (matchBit != bit)
|
||||
{
|
||||
while (symbol < 0x100)
|
||||
{
|
||||
symbol = (symbol << 1) | await _decoders[symbol].DecodeAsync(rangeDecoder, cancellationToken);
|
||||
symbol = (symbol << 1) | _decoders[symbol].Decode(rangeDecoder);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -113,12 +113,12 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Decoder2[]_coders;
|
||||
private readonly int _numPrevBits;
|
||||
private readonly int _numPosBits;
|
||||
private readonly uint _posMask;
|
||||
|
||||
public LiteralDecoder(int numPosBits, int numPrevBits)
|
||||
private Decoder2[] _coders;
|
||||
private int _numPrevBits;
|
||||
private int _numPosBits;
|
||||
private uint _posMask;
|
||||
|
||||
public void Create(int numPosBits, int numPrevBits)
|
||||
{
|
||||
if (_coders != null && _numPrevBits == numPrevBits &&
|
||||
_numPosBits == numPosBits)
|
||||
@@ -150,18 +150,18 @@ namespace SharpCompress.Compressors.LZMA
|
||||
return ((pos & _posMask) << _numPrevBits) + (uint)(prevByte >> (8 - _numPrevBits));
|
||||
}
|
||||
|
||||
public ValueTask<byte> DecodeNormalAsync(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, CancellationToken cancellationToken)
|
||||
public byte DecodeNormal(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte)
|
||||
{
|
||||
return _coders[GetState(pos, prevByte)].DecodeNormalAsync(rangeDecoder, cancellationToken);
|
||||
return _coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder);
|
||||
}
|
||||
|
||||
public ValueTask<byte> DecodeWithMatchByteAsync(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte, CancellationToken cancellationToken)
|
||||
public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte)
|
||||
{
|
||||
return _coders[GetState(pos, prevByte)].DecodeWithMatchByteAsync(rangeDecoder, matchByte, cancellationToken);
|
||||
return _coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte);
|
||||
}
|
||||
}
|
||||
|
||||
private OutWindow? _outWindow;
|
||||
private OutWindow _outWindow;
|
||||
|
||||
private readonly BitDecoder[] _isMatchDecoders = new BitDecoder[Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX];
|
||||
private readonly BitDecoder[] _isRepDecoders = new BitDecoder[Base.K_NUM_STATES];
|
||||
@@ -173,18 +173,18 @@ namespace SharpCompress.Compressors.LZMA
|
||||
private readonly BitTreeDecoder[] _posSlotDecoder = new BitTreeDecoder[Base.K_NUM_LEN_TO_POS_STATES];
|
||||
private readonly BitDecoder[] _posDecoders = new BitDecoder[Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX];
|
||||
|
||||
private BitTreeDecoder _posAlignDecoder = new(Base.K_NUM_ALIGN_BITS);
|
||||
private BitTreeDecoder _posAlignDecoder = new BitTreeDecoder(Base.K_NUM_ALIGN_BITS);
|
||||
|
||||
private readonly LenDecoder _lenDecoder = new();
|
||||
private readonly LenDecoder _repLenDecoder = new();
|
||||
private readonly LenDecoder _lenDecoder = new LenDecoder();
|
||||
private readonly LenDecoder _repLenDecoder = new LenDecoder();
|
||||
|
||||
private LiteralDecoder? _literalDecoder;
|
||||
private readonly LiteralDecoder _literalDecoder = new LiteralDecoder();
|
||||
|
||||
private int _dictionarySize;
|
||||
|
||||
private uint _posStateMask;
|
||||
|
||||
private Base.State _state = new();
|
||||
private Base.State _state = new Base.State();
|
||||
private uint _rep0, _rep1, _rep2, _rep3;
|
||||
|
||||
public Decoder()
|
||||
@@ -196,16 +196,15 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
}
|
||||
|
||||
private OutWindow CreateDictionary()
|
||||
private void CreateDictionary()
|
||||
{
|
||||
if (_dictionarySize < 0)
|
||||
{
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
var outWindow = new OutWindow();
|
||||
_outWindow = new OutWindow();
|
||||
int blockSize = Math.Max(_dictionarySize, (1 << 12));
|
||||
outWindow.Create(blockSize);
|
||||
return outWindow;
|
||||
_outWindow.Create(blockSize);
|
||||
}
|
||||
|
||||
private void SetLiteralProperties(int lp, int lc)
|
||||
@@ -218,7 +217,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
{
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
_literalDecoder = new(lp, lc);
|
||||
_literalDecoder.Create(lp, lc);
|
||||
}
|
||||
|
||||
private void SetPosBitsProperties(int pb)
|
||||
@@ -250,7 +249,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_isRepG2Decoders[i].Init();
|
||||
}
|
||||
|
||||
_literalDecoder!.Init();
|
||||
_literalDecoder.Init();
|
||||
for (i = 0; i < Base.K_NUM_LEN_TO_POS_STATES; i++)
|
||||
{
|
||||
_posSlotDecoder[i].Init();
|
||||
@@ -273,12 +272,12 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_rep3 = 0;
|
||||
}
|
||||
|
||||
public async ValueTask CodeAsync(Stream inStream, Stream outStream,
|
||||
Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken cancellationToken)
|
||||
public void Code(Stream inStream, Stream outStream,
|
||||
Int64 inSize, Int64 outSize, ICodeProgress progress)
|
||||
{
|
||||
if (_outWindow is null)
|
||||
{
|
||||
_outWindow = CreateDictionary();
|
||||
CreateDictionary();
|
||||
}
|
||||
_outWindow.Init(outStream);
|
||||
if (outSize > 0)
|
||||
@@ -291,9 +290,9 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
|
||||
RangeCoder.Decoder rangeDecoder = new RangeCoder.Decoder();
|
||||
await rangeDecoder.InitAsync(inStream, cancellationToken);
|
||||
rangeDecoder.Init(inStream);
|
||||
|
||||
await CodeAsync(_dictionarySize, _outWindow, rangeDecoder, cancellationToken);
|
||||
Code(_dictionarySize, _outWindow, rangeDecoder);
|
||||
|
||||
_outWindow.ReleaseStream();
|
||||
rangeDecoder.ReleaseStream();
|
||||
@@ -309,9 +308,8 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_outWindow = null;
|
||||
}
|
||||
|
||||
internal async ValueTask<bool> CodeAsync(int dictionarySize, OutWindow outWindow, RangeCoder.Decoder rangeDecoder, CancellationToken cancellationToken)
|
||||
internal bool Code(int dictionarySize, OutWindow outWindow, RangeCoder.Decoder rangeDecoder)
|
||||
{
|
||||
_literalDecoder ??= _literalDecoder.CheckNotNull(nameof(_literalDecoder));
|
||||
int dictionarySizeCheck = Math.Max(dictionarySize, 1);
|
||||
|
||||
outWindow.CopyPending();
|
||||
@@ -319,19 +317,19 @@ namespace SharpCompress.Compressors.LZMA
|
||||
while (outWindow.HasSpace)
|
||||
{
|
||||
uint posState = (uint)outWindow._total & _posStateMask;
|
||||
if (await _isMatchDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].DecodeAsync(rangeDecoder, cancellationToken) == 0)
|
||||
if (_isMatchDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Decode(rangeDecoder) == 0)
|
||||
{
|
||||
byte b;
|
||||
byte prevByte = outWindow.GetByte(0);
|
||||
if (!_state.IsCharState())
|
||||
{
|
||||
b = await _literalDecoder.DecodeWithMatchByteAsync(rangeDecoder,
|
||||
b = _literalDecoder.DecodeWithMatchByte(rangeDecoder,
|
||||
(uint)outWindow._total, prevByte,
|
||||
outWindow.GetByte((int)_rep0), cancellationToken);
|
||||
outWindow.GetByte((int)_rep0));
|
||||
}
|
||||
else
|
||||
{
|
||||
b = await _literalDecoder.DecodeNormalAsync(rangeDecoder, (uint)outWindow._total, prevByte, cancellationToken);
|
||||
b = _literalDecoder.DecodeNormal(rangeDecoder, (uint)outWindow._total, prevByte);
|
||||
}
|
||||
outWindow.PutByte(b);
|
||||
_state.UpdateChar();
|
||||
@@ -339,13 +337,13 @@ namespace SharpCompress.Compressors.LZMA
|
||||
else
|
||||
{
|
||||
uint len;
|
||||
if (await _isRepDecoders[_state._index].DecodeAsync(rangeDecoder, cancellationToken) == 1)
|
||||
if (_isRepDecoders[_state._index].Decode(rangeDecoder) == 1)
|
||||
{
|
||||
if (await _isRepG0Decoders[_state._index].DecodeAsync(rangeDecoder, cancellationToken) == 0)
|
||||
if (_isRepG0Decoders[_state._index].Decode(rangeDecoder) == 0)
|
||||
{
|
||||
if (
|
||||
await _isRep0LongDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].DecodeAsync(
|
||||
rangeDecoder, cancellationToken) == 0)
|
||||
_isRep0LongDecoders[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Decode(
|
||||
rangeDecoder) == 0)
|
||||
{
|
||||
_state.UpdateShortRep();
|
||||
outWindow.PutByte(outWindow.GetByte((int)_rep0));
|
||||
@@ -355,13 +353,13 @@ namespace SharpCompress.Compressors.LZMA
|
||||
else
|
||||
{
|
||||
UInt32 distance;
|
||||
if (await _isRepG1Decoders[_state._index].DecodeAsync(rangeDecoder, cancellationToken) == 0)
|
||||
if (_isRepG1Decoders[_state._index].Decode(rangeDecoder) == 0)
|
||||
{
|
||||
distance = _rep1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (await _isRepG2Decoders[_state._index].DecodeAsync(rangeDecoder, cancellationToken) == 0)
|
||||
if (_isRepG2Decoders[_state._index].Decode(rangeDecoder) == 0)
|
||||
{
|
||||
distance = _rep2;
|
||||
}
|
||||
@@ -375,7 +373,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_rep1 = _rep0;
|
||||
_rep0 = distance;
|
||||
}
|
||||
len = await _repLenDecoder.DecodeAsync(rangeDecoder, posState, cancellationToken) + Base.K_MATCH_MIN_LEN;
|
||||
len = _repLenDecoder.Decode(rangeDecoder, posState) + Base.K_MATCH_MIN_LEN;
|
||||
_state.UpdateRep();
|
||||
}
|
||||
else
|
||||
@@ -383,22 +381,23 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_rep3 = _rep2;
|
||||
_rep2 = _rep1;
|
||||
_rep1 = _rep0;
|
||||
len = Base.K_MATCH_MIN_LEN + await _lenDecoder.DecodeAsync(rangeDecoder, posState, cancellationToken);
|
||||
len = Base.K_MATCH_MIN_LEN + _lenDecoder.Decode(rangeDecoder, posState);
|
||||
_state.UpdateMatch();
|
||||
uint posSlot = await _posSlotDecoder[Base.GetLenToPosState(len)].DecodeAsync(rangeDecoder, cancellationToken);
|
||||
uint posSlot = _posSlotDecoder[Base.GetLenToPosState(len)].Decode(rangeDecoder);
|
||||
if (posSlot >= Base.K_START_POS_MODEL_INDEX)
|
||||
{
|
||||
int numDirectBits = (int)((posSlot >> 1) - 1);
|
||||
_rep0 = ((2 | (posSlot & 1)) << numDirectBits);
|
||||
if (posSlot < Base.K_END_POS_MODEL_INDEX)
|
||||
{
|
||||
_rep0 += await BitTreeDecoder.ReverseDecode(_posDecoders,
|
||||
_rep0 - posSlot - 1, rangeDecoder, numDirectBits, cancellationToken);
|
||||
_rep0 += BitTreeDecoder.ReverseDecode(_posDecoders,
|
||||
_rep0 - posSlot - 1, rangeDecoder, numDirectBits);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rep0 += (await rangeDecoder.DecodeDirectBitsAsync(numDirectBits - Base.K_NUM_ALIGN_BITS, cancellationToken) << Base.K_NUM_ALIGN_BITS);
|
||||
_rep0 += await _posAlignDecoder.ReverseDecode(rangeDecoder, cancellationToken);
|
||||
_rep0 += (rangeDecoder.DecodeDirectBits(
|
||||
numDirectBits - Base.K_NUM_ALIGN_BITS) << Base.K_NUM_ALIGN_BITS);
|
||||
_rep0 += _posAlignDecoder.ReverseDecode(rangeDecoder);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -451,7 +450,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
{
|
||||
if (_outWindow is null)
|
||||
{
|
||||
_outWindow = CreateDictionary();
|
||||
CreateDictionary();
|
||||
}
|
||||
_outWindow.Train(stream);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.LZMA.LZ;
|
||||
using SharpCompress.Compressors.LZMA.RangeCoder;
|
||||
|
||||
@@ -63,7 +61,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
return (UInt32)(G_FAST_POS[pos >> 26] + 52);
|
||||
}
|
||||
|
||||
private Base.State _state = new();
|
||||
private Base.State _state = new Base.State();
|
||||
private Byte _previousByte;
|
||||
private readonly UInt32[] _repDistances = new UInt32[Base.K_NUM_REP_DISTANCES];
|
||||
|
||||
@@ -99,18 +97,18 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask EncodeAsync(RangeCoder.Encoder rangeEncoder, byte symbol)
|
||||
public void Encode(RangeCoder.Encoder rangeEncoder, byte symbol)
|
||||
{
|
||||
uint context = 1;
|
||||
for (int i = 7; i >= 0; i--)
|
||||
{
|
||||
uint bit = (uint)((symbol >> i) & 1);
|
||||
await _encoders[context].EncodeAsync(rangeEncoder, bit);
|
||||
_encoders[context].Encode(rangeEncoder, bit);
|
||||
context = (context << 1) | bit;
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask EncodeMatchedAsync(RangeCoder.Encoder rangeEncoder, byte matchByte, byte symbol)
|
||||
public void EncodeMatched(RangeCoder.Encoder rangeEncoder, byte matchByte, byte symbol)
|
||||
{
|
||||
uint context = 1;
|
||||
bool same = true;
|
||||
@@ -124,7 +122,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
state += ((1 + matchBit) << 8);
|
||||
same = (matchBit == bit);
|
||||
}
|
||||
await _encoders[state].EncodeAsync(rangeEncoder, bit);
|
||||
_encoders[state].Encode(rangeEncoder, bit);
|
||||
context = (context << 1) | bit;
|
||||
}
|
||||
}
|
||||
@@ -198,11 +196,11 @@ namespace SharpCompress.Compressors.LZMA
|
||||
|
||||
private class LenEncoder
|
||||
{
|
||||
private BitEncoder _choice = new();
|
||||
private BitEncoder _choice2 = new();
|
||||
private BitEncoder _choice = new BitEncoder();
|
||||
private BitEncoder _choice2 = new BitEncoder();
|
||||
private readonly BitTreeEncoder[] _lowCoder = new BitTreeEncoder[Base.K_NUM_POS_STATES_ENCODING_MAX];
|
||||
private readonly BitTreeEncoder[] _midCoder = new BitTreeEncoder[Base.K_NUM_POS_STATES_ENCODING_MAX];
|
||||
private BitTreeEncoder _highCoder = new(Base.K_NUM_HIGH_LEN_BITS);
|
||||
private BitTreeEncoder _highCoder = new BitTreeEncoder(Base.K_NUM_HIGH_LEN_BITS);
|
||||
|
||||
public LenEncoder()
|
||||
{
|
||||
@@ -225,26 +223,26 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_highCoder.Init();
|
||||
}
|
||||
|
||||
public async ValueTask EncodeAsync(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState)
|
||||
public void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState)
|
||||
{
|
||||
if (symbol < Base.K_NUM_LOW_LEN_SYMBOLS)
|
||||
{
|
||||
await _choice.EncodeAsync(rangeEncoder, 0);
|
||||
await _lowCoder[posState].EncodeAsync(rangeEncoder, symbol);
|
||||
_choice.Encode(rangeEncoder, 0);
|
||||
_lowCoder[posState].Encode(rangeEncoder, symbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
symbol -= Base.K_NUM_LOW_LEN_SYMBOLS;
|
||||
await _choice.EncodeAsync(rangeEncoder, 1);
|
||||
_choice.Encode(rangeEncoder, 1);
|
||||
if (symbol < Base.K_NUM_MID_LEN_SYMBOLS)
|
||||
{
|
||||
await _choice2.EncodeAsync(rangeEncoder, 0);
|
||||
await _midCoder[posState].EncodeAsync(rangeEncoder, symbol);
|
||||
_choice2.Encode(rangeEncoder, 0);
|
||||
_midCoder[posState].Encode(rangeEncoder, symbol);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _choice2.EncodeAsync(rangeEncoder, 1);
|
||||
await _highCoder.EncodeAsync(rangeEncoder, symbol - Base.K_NUM_MID_LEN_SYMBOLS);
|
||||
_choice2.Encode(rangeEncoder, 1);
|
||||
_highCoder.Encode(rangeEncoder, symbol - Base.K_NUM_MID_LEN_SYMBOLS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,9 +309,9 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
}
|
||||
|
||||
public new async ValueTask EncodeAsync(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState)
|
||||
public new void Encode(RangeCoder.Encoder rangeEncoder, UInt32 symbol, UInt32 posState)
|
||||
{
|
||||
await base.EncodeAsync(rangeEncoder, symbol, posState);
|
||||
base.Encode(rangeEncoder, symbol, posState);
|
||||
if (--_counters[posState] == 0)
|
||||
{
|
||||
UpdateTable(posState);
|
||||
@@ -363,7 +361,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
|
||||
private readonly Optimal[] _optimum = new Optimal[K_NUM_OPTS];
|
||||
private BinTree _matchFinder;
|
||||
private readonly RangeCoder.Encoder _rangeEncoder = new();
|
||||
private readonly RangeCoder.Encoder _rangeEncoder = new RangeCoder.Encoder();
|
||||
|
||||
private readonly BitEncoder[] _isMatch =
|
||||
new BitEncoder[Base.K_NUM_STATES << Base.K_NUM_POS_STATES_BITS_MAX];
|
||||
@@ -381,12 +379,12 @@ namespace SharpCompress.Compressors.LZMA
|
||||
private readonly BitEncoder[] _posEncoders =
|
||||
new BitEncoder[Base.K_NUM_FULL_DISTANCES - Base.K_END_POS_MODEL_INDEX];
|
||||
|
||||
private BitTreeEncoder _posAlignEncoder = new(Base.K_NUM_ALIGN_BITS);
|
||||
private BitTreeEncoder _posAlignEncoder = new BitTreeEncoder(Base.K_NUM_ALIGN_BITS);
|
||||
|
||||
private readonly LenPriceTableEncoder _lenEncoder = new();
|
||||
private readonly LenPriceTableEncoder _repMatchLenEncoder = new();
|
||||
private readonly LenPriceTableEncoder _lenEncoder = new LenPriceTableEncoder();
|
||||
private readonly LenPriceTableEncoder _repMatchLenEncoder = new LenPriceTableEncoder();
|
||||
|
||||
private readonly LiteralEncoder _literalEncoder = new();
|
||||
private readonly LiteralEncoder _literalEncoder = new LiteralEncoder();
|
||||
|
||||
private readonly UInt32[] _matchDistances = new UInt32[Base.K_MATCH_MAX_LEN * 2 + 2];
|
||||
|
||||
@@ -1191,40 +1189,40 @@ namespace SharpCompress.Compressors.LZMA
|
||||
return (smallDist < ((UInt32)(1) << (32 - kDif)) && bigDist >= (smallDist << kDif));
|
||||
}
|
||||
|
||||
private async ValueTask WriteEndMarkerAsync(UInt32 posState)
|
||||
private void WriteEndMarker(UInt32 posState)
|
||||
{
|
||||
if (!_writeEndMark)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].EncodeAsync(_rangeEncoder, 1);
|
||||
await _isRep[_state._index].EncodeAsync(_rangeEncoder, 0);
|
||||
_isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Encode(_rangeEncoder, 1);
|
||||
_isRep[_state._index].Encode(_rangeEncoder, 0);
|
||||
_state.UpdateMatch();
|
||||
UInt32 len = Base.K_MATCH_MIN_LEN;
|
||||
await _lenEncoder.EncodeAsync(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState);
|
||||
_lenEncoder.Encode(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState);
|
||||
UInt32 posSlot = (1 << Base.K_NUM_POS_SLOT_BITS) - 1;
|
||||
UInt32 lenToPosState = Base.GetLenToPosState(len);
|
||||
await _posSlotEncoder[lenToPosState].EncodeAsync(_rangeEncoder, posSlot);
|
||||
_posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot);
|
||||
int footerBits = 30;
|
||||
UInt32 posReduced = (((UInt32)1) << footerBits) - 1;
|
||||
await _rangeEncoder.EncodeDirectBits(posReduced >> Base.K_NUM_ALIGN_BITS, footerBits - Base.K_NUM_ALIGN_BITS);
|
||||
await _posAlignEncoder.ReverseEncodeAsync(_rangeEncoder, posReduced & Base.K_ALIGN_MASK);
|
||||
_rangeEncoder.EncodeDirectBits(posReduced >> Base.K_NUM_ALIGN_BITS, footerBits - Base.K_NUM_ALIGN_BITS);
|
||||
_posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.K_ALIGN_MASK);
|
||||
}
|
||||
|
||||
private async ValueTask FlushAsync(UInt32 nowPos)
|
||||
private void Flush(UInt32 nowPos)
|
||||
{
|
||||
ReleaseMfStream();
|
||||
await WriteEndMarkerAsync(nowPos & _posStateMask);
|
||||
await _rangeEncoder.FlushData();
|
||||
await _rangeEncoder.FlushAsync();
|
||||
WriteEndMarker(nowPos & _posStateMask);
|
||||
_rangeEncoder.FlushData();
|
||||
_rangeEncoder.FlushStream();
|
||||
}
|
||||
|
||||
public async ValueTask<(Int64, Int64, bool)> CodeOneBlockAsync()
|
||||
public void CodeOneBlock(out Int64 inSize, out Int64 outSize, out bool finished)
|
||||
{
|
||||
long inSize = 0;
|
||||
long outSize = 0;
|
||||
var finished = true;
|
||||
inSize = 0;
|
||||
outSize = 0;
|
||||
finished = true;
|
||||
|
||||
if (_inStream != null)
|
||||
{
|
||||
@@ -1235,7 +1233,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
|
||||
if (_finished)
|
||||
{
|
||||
return (inSize, outSize, finished);
|
||||
return;
|
||||
}
|
||||
_finished = true;
|
||||
|
||||
@@ -1256,20 +1254,20 @@ namespace SharpCompress.Compressors.LZMA
|
||||
if (_processingMode && _matchFinder.IsDataStarved)
|
||||
{
|
||||
_finished = false;
|
||||
return (inSize, outSize, finished);
|
||||
return;
|
||||
}
|
||||
if (_matchFinder.GetNumAvailableBytes() == 0)
|
||||
{
|
||||
await FlushAsync((UInt32)_nowPos64);
|
||||
return (inSize, outSize, finished);
|
||||
Flush((UInt32)_nowPos64);
|
||||
return;
|
||||
}
|
||||
UInt32 len, numDistancePairs; // it's not used
|
||||
ReadMatchDistances(out len, out numDistancePairs);
|
||||
UInt32 posState = (UInt32)(_nowPos64) & _posStateMask;
|
||||
await _isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].EncodeAsync(_rangeEncoder, 0);
|
||||
_isMatch[(_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState].Encode(_rangeEncoder, 0);
|
||||
_state.UpdateChar();
|
||||
Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset));
|
||||
await _literalEncoder.GetSubCoder((UInt32)(_nowPos64), _previousByte).EncodeAsync(_rangeEncoder, curByte);
|
||||
_literalEncoder.GetSubCoder((UInt32)(_nowPos64), _previousByte).Encode(_rangeEncoder, curByte);
|
||||
_previousByte = curByte;
|
||||
_additionalOffset--;
|
||||
_nowPos64++;
|
||||
@@ -1277,19 +1275,19 @@ namespace SharpCompress.Compressors.LZMA
|
||||
if (_processingMode && _matchFinder.IsDataStarved)
|
||||
{
|
||||
_finished = false;
|
||||
return (inSize, outSize, finished);
|
||||
return;
|
||||
}
|
||||
if (_matchFinder.GetNumAvailableBytes() == 0)
|
||||
{
|
||||
await FlushAsync((UInt32)_nowPos64);
|
||||
return (inSize, outSize, finished);
|
||||
Flush((UInt32)_nowPos64);
|
||||
return;
|
||||
}
|
||||
while (true)
|
||||
{
|
||||
if (_processingMode && _matchFinder.IsDataStarved)
|
||||
{
|
||||
_finished = false;
|
||||
return (inSize, outSize, finished);
|
||||
return;
|
||||
}
|
||||
|
||||
UInt32 pos;
|
||||
@@ -1299,51 +1297,51 @@ namespace SharpCompress.Compressors.LZMA
|
||||
UInt32 complexState = (_state._index << Base.K_NUM_POS_STATES_BITS_MAX) + posState;
|
||||
if (len == 1 && pos == 0xFFFFFFFF)
|
||||
{
|
||||
await _isMatch[complexState].EncodeAsync(_rangeEncoder, 0);
|
||||
_isMatch[complexState].Encode(_rangeEncoder, 0);
|
||||
Byte curByte = _matchFinder.GetIndexByte((Int32)(0 - _additionalOffset));
|
||||
LiteralEncoder.Encoder2 subCoder = _literalEncoder.GetSubCoder((UInt32)_nowPos64, _previousByte);
|
||||
if (!_state.IsCharState())
|
||||
{
|
||||
Byte matchByte =
|
||||
_matchFinder.GetIndexByte((Int32)(0 - _repDistances[0] - 1 - _additionalOffset));
|
||||
await subCoder.EncodeMatchedAsync(_rangeEncoder, matchByte, curByte);
|
||||
subCoder.EncodeMatched(_rangeEncoder, matchByte, curByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
await subCoder.EncodeAsync(_rangeEncoder, curByte);
|
||||
subCoder.Encode(_rangeEncoder, curByte);
|
||||
}
|
||||
_previousByte = curByte;
|
||||
_state.UpdateChar();
|
||||
}
|
||||
else
|
||||
{
|
||||
await _isMatch[complexState].EncodeAsync(_rangeEncoder, 1);
|
||||
_isMatch[complexState].Encode(_rangeEncoder, 1);
|
||||
if (pos < Base.K_NUM_REP_DISTANCES)
|
||||
{
|
||||
await _isRep[_state._index].EncodeAsync(_rangeEncoder, 1);
|
||||
_isRep[_state._index].Encode(_rangeEncoder, 1);
|
||||
if (pos == 0)
|
||||
{
|
||||
await _isRepG0[_state._index].EncodeAsync(_rangeEncoder, 0);
|
||||
_isRepG0[_state._index].Encode(_rangeEncoder, 0);
|
||||
if (len == 1)
|
||||
{
|
||||
await _isRep0Long[complexState].EncodeAsync(_rangeEncoder, 0);
|
||||
_isRep0Long[complexState].Encode(_rangeEncoder, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _isRep0Long[complexState].EncodeAsync(_rangeEncoder, 1);
|
||||
_isRep0Long[complexState].Encode(_rangeEncoder, 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
await _isRepG0[_state._index].EncodeAsync(_rangeEncoder, 1);
|
||||
_isRepG0[_state._index].Encode(_rangeEncoder, 1);
|
||||
if (pos == 1)
|
||||
{
|
||||
await _isRepG1[_state._index].EncodeAsync(_rangeEncoder, 0);
|
||||
_isRepG1[_state._index].Encode(_rangeEncoder, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _isRepG1[_state._index].EncodeAsync(_rangeEncoder, 1);
|
||||
await _isRepG2[_state._index].EncodeAsync(_rangeEncoder, pos - 2);
|
||||
_isRepG1[_state._index].Encode(_rangeEncoder, 1);
|
||||
_isRepG2[_state._index].Encode(_rangeEncoder, pos - 2);
|
||||
}
|
||||
}
|
||||
if (len == 1)
|
||||
@@ -1352,7 +1350,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
else
|
||||
{
|
||||
await _repMatchLenEncoder.EncodeAsync(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState);
|
||||
_repMatchLenEncoder.Encode(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState);
|
||||
_state.UpdateRep();
|
||||
}
|
||||
UInt32 distance = _repDistances[pos];
|
||||
@@ -1367,13 +1365,13 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
else
|
||||
{
|
||||
await _isRep[_state._index].EncodeAsync(_rangeEncoder, 0);
|
||||
_isRep[_state._index].Encode(_rangeEncoder, 0);
|
||||
_state.UpdateMatch();
|
||||
await _lenEncoder.EncodeAsync(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState);
|
||||
_lenEncoder.Encode(_rangeEncoder, len - Base.K_MATCH_MIN_LEN, posState);
|
||||
pos -= Base.K_NUM_REP_DISTANCES;
|
||||
UInt32 posSlot = GetPosSlot(pos);
|
||||
UInt32 lenToPosState = Base.GetLenToPosState(len);
|
||||
await _posSlotEncoder[lenToPosState].EncodeAsync(_rangeEncoder, posSlot);
|
||||
_posSlotEncoder[lenToPosState].Encode(_rangeEncoder, posSlot);
|
||||
|
||||
if (posSlot >= Base.K_START_POS_MODEL_INDEX)
|
||||
{
|
||||
@@ -1383,15 +1381,15 @@ namespace SharpCompress.Compressors.LZMA
|
||||
|
||||
if (posSlot < Base.K_END_POS_MODEL_INDEX)
|
||||
{
|
||||
await BitTreeEncoder.ReverseEncodeAsync(_posEncoders,
|
||||
baseVal - posSlot - 1, _rangeEncoder, footerBits,
|
||||
posReduced);
|
||||
BitTreeEncoder.ReverseEncode(_posEncoders,
|
||||
baseVal - posSlot - 1, _rangeEncoder, footerBits,
|
||||
posReduced);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _rangeEncoder.EncodeDirectBits(posReduced >> Base.K_NUM_ALIGN_BITS,
|
||||
_rangeEncoder.EncodeDirectBits(posReduced >> Base.K_NUM_ALIGN_BITS,
|
||||
footerBits - Base.K_NUM_ALIGN_BITS);
|
||||
await _posAlignEncoder.ReverseEncodeAsync(_rangeEncoder, posReduced & Base.K_ALIGN_MASK);
|
||||
_posAlignEncoder.ReverseEncode(_rangeEncoder, posReduced & Base.K_ALIGN_MASK);
|
||||
_alignPriceCount++;
|
||||
}
|
||||
}
|
||||
@@ -1423,19 +1421,19 @@ namespace SharpCompress.Compressors.LZMA
|
||||
if (_processingMode && _matchFinder.IsDataStarved)
|
||||
{
|
||||
_finished = false;
|
||||
return (inSize, outSize, finished);
|
||||
return;
|
||||
}
|
||||
if (_matchFinder.GetNumAvailableBytes() == 0)
|
||||
{
|
||||
await FlushAsync((UInt32)_nowPos64);
|
||||
return (inSize, outSize, finished);
|
||||
Flush((UInt32)_nowPos64);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_nowPos64 - progressPosValuePrev >= (1 << 12))
|
||||
{
|
||||
_finished = false;
|
||||
finished = false;
|
||||
return (inSize, outSize, finished);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1490,8 +1488,8 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_nowPos64 = 0;
|
||||
}
|
||||
|
||||
public async ValueTask CodeAsync(Stream inStream, Stream outStream,
|
||||
Int64 inSize, Int64 outSize, ICodeProgress progress, CancellationToken cancellationToken)
|
||||
public void Code(Stream inStream, Stream outStream,
|
||||
Int64 inSize, Int64 outSize, ICodeProgress progress)
|
||||
{
|
||||
_needReleaseMfStream = false;
|
||||
_processingMode = false;
|
||||
@@ -1500,7 +1498,10 @@ namespace SharpCompress.Compressors.LZMA
|
||||
SetStreams(inStream, outStream, inSize, outSize);
|
||||
while (true)
|
||||
{
|
||||
var (processedInSize, processedOutSize, finished) = await CodeOneBlockAsync();
|
||||
Int64 processedInSize;
|
||||
Int64 processedOutSize;
|
||||
bool finished;
|
||||
CodeOneBlock(out processedInSize, out processedOutSize, out finished);
|
||||
if (finished)
|
||||
{
|
||||
return;
|
||||
@@ -1517,7 +1518,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<long> CodeAsync(Stream inStream, bool final)
|
||||
public long Code(Stream inStream, bool final)
|
||||
{
|
||||
_matchFinder.SetStream(inStream);
|
||||
_processingMode = !final;
|
||||
@@ -1525,7 +1526,10 @@ namespace SharpCompress.Compressors.LZMA
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var (processedInSize, processedOutSize, finished) = await CodeOneBlockAsync();
|
||||
Int64 processedInSize;
|
||||
Int64 processedOutSize;
|
||||
bool finished;
|
||||
CodeOneBlock(out processedInSize, out processedOutSize, out finished);
|
||||
if (finished)
|
||||
{
|
||||
return processedInSize;
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.LZMA.LZ;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA
|
||||
{
|
||||
public class LzmaStream : AsyncStream
|
||||
public class LzmaStream : Stream
|
||||
{
|
||||
private Stream _inputStream;
|
||||
private long _inputSize;
|
||||
private long _outputSize;
|
||||
private readonly Stream _inputStream;
|
||||
private readonly long _inputSize;
|
||||
private readonly long _outputSize;
|
||||
|
||||
private int _dictionarySize;
|
||||
private OutWindow _outWindow = new OutWindow();
|
||||
private RangeCoder.Decoder _rangeDecoder = new RangeCoder.Decoder();
|
||||
private readonly int _dictionarySize;
|
||||
private readonly OutWindow _outWindow = new OutWindow();
|
||||
private readonly RangeCoder.Decoder _rangeDecoder = new RangeCoder.Decoder();
|
||||
private Decoder _decoder;
|
||||
|
||||
private long _position;
|
||||
@@ -29,60 +25,70 @@ namespace SharpCompress.Compressors.LZMA
|
||||
private long _inputPosition;
|
||||
|
||||
// LZMA2
|
||||
private bool _isLzma2;
|
||||
private readonly bool _isLzma2;
|
||||
private bool _uncompressedChunk;
|
||||
private bool _needDictReset = true;
|
||||
private bool _needProps = true;
|
||||
|
||||
private readonly Encoder _encoder;
|
||||
private bool _isDisposed;
|
||||
|
||||
private LzmaStream() {}
|
||||
|
||||
public static async ValueTask<LzmaStream> CreateAsync(byte[] properties, Stream inputStream, long inputSize = -1, long outputSize = -1,
|
||||
Stream presetDictionary = null, bool? isLzma2 = null, CancellationToken cancellationToken = default)
|
||||
public LzmaStream(byte[] properties, Stream inputStream)
|
||||
: this(properties, inputStream, -1, -1, null, properties.Length < 5)
|
||||
{
|
||||
var ls = new LzmaStream();
|
||||
ls._inputStream = inputStream;
|
||||
ls._inputSize = inputSize;
|
||||
ls._outputSize = outputSize;
|
||||
ls._isLzma2 = isLzma2 ?? properties.Length < 5;
|
||||
}
|
||||
|
||||
if (!ls._isLzma2)
|
||||
public LzmaStream(byte[] properties, Stream inputStream, long inputSize)
|
||||
: this(properties, inputStream, inputSize, -1, null, properties.Length < 5)
|
||||
{
|
||||
}
|
||||
|
||||
public LzmaStream(byte[] properties, Stream inputStream, long inputSize, long outputSize)
|
||||
: this(properties, inputStream, inputSize, outputSize, null, properties.Length < 5)
|
||||
{
|
||||
}
|
||||
|
||||
public LzmaStream(byte[] properties, Stream inputStream, long inputSize, long outputSize,
|
||||
Stream presetDictionary, bool isLzma2)
|
||||
{
|
||||
_inputStream = inputStream;
|
||||
_inputSize = inputSize;
|
||||
_outputSize = outputSize;
|
||||
_isLzma2 = isLzma2;
|
||||
|
||||
if (!isLzma2)
|
||||
{
|
||||
ls._dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1));
|
||||
ls._outWindow.Create(ls._dictionarySize);
|
||||
_dictionarySize = BinaryPrimitives.ReadInt32LittleEndian(properties.AsSpan(1));
|
||||
_outWindow.Create(_dictionarySize);
|
||||
if (presetDictionary != null)
|
||||
{
|
||||
ls._outWindow.Train(presetDictionary);
|
||||
_outWindow.Train(presetDictionary);
|
||||
}
|
||||
|
||||
await ls._rangeDecoder.InitAsync(inputStream, cancellationToken);
|
||||
_rangeDecoder.Init(inputStream);
|
||||
|
||||
ls._decoder = new Decoder();
|
||||
ls._decoder.SetDecoderProperties(properties);
|
||||
ls.Properties = properties;
|
||||
_decoder = new Decoder();
|
||||
_decoder.SetDecoderProperties(properties);
|
||||
Properties = properties;
|
||||
|
||||
ls._availableBytes = outputSize < 0 ? long.MaxValue : outputSize;
|
||||
ls._rangeDecoderLimit = inputSize;
|
||||
_availableBytes = outputSize < 0 ? long.MaxValue : outputSize;
|
||||
_rangeDecoderLimit = inputSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
ls. _dictionarySize = 2 | (properties[0] & 1);
|
||||
ls. _dictionarySize <<= (properties[0] >> 1) + 11;
|
||||
_dictionarySize = 2 | (properties[0] & 1);
|
||||
_dictionarySize <<= (properties[0] >> 1) + 11;
|
||||
|
||||
ls._outWindow.Create(ls._dictionarySize);
|
||||
_outWindow.Create(_dictionarySize);
|
||||
if (presetDictionary != null)
|
||||
{
|
||||
ls._outWindow.Train(presetDictionary);
|
||||
ls._needDictReset = false;
|
||||
_outWindow.Train(presetDictionary);
|
||||
_needDictReset = false;
|
||||
}
|
||||
|
||||
ls. Properties = new byte[1];
|
||||
ls._availableBytes = 0;
|
||||
Properties = new byte[1];
|
||||
_availableBytes = 0;
|
||||
}
|
||||
|
||||
return ls;
|
||||
}
|
||||
|
||||
public LzmaStream(LzmaEncoderProperties properties, bool isLzma2, Stream outputStream)
|
||||
@@ -120,25 +126,33 @@ namespace SharpCompress.Compressors.LZMA
|
||||
|
||||
public override bool CanWrite => _encoder != null;
|
||||
|
||||
public override async ValueTask DisposeAsync()
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_isDisposed = true;
|
||||
if (_encoder != null)
|
||||
if (disposing)
|
||||
{
|
||||
_position = await _encoder.CodeAsync(null, true);
|
||||
if (_encoder != null)
|
||||
{
|
||||
_position = _encoder.Code(null, true);
|
||||
}
|
||||
_inputStream?.Dispose();
|
||||
}
|
||||
_inputStream?.DisposeAsync();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
public override long Length => _position + _availableBytes;
|
||||
|
||||
public override long Position { get => _position; set => throw new NotSupportedException(); }
|
||||
|
||||
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_endReached)
|
||||
{
|
||||
@@ -152,7 +166,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
{
|
||||
if (_isLzma2)
|
||||
{
|
||||
await DecodeChunkHeader(cancellationToken);
|
||||
DecodeChunkHeader();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -175,7 +189,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
{
|
||||
_inputPosition += _outWindow.CopyStream(_inputStream, toProcess);
|
||||
}
|
||||
else if (await _decoder.CodeAsync(_dictionarySize, _outWindow, _rangeDecoder, cancellationToken)
|
||||
else if (_decoder.Code(_dictionarySize, _outWindow, _rangeDecoder)
|
||||
&& _outputSize < 0)
|
||||
{
|
||||
_availableBytes = _outWindow.AvailableBytes;
|
||||
@@ -217,7 +231,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
return total;
|
||||
}
|
||||
|
||||
private async ValueTask DecodeChunkHeader(CancellationToken cancellationToken)
|
||||
private void DecodeChunkHeader()
|
||||
{
|
||||
int control = _inputStream.ReadByte();
|
||||
_inputPosition++;
|
||||
@@ -269,7 +283,7 @@ namespace SharpCompress.Compressors.LZMA
|
||||
_decoder.SetDecoderProperties(Properties);
|
||||
}
|
||||
|
||||
await _rangeDecoder.InitAsync(_inputStream, cancellationToken);
|
||||
_rangeDecoder.Init(_inputStream);
|
||||
}
|
||||
else if (control > 0x02)
|
||||
{
|
||||
@@ -293,25 +307,14 @@ namespace SharpCompress.Compressors.LZMA
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_encoder != null)
|
||||
{
|
||||
_position = await _encoder.CodeAsync(new MemoryStream(buffer, offset, count), false);
|
||||
_position = _encoder.Code(new MemoryStream(buffer, offset, count), false);
|
||||
}
|
||||
}
|
||||
|
||||
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = new CancellationToken())
|
||||
{
|
||||
if (_encoder != null)
|
||||
{
|
||||
var m = ArrayPool<byte>.Shared.Rent(buffer.Length);
|
||||
buffer.CopyTo(m.AsMemory().Slice(0, buffer.Length));
|
||||
_position = await _encoder.CodeAsync(new MemoryStream(m, 0, buffer.Length), false);
|
||||
ArrayPool<byte>.Shared.Return(m);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Properties { get; private set; }
|
||||
public byte[] Properties { get; } = new byte[5];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
{
|
||||
internal class Encoder : IAsyncDisposable
|
||||
internal class Encoder
|
||||
{
|
||||
public const uint K_TOP_VALUE = (1 << 24);
|
||||
|
||||
@@ -41,46 +38,43 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
_cache = 0;
|
||||
}
|
||||
|
||||
public async ValueTask FlushData()
|
||||
public void FlushData()
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
await ShiftLowAsync();
|
||||
ShiftLow();
|
||||
}
|
||||
}
|
||||
|
||||
public Task FlushAsync()
|
||||
public void FlushStream()
|
||||
{
|
||||
return _stream.FlushAsync();
|
||||
_stream.Flush();
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
public void CloseStream()
|
||||
{
|
||||
return _stream.DisposeAsync();
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask EncodeAsync(uint start, uint size, uint total)
|
||||
public void Encode(uint start, uint size, uint total)
|
||||
{
|
||||
_low += start * (_range /= total);
|
||||
_range *= size;
|
||||
while (_range < K_TOP_VALUE)
|
||||
{
|
||||
_range <<= 8;
|
||||
await ShiftLowAsync();
|
||||
ShiftLow();
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask ShiftLowAsync()
|
||||
public void ShiftLow()
|
||||
{
|
||||
if ((uint)_low < 0xFF000000 || (uint)(_low >> 32) == 1)
|
||||
{
|
||||
using var buffer = MemoryPool<byte>.Shared.Rent(1);
|
||||
var b = buffer.Memory.Slice(0,1);
|
||||
byte temp = _cache;
|
||||
do
|
||||
{
|
||||
b.Span[0] = (byte)(temp + (_low >> 32));
|
||||
await _stream.WriteAsync(b);
|
||||
_stream.WriteByte((byte)(temp + (_low >> 32)));
|
||||
temp = 0xFF;
|
||||
}
|
||||
while (--_cacheSize != 0);
|
||||
@@ -90,7 +84,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
_low = ((uint)_low) << 8;
|
||||
}
|
||||
|
||||
public async ValueTask EncodeDirectBits(uint v, int numTotalBits)
|
||||
public void EncodeDirectBits(uint v, int numTotalBits)
|
||||
{
|
||||
for (int i = numTotalBits - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -102,12 +96,12 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
if (_range < K_TOP_VALUE)
|
||||
{
|
||||
_range <<= 8;
|
||||
await ShiftLowAsync();
|
||||
ShiftLow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask EncodeBitAsync(uint size0, int numTotalBits, uint symbol)
|
||||
public void EncodeBit(uint size0, int numTotalBits, uint symbol)
|
||||
{
|
||||
uint newBound = (_range >> numTotalBits) * size0;
|
||||
if (symbol == 0)
|
||||
@@ -122,7 +116,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
while (_range < K_TOP_VALUE)
|
||||
{
|
||||
_range <<= 8;
|
||||
await ShiftLowAsync();
|
||||
ShiftLow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +129,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
}
|
||||
}
|
||||
|
||||
internal class Decoder: IAsyncDisposable
|
||||
internal class Decoder
|
||||
{
|
||||
public const uint K_TOP_VALUE = (1 << 24);
|
||||
public uint _range;
|
||||
@@ -145,7 +139,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
public Stream _stream;
|
||||
public long _total;
|
||||
|
||||
public async ValueTask InitAsync(Stream stream, CancellationToken cancellationToken)
|
||||
public void Init(Stream stream)
|
||||
{
|
||||
// Stream.Init(stream);
|
||||
_stream = stream;
|
||||
@@ -154,7 +148,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
_range = 0xFFFFFFFF;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
_code = (_code << 8) | await _stream.ReadByteAsync(cancellationToken);
|
||||
_code = (_code << 8) | (byte)_stream.ReadByte();
|
||||
}
|
||||
_total = 5;
|
||||
}
|
||||
@@ -165,34 +159,44 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
_stream = null;
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
public void CloseStream()
|
||||
{
|
||||
return _stream.DisposeAsync();
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
public async ValueTask NormalizeAsync(CancellationToken cancellationToken)
|
||||
public void Normalize()
|
||||
{
|
||||
while (_range < K_TOP_VALUE)
|
||||
{
|
||||
_code = (_code << 8) | await _stream.ReadByteAsync(cancellationToken);
|
||||
_code = (_code << 8) | (byte)_stream.ReadByte();
|
||||
_range <<= 8;
|
||||
_total++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Normalize2()
|
||||
{
|
||||
if (_range < K_TOP_VALUE)
|
||||
{
|
||||
_code = (_code << 8) | (byte)_stream.ReadByte();
|
||||
_range <<= 8;
|
||||
_total++;
|
||||
}
|
||||
}
|
||||
|
||||
public uint GetThreshold(uint total)
|
||||
{
|
||||
return _code / (_range /= total);
|
||||
}
|
||||
|
||||
public async ValueTask DecodeAsync(uint start, uint size, CancellationToken cancellationToken)
|
||||
public void Decode(uint start, uint size)
|
||||
{
|
||||
_code -= start * _range;
|
||||
_range *= size;
|
||||
await NormalizeAsync(cancellationToken);
|
||||
Normalize();
|
||||
}
|
||||
|
||||
public async ValueTask<uint> DecodeDirectBitsAsync(int numTotalBits, CancellationToken cancellationToken)
|
||||
public uint DecodeDirectBits(int numTotalBits)
|
||||
{
|
||||
uint range = _range;
|
||||
uint code = _code;
|
||||
@@ -214,7 +218,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
|
||||
if (range < K_TOP_VALUE)
|
||||
{
|
||||
code = (code << 8) | await _stream.ReadByteAsync(cancellationToken);
|
||||
code = (code << 8) | (byte)_stream.ReadByte();
|
||||
range <<= 8;
|
||||
_total++;
|
||||
}
|
||||
@@ -224,7 +228,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
return result;
|
||||
}
|
||||
|
||||
public async ValueTask<uint> DecodeBitAsync(uint size0, int numTotalBits, CancellationToken cancellationToken)
|
||||
public uint DecodeBit(uint size0, int numTotalBits)
|
||||
{
|
||||
uint newBound = (_range >> numTotalBits) * size0;
|
||||
uint symbol;
|
||||
@@ -239,7 +243,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
_code -= newBound;
|
||||
_range -= newBound;
|
||||
}
|
||||
await NormalizeAsync(cancellationToken);
|
||||
Normalize();
|
||||
return symbol;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
{
|
||||
@@ -32,7 +29,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask EncodeAsync(Encoder encoder, uint symbol)
|
||||
public void Encode(Encoder encoder, uint symbol)
|
||||
{
|
||||
// encoder.EncodeBit(Prob, kNumBitModelTotalBits, symbol);
|
||||
// UpdateModel(symbol);
|
||||
@@ -51,7 +48,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
if (encoder._range < Encoder.K_TOP_VALUE)
|
||||
{
|
||||
encoder._range <<= 8;
|
||||
await encoder.ShiftLowAsync();
|
||||
encoder.ShiftLow();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +110,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
_prob = K_BIT_MODEL_TOTAL >> 1;
|
||||
}
|
||||
|
||||
public async ValueTask<uint> DecodeAsync(Decoder rangeDecoder, CancellationToken cancellationToken)
|
||||
public uint Decode(Decoder rangeDecoder)
|
||||
{
|
||||
uint newBound = (rangeDecoder._range >> K_NUM_BIT_MODEL_TOTAL_BITS) * _prob;
|
||||
if (rangeDecoder._code < newBound)
|
||||
@@ -122,7 +119,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
_prob += (K_BIT_MODEL_TOTAL - _prob) >> K_NUM_MOVE_BITS;
|
||||
if (rangeDecoder._range < Decoder.K_TOP_VALUE)
|
||||
{
|
||||
rangeDecoder._code = (rangeDecoder._code << 8) | await rangeDecoder._stream.ReadByteAsync(cancellationToken);
|
||||
rangeDecoder._code = (rangeDecoder._code << 8) | (byte)rangeDecoder._stream.ReadByte();
|
||||
rangeDecoder._range <<= 8;
|
||||
rangeDecoder._total++;
|
||||
}
|
||||
@@ -133,7 +130,7 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder
|
||||
_prob -= (_prob) >> K_NUM_MOVE_BITS;
|
||||
if (rangeDecoder._range < Decoder.K_TOP_VALUE)
|
||||
{
|
||||
rangeDecoder._code = (rangeDecoder._code << 8) | await rangeDecoder._stream.ReadByteAsync(cancellationToken);
|
||||
rangeDecoder._code = (rangeDecoder._code << 8) | (byte)rangeDecoder._stream.ReadByte();
|
||||
rangeDecoder._range <<= 8;
|
||||
rangeDecoder._total++;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user