mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-11-15 17:02:03 +00:00
Merge remote-tracking branch 'origin/release'
# Conflicts: # src/SharpCompress/Compressors/LZMA/LZ/LzOutWindow.cs # src/SharpCompress/Compressors/LZMA/LzmaStream.cs
This commit is contained in:
@@ -37,6 +37,12 @@ public partial class ArcFilePart
|
||||
.ConfigureAwait(false);
|
||||
break;
|
||||
case CompressionType.Crunched:
|
||||
if (Header.CompressedSize > int.MaxValue)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"ARC: compressed size exceeds the supported range."
|
||||
);
|
||||
}
|
||||
if (Header.OriginalSize > 128 * 1024)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
|
||||
@@ -52,6 +52,12 @@ public partial class ArcFilePart : FilePart
|
||||
compressedStream = SqueezeStream.Create(_stream, (int)Header.CompressedSize);
|
||||
break;
|
||||
case CompressionType.Crunched:
|
||||
if (Header.CompressedSize > int.MaxValue)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
"ARC: compressed size exceeds the supported range."
|
||||
);
|
||||
}
|
||||
if (Header.OriginalSize > 128 * 1024)
|
||||
{
|
||||
throw new NotSupportedException(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
@@ -8,7 +9,9 @@ internal partial class Zip64DirectoryEndHeader
|
||||
{
|
||||
internal override async ValueTask Read(AsyncBinaryReader reader)
|
||||
{
|
||||
SizeOfDirectoryEndRecord = (long)await reader.ReadUInt64Async().ConfigureAwait(false);
|
||||
SizeOfDirectoryEndRecord = ValidateRecordSize(
|
||||
await reader.ReadUInt64Async().ConfigureAwait(false)
|
||||
);
|
||||
VersionMadeBy = await reader.ReadUInt16Async().ConfigureAwait(false);
|
||||
VersionNeededToExtract = await reader.ReadUInt16Async().ConfigureAwait(false);
|
||||
VolumeNumber = await reader.ReadUInt32Async().ConfigureAwait(false);
|
||||
@@ -18,10 +21,25 @@ internal partial class Zip64DirectoryEndHeader
|
||||
DirectorySize = (long)await reader.ReadUInt64Async().ConfigureAwait(false);
|
||||
DirectoryStartOffsetRelativeToDisk = (long)
|
||||
await reader.ReadUInt64Async().ConfigureAwait(false);
|
||||
var size = (int)(
|
||||
SizeOfDirectoryEndRecord - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS
|
||||
);
|
||||
DataSector = new byte[size];
|
||||
await reader.ReadBytesAsync(DataSector, 0, size).ConfigureAwait(false);
|
||||
var remaining =
|
||||
SizeOfDirectoryEndRecord - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS;
|
||||
if (remaining == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var buffer = _pool.Rent(4096);
|
||||
try
|
||||
{
|
||||
while (remaining > 0)
|
||||
{
|
||||
var size = (int)Math.Min(4096, remaining);
|
||||
await reader.ReadBytesAsync(buffer, 0, size).ConfigureAwait(false);
|
||||
remaining -= size;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pool.Return(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
|
||||
namespace SharpCompress.Common.Zip.Headers;
|
||||
|
||||
internal partial class Zip64DirectoryEndHeader : ZipHeader
|
||||
{
|
||||
private readonly ArrayPool<byte> _pool;
|
||||
|
||||
public Zip64DirectoryEndHeader()
|
||||
: base(ZipHeaderType.Zip64DirectoryEnd) { }
|
||||
: this(ArrayPool<byte>.Shared) { }
|
||||
|
||||
internal Zip64DirectoryEndHeader(ArrayPool<byte> pool)
|
||||
: base(ZipHeaderType.Zip64DirectoryEnd) => _pool = pool;
|
||||
|
||||
internal override void Read(BinaryReader reader)
|
||||
{
|
||||
SizeOfDirectoryEndRecord = (long)reader.ReadUInt64();
|
||||
SizeOfDirectoryEndRecord = ValidateRecordSize(reader.ReadUInt64());
|
||||
VersionMadeBy = reader.ReadUInt16();
|
||||
VersionNeededToExtract = reader.ReadUInt16();
|
||||
VolumeNumber = reader.ReadUInt32();
|
||||
@@ -18,12 +25,42 @@ internal partial class Zip64DirectoryEndHeader : ZipHeader
|
||||
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
|
||||
)
|
||||
);
|
||||
// Extensible data is not interpreted. Consume it without allocating from its declared size.
|
||||
var remaining =
|
||||
SizeOfDirectoryEndRecord - SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS;
|
||||
if (remaining == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var buffer = _pool.Rent(4096);
|
||||
try
|
||||
{
|
||||
while (remaining > 0)
|
||||
{
|
||||
var read = reader.Read(buffer, 0, (int)Math.Min(4096, remaining));
|
||||
if (read == 0)
|
||||
{
|
||||
throw new IncompleteArchiveException("Truncated ZIP64 extensible data.");
|
||||
}
|
||||
remaining -= read;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pool.Return(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private static long ValidateRecordSize(ulong size)
|
||||
{
|
||||
if (
|
||||
size < SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS
|
||||
|| size > long.MaxValue
|
||||
)
|
||||
{
|
||||
throw new InvalidFormatException("Invalid ZIP64 end record size.");
|
||||
}
|
||||
return (long)size;
|
||||
}
|
||||
|
||||
private const int SIZE_OF_FIXED_HEADER_DATA_EXCEPT_SIGNATURE_AND_SIZE_FIELDS = 44;
|
||||
@@ -45,6 +82,4 @@ internal partial class Zip64DirectoryEndHeader : ZipHeader
|
||||
public long DirectorySize { get; private set; }
|
||||
|
||||
public long DirectoryStartOffsetRelativeToDisk { get; private set; }
|
||||
|
||||
public byte[]? DataSector { get; private set; }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.ArcLzw;
|
||||
|
||||
@@ -15,37 +17,19 @@ public partial class ArcLzwStream
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
if (_processed)
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_processed = true;
|
||||
var data = new byte[_compressedSize];
|
||||
int totalRead = 0;
|
||||
while (totalRead < _compressedSize)
|
||||
if (!_processed)
|
||||
{
|
||||
int read = await _stream
|
||||
.ReadAsync(data, totalRead, _compressedSize - totalRead, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
totalRead += read;
|
||||
using var limited = new ReadOnlySubStream(_stream, _compressedSize);
|
||||
using var data = new PooledMemoryStream();
|
||||
await limited.CopyToAsync(data, 81920, cancellationToken).ConfigureAwait(false);
|
||||
FinishDecompression(data);
|
||||
}
|
||||
var decoded = Decompress(data, _useCrunched);
|
||||
var result = decoded.Count;
|
||||
if (_useCrunched)
|
||||
{
|
||||
var unpacked = RLE.UnpackRLE(decoded.ToArray());
|
||||
unpacked.CopyTo(buffer, 0);
|
||||
result = unpacked.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
decoded.CopyTo(buffer, 0);
|
||||
}
|
||||
return result;
|
||||
return Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
#if !LEGACY_DOTNET
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.RLE90;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.ArcLzw;
|
||||
|
||||
@@ -13,6 +14,8 @@ public partial class ArcLzwStream : Stream
|
||||
private bool _processed;
|
||||
private bool _useCrunched;
|
||||
private int _compressedSize;
|
||||
private byte[]? _decoded;
|
||||
private int _decodedPosition;
|
||||
|
||||
private const int BITS = 12;
|
||||
private const int CRUNCH_BITS = 12;
|
||||
@@ -34,6 +37,10 @@ public partial class ArcLzwStream : Stream
|
||||
|
||||
public ArcLzwStream(Stream stream, int compressedSize, bool useCrunched = true)
|
||||
{
|
||||
if (compressedSize < 0)
|
||||
{
|
||||
throw new InvalidFormatException("ARC: invalid compressed size.");
|
||||
}
|
||||
_stream = stream;
|
||||
_useCrunched = useCrunched;
|
||||
_compressedSize = compressedSize;
|
||||
@@ -177,28 +184,35 @@ public partial class ArcLzwStream : Stream
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (_processed)
|
||||
if (count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_processed = true;
|
||||
var data = new byte[_compressedSize];
|
||||
_stream.Read(data, 0, _compressedSize);
|
||||
var decoded = Decompress(data, _useCrunched);
|
||||
var result = decoded.Count;
|
||||
if (_useCrunched)
|
||||
if (!_processed)
|
||||
{
|
||||
var unpacked = RLE.UnpackRLE(decoded.ToArray());
|
||||
unpacked.CopyTo(buffer, 0);
|
||||
result = unpacked.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
decoded.CopyTo(buffer, 0);
|
||||
using var limited = new ReadOnlySubStream(_stream, _compressedSize);
|
||||
using var data = new PooledMemoryStream();
|
||||
limited.CopyTo(data);
|
||||
FinishDecompression(data);
|
||||
}
|
||||
var result = Math.Min(count, _decoded!.Length - _decodedPosition);
|
||||
Buffer.BlockCopy(_decoded, _decodedPosition, buffer, offset, result);
|
||||
_decodedPosition += result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private void FinishDecompression(PooledMemoryStream data)
|
||||
{
|
||||
if (data.Length != _compressedSize)
|
||||
{
|
||||
throw new IncompleteArchiveException("Truncated ARC compressed data.");
|
||||
}
|
||||
// Buffer only bytes actually read, never the size claimed by the ARC header.
|
||||
var decoded = Decompress(data.ToArray(), _useCrunched);
|
||||
_decoded = _useCrunched ? RLE.UnpackRLE(decoded.ToArray()).ToArray() : decoded.ToArray();
|
||||
_processed = true;
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ internal partial class OutWindow : IAsyncDisposable
|
||||
var fastSize = Math.Min(Math.Min(sizeUntilWindowEnd, sizeUntilOverlap), targetSize);
|
||||
if (fastSize >= 2)
|
||||
{
|
||||
EnsureCapacity(_pos + fastSize);
|
||||
_buffer.AsSpan(pos, fastSize).CopyTo(_buffer.AsSpan(_pos, fastSize));
|
||||
_pos += fastSize;
|
||||
pos += fastSize;
|
||||
@@ -120,6 +121,7 @@ internal partial class OutWindow : IAsyncDisposable
|
||||
|
||||
public async ValueTask PutByteAsync(byte b, CancellationToken cancellationToken = default)
|
||||
{
|
||||
EnsureCapacity(_pos + 1);
|
||||
_buffer[_pos++] = b;
|
||||
_total++;
|
||||
if (_pos >= _windowSize)
|
||||
@@ -139,7 +141,7 @@ internal partial class OutWindow : IAsyncDisposable
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var curSize = _windowSize - _pos;
|
||||
var curSize = Math.Min(4096, _windowSize - _pos);
|
||||
if (curSize > _limit - _total)
|
||||
{
|
||||
curSize = (int)(_limit - _total);
|
||||
@@ -148,6 +150,7 @@ internal partial class OutWindow : IAsyncDisposable
|
||||
{
|
||||
curSize = size;
|
||||
}
|
||||
EnsureCapacity(_pos + curSize);
|
||||
var numReadBytes = await stream
|
||||
.ReadAsync(_buffer, _pos, curSize, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -173,7 +176,7 @@ internal partial class OutWindow : IAsyncDisposable
|
||||
stream.Position = len - size;
|
||||
_total = 0;
|
||||
_limit = size;
|
||||
_pos = _windowSize - size;
|
||||
_pos = 0;
|
||||
await CopyStreamAsync(stream, size).ConfigureAwait(false);
|
||||
if (_pos == _windowSize)
|
||||
{
|
||||
|
||||
@@ -51,6 +51,8 @@ internal partial class OutWindow : IDisposable
|
||||
internal int FastWindowSize => _windowSize;
|
||||
internal long FastLimit => _limit;
|
||||
|
||||
internal void EnsureFastCapacity() => EnsureCapacity(_pos + 1);
|
||||
|
||||
internal void FastFlush() => Flush();
|
||||
|
||||
internal void SetPendingFast(int distance, int len)
|
||||
@@ -60,23 +62,24 @@ internal partial class OutWindow : IDisposable
|
||||
}
|
||||
#endif
|
||||
|
||||
internal int BufferCapacity => _buffer?.Length ?? 0;
|
||||
|
||||
public void Create(int windowSize)
|
||||
{
|
||||
if (windowSize <= 0)
|
||||
{
|
||||
throw new InvalidFormatException($"LZMA: invalid dictionary size {windowSize}");
|
||||
}
|
||||
if (_windowSize != windowSize)
|
||||
if (_windowSize != windowSize || _buffer is null)
|
||||
{
|
||||
// Allocate first so failed growth leaves the existing buffer owned by this window.
|
||||
var replacement = _pool.Rent(windowSize);
|
||||
var replacement = _pool.Rent(Math.Min(windowSize, 4096));
|
||||
if (_buffer is not null)
|
||||
{
|
||||
_pool.Return(_buffer);
|
||||
}
|
||||
_buffer = replacement;
|
||||
}
|
||||
_buffer[windowSize - 1] = 0;
|
||||
_windowSize = windowSize;
|
||||
_pos = 0;
|
||||
_streamPos = 0;
|
||||
@@ -85,6 +88,20 @@ internal partial class OutWindow : IDisposable
|
||||
_limit = 0;
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int required)
|
||||
{
|
||||
if (required <= _buffer.Length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Grow from emitted history, never from the advertised dictionary or output size.
|
||||
var capacity = (int)Math.Min(_windowSize, Math.Max(required, (long)_buffer.Length * 2));
|
||||
var replacement = _pool.Rent(capacity);
|
||||
_buffer.AsSpan(0, _pos).CopyTo(replacement);
|
||||
_pool.Return(_buffer);
|
||||
_buffer = replacement;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
@@ -122,7 +139,7 @@ internal partial class OutWindow : IDisposable
|
||||
stream.Position = len - size;
|
||||
_total = 0;
|
||||
_limit = size;
|
||||
_pos = _windowSize - size;
|
||||
_pos = 0;
|
||||
CopyStream(stream, size);
|
||||
if (_pos == _windowSize)
|
||||
{
|
||||
@@ -186,6 +203,7 @@ internal partial class OutWindow : IDisposable
|
||||
var fastSize = Math.Min(Math.Min(sizeUntilWindowEnd, sizeUntilOverlap), targetSize);
|
||||
if (fastSize >= 2)
|
||||
{
|
||||
EnsureCapacity(_pos + fastSize);
|
||||
_buffer.AsSpan(pos, fastSize).CopyTo(_buffer.AsSpan(_pos, fastSize));
|
||||
_pos += fastSize;
|
||||
pos += fastSize;
|
||||
@@ -211,6 +229,7 @@ internal partial class OutWindow : IDisposable
|
||||
|
||||
public void PutByte(byte b)
|
||||
{
|
||||
EnsureCapacity(_pos + 1);
|
||||
_buffer[_pos++] = b;
|
||||
_total++;
|
||||
if (_pos >= _windowSize)
|
||||
@@ -221,6 +240,11 @@ internal partial class OutWindow : IDisposable
|
||||
|
||||
public byte GetByte(int distance)
|
||||
{
|
||||
// The initial literal context is zero; no dictionary history has been emitted yet.
|
||||
if (_total == 0 && distance == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
var pos = _pos - distance - 1;
|
||||
if (pos < 0)
|
||||
{
|
||||
@@ -234,7 +258,7 @@ internal partial class OutWindow : IDisposable
|
||||
var size = len;
|
||||
while (size > 0 && _pos < _windowSize && _total < _limit)
|
||||
{
|
||||
var curSize = _windowSize - _pos;
|
||||
var curSize = Math.Min(4096, _windowSize - _pos);
|
||||
if (curSize > _limit - _total)
|
||||
{
|
||||
curSize = (int)(_limit - _total);
|
||||
@@ -243,6 +267,7 @@ internal partial class OutWindow : IDisposable
|
||||
{
|
||||
curSize = size;
|
||||
}
|
||||
EnsureCapacity(_pos + curSize);
|
||||
var numReadBytes = stream.Read(_buffer, _pos, curSize);
|
||||
if (numReadBytes == 0)
|
||||
{
|
||||
|
||||
@@ -151,6 +151,10 @@ public partial class Decoder
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly byte GetByte(int distance)
|
||||
{
|
||||
if (Total == 0 && distance == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
// A previous decoding failure can leave a rep distance outside the dictionary.
|
||||
// Unlike a managed array access, this pointer read has no implicit bounds check.
|
||||
if ((uint)distance >= (uint)WindowSize)
|
||||
@@ -228,11 +232,31 @@ public partial class Decoder
|
||||
OutWindow outWindow,
|
||||
RangeCoder.Decoder rangeDecoder
|
||||
)
|
||||
{
|
||||
do
|
||||
{
|
||||
outWindow.CopyPending();
|
||||
if (!outWindow.HasSpace)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Grow only after filling the current allocation, and never while it is pinned.
|
||||
outWindow.EnsureFastCapacity();
|
||||
if (CodeFastCore(dictionarySize, outWindow, rangeDecoder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} while (outWindow.HasSpace);
|
||||
return false;
|
||||
}
|
||||
|
||||
private unsafe bool CodeFastCore(
|
||||
int dictionarySize,
|
||||
OutWindow outWindow,
|
||||
RangeCoder.Decoder rangeDecoder
|
||||
)
|
||||
{
|
||||
var dictionarySizeCheck = Math.Max(dictionarySize, 1);
|
||||
|
||||
outWindow.CopyPending();
|
||||
|
||||
var dic = outWindow.FastBuffer;
|
||||
var inBuf = rangeDecoder.FastBufferArray;
|
||||
bool result;
|
||||
@@ -270,7 +294,11 @@ public partial class Decoder
|
||||
Pos = outWindow.FastPos,
|
||||
Total = outWindow.FastTotal,
|
||||
WindowSize = outWindow.FastWindowSize,
|
||||
Limit = outWindow.FastLimit,
|
||||
// The next decode call can grow the dictionary after this pin is released.
|
||||
Limit = Math.Min(
|
||||
outWindow.FastLimit,
|
||||
outWindow.FastTotal + dic.Length - outWindow.FastPos
|
||||
),
|
||||
OutWindow = outWindow,
|
||||
};
|
||||
|
||||
|
||||
@@ -215,6 +215,11 @@ public partial class LzmaStream
|
||||
}
|
||||
}
|
||||
|
||||
if (_availableBytes < 0)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
|
||||
var toProcess = count - total;
|
||||
if (toProcess > _availableBytes)
|
||||
{
|
||||
@@ -238,6 +243,10 @@ public partial class LzmaStream
|
||||
}
|
||||
|
||||
var read = _outWindow.Read(buffer, offset, toProcess);
|
||||
if (read == 0 && _availableBytes > 0)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
total += read;
|
||||
offset += read;
|
||||
_position += read;
|
||||
@@ -337,6 +346,11 @@ public partial class LzmaStream
|
||||
}
|
||||
}
|
||||
|
||||
if (_availableBytes < 0)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
|
||||
var toProcess = count - total;
|
||||
if (toProcess > _availableBytes)
|
||||
{
|
||||
@@ -360,6 +374,10 @@ public partial class LzmaStream
|
||||
}
|
||||
|
||||
var read = _outWindow.Read(buffer, offset, toProcess);
|
||||
if (read == 0 && _availableBytes > 0)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
total += read;
|
||||
offset += read;
|
||||
_position += read;
|
||||
|
||||
@@ -73,6 +73,11 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
|
||||
}
|
||||
else
|
||||
{
|
||||
// Larger standard dictionaries exceed this decoder's positive Int32 window range.
|
||||
if (properties[0] > 37)
|
||||
{
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
_dictionarySize = 2 | (properties[0] & 1);
|
||||
_dictionarySize <<= (properties[0] >> 1) + 11;
|
||||
|
||||
@@ -338,6 +343,11 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
if (_availableBytes < 0)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
|
||||
var toProcess = count - total;
|
||||
if (toProcess > _availableBytes)
|
||||
{
|
||||
@@ -355,6 +365,11 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
|
||||
}
|
||||
|
||||
var read = _outWindow.Read(buffer, offset, toProcess);
|
||||
// Unknown-size raw LZMA can end without output; otherwise no progress is corruption.
|
||||
if (read == 0 && _availableBytes > 0)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
total += read;
|
||||
offset += read;
|
||||
_position += read;
|
||||
@@ -507,10 +522,21 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
private int ReadChunkHeaderByte()
|
||||
{
|
||||
var value = _inputStream!.ReadByte();
|
||||
// EOF is not an LZMA2 end marker and must never enter chunk-size arithmetic.
|
||||
if (value < 0)
|
||||
{
|
||||
throw new DataErrorException();
|
||||
}
|
||||
_inputPosition++;
|
||||
return value;
|
||||
}
|
||||
|
||||
private void DecodeChunkHeader()
|
||||
{
|
||||
var control = _inputStream!.ReadByte();
|
||||
_inputPosition++;
|
||||
var control = ReadChunkHeaderByte();
|
||||
|
||||
if (control == 0x00)
|
||||
{
|
||||
@@ -539,17 +565,14 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
|
||||
_uncompressedChunk = false;
|
||||
|
||||
_availableBytes = (control & 0x1F) << 16;
|
||||
_availableBytes += (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1;
|
||||
_inputPosition += 2;
|
||||
_availableBytes += (ReadChunkHeaderByte() << 8) + ReadChunkHeaderByte() + 1;
|
||||
|
||||
_rangeDecoderLimit = (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1;
|
||||
_inputPosition += 2;
|
||||
_rangeDecoderLimit = (ReadChunkHeaderByte() << 8) + ReadChunkHeaderByte() + 1;
|
||||
|
||||
if (control >= 0xC0)
|
||||
{
|
||||
_needProps = false;
|
||||
Properties[0] = (byte)_inputStream.ReadByte();
|
||||
_inputPosition++;
|
||||
Properties[0] = (byte)ReadChunkHeaderByte();
|
||||
|
||||
_decoder = new Decoder();
|
||||
_decoder.SetDecoderProperties(Properties);
|
||||
@@ -580,8 +603,7 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
|
||||
else
|
||||
{
|
||||
_uncompressedChunk = true;
|
||||
_availableBytes = (_inputStream.ReadByte() << 8) + _inputStream.ReadByte() + 1;
|
||||
_inputPosition += 2;
|
||||
_availableBytes = (ReadChunkHeaderByte() << 8) + ReadChunkHeaderByte() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -206,7 +206,8 @@ public class PpmdStream : Stream, IAsyncDisposable
|
||||
_isDisposed = true;
|
||||
if (_compress)
|
||||
{
|
||||
await _model.EncodeBlockAsync(_stream, new MemoryStream(), true).ConfigureAwait(false);
|
||||
using var input = new PooledMemoryStream();
|
||||
await _model.EncodeBlockAsync(_stream, input, true).ConfigureAwait(false);
|
||||
}
|
||||
_modelH?.Dispose();
|
||||
_modelH = null;
|
||||
|
||||
@@ -9,6 +9,7 @@ internal class BitStream
|
||||
private int _bitIdx;
|
||||
private int _bitsLeft;
|
||||
private ulong _bitBuffer;
|
||||
private bool _lastReadValid;
|
||||
private static uint[] _maskBits = new uint[17]
|
||||
{
|
||||
0U,
|
||||
@@ -62,6 +63,11 @@ internal class BitStream
|
||||
_bitsLeft += 8;
|
||||
}
|
||||
}
|
||||
_lastReadValid = _bitsLeft >= nbits;
|
||||
if (!_lastReadValid)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
result = (int)(_bitBuffer & _maskBits[nbits]);
|
||||
_bitBuffer >>= nbits;
|
||||
_bitsLeft -= nbits;
|
||||
@@ -70,10 +76,6 @@ internal class BitStream
|
||||
|
||||
public bool Advance(int count)
|
||||
{
|
||||
if (_byteIdx > _srcLen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return _lastReadValid;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
|
||||
namespace SharpCompress.Compressors.Shrink;
|
||||
|
||||
@@ -238,6 +239,63 @@ public class HwUnshrink
|
||||
byte[] dst,
|
||||
int dstCap,
|
||||
out int dstUsed
|
||||
) => UnshrinkCore(src, srcLen, out srcUsed, ref dst, dstCap, out dstUsed, null);
|
||||
|
||||
internal static UnshrnkStatus UnshrinkGrowing(
|
||||
byte[] src,
|
||||
int srcLen,
|
||||
int dstCap,
|
||||
ArrayPool<byte> pool,
|
||||
out byte[] dst,
|
||||
out int dstUsed
|
||||
)
|
||||
{
|
||||
dst = Array.Empty<byte>();
|
||||
try
|
||||
{
|
||||
return UnshrinkCore(src, srcLen, out _, ref dst, dstCap, out dstUsed, pool);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (dst.Length > 0)
|
||||
{
|
||||
pool.Return(dst);
|
||||
dst = Array.Empty<byte>();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReserveOutput(ref byte[] dst, int dstPos, int dstCap, ArrayPool<byte>? pool)
|
||||
{
|
||||
if (pool is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// A code string is bounded by the code table's ushort length. Reserve at most
|
||||
// one string ahead of actual output, not the size advertised by the ZIP header.
|
||||
var required = (int)Math.Min(dstCap, (long)dstPos + ushort.MaxValue + 1);
|
||||
if (required > dst.Length)
|
||||
{
|
||||
var capacity = (int)Math.Min(dstCap, Math.Max(required, (long)dst.Length * 2));
|
||||
var replacement = pool.Rent(capacity);
|
||||
dst.AsSpan(0, dstPos).CopyTo(replacement);
|
||||
if (dst.Length > 0)
|
||||
{
|
||||
pool.Return(dst);
|
||||
}
|
||||
dst = replacement;
|
||||
}
|
||||
}
|
||||
|
||||
private static UnshrnkStatus UnshrinkCore(
|
||||
byte[] src,
|
||||
int srcLen,
|
||||
out int srcUsed,
|
||||
ref byte[] dst,
|
||||
int dstCap,
|
||||
out int dstUsed,
|
||||
ArrayPool<byte>? pool
|
||||
)
|
||||
{
|
||||
var codeTab = new CodeTabEntry[HASHTAB_SIZE];
|
||||
@@ -279,6 +337,7 @@ public class HwUnshrink
|
||||
return UnshrnkStatus.Full;
|
||||
}
|
||||
|
||||
ReserveOutput(ref dst, dstPos, dstCap, pool);
|
||||
firstByte = (byte)currCode;
|
||||
dst[dstPos] = (byte)currCode;
|
||||
codeTab[currCode].lastDstPos = dstPos;
|
||||
@@ -301,6 +360,7 @@ public class HwUnshrink
|
||||
return UnshrnkStatus.Full;
|
||||
}
|
||||
|
||||
ReserveOutput(ref dst, dstPos, dstCap, pool);
|
||||
// Handle KwKwK: next code used before being added.
|
||||
if (currCode == queue.codes[queue.nextIdx])
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Shrink;
|
||||
|
||||
@@ -28,15 +29,10 @@ internal partial class ShrinkStream : Stream
|
||||
// Read actual compressed data from the stream rather than pre-allocating based on the
|
||||
// declared compressed size, which may be crafted to cause an OutOfMemoryException.
|
||||
// The stream is already bounded by ReadOnlySubStream in ZipFilePart.
|
||||
using var srcMs = new MemoryStream();
|
||||
using var srcMs = new PooledMemoryStream();
|
||||
await _inStream.CopyToAsync(srcMs, 81920, cancellationToken).ConfigureAwait(false);
|
||||
var src = srcMs.ToArray();
|
||||
var srcLen = src.Length;
|
||||
|
||||
// Decompress synchronously (CPU-bound operation)
|
||||
HwUnshrink.Unshrink(src, srcLen, out _, _byteOut, (int)_uncompressedSize, out var dstUsed);
|
||||
_outBytesCount = dstUsed;
|
||||
_decompressed = true;
|
||||
// Decompress synchronously (CPU-bound operation).
|
||||
Decode(srcMs);
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
@@ -46,6 +42,7 @@ internal partial class ShrinkStream : Stream
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!_decompressed)
|
||||
@@ -72,6 +69,7 @@ internal partial class ShrinkStream : Stream
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (!_decompressed)
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.IO;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Compressors.Shrink;
|
||||
|
||||
internal partial class ShrinkStream : Stream
|
||||
{
|
||||
private readonly Stream _inStream;
|
||||
private readonly ArrayPool<byte> _pool;
|
||||
|
||||
private readonly long _uncompressedSize;
|
||||
private readonly byte[] _byteOut;
|
||||
private byte[] _byteOut = Array.Empty<byte>();
|
||||
private long _outBytesCount;
|
||||
private bool _decompressed;
|
||||
private bool _disposed;
|
||||
private long _position;
|
||||
|
||||
public ShrinkStream(Stream stream, long uncompressedSize)
|
||||
: this(stream, uncompressedSize, ArrayPool<byte>.Shared) { }
|
||||
|
||||
internal ShrinkStream(Stream stream, long uncompressedSize, ArrayPool<byte> pool)
|
||||
{
|
||||
if (uncompressedSize > int.MaxValue)
|
||||
if (uncompressedSize < 0 || uncompressedSize > int.MaxValue)
|
||||
{
|
||||
throw new InvalidFormatException(
|
||||
$"Shrink: declared uncompressed size {uncompressedSize} exceeds maximum supported size."
|
||||
@@ -24,13 +31,13 @@ internal partial class ShrinkStream : Stream
|
||||
}
|
||||
|
||||
_inStream = stream;
|
||||
_pool = pool;
|
||||
|
||||
_uncompressedSize = uncompressedSize;
|
||||
_byteOut = new byte[(int)_uncompressedSize];
|
||||
_outBytesCount = 0L;
|
||||
}
|
||||
|
||||
public override bool CanRead => true;
|
||||
public override bool CanRead => !_disposed;
|
||||
|
||||
public override bool CanSeek => true;
|
||||
|
||||
@@ -48,27 +55,15 @@ internal partial class ShrinkStream : Stream
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (!_decompressed)
|
||||
{
|
||||
// Read actual compressed data from the stream rather than pre-allocating based on the
|
||||
// declared compressed size, which may be crafted to cause an OutOfMemoryException.
|
||||
// The stream is already bounded by ReadOnlySubStream in ZipFilePart.
|
||||
using var srcMs = new MemoryStream();
|
||||
using var srcMs = new PooledMemoryStream();
|
||||
_inStream.CopyTo(srcMs);
|
||||
var src = srcMs.ToArray();
|
||||
var srcLen = src.Length;
|
||||
|
||||
HwUnshrink.Unshrink(
|
||||
src,
|
||||
srcLen,
|
||||
out _,
|
||||
_byteOut,
|
||||
(int)_uncompressedSize,
|
||||
out var dstUsed
|
||||
);
|
||||
_outBytesCount = dstUsed;
|
||||
_decompressed = true;
|
||||
_position = 0;
|
||||
Decode(srcMs);
|
||||
}
|
||||
|
||||
long remaining = _outBytesCount - _position;
|
||||
@@ -86,6 +81,70 @@ internal partial class ShrinkStream : Stream
|
||||
public override long Seek(long offset, SeekOrigin origin) =>
|
||||
throw new NotImplementedException();
|
||||
|
||||
private void Decode(PooledMemoryStream source)
|
||||
{
|
||||
var length = checked((int)source.Length);
|
||||
var src = _pool.Rent(length);
|
||||
var output = Array.Empty<byte>();
|
||||
try
|
||||
{
|
||||
source.Position = 0;
|
||||
if (source.Read(src, 0, length) != length)
|
||||
{
|
||||
throw new InvalidFormatException("Shrink: truncated compressed data.");
|
||||
}
|
||||
// A pool rental may be larger than the compressed data; never decode its unused tail.
|
||||
var status = HwUnshrink.UnshrinkGrowing(
|
||||
src,
|
||||
length,
|
||||
(int)_uncompressedSize,
|
||||
_pool,
|
||||
out output,
|
||||
out var used
|
||||
);
|
||||
if (status != HwUnshrink.UnshrnkStatus.Ok || used != _uncompressedSize)
|
||||
{
|
||||
throw new InvalidFormatException("Shrink: invalid or truncated compressed data.");
|
||||
}
|
||||
_byteOut = output;
|
||||
output = Array.Empty<byte>();
|
||||
_outBytesCount = used;
|
||||
_decompressed = true;
|
||||
_position = 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_pool.Return(src);
|
||||
if (output.Length > 0)
|
||||
{
|
||||
_pool.Return(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
_disposed = true;
|
||||
var output = _byteOut;
|
||||
_byteOut = Array.Empty<byte>();
|
||||
if (output.Length > 0)
|
||||
{
|
||||
_pool.Return(output);
|
||||
}
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(ShrinkStream));
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetLength(long value) => throw new NotImplementedException();
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count) =>
|
||||
|
||||
@@ -55,7 +55,7 @@ public partial class SevenZipWriter
|
||||
var filesInfo = new SevenZipFilesInfoWriter { Entries = entries.ToArray() };
|
||||
|
||||
// Write header to a temporary stream first
|
||||
using var headerStream = new MemoryStream();
|
||||
using var headerStream = new PooledMemoryStream();
|
||||
ArchiveHeaderWriter.WriteRawHeader(headerStream, mainStreamsInfo, filesInfo);
|
||||
|
||||
// Optionally compress the header
|
||||
@@ -70,7 +70,7 @@ public partial class SevenZipWriter
|
||||
}
|
||||
|
||||
private async ValueTask WriteCompressedHeaderAsync(
|
||||
MemoryStream rawHeaderStream,
|
||||
PooledMemoryStream rawHeaderStream,
|
||||
long endOfPackedData
|
||||
)
|
||||
{
|
||||
@@ -97,7 +97,7 @@ public partial class SevenZipWriter
|
||||
};
|
||||
|
||||
// Write encoded header to a second temporary stream
|
||||
using var encodedHeaderStream = new MemoryStream();
|
||||
using var encodedHeaderStream = new PooledMemoryStream();
|
||||
ArchiveHeaderWriter.WriteEncodedHeader(encodedHeaderStream, headerStreamsInfo);
|
||||
|
||||
// Write the encoded header to the output
|
||||
@@ -125,7 +125,7 @@ public partial class SevenZipWriter
|
||||
}
|
||||
|
||||
private async ValueTask WriteRawHeaderToOutputAsync(
|
||||
MemoryStream rawHeaderStream,
|
||||
PooledMemoryStream rawHeaderStream,
|
||||
long endOfPackedData
|
||||
)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Common.Zip;
|
||||
using SharpCompress.Common.Zip.Headers;
|
||||
using SharpCompress.IO;
|
||||
|
||||
namespace SharpCompress.Writers.Zip;
|
||||
|
||||
@@ -24,7 +25,7 @@ public partial class ZipWriter
|
||||
|
||||
// Buffer the entire central directory + end record into memory, then write async.
|
||||
// This avoids synchronous writes to the underlying stream during finalization.
|
||||
using var ms = new MemoryStream();
|
||||
using var ms = new PooledMemoryStream();
|
||||
ulong size = 0;
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
@@ -157,9 +158,9 @@ public partial class ZipWriter
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
// Build the header synchronously into a MemoryStream, then async-copy to OutputStream.
|
||||
// Build the header synchronously into a pooled stream, then async-copy to OutputStream.
|
||||
// This avoids any synchronous writes to the potentially async-only output stream.
|
||||
using var ms = new MemoryStream();
|
||||
using var ms = new PooledMemoryStream();
|
||||
var outputStream = OutputStream.NotNull();
|
||||
var result = WriteHeader(
|
||||
ms,
|
||||
|
||||
Reference in New Issue
Block a user