diff --git a/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs b/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs index c0b25ebb..57aa20a5 100644 --- a/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs +++ b/src/SharpCompress/Common/Rar/AsyncMarkingBinaryReader.cs @@ -45,7 +45,21 @@ internal class AsyncMarkingBinaryReader : IDisposable { CurrentReadByteCount += count; var bytes = new byte[count]; - await _reader.ReadBytesAsync(bytes, 0, count, cancellationToken).ConfigureAwait(false); + try + { + await _reader.ReadBytesAsync(bytes, 0, count, cancellationToken).ConfigureAwait(false); + } + catch (IncompleteArchiveException ex) + { + throw new InvalidFormatException( + string.Format( + Constants.DefaultCultureInfo, + "Could not read the requested amount of bytes. End of stream reached. Requested: {0}", + count + ), + ex + ); + } return bytes; } diff --git a/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs b/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs index 793dccae..6405423a 100644 --- a/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs +++ b/src/SharpCompress/Compressors/PPMd/H/ModelPPM.cs @@ -281,13 +281,13 @@ internal class ModelPpm internal bool DecodeInit(IRarUnpack unpackRead, int escChar) { - var maxOrder = unpackRead.Char & 0xff; + var maxOrder = unpackRead.ReadChar() & 0xff; var reset = ((maxOrder & 0x20) != 0); var maxMb = 0; if (reset) { - maxMb = unpackRead.Char; + maxMb = unpackRead.ReadChar(); } else { @@ -298,7 +298,7 @@ internal class ModelPpm } if ((maxOrder & 0x40) != 0) { - escChar = unpackRead.Char; + escChar = unpackRead.ReadChar(); unpackRead.PpmEscChar = escChar; } Coder = new RangeCoder(unpackRead); @@ -333,6 +333,65 @@ internal class ModelPpm return (_minContext.Address != 0); } + internal async ValueTask DecodeInitAsync( + IRarUnpack unpackRead, + int escChar, + CancellationToken cancellationToken = default + ) + { + var maxOrder = + await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false) & 0xff; + var reset = ((maxOrder & 0x20) != 0); + + var maxMb = 0; + if (reset) + { + maxMb = await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false); + } + else + { + if (SubAlloc.GetAllocatedMemory() == 0) + { + return false; + } + } + if ((maxOrder & 0x40) != 0) + { + escChar = await unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false); + unpackRead.PpmEscChar = escChar; + } + Coder = new RangeCoder(); + await Coder.InitAsync(unpackRead, cancellationToken).ConfigureAwait(false); + if (reset) + { + maxOrder = (maxOrder & 0x1f) + 1; + if (maxOrder > 16) + { + maxOrder = 16 + ((maxOrder - 16) * 3); + } + if (maxOrder == 1) + { + SubAlloc.StopSubAllocator(); + return false; + } + SubAlloc.StartSubAllocator((maxMb + 1) << 20); + _minContext = new PpmContext(Heap); + + _maxContext = new PpmContext(Heap); + FoundState = new State(Heap); + _dummySee2Cont = new See2Context(); + for (var i = 0; i < 25; i++) + { + for (var j = 0; j < 16; j++) + { + _see2Cont[i][j] = new See2Context(); + } + } + StartModelRare(maxOrder); + } + return _minContext.Address != 0; + } + public virtual int DecodeChar() { // Debug diff --git a/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs b/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs index 0a3b2321..7cda0fec 100644 --- a/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs +++ b/src/SharpCompress/Compressors/PPMd/H/RangeCoder.cs @@ -19,7 +19,7 @@ internal class RangeCoder private long _low, _code, _range; - private readonly IRarUnpack _unpackRead; + private IRarUnpack _unpackRead; private readonly Stream _stream; internal RangeCoder(IRarUnpack unpackRead) @@ -36,6 +36,24 @@ internal class RangeCoder internal RangeCoder() { } + internal async ValueTask InitAsync( + IRarUnpack unpackRead, + CancellationToken cancellationToken = default + ) + { + _unpackRead = unpackRead; + SubRange = new SubRange(); + + _low = _code = 0L; + _range = 0xFFFFffffL; + for (var i = 0; i < 4; i++) + { + _code = + ((_code << 8) | await ReadCharAsync(cancellationToken).ConfigureAwait(false)) + & UINT_MASK; + } + } + private void Init() { SubRange = new SubRange(); @@ -44,7 +62,7 @@ internal class RangeCoder _range = 0xFFFFffffL; for (var i = 0; i < 4; i++) { - _code = ((_code << 8) | Char) & UINT_MASK; + _code = ((_code << 8) | ReadChar()) & UINT_MASK; } } @@ -73,20 +91,17 @@ internal class RangeCoder } } - private long Char + private long ReadChar() { - get + if (_unpackRead != null) { - if (_unpackRead != null) - { - return (_unpackRead.Char); - } - if (_stream != null) - { - return _stream.ReadByte(); - } - return -1; + return (_unpackRead.ReadChar()); } + if (_stream != null) + { + return _stream.ReadByte(); + } + return -1; } internal SubRange SubRange { get; private set; } @@ -121,7 +136,7 @@ internal class RangeCoder _range = (-_low & (BOT - 1)) & UINT_MASK; c2 = false; } - _code = ((_code << 8) | Char) & UINT_MASK; + _code = ((_code << 8) | ReadChar()) & UINT_MASK; _range = (_range << 8) & UINT_MASK; _low = (_low << 8) & UINT_MASK; } @@ -131,7 +146,7 @@ internal class RangeCoder { if (_unpackRead != null) { - return _unpackRead.Char; + return await _unpackRead.ReadCharAsync(cancellationToken).ConfigureAwait(false); } if (_stream != null) { diff --git a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs index 7626f4ac..45729bf5 100644 --- a/src/SharpCompress/Compressors/Rar/IRarUnpack.cs +++ b/src/SharpCompress/Compressors/Rar/IRarUnpack.cs @@ -22,6 +22,7 @@ internal interface IRarUnpack bool Suspended { get; set; } long DestSize { get; } - int Char { get; } + int ReadChar(); + ValueTask ReadCharAsync(CancellationToken cancellationToken); int PpmEscChar { get; set; } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs index 37655cf1..de4d63e9 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.Async.cs @@ -1,5 +1,6 @@ using System; using System.Buffers; +using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -112,7 +113,10 @@ internal sealed partial class Unpack { return; } - if ((!solid || !tablesRead) && !ReadTables()) + if ( + (!solid || !tablesRead) + && !await ReadTablesAsync(cancellationToken).ConfigureAwait(false) + ) { return; } @@ -137,7 +141,7 @@ internal sealed partial class Unpack if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 260 && wrPtr != unpPtr) { - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); if (destUnpSize < 0) { return; @@ -150,7 +154,7 @@ internal sealed partial class Unpack } if (unpBlockType == BlockTypes.BLOCK_PPM) { - var Ch = ppm.DecodeChar(); + var Ch = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); if (Ch == -1) { ppmError = true; @@ -158,10 +162,10 @@ internal sealed partial class Unpack } if (Ch == PpmEscChar) { - var NextCh = ppm.DecodeChar(); + var NextCh = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); if (NextCh == 0) { - if (!ReadTables()) + if (!await ReadTablesAsync(cancellationToken).ConfigureAwait(false)) { break; } @@ -173,7 +177,7 @@ internal sealed partial class Unpack } if (NextCh == 3) { - if (!ReadVMCodePPM()) + if (!await ReadVMCodePPMAsync(cancellationToken).ConfigureAwait(false)) { break; } @@ -186,7 +190,8 @@ internal sealed partial class Unpack var failed = false; for (var I = 0; I < 4 && !failed; I++) { - var ch = ppm.DecodeChar(); + var ch = await ppm.DecodeCharAsync(cancellationToken) + .ConfigureAwait(false); if (ch == -1) { failed = true; @@ -212,7 +217,8 @@ internal sealed partial class Unpack } if (NextCh == 5) { - var Length = ppm.DecodeChar(); + var Length = await ppm.DecodeCharAsync(cancellationToken) + .ConfigureAwait(false); if (Length == -1) { break; @@ -294,7 +300,7 @@ internal sealed partial class Unpack } if (Number == 256) { - if (!ReadEndOfBlock()) + if (!await ReadEndOfBlockAsync(cancellationToken).ConfigureAwait(false)) { break; } @@ -302,7 +308,7 @@ internal sealed partial class Unpack } if (Number == 257) { - if (!ReadVMCode()) + if (!await ReadVMCodeAsync(cancellationToken).ConfigureAwait(false)) { break; } @@ -350,7 +356,7 @@ internal sealed partial class Unpack CopyString(2, Distance); } } - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); } private async Task UnpWriteBufAsync(CancellationToken cancellationToken = default) @@ -600,4 +606,267 @@ internal sealed partial class Unpack writtenFileSize += size; destUnpSize -= size; } + + private async Task ReadTablesAsync(CancellationToken cancellationToken = default) + { + var bitLengthArray = ArrayPool.Shared.Rent(PackDef.BC); + var bitLength = new Memory(bitLengthArray, 0, PackDef.BC); + var tableArray = ArrayPool.Shared.Rent(PackDef.HUFF_TABLE_SIZE); + var table = new Memory(tableArray, 0, PackDef.HUFF_TABLE_SIZE); + + try + { + if (inAddr > readTop - 25) + { + if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + AddBits((8 - inBit) & 7); + long bitField = GetBits() & unchecked((int)0xffFFffFF); + if ((bitField & 0x8000) != 0) + { + unpBlockType = BlockTypes.BLOCK_PPM; + return await ppm.DecodeInitAsync(this, PpmEscChar, cancellationToken) + .ConfigureAwait(false); + } + + unpBlockType = BlockTypes.BLOCK_LZ; + + prevLowDist = 0; + lowDistRepCount = 0; + + if ((bitField & 0x4000) == 0) + { + new Span(unpOldTable).Clear(); + } + + AddBits(2); + + for (var i = 0; i < PackDef.BC; i++) + { + var length = (Utility.URShift(GetBits(), 12)) & 0xFF; + AddBits(4); + if (length == 15) + { + var zeroCount = (Utility.URShift(GetBits(), 12)) & 0xFF; + AddBits(4); + if (zeroCount == 0) + { + bitLength.Span[i] = 15; + } + else + { + zeroCount += 2; + while (zeroCount-- > 0 && i < bitLength.Length) + { + bitLength.Span[i++] = 0; + } + + i--; + } + } + else + { + bitLength.Span[i] = (byte)length; + } + } + + UnpackUtility.makeDecodeTables(bitLength.Span, 0, BD, PackDef.BC); + + var TableSize = PackDef.HUFF_TABLE_SIZE; + + for (var i = 0; i < TableSize; ) + { + if (inAddr > readTop - 5) + { + if (!await unpReadBufAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + } + + var Number = this.decodeNumber(BD); + if (Number < 16) + { + table.Span[i] = (byte)((Number + unpOldTable[i]) & 0xf); + i++; + } + else if (Number < 18) + { + int N; + if (Number == 16) + { + N = (Utility.URShift(GetBits(), 13)) + 3; + AddBits(3); + } + else + { + N = (Utility.URShift(GetBits(), 9)) + 11; + AddBits(7); + } + + while (N-- > 0 && i < TableSize) + { + table.Span[i] = table.Span[i - 1]; + i++; + } + } + else + { + int N; + if (Number == 18) + { + N = (Utility.URShift(GetBits(), 13)) + 3; + AddBits(3); + } + else + { + N = (Utility.URShift(GetBits(), 9)) + 11; + AddBits(7); + } + + while (N-- > 0 && i < TableSize) + { + table.Span[i++] = 0; + } + } + } + + tablesRead = true; + if (inAddr > readTop) + { + return false; + } + + UnpackUtility.makeDecodeTables(table.Span, 0, LD, PackDef.NC); + UnpackUtility.makeDecodeTables(table.Span, PackDef.NC, DD, PackDef.DC); + UnpackUtility.makeDecodeTables(table.Span, PackDef.NC + PackDef.DC, LDD, PackDef.LDC); + UnpackUtility.makeDecodeTables( + table.Span, + PackDef.NC + PackDef.DC + PackDef.LDC, + RD, + PackDef.RC + ); + + table.Span.CopyTo(unpOldTable); + return true; + } + finally + { + ArrayPool.Shared.Return(bitLengthArray); + ArrayPool.Shared.Return(tableArray); + } + } + + private async Task ReadEndOfBlockAsync(CancellationToken cancellationToken = default) + { + var BitField = GetBits(); + bool NewTable, + NewFile = false; + if ((BitField & 0x8000) != 0) + { + NewTable = true; + AddBits(1); + } + else + { + NewFile = true; + NewTable = (BitField & 0x4000) != 0; + AddBits(2); + } + tablesRead = !NewTable; + return !( + NewFile || NewTable && !await ReadTablesAsync(cancellationToken).ConfigureAwait(false) + ); + } + + private async Task ReadVMCodeAsync(CancellationToken cancellationToken = default) + { + var FirstByte = GetBits() >> 8; + AddBits(8); + var Length = (FirstByte & 7) + 1; + if (Length == 7) + { + Length = (GetBits() >> 8) + 7; + AddBits(8); + } + else if (Length == 8) + { + Length = GetBits(); + AddBits(16); + } + + var vmCode = new List(); + for (var I = 0; I < Length; I++) + { + if ( + inAddr >= readTop - 1 + && !await unpReadBufAsync(cancellationToken).ConfigureAwait(false) + && I < Length - 1 + ) + { + return false; + } + vmCode.Add((byte)(GetBits() >> 8)); + AddBits(8); + } + return AddVMCode(FirstByte, vmCode); + } + + public async ValueTask ReadCharAsync(CancellationToken cancellationToken = default) + { + if (inAddr > MAX_SIZE - 30) + { + await unpReadBufAsync(cancellationToken).ConfigureAwait(false); + } + return InBuf[inAddr++] & 0xff; + } + + private async Task ReadVMCodePPMAsync(CancellationToken cancellationToken = default) + { + var FirstByte = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (FirstByte == -1) + { + return false; + } + var Length = (FirstByte & 7) + 1; + if (Length == 7) + { + var B1 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (B1 == -1) + { + return false; + } + Length = B1 + 7; + } + else if (Length == 8) + { + var B1 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (B1 == -1) + { + return false; + } + var B2 = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (B2 == -1) + { + return false; + } + Length = (B1 * 256) + B2; + } + + var vmCode = new List(); + for (var I = 0; I < Length; I++) + { + var Ch = await ppm.DecodeCharAsync(cancellationToken).ConfigureAwait(false); + if (Ch == -1) + { + return false; + } + vmCode.Add((byte)Ch); + } + return AddVMCode(FirstByte, vmCode); + } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs index 929c4eb7..65697a6a 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack.cs @@ -55,16 +55,13 @@ internal sealed partial class Unpack : BitInput, IRarUnpack set => suspended = value; } - public int Char + public int ReadChar() { - get + if (inAddr > MAX_SIZE - 30) { - if (inAddr > MAX_SIZE - 30) - { - unpReadBuf(); - } - return (InBuf[inAddr++] & 0xff); + unpReadBuf(); } + return (InBuf[inAddr++] & 0xff); } public int PpmEscChar { get; set; } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs index 2975fbbf..f2e8c4ca 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack15.Async.cs @@ -48,7 +48,7 @@ internal partial class Unpack } if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) { - oldUnpWriteBuf(); + await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); if (suspended) { return; @@ -105,7 +105,7 @@ internal partial class Unpack } } } - oldUnpWriteBuf(); + await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); } private async Task unpReadBufAsync(CancellationToken cancellationToken = default) diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs index 69bc6de0..9ca54f31 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack20.Async.cs @@ -45,7 +45,7 @@ internal partial class Unpack } if (((wrPtr - unpPtr) & PackDef.MAXWINMASK) < 270 && wrPtr != unpPtr) { - oldUnpWriteBuf(); + await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); if (suspended) { return; @@ -157,8 +157,8 @@ internal partial class Unpack CopyString20(2, Distance); } } - ReadLastTables(); - oldUnpWriteBuf(); + await ReadLastTablesAsync(cancellationToken).ConfigureAwait(false); + await oldUnpWriteBufAsync(cancellationToken).ConfigureAwait(false); } private async Task ReadTables20Async(CancellationToken cancellationToken = default) @@ -272,4 +272,25 @@ internal partial class Unpack } return true; } + + private async Task ReadLastTablesAsync(CancellationToken cancellationToken = default) + { + if (readTop >= inAddr + 5) + { + if (UnpAudioBlock != 0) + { + if (this.decodeNumber(MD[UnpCurChannel]) == 256) + { + await ReadTables20Async(cancellationToken).ConfigureAwait(false); + } + } + else + { + if (this.decodeNumber(LD) == 269) + { + await ReadTables20Async(cancellationToken).ConfigureAwait(false); + } + } + } + } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs index 48df787e..42f7d439 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV1/Unpack50.Async.cs @@ -70,7 +70,7 @@ internal partial class Unpack // So we can safefly use these tables below. if ( !await ReadBlockHeaderAsync(cancellationToken).ConfigureAwait(false) - || !ReadTables() + || !await ReadTablesAsync(cancellationToken).ConfigureAwait(false) || !TablesRead5 ) { @@ -101,7 +101,7 @@ internal partial class Unpack } if ( !await ReadBlockHeaderAsync(cancellationToken).ConfigureAwait(false) - || !ReadTables() + || !await ReadTablesAsync(cancellationToken).ConfigureAwait(false) ) { return; @@ -118,7 +118,7 @@ internal partial class Unpack && WriteBorder != UnpPtr ) { - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); if (WrittenFileSize > DestUnpSize) { return; @@ -197,7 +197,7 @@ internal partial class Unpack var Filter = new UnpackFilter(); if ( !await ReadFilterAsync(Filter, cancellationToken).ConfigureAwait(false) - || !AddFilter(Filter) + || !await AddFilterAsync(Filter, cancellationToken).ConfigureAwait(false) ) { break; @@ -232,7 +232,7 @@ internal partial class Unpack continue; } } - UnpWriteBuf(); + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); } private async Task ReadBlockHeaderAsync(CancellationToken cancellationToken = default) @@ -318,4 +318,24 @@ internal partial class Unpack return true; } + + private async Task AddFilterAsync( + UnpackFilter Filter, + CancellationToken cancellationToken = default + ) + { + if (Filters.Count >= MAX_UNPACK_FILTERS) + { + await UnpWriteBufAsync(cancellationToken).ConfigureAwait(false); + if (Filters.Count >= MAX_UNPACK_FILTERS) + { + InitFilters(); + } + } + + Filter.NextWindow = WrPtr != UnpPtr && ((WrPtr - UnpPtr) & MaxWinMask) <= Filter.BlockStart; + Filter.uBlockStart = (uint)((Filter.BlockStart + UnpPtr) & MaxWinMask); + Filters.Add(Filter); + return true; + } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs index 7784d98a..27cf0a67 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.cs @@ -154,17 +154,25 @@ internal partial class Unpack : IRarUnpack public long DestSize => DestUnpSize; - public int Char + public int ReadChar() { - get + // TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code + if (InAddr > MAX_SIZE - 30) { - // TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code - if (InAddr > MAX_SIZE - 30) - { - UnpReadBuf(); - } - return InBuf[InAddr++]; + UnpReadBuf(); } + return InBuf[InAddr++]; + } + + public async ValueTask ReadCharAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + // TODO: coderb: not sure where the "MAXSIZE-30" comes from, ported from V1 code + if (InAddr > MAX_SIZE - 30) + { + await UnpReadBufAsync(cancellationToken).ConfigureAwait(false); + } + return InBuf[InAddr++]; } public int PpmEscChar diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs index e615527e..de8036b2 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack15_async.cs @@ -41,7 +41,7 @@ internal partial class Unpack if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) { - UnpWriteBuf20(); + await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false); } if (StMode != 0) @@ -95,6 +95,6 @@ internal partial class Unpack } } } - UnpWriteBuf20(); + await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false); } } diff --git a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs index 0619189a..89083eb4 100644 --- a/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs +++ b/src/SharpCompress/Compressors/Rar/UnpackV2017/Unpack.unpack20_async.cs @@ -49,7 +49,7 @@ internal partial class Unpack if (((WrPtr - UnpPtr) & MaxWinMask) < 270 && WrPtr != UnpPtr) { - UnpWriteBuf20(); + await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false); if (Suspended) { return; @@ -165,8 +165,8 @@ internal partial class Unpack continue; } } - ReadLastTables(); - UnpWriteBuf20(); + await ReadLastTables20Async(cancellationToken).ConfigureAwait(false); + await UnpWriteBuf20Async(cancellationToken).ConfigureAwait(false); } private async Task UnpWriteBuf20Async(CancellationToken cancellationToken = default) @@ -316,4 +316,22 @@ internal partial class Unpack Array.Copy(Table, 0, this.UnpOldTable20, 0, UnpOldTable20.Length); return true; } + + private async Task ReadLastTables20Async(CancellationToken cancellationToken = default) + { + if (ReadTop >= Inp.InAddr + 5) + { + if (UnpAudioBlock) + { + if (DecodeNumber(Inp, MD[UnpCurChannel]) == 256) + { + await ReadTables20Async(cancellationToken).ConfigureAwait(false); + } + } + else if (DecodeNumber(Inp, BlockTables.LD) == 269) + { + await ReadTables20Async(cancellationToken).ConfigureAwait(false); + } + } + } } diff --git a/src/SharpCompress/IO/AsyncBinaryReader.cs b/src/SharpCompress/IO/AsyncBinaryReader.cs index 9fef056a..f3e8c91b 100644 --- a/src/SharpCompress/IO/AsyncBinaryReader.cs +++ b/src/SharpCompress/IO/AsyncBinaryReader.cs @@ -60,15 +60,10 @@ public sealed class AsyncBinaryReader : IDisposable int offset, int count, CancellationToken ct = default - ) - { - await _stream.ReadExactAsync(bytes, offset, count, ct).ConfigureAwait(false); - } + ) => await _stream.ReadExactAsync(bytes, offset, count, ct).ConfigureAwait(false); - public async ValueTask SkipAsync(int count, CancellationToken ct = default) - { + public async ValueTask SkipAsync(int count, CancellationToken ct = default) => await _stream.SkipAsync(count, ct).ConfigureAwait(false); - } public void Dispose() { diff --git a/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs b/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs index 1e796962..937df54d 100644 --- a/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs +++ b/tests/SharpCompress.Test/Mocks/AsyncOnlyStream.cs @@ -5,14 +5,9 @@ using System.Threading.Tasks; namespace SharpCompress.Test.Mocks; -public class AsyncOnlyStream : Stream +public class AsyncOnlyStream(Stream stream) : Stream { - private readonly Stream _stream; - - public AsyncOnlyStream(Stream stream) - { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - } + private readonly Stream _stream = stream ?? throw new ArgumentNullException(nameof(stream)); public override bool CanRead => _stream.CanRead; public override bool CanSeek => _stream.CanSeek; diff --git a/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs index bba174d6..3293fe75 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveAsyncTests.cs @@ -13,6 +13,31 @@ namespace SharpCompress.Test.Rar; public class RarArchiveAsyncTests : ArchiveTests { + [Theory] + [InlineData("Rar15.rar")] + [InlineData("Rar2.rar")] + [InlineData("Rar.rar")] + [InlineData("Rar.Audio_program.rar")] + [InlineData("Rar5.rar")] + [InlineData("Rar5.solid.rar")] + public async ValueTask Rar_Archive_Recently_Changed_Unpackers_Async(string filename) + { + var extractedEntries = 0; + await using var archive = await RarArchive.OpenAsyncArchive( + Path.Combine(TEST_ARCHIVES_PATH, filename), + new ReaderOptions { LookForHeader = true } + ); + + await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory)) + { + using var output = new AsyncOnlyStream(new MemoryStream()); + await entry.WriteToAsync(output); + extractedEntries++; + } + + Assert.True(extractedEntries > 0); + } + [Fact] public async ValueTask Rar_EncryptedFileAndHeader_Archive_Async() => await ReadRarPasswordAsync("Rar.encrypted_filesAndHeader.rar", "test"); diff --git a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs index e4fef73f..130c20b6 100644 --- a/tests/SharpCompress.Test/Rar/RarArchiveTests.cs +++ b/tests/SharpCompress.Test/Rar/RarArchiveTests.cs @@ -13,6 +13,32 @@ namespace SharpCompress.Test.Rar; public class RarArchiveTests : ArchiveTests { + [Theory] + [InlineData("Rar15.rar")] + [InlineData("Rar2.rar")] + [InlineData("Rar.rar")] + [InlineData("Rar.Audio_program.rar")] + [InlineData("Rar5.rar")] + [InlineData("Rar5.solid.rar")] + public void Rar_Archive_Recently_Changed_Unpackers_Sync(string filename) + { + var extractedEntries = 0; + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); + using var archive = RarArchive.OpenArchive( + stream, + new ReaderOptions { LookForHeader = true } + ); + + foreach (var entry in archive.Entries.Where(entry => !entry.IsDirectory)) + { + using var output = new MemoryStream(); + entry.WriteTo(output); + extractedEntries++; + } + + Assert.True(extractedEntries > 0); + } + [Fact] public void Rar_EncryptedFileAndHeader_Archive() => ReadRarPassword("Rar.encrypted_filesAndHeader.rar", "test"); diff --git a/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs b/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs index d6d82e26..11c40f77 100644 --- a/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs +++ b/tests/SharpCompress.Test/Rar/RarReaderAsyncTests.cs @@ -14,6 +14,30 @@ namespace SharpCompress.Test.Rar; public class RarReaderAsyncTests : ReaderTests { + [Theory] + [InlineData("Rar15.rar")] + [InlineData("Rar.rar")] + [InlineData("Rar.Audio_program.rar")] + [InlineData("Rar5.rar")] + [InlineData("Rar5.solid.rar")] + public async ValueTask Rar_Reader_Async_Uses_Only_Async_Stream_Operations(string filename) + { + using var stream = File.OpenRead(Path.Combine(TEST_ARCHIVES_PATH, filename)); + await using var reader = await ReaderFactory.OpenAsyncReader( + new AsyncOnlyStream(stream), + new ReaderOptions { LookForHeader = true } + ); + + while (await reader.MoveToNextEntryAsync()) + { + if (!reader.Entry.IsDirectory) + { + using var output = new AsyncOnlyStream(new MemoryStream()); + await reader.WriteEntryToAsync(output); + } + } + } + [Fact] public async ValueTask Rar_Multi_Reader_Async() => await DoRar_Multi_Reader_Async([