mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-02-04 05:25:00 +00:00
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.Xz;
|
||||
|
||||
@@ -30,6 +32,28 @@ public static class BinaryUtils
|
||||
internal static uint ReadLittleEndianUInt32(this Stream stream) =>
|
||||
unchecked((uint)ReadLittleEndianInt32(stream));
|
||||
|
||||
public static async Task<int> ReadLittleEndianInt32Async(
|
||||
this Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var bytes = new byte[4];
|
||||
var read = await stream.ReadFullyAsync(bytes, cancellationToken).ConfigureAwait(false);
|
||||
if (!read)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
return BinaryPrimitives.ReadInt32LittleEndian(bytes);
|
||||
}
|
||||
|
||||
internal static async Task<uint> ReadLittleEndianUInt32Async(
|
||||
this Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
) =>
|
||||
unchecked(
|
||||
(uint)await ReadLittleEndianInt32Async(stream, cancellationToken).ConfigureAwait(false)
|
||||
);
|
||||
|
||||
internal static byte[] ToBigEndianBytes(this uint uint32)
|
||||
{
|
||||
var result = BitConverter.GetBytes(uint32);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
|
||||
namespace SharpCompress.Compressors.Xz;
|
||||
@@ -39,4 +41,75 @@ internal static class MultiByteIntegers
|
||||
}
|
||||
return Output;
|
||||
}
|
||||
|
||||
public static async Task<ulong> ReadXZIntegerAsync(
|
||||
this BinaryReader reader,
|
||||
CancellationToken cancellationToken = default,
|
||||
int MaxBytes = 9
|
||||
)
|
||||
{
|
||||
if (MaxBytes <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(MaxBytes));
|
||||
}
|
||||
|
||||
if (MaxBytes > 9)
|
||||
{
|
||||
MaxBytes = 9;
|
||||
}
|
||||
|
||||
var LastByte = await ReadByteAsync(reader, cancellationToken).ConfigureAwait(false);
|
||||
var Output = (ulong)LastByte & 0x7F;
|
||||
|
||||
var i = 0;
|
||||
while ((LastByte & 0x80) != 0)
|
||||
{
|
||||
if (++i >= MaxBytes)
|
||||
{
|
||||
throw new InvalidFormatException();
|
||||
}
|
||||
|
||||
LastByte = await ReadByteAsync(reader, cancellationToken).ConfigureAwait(false);
|
||||
if (LastByte == 0)
|
||||
{
|
||||
throw new InvalidFormatException();
|
||||
}
|
||||
|
||||
Output |= ((ulong)(LastByte & 0x7F)) << (i * 7);
|
||||
}
|
||||
return Output;
|
||||
}
|
||||
|
||||
public static async Task<byte> ReadByteAsync(
|
||||
this BinaryReader reader,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
var bytesRead = await reader
|
||||
.BaseStream.ReadAsync(buffer, 0, 1, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (bytesRead != 1)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
return buffer[0];
|
||||
}
|
||||
|
||||
public static async Task<byte[]> ReadBytesAsync(
|
||||
this BinaryReader reader,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var buffer = new byte[count];
|
||||
var bytesRead = await reader
|
||||
.BaseStream.ReadAsync(buffer, 0, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (bytesRead != count)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.Xz.Filters;
|
||||
|
||||
@@ -72,6 +74,49 @@ public sealed class XZBlock : XZReadOnlyStream
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var bytesRead = 0;
|
||||
if (!HeaderIsLoaded)
|
||||
{
|
||||
await LoadHeaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!_streamConnected)
|
||||
{
|
||||
ConnectStream();
|
||||
}
|
||||
|
||||
if (!_endOfStream)
|
||||
{
|
||||
bytesRead = await _decomStream
|
||||
.ReadAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (bytesRead != count)
|
||||
{
|
||||
_endOfStream = true;
|
||||
}
|
||||
|
||||
if (_endOfStream && !_paddingSkipped)
|
||||
{
|
||||
await SkipPaddingAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (_endOfStream && !_crcChecked)
|
||||
{
|
||||
await CheckCrcAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private void SkipPadding()
|
||||
{
|
||||
var bytes = (BaseStream.Position - _startPosition) % 4;
|
||||
@@ -87,6 +132,23 @@ public sealed class XZBlock : XZReadOnlyStream
|
||||
_paddingSkipped = true;
|
||||
}
|
||||
|
||||
private async Task SkipPaddingAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bytes = (BaseStream.Position - _startPosition) % 4;
|
||||
if (bytes > 0)
|
||||
{
|
||||
var paddingBytes = new byte[4 - bytes];
|
||||
await BaseStream
|
||||
.ReadAsync(paddingBytes, 0, paddingBytes.Length, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (paddingBytes.Any(b => b != 0))
|
||||
{
|
||||
throw new InvalidFormatException("Padding bytes were non-null");
|
||||
}
|
||||
}
|
||||
_paddingSkipped = true;
|
||||
}
|
||||
|
||||
private void CheckCrc()
|
||||
{
|
||||
var crc = new byte[_checkSize];
|
||||
@@ -96,6 +158,15 @@ public sealed class XZBlock : XZReadOnlyStream
|
||||
_crcChecked = true;
|
||||
}
|
||||
|
||||
private async Task CheckCrcAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var crc = new byte[_checkSize];
|
||||
await BaseStream.ReadAsync(crc, 0, _checkSize, cancellationToken).ConfigureAwait(false);
|
||||
// Actually do a check (and read in the bytes
|
||||
// into the function throughout the stream read).
|
||||
_crcChecked = true;
|
||||
}
|
||||
|
||||
private void ConnectStream()
|
||||
{
|
||||
_decomStream = BaseStream;
|
||||
@@ -123,6 +194,21 @@ public sealed class XZBlock : XZReadOnlyStream
|
||||
HeaderIsLoaded = true;
|
||||
}
|
||||
|
||||
private async Task LoadHeaderAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await ReadHeaderSizeAsync(cancellationToken).ConfigureAwait(false);
|
||||
var headerCache = await CacheHeaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
using (var cache = new MemoryStream(headerCache))
|
||||
using (var cachedReader = new BinaryReader(cache))
|
||||
{
|
||||
cachedReader.BaseStream.Position = 1; // skip the header size byte
|
||||
ReadBlockFlags(cachedReader);
|
||||
ReadFilters(cachedReader);
|
||||
}
|
||||
HeaderIsLoaded = true;
|
||||
}
|
||||
|
||||
private void ReadHeaderSize()
|
||||
{
|
||||
_blockHeaderSizeByte = (byte)BaseStream.ReadByte();
|
||||
@@ -132,6 +218,17 @@ public sealed class XZBlock : XZReadOnlyStream
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReadHeaderSizeAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var buffer = new byte[1];
|
||||
await BaseStream.ReadAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
|
||||
_blockHeaderSizeByte = buffer[0];
|
||||
if (_blockHeaderSizeByte == 0)
|
||||
{
|
||||
throw new XZIndexMarkerReachedException();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] CacheHeader()
|
||||
{
|
||||
var blockHeaderWithoutCrc = new byte[BlockHeaderSize - 4];
|
||||
@@ -139,7 +236,7 @@ public sealed class XZBlock : XZReadOnlyStream
|
||||
var read = BaseStream.Read(blockHeaderWithoutCrc, 1, BlockHeaderSize - 5);
|
||||
if (read != BlockHeaderSize - 5)
|
||||
{
|
||||
throw new EndOfStreamException("Reached end of stream unexectedly");
|
||||
throw new EndOfStreamException("Reached end of stream unexpectedly");
|
||||
}
|
||||
|
||||
var crc = BaseStream.ReadLittleEndianUInt32();
|
||||
@@ -152,6 +249,30 @@ public sealed class XZBlock : XZReadOnlyStream
|
||||
return blockHeaderWithoutCrc;
|
||||
}
|
||||
|
||||
private async Task<byte[]> CacheHeaderAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var blockHeaderWithoutCrc = new byte[BlockHeaderSize - 4];
|
||||
blockHeaderWithoutCrc[0] = _blockHeaderSizeByte;
|
||||
var read = await BaseStream
|
||||
.ReadAsync(blockHeaderWithoutCrc, 1, BlockHeaderSize - 5, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (read != BlockHeaderSize - 5)
|
||||
{
|
||||
throw new EndOfStreamException("Reached end of stream unexpectedly");
|
||||
}
|
||||
|
||||
var crc = await BaseStream
|
||||
.ReadLittleEndianUInt32Async(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var calcCrc = Crc32.Compute(blockHeaderWithoutCrc);
|
||||
if (crc != calcCrc)
|
||||
{
|
||||
throw new InvalidFormatException("Block header corrupt");
|
||||
}
|
||||
|
||||
return blockHeaderWithoutCrc;
|
||||
}
|
||||
|
||||
private void ReadBlockFlags(BinaryReader reader)
|
||||
{
|
||||
var blockFlags = reader.ReadByte();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -27,6 +29,16 @@ public class XZFooter
|
||||
return footer;
|
||||
}
|
||||
|
||||
public static async Task<XZFooter> FromStreamAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var footer = new XZFooter(new BinaryReader(stream, Encoding.UTF8, true));
|
||||
await footer.ProcessAsync(cancellationToken).ConfigureAwait(false);
|
||||
return footer;
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
var crc = _reader.ReadLittleEndianUInt32();
|
||||
@@ -49,4 +61,29 @@ public class XZFooter
|
||||
throw new InvalidFormatException("Magic footer missing");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var crc = await _reader
|
||||
.BaseStream.ReadLittleEndianUInt32Async(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var footerBytes = await _reader.ReadBytesAsync(6, cancellationToken).ConfigureAwait(false);
|
||||
var myCrc = Crc32.Compute(footerBytes);
|
||||
if (crc != myCrc)
|
||||
{
|
||||
throw new InvalidFormatException("Footer corrupt");
|
||||
}
|
||||
|
||||
using (var stream = new MemoryStream(footerBytes))
|
||||
using (var reader = new BinaryReader(stream))
|
||||
{
|
||||
BackwardSize = (reader.ReadLittleEndianUInt32() + 1) * 4;
|
||||
StreamFlags = reader.ReadBytes(2);
|
||||
}
|
||||
var magBy = await _reader.ReadBytesAsync(2, cancellationToken).ConfigureAwait(false);
|
||||
if (!magBy.AsSpan().SequenceEqual(_magicBytes))
|
||||
{
|
||||
throw new InvalidFormatException("Magic footer missing");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -23,12 +25,28 @@ public class XZHeader
|
||||
return header;
|
||||
}
|
||||
|
||||
public static async Task<XZHeader> FromStreamAsync(
|
||||
Stream stream,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var header = new XZHeader(new BinaryReader(stream, Encoding.UTF8, true));
|
||||
await header.ProcessAsync(cancellationToken).ConfigureAwait(false);
|
||||
return header;
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
CheckMagicBytes(_reader.ReadBytes(6));
|
||||
ProcessStreamFlags();
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
CheckMagicBytes(await _reader.ReadBytesAsync(6, cancellationToken).ConfigureAwait(false));
|
||||
await ProcessStreamFlagsAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void ProcessStreamFlags()
|
||||
{
|
||||
var streamFlags = _reader.ReadBytes(2);
|
||||
@@ -47,6 +65,26 @@ public class XZHeader
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessStreamFlagsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var streamFlags = await _reader.ReadBytesAsync(2, cancellationToken).ConfigureAwait(false);
|
||||
var crc = await _reader
|
||||
.BaseStream.ReadLittleEndianUInt32Async(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var calcCrc = Crc32.Compute(streamFlags);
|
||||
if (crc != calcCrc)
|
||||
{
|
||||
throw new InvalidFormatException("Stream header corrupt");
|
||||
}
|
||||
|
||||
BlockCheckType = (CheckType)(streamFlags[1] & 0x0F);
|
||||
var futureUse = (byte)(streamFlags[1] & 0xF0);
|
||||
if (futureUse != 0 || streamFlags[0] != 0)
|
||||
{
|
||||
throw new InvalidFormatException("Unknown XZ Stream Version");
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckMagicBytes(byte[] header)
|
||||
{
|
||||
if (!header.SequenceEqual(MagicHeader))
|
||||
|
||||
@@ -3,6 +3,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -39,6 +41,20 @@ public class XZIndex
|
||||
return index;
|
||||
}
|
||||
|
||||
public static async Task<XZIndex> FromStreamAsync(
|
||||
Stream stream,
|
||||
bool indexMarkerAlreadyVerified,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var index = new XZIndex(
|
||||
new BinaryReader(stream, Encoding.UTF8, true),
|
||||
indexMarkerAlreadyVerified
|
||||
);
|
||||
await index.ProcessAsync(cancellationToken).ConfigureAwait(false);
|
||||
return index;
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
if (!_indexMarkerAlreadyVerified)
|
||||
@@ -55,6 +71,26 @@ public class XZIndex
|
||||
VerifyCrc32();
|
||||
}
|
||||
|
||||
public async Task ProcessAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!_indexMarkerAlreadyVerified)
|
||||
{
|
||||
await VerifyIndexMarkerAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
NumberOfRecords = await _reader.ReadXZIntegerAsync(cancellationToken).ConfigureAwait(false);
|
||||
for (ulong i = 0; i < NumberOfRecords; i++)
|
||||
{
|
||||
Records.Add(
|
||||
await XZIndexRecord
|
||||
.FromBinaryReaderAsync(_reader, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
);
|
||||
}
|
||||
await SkipPaddingAsync(cancellationToken).ConfigureAwait(false);
|
||||
await VerifyCrc32Async(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void VerifyIndexMarker()
|
||||
{
|
||||
var marker = _reader.ReadByte();
|
||||
@@ -64,6 +100,15 @@ public class XZIndex
|
||||
}
|
||||
}
|
||||
|
||||
private async Task VerifyIndexMarkerAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var marker = await _reader.ReadByteAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (marker != 0)
|
||||
{
|
||||
throw new InvalidFormatException("Not an index block");
|
||||
}
|
||||
}
|
||||
|
||||
private void SkipPadding()
|
||||
{
|
||||
var bytes = (int)(_reader.BaseStream.Position - StreamStartPosition) % 4;
|
||||
@@ -77,9 +122,32 @@ public class XZIndex
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SkipPaddingAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var bytes = (int)(_reader.BaseStream.Position - StreamStartPosition) % 4;
|
||||
if (bytes > 0)
|
||||
{
|
||||
var paddingBytes = await _reader
|
||||
.ReadBytesAsync(4 - bytes, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (paddingBytes.Any(b => b != 0))
|
||||
{
|
||||
throw new InvalidFormatException("Padding bytes were non-null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void VerifyCrc32()
|
||||
{
|
||||
var crc = _reader.ReadLittleEndianUInt32();
|
||||
// TODO verify this matches
|
||||
}
|
||||
|
||||
private async Task VerifyCrc32Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var crc = await _reader
|
||||
.BaseStream.ReadLittleEndianUInt32Async(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
// TODO verify this matches
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SharpCompress.Compressors.Xz;
|
||||
|
||||
@@ -18,4 +20,16 @@ public class XZIndexRecord
|
||||
record.UncompressedSize = br.ReadXZInteger();
|
||||
return record;
|
||||
}
|
||||
|
||||
public static async Task<XZIndexRecord> FromBinaryReaderAsync(
|
||||
BinaryReader br,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var record = new XZIndexRecord();
|
||||
record.UnpaddedSize = await br.ReadXZIntegerAsync(cancellationToken).ConfigureAwait(false);
|
||||
record.UncompressedSize = await br.ReadXZIntegerAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return record;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.IO;
|
||||
|
||||
@@ -104,6 +106,35 @@ public sealed class XZStream : XZReadOnlyStream, IStreamStack
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
public override async Task<int> ReadAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var bytesRead = 0;
|
||||
if (_endOfStream)
|
||||
{
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
if (!HeaderIsRead)
|
||||
{
|
||||
await ReadHeaderAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
bytesRead = await ReadBlocksAsync(buffer, offset, count, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (bytesRead < count)
|
||||
{
|
||||
_endOfStream = true;
|
||||
await ReadIndexAsync(cancellationToken).ConfigureAwait(false);
|
||||
await ReadFooterAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private void ReadHeader()
|
||||
{
|
||||
Header = XZHeader.FromStream(BaseStream);
|
||||
@@ -111,12 +142,31 @@ public sealed class XZStream : XZReadOnlyStream, IStreamStack
|
||||
HeaderIsRead = true;
|
||||
}
|
||||
|
||||
private async Task ReadHeaderAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
Header = await XZHeader
|
||||
.FromStreamAsync(BaseStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
AssertBlockCheckTypeIsSupported();
|
||||
HeaderIsRead = true;
|
||||
}
|
||||
|
||||
private void ReadIndex() => Index = XZIndex.FromStream(BaseStream, true);
|
||||
|
||||
// TODO veryfy Index
|
||||
private async Task ReadIndexAsync(CancellationToken cancellationToken = default) =>
|
||||
Index = await XZIndex
|
||||
.FromStreamAsync(BaseStream, true, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// TODO verify Index
|
||||
private void ReadFooter() => Footer = XZFooter.FromStream(BaseStream);
|
||||
|
||||
// TODO verify footer
|
||||
private async Task ReadFooterAsync(CancellationToken cancellationToken = default) =>
|
||||
Footer = await XZFooter
|
||||
.FromStreamAsync(BaseStream, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
private int ReadBlocks(byte[] buffer, int offset, int count)
|
||||
{
|
||||
var bytesRead = 0;
|
||||
@@ -152,6 +202,48 @@ public sealed class XZStream : XZReadOnlyStream, IStreamStack
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private async Task<int> ReadBlocksAsync(
|
||||
byte[] buffer,
|
||||
int offset,
|
||||
int count,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var bytesRead = 0;
|
||||
if (_currentBlock is null)
|
||||
{
|
||||
NextBlock();
|
||||
}
|
||||
|
||||
for (; ; )
|
||||
{
|
||||
try
|
||||
{
|
||||
if (bytesRead >= count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var remaining = count - bytesRead;
|
||||
var newOffset = offset + bytesRead;
|
||||
var justRead = await _currentBlock
|
||||
.ReadAsync(buffer, newOffset, remaining, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (justRead < remaining)
|
||||
{
|
||||
NextBlock();
|
||||
}
|
||||
|
||||
bytesRead += justRead;
|
||||
}
|
||||
catch (XZIndexMarkerReachedException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
private void NextBlock() =>
|
||||
_currentBlock = new XZBlock(BaseStream, Header.BlockCheckType, Header.BlockCheckSize);
|
||||
}
|
||||
|
||||
@@ -447,6 +447,31 @@ internal static class Utility
|
||||
}
|
||||
#endif
|
||||
|
||||
public static async Task<bool> ReadFullyAsync(
|
||||
this Stream stream,
|
||||
byte[] buffer,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
var total = 0;
|
||||
int read;
|
||||
while (
|
||||
(
|
||||
read = await stream
|
||||
.ReadAsync(buffer, total, buffer.Length - total, cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
) > 0
|
||||
)
|
||||
{
|
||||
total += read;
|
||||
if (total >= buffer.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return (total >= buffer.Length);
|
||||
}
|
||||
|
||||
public static string TrimNulls(this string source) => source.Replace('\0', ' ').Trim();
|
||||
|
||||
/// <summary>
|
||||
|
||||
125
tests/SharpCompress.Test/Xz/XZBlockAsyncTests.cs
Normal file
125
tests/SharpCompress.Test/Xz/XZBlockAsyncTests.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Xz;
|
||||
|
||||
public class XzBlockAsyncTests : XzTestsBase
|
||||
{
|
||||
protected override void Rewind(Stream stream) => stream.Position = 12;
|
||||
|
||||
protected override void RewindIndexed(Stream stream) => stream.Position = 12;
|
||||
|
||||
private static async Task<byte[]> ReadBytesAsync(XZBlock block, int bytesToRead)
|
||||
{
|
||||
var buffer = new byte[bytesToRead];
|
||||
var read = await block.ReadAsync(buffer, 0, bytesToRead).ConfigureAwait(false);
|
||||
if (read != bytesToRead)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OnFindIndexBlockThrowAsync()
|
||||
{
|
||||
var bytes = new byte[] { 0 };
|
||||
using Stream indexBlockStream = new MemoryStream(bytes);
|
||||
var xzBlock = new XZBlock(indexBlockStream, CheckType.CRC64, 8);
|
||||
await Assert.ThrowsAsync<XZIndexMarkerReachedException>(async () =>
|
||||
{
|
||||
await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CrcIncorrectThrowsAsync()
|
||||
{
|
||||
var bytes = (byte[])Compressed.Clone();
|
||||
bytes[20]++;
|
||||
using Stream badCrcStream = new MemoryStream(bytes);
|
||||
Rewind(badCrcStream);
|
||||
var xzBlock = new XZBlock(badCrcStream, CheckType.CRC64, 8);
|
||||
var ex = await Assert.ThrowsAsync<InvalidFormatException>(async () =>
|
||||
{
|
||||
await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false);
|
||||
});
|
||||
Assert.Equal("Block header corrupt", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadMAsync()
|
||||
{
|
||||
var xzBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8);
|
||||
Assert.Equal(
|
||||
Encoding.ASCII.GetBytes("M"),
|
||||
await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadMaryAsync()
|
||||
{
|
||||
var xzBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8);
|
||||
Assert.Equal(
|
||||
Encoding.ASCII.GetBytes("M"),
|
||||
await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false)
|
||||
);
|
||||
Assert.Equal(
|
||||
Encoding.ASCII.GetBytes("a"),
|
||||
await ReadBytesAsync(xzBlock, 1).ConfigureAwait(false)
|
||||
);
|
||||
Assert.Equal(
|
||||
Encoding.ASCII.GetBytes("ry"),
|
||||
await ReadBytesAsync(xzBlock, 2).ConfigureAwait(false)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadPoemWithStreamReaderAsync()
|
||||
{
|
||||
var xzBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8);
|
||||
var sr = new StreamReader(xzBlock);
|
||||
Assert.Equal(await sr.ReadToEndAsync().ConfigureAwait(false), Original);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NoopWhenNoPaddingAsync()
|
||||
{
|
||||
// CompressedStream's only block has no padding.
|
||||
var xzBlock = new XZBlock(CompressedStream, CheckType.CRC64, 8);
|
||||
var sr = new StreamReader(xzBlock);
|
||||
await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
Assert.Equal(0L, CompressedStream.Position % 4L);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SkipsPaddingWhenPresentAsync()
|
||||
{
|
||||
// CompressedIndexedStream's first block has 1-byte padding.
|
||||
var xzBlock = new XZBlock(CompressedIndexedStream, CheckType.CRC64, 8);
|
||||
var sr = new StreamReader(xzBlock);
|
||||
await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
Assert.Equal(0L, CompressedIndexedStream.Position % 4L);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandlesPaddingInUnalignedBlockAsync()
|
||||
{
|
||||
var compressedUnaligned = new byte[Compressed.Length + 1];
|
||||
Compressed.CopyTo(compressedUnaligned, 1);
|
||||
var compressedUnalignedStream = new MemoryStream(compressedUnaligned);
|
||||
compressedUnalignedStream.Position = 13;
|
||||
|
||||
// Compressed's only block has no padding.
|
||||
var xzBlock = new XZBlock(compressedUnalignedStream, CheckType.CRC64, 8);
|
||||
var sr = new StreamReader(xzBlock);
|
||||
await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
Assert.Equal(1L, compressedUnalignedStream.Position % 4L);
|
||||
}
|
||||
}
|
||||
83
tests/SharpCompress.Test/Xz/XZHeaderAsyncTests.cs
Normal file
83
tests/SharpCompress.Test/Xz/XZHeaderAsyncTests.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Xz;
|
||||
|
||||
public class XzHeaderAsyncTests : XzTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public async Task ChecksMagicNumberAsync()
|
||||
{
|
||||
var bytes = (byte[])Compressed.Clone();
|
||||
bytes[3]++;
|
||||
using Stream badMagicNumberStream = new MemoryStream(bytes);
|
||||
var br = new BinaryReader(badMagicNumberStream);
|
||||
var header = new XZHeader(br);
|
||||
var ex = await Assert.ThrowsAsync<InvalidFormatException>(async () =>
|
||||
{
|
||||
await header.ProcessAsync().ConfigureAwait(false);
|
||||
});
|
||||
Assert.Equal("Invalid XZ Stream", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CorruptHeaderThrowsAsync()
|
||||
{
|
||||
var bytes = (byte[])Compressed.Clone();
|
||||
bytes[8]++;
|
||||
using Stream badCrcStream = new MemoryStream(bytes);
|
||||
var br = new BinaryReader(badCrcStream);
|
||||
var header = new XZHeader(br);
|
||||
var ex = await Assert.ThrowsAsync<InvalidFormatException>(async () =>
|
||||
{
|
||||
await header.ProcessAsync().ConfigureAwait(false);
|
||||
});
|
||||
Assert.Equal("Stream header corrupt", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BadVersionIfCrcOkButStreamFlagUnknownAsync()
|
||||
{
|
||||
var bytes = (byte[])Compressed.Clone();
|
||||
byte[] streamFlags = [0x00, 0xF4];
|
||||
var crc = Crc32.Compute(streamFlags).ToLittleEndianBytes();
|
||||
streamFlags.CopyTo(bytes, 6);
|
||||
crc.CopyTo(bytes, 8);
|
||||
using Stream badFlagStream = new MemoryStream(bytes);
|
||||
var br = new BinaryReader(badFlagStream);
|
||||
var header = new XZHeader(br);
|
||||
var ex = await Assert.ThrowsAsync<InvalidFormatException>(async () =>
|
||||
{
|
||||
await header.ProcessAsync().ConfigureAwait(false);
|
||||
});
|
||||
Assert.Equal("Unknown XZ Stream Version", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessesBlockCheckTypeAsync()
|
||||
{
|
||||
var br = new BinaryReader(CompressedStream);
|
||||
var header = new XZHeader(br);
|
||||
await header.ProcessAsync().ConfigureAwait(false);
|
||||
Assert.Equal(CheckType.CRC64, header.BlockCheckType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanCalculateBlockCheckSizeAsync()
|
||||
{
|
||||
var br = new BinaryReader(CompressedStream);
|
||||
var header = new XZHeader(br);
|
||||
await header.ProcessAsync().ConfigureAwait(false);
|
||||
Assert.Equal(8, header.BlockCheckSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProcessesStreamHeaderFromFactoryAsync()
|
||||
{
|
||||
var header = await XZHeader.FromStreamAsync(CompressedStream).ConfigureAwait(false);
|
||||
Assert.Equal(CheckType.CRC64, header.BlockCheckType);
|
||||
}
|
||||
}
|
||||
85
tests/SharpCompress.Test/Xz/XZIndexAsyncTests.cs
Normal file
85
tests/SharpCompress.Test/Xz/XZIndexAsyncTests.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Common;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Xz;
|
||||
|
||||
public class XzIndexAsyncTests : XzTestsBase
|
||||
{
|
||||
protected override void RewindEmpty(Stream stream) => stream.Position = 12;
|
||||
|
||||
protected override void Rewind(Stream stream) => stream.Position = 356;
|
||||
|
||||
protected override void RewindIndexed(Stream stream) => stream.Position = 612;
|
||||
|
||||
[Fact]
|
||||
public void RecordsStreamStartOnInit()
|
||||
{
|
||||
using Stream badStream = new MemoryStream([1, 2, 3, 4, 5]);
|
||||
var br = new BinaryReader(badStream);
|
||||
var index = new XZIndex(br, false);
|
||||
Assert.Equal(0, index.StreamStartPosition);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ThrowsIfHasNoIndexMarkerAsync()
|
||||
{
|
||||
using Stream badStream = new MemoryStream([1, 2, 3, 4, 5]);
|
||||
var br = new BinaryReader(badStream);
|
||||
var index = new XZIndex(br, false);
|
||||
await Assert.ThrowsAsync<InvalidFormatException>(async () =>
|
||||
await index.ProcessAsync().ConfigureAwait(false)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadsNoRecordAsync()
|
||||
{
|
||||
var br = new BinaryReader(CompressedEmptyStream);
|
||||
var index = new XZIndex(br, false);
|
||||
await index.ProcessAsync().ConfigureAwait(false);
|
||||
Assert.Equal((ulong)0, index.NumberOfRecords);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadsOneRecordAsync()
|
||||
{
|
||||
var br = new BinaryReader(CompressedStream);
|
||||
var index = new XZIndex(br, false);
|
||||
await index.ProcessAsync().ConfigureAwait(false);
|
||||
Assert.Equal((ulong)1, index.NumberOfRecords);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadsMultipleRecordsAsync()
|
||||
{
|
||||
var br = new BinaryReader(CompressedIndexedStream);
|
||||
var index = new XZIndex(br, false);
|
||||
await index.ProcessAsync().ConfigureAwait(false);
|
||||
Assert.Equal((ulong)2, index.NumberOfRecords);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadsFirstRecordAsync()
|
||||
{
|
||||
var br = new BinaryReader(CompressedStream);
|
||||
var index = new XZIndex(br, false);
|
||||
await index.ProcessAsync().ConfigureAwait(false);
|
||||
Assert.Equal((ulong)OriginalBytes.Length, index.Records[0].UncompressedSize);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SkipsPaddingAsync()
|
||||
{
|
||||
// Index with 3-byte padding.
|
||||
using Stream badStream = new MemoryStream(
|
||||
[0x00, 0x01, 0x10, 0x80, 0x01, 0x00, 0x00, 0x00, 0xB1, 0x01, 0xD9, 0xC9, 0xFF]
|
||||
);
|
||||
var br = new BinaryReader(badStream);
|
||||
var index = new XZIndex(br, false);
|
||||
await index.ProcessAsync().ConfigureAwait(false);
|
||||
Assert.Equal(0L, badStream.Position % 4L);
|
||||
}
|
||||
}
|
||||
36
tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs
Normal file
36
tests/SharpCompress.Test/Xz/XZStreamAsyncTests.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using SharpCompress.Compressors.Xz;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpCompress.Test.Xz;
|
||||
|
||||
public class XzStreamAsyncTests : XzTestsBase
|
||||
{
|
||||
[Fact]
|
||||
public async Task CanReadEmptyStreamAsync()
|
||||
{
|
||||
var xz = new XZStream(CompressedEmptyStream);
|
||||
using var sr = new StreamReader(xz);
|
||||
var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
Assert.Equal(OriginalEmpty, uncompressed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadStreamAsync()
|
||||
{
|
||||
var xz = new XZStream(CompressedStream);
|
||||
using var sr = new StreamReader(xz);
|
||||
var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
Assert.Equal(Original, uncompressed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReadIndexedStreamAsync()
|
||||
{
|
||||
var xz = new XZStream(CompressedIndexedStream);
|
||||
using var sr = new StreamReader(xz);
|
||||
var uncompressed = await sr.ReadToEndAsync().ConfigureAwait(false);
|
||||
Assert.Equal(OriginalIndexed, uncompressed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user