diff --git a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs index 18a87969..74a3ce4c 100644 --- a/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs +++ b/src/SharpCompress/Archives/Rar/RarArchiveEntry.cs @@ -86,8 +86,11 @@ public class RarArchiveEntry : RarEntry, IArchiveEntry ); } - public Task OpenEntryStreamAsync(CancellationToken cancellationToken = default) => - Task.FromResult(OpenEntryStream()); + public Task OpenEntryStreamAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(OpenEntryStream()); + } public bool IsComplete { diff --git a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs index b96533ce..d20fecd0 100644 --- a/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs +++ b/src/SharpCompress/Compressors/Rar/MultiVolumeReadOnlyStream.cs @@ -150,6 +150,136 @@ internal sealed class MultiVolumeReadOnlyStream : Stream, IStreamStack return totalRead; } + public override async System.Threading.Tasks.Task ReadAsync( + byte[] buffer, + int offset, + int count, + System.Threading.CancellationToken cancellationToken + ) + { + var totalRead = 0; + var currentOffset = offset; + var currentCount = count; + while (currentCount > 0) + { + var readSize = currentCount; + if (currentCount > maxPosition - currentPosition) + { + readSize = (int)(maxPosition - currentPosition); + } + + var read = await currentStream + .ReadAsync(buffer, currentOffset, readSize, cancellationToken) + .ConfigureAwait(false); + if (read < 0) + { + throw new EndOfStreamException(); + } + + currentPosition += read; + currentOffset += read; + currentCount -= read; + totalRead += read; + if ( + ((maxPosition - currentPosition) == 0) + && filePartEnumerator.Current.FileHeader.IsSplitAfter + ) + { + if (filePartEnumerator.Current.FileHeader.R4Salt != null) + { + throw new InvalidFormatException( + "Sharpcompress currently does not support multi-volume decryption." + ); + } + var fileName = filePartEnumerator.Current.FileHeader.FileName; + if (!filePartEnumerator.MoveNext()) + { + throw new InvalidFormatException( + "Multi-part rar file is incomplete. Entry expects a new volume: " + + fileName + ); + } + InitializeNextFilePart(); + } + else + { + break; + } + } + currentPartTotalReadBytes += totalRead; + currentEntryTotalReadBytes += totalRead; + streamListener.FireCompressedBytesRead( + currentPartTotalReadBytes, + currentEntryTotalReadBytes + ); + return totalRead; + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public override async System.Threading.Tasks.ValueTask ReadAsync( + Memory buffer, + System.Threading.CancellationToken cancellationToken = default + ) + { + var totalRead = 0; + var currentOffset = 0; + var currentCount = buffer.Length; + while (currentCount > 0) + { + var readSize = currentCount; + if (currentCount > maxPosition - currentPosition) + { + readSize = (int)(maxPosition - currentPosition); + } + + var read = await currentStream + .ReadAsync(buffer.Slice(currentOffset, readSize), cancellationToken) + .ConfigureAwait(false); + if (read < 0) + { + throw new EndOfStreamException(); + } + + currentPosition += read; + currentOffset += read; + currentCount -= read; + totalRead += read; + if ( + ((maxPosition - currentPosition) == 0) + && filePartEnumerator.Current.FileHeader.IsSplitAfter + ) + { + if (filePartEnumerator.Current.FileHeader.R4Salt != null) + { + throw new InvalidFormatException( + "Sharpcompress currently does not support multi-volume decryption." + ); + } + var fileName = filePartEnumerator.Current.FileHeader.FileName; + if (!filePartEnumerator.MoveNext()) + { + throw new InvalidFormatException( + "Multi-part rar file is incomplete. Entry expects a new volume: " + + fileName + ); + } + InitializeNextFilePart(); + } + else + { + break; + } + } + currentPartTotalReadBytes += totalRead; + currentEntryTotalReadBytes += totalRead; + streamListener.FireCompressedBytesRead( + currentPartTotalReadBytes, + currentEntryTotalReadBytes + ); + return totalRead; + } +#endif + public override bool CanRead => true; public override bool CanSeek => false; diff --git a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs index c0aead0d..5de6bf03 100644 --- a/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarBLAKE2spStream.cs @@ -333,4 +333,59 @@ internal class RarBLAKE2spStream : RarStream, IStreamStack return result; } + + public override async System.Threading.Tasks.Task ReadAsync( + byte[] buffer, + int offset, + int count, + System.Threading.CancellationToken cancellationToken + ) + { + var result = await base.ReadAsync(buffer, offset, count, cancellationToken) + .ConfigureAwait(false); + if (result != 0) + { + Update(_blake2sp, new ReadOnlySpan(buffer, offset, result), result); + } + else + { + _hash = Final(_blake2sp); + if (!disableCRCCheck && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) && count != 0) + { + // NOTE: we use the last FileHeader in a multipart volume to check CRC + throw new InvalidFormatException("file crc mismatch"); + } + } + + return result; + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public override async System.Threading.Tasks.ValueTask ReadAsync( + Memory buffer, + System.Threading.CancellationToken cancellationToken = default + ) + { + var result = await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + if (result != 0) + { + Update(_blake2sp, buffer.Span.Slice(0, result), result); + } + else + { + _hash = Final(_blake2sp); + if ( + !disableCRCCheck + && !(GetCrc().SequenceEqual(readStream.CurrentCrc)) + && buffer.Length != 0 + ) + { + // NOTE: we use the last FileHeader in a multipart volume to check CRC + throw new InvalidFormatException("file crc mismatch"); + } + } + + return result; + } +#endif } diff --git a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs index 30191035..5473370f 100644 --- a/src/SharpCompress/Compressors/Rar/RarCrcStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarCrcStream.cs @@ -77,4 +77,55 @@ internal class RarCrcStream : RarStream, IStreamStack return result; } + + public override async System.Threading.Tasks.Task ReadAsync( + byte[] buffer, + int offset, + int count, + System.Threading.CancellationToken cancellationToken + ) + { + var result = await base.ReadAsync(buffer, offset, count, cancellationToken) + .ConfigureAwait(false); + if (result != 0) + { + currentCrc = RarCRC.CheckCrc(currentCrc, buffer, offset, result); + } + else if ( + !disableCRC + && GetCrc() != BitConverter.ToUInt32(readStream.CurrentCrc, 0) + && count != 0 + ) + { + // NOTE: we use the last FileHeader in a multipart volume to check CRC + throw new InvalidFormatException("file crc mismatch"); + } + + return result; + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public override async System.Threading.Tasks.ValueTask ReadAsync( + Memory buffer, + System.Threading.CancellationToken cancellationToken = default + ) + { + var result = await base.ReadAsync(buffer, cancellationToken).ConfigureAwait(false); + if (result != 0) + { + currentCrc = RarCRC.CheckCrc(currentCrc, buffer.Span.ToArray(), 0, result); + } + else if ( + !disableCRC + && GetCrc() != BitConverter.ToUInt32(readStream.CurrentCrc, 0) + && buffer.Length != 0 + ) + { + // NOTE: we use the last FileHeader in a multipart volume to check CRC + throw new InvalidFormatException("file crc mismatch"); + } + + return result; + } +#endif } diff --git a/src/SharpCompress/Compressors/Rar/RarStream.cs b/src/SharpCompress/Compressors/Rar/RarStream.cs index f2f4b219..d44cf948 100644 --- a/src/SharpCompress/Compressors/Rar/RarStream.cs +++ b/src/SharpCompress/Compressors/Rar/RarStream.cs @@ -131,6 +131,38 @@ internal class RarStream : Stream, IStreamStack return outTotal; } + public override System.Threading.Tasks.Task ReadAsync( + byte[] buffer, + int offset, + int count, + System.Threading.CancellationToken cancellationToken + ) + { + cancellationToken.ThrowIfCancellationRequested(); + return System.Threading.Tasks.Task.FromResult(Read(buffer, offset, count)); + } + +#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER + public override System.Threading.Tasks.ValueTask ReadAsync( + Memory buffer, + System.Threading.CancellationToken cancellationToken = default + ) + { + cancellationToken.ThrowIfCancellationRequested(); + var array = System.Buffers.ArrayPool.Shared.Rent(buffer.Length); + try + { + var bytesRead = Read(array, 0, buffer.Length); + new ReadOnlySpan(array, 0, bytesRead).CopyTo(buffer.Span); + return new System.Threading.Tasks.ValueTask(bytesRead); + } + finally + { + System.Buffers.ArrayPool.Shared.Return(array); + } + } +#endif + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); public override void SetLength(long value) => throw new NotSupportedException();