mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-10-05 05:08:57 +00:00
148 lines
4.9 KiB
C#
148 lines
4.9 KiB
C#
using System;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using SharpCompress.Common.Tar.Headers;
|
|
using SharpCompress.Compressors;
|
|
using SharpCompress.Compressors.Deflate;
|
|
using SharpCompress.Providers;
|
|
|
|
namespace SharpCompress.Common.GZip;
|
|
|
|
internal sealed partial class GZipFilePart
|
|
{
|
|
internal static async ValueTask<GZipFilePart> CreateAsync(
|
|
Stream stream,
|
|
IArchiveEncoding archiveEncoding,
|
|
CompressionProviderRegistry compressionProviders,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
var part = new GZipFilePart(stream, archiveEncoding, compressionProviders);
|
|
|
|
await part.ReadAndValidateGzipHeaderAsync(cancellationToken).ConfigureAwait(false);
|
|
if (stream.CanSeek)
|
|
{
|
|
var position = stream.Position;
|
|
stream.Position = stream.Length - 8;
|
|
await part.ReadTrailerAsync(cancellationToken).ConfigureAwait(false);
|
|
stream.Position = position;
|
|
part.EntryStartPosition = position;
|
|
}
|
|
else
|
|
{
|
|
// For non-seekable streams, we can't read the trailer or track position.
|
|
// Set to 0 since the stream will be read sequentially from its current position.
|
|
part.EntryStartPosition = 0;
|
|
}
|
|
return part;
|
|
}
|
|
|
|
private async ValueTask ReadTrailerAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
|
|
var trailer = new byte[8];
|
|
_ = await _stream.ReadFullyAsync(trailer, 0, 8, cancellationToken).ConfigureAwait(false);
|
|
|
|
Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
|
|
UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.AsSpan().Slice(4));
|
|
}
|
|
|
|
private async ValueTask ReadAndValidateGzipHeaderAsync(
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
// read the header on the first read
|
|
var header = new byte[10];
|
|
var n = await _stream.ReadAsync(header, 0, 10, cancellationToken).ConfigureAwait(false);
|
|
|
|
// workitem 8501: handle edge case (decompress empty stream)
|
|
if (n == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (n != 10)
|
|
{
|
|
throw new ZlibException("Not a valid GZIP stream.");
|
|
}
|
|
|
|
if (header[0] != 0x1F || header[1] != 0x8B || header[2] != 8)
|
|
{
|
|
throw new ZlibException("Bad GZIP header.");
|
|
}
|
|
|
|
var timet = BinaryPrimitives.ReadInt32LittleEndian(header.AsSpan().Slice(4));
|
|
DateModified = TarHeader.EPOCH.AddSeconds(timet);
|
|
if ((header[3] & 0x04) == 0x04)
|
|
{
|
|
// read and discard extra field
|
|
var lengthField = new byte[2];
|
|
_ = await _stream.ReadAsync(lengthField, 0, 2, cancellationToken).ConfigureAwait(false);
|
|
|
|
var extraLength = (short)(lengthField[0] + (lengthField[1] * 256));
|
|
var extra = new byte[extraLength];
|
|
|
|
if (!await _stream.ReadFullyAsync(extra, cancellationToken).ConfigureAwait(false))
|
|
{
|
|
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
|
}
|
|
}
|
|
if ((header[3] & 0x08) == 0x08)
|
|
{
|
|
_name = await ReadZeroTerminatedStringAsync(_stream, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
}
|
|
if ((header[3] & 0x10) == 0x010)
|
|
{
|
|
await ReadZeroTerminatedStringAsync(_stream, cancellationToken).ConfigureAwait(false);
|
|
}
|
|
if ((header[3] & 0x02) == 0x02)
|
|
{
|
|
var buf = new byte[1];
|
|
_ = await _stream.ReadAsync(buf, 0, 1, cancellationToken).ConfigureAwait(false); // CRC16, ignore
|
|
}
|
|
}
|
|
|
|
private async ValueTask<string> ReadZeroTerminatedStringAsync(
|
|
Stream stream,
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
var buf1 = new byte[1];
|
|
var list = new List<byte>();
|
|
var done = false;
|
|
do
|
|
{
|
|
// workitem 7740
|
|
var n = await stream.ReadAsync(buf1, 0, 1, cancellationToken).ConfigureAwait(false);
|
|
if (n != 1)
|
|
{
|
|
throw new ZlibException("Unexpected EOF reading GZIP header.");
|
|
}
|
|
if (buf1[0] == 0)
|
|
{
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
list.Add(buf1[0]);
|
|
}
|
|
} while (!done);
|
|
var buffer = list.ToArray();
|
|
return ArchiveEncoding.Decode(buffer);
|
|
}
|
|
|
|
internal override async ValueTask<Stream?> GetCompressedStreamAsync(
|
|
CancellationToken cancellationToken = default
|
|
)
|
|
{
|
|
// GZip uses Deflate compression
|
|
return await _compressionProviders
|
|
.CreateDecompressStreamAsync(CompressionType.Deflate, _stream, cancellationToken)
|
|
.ConfigureAwait(false);
|
|
}
|
|
}
|