mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-09-25 16:34:45 +00:00
183 lines
5.4 KiB
C#
183 lines
5.4 KiB
C#
using System;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using SharpCompress.Common.Tar.Headers;
|
|
using SharpCompress.Compressors;
|
|
using SharpCompress.Compressors.Deflate;
|
|
|
|
namespace SharpCompress.Common.GZip;
|
|
|
|
internal sealed partial class GZipFilePart : FilePart
|
|
{
|
|
private string? _name;
|
|
private readonly Stream _stream;
|
|
private readonly CompressionProviderRegistry? _compressionProviders;
|
|
|
|
internal static GZipFilePart Create(Stream stream, IArchiveEncoding archiveEncoding)
|
|
{
|
|
var part = new GZipFilePart(stream, archiveEncoding, null);
|
|
|
|
part.ReadAndValidateGzipHeader();
|
|
if (stream.CanSeek)
|
|
{
|
|
var position = stream.Position;
|
|
stream.Position = stream.Length - 8;
|
|
part.ReadTrailer();
|
|
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;
|
|
}
|
|
|
|
internal static GZipFilePart Create(
|
|
Stream stream,
|
|
IArchiveEncoding archiveEncoding,
|
|
CompressionProviderRegistry? compressionProviders
|
|
)
|
|
{
|
|
var part = new GZipFilePart(stream, archiveEncoding, compressionProviders);
|
|
|
|
part.ReadAndValidateGzipHeader();
|
|
if (stream.CanSeek)
|
|
{
|
|
var position = stream.Position;
|
|
stream.Position = stream.Length - 8;
|
|
part.ReadTrailer();
|
|
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 GZipFilePart(Stream stream, IArchiveEncoding archiveEncoding)
|
|
: base(archiveEncoding) => _stream = stream;
|
|
|
|
private GZipFilePart(
|
|
Stream stream,
|
|
IArchiveEncoding archiveEncoding,
|
|
CompressionProviderRegistry? compressionProviders
|
|
)
|
|
: base(archiveEncoding)
|
|
{
|
|
_stream = stream;
|
|
_compressionProviders = compressionProviders;
|
|
}
|
|
|
|
internal long EntryStartPosition { get; private set; }
|
|
|
|
internal DateTime? DateModified { get; private set; }
|
|
internal uint? Crc { get; private set; }
|
|
internal uint? UncompressedSize { get; private set; }
|
|
|
|
internal override string? FilePartName => _name;
|
|
|
|
internal override Stream GetCompressedStream()
|
|
{
|
|
var providers = _compressionProviders ?? CompressionProviderRegistry.Default;
|
|
return providers.CreateDecompressStream(CompressionType.Deflate, _stream);
|
|
}
|
|
|
|
internal override Stream GetRawStream() => _stream;
|
|
|
|
private void ReadTrailer()
|
|
{
|
|
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
|
|
Span<byte> trailer = stackalloc byte[8];
|
|
_stream.ReadFully(trailer);
|
|
|
|
Crc = BinaryPrimitives.ReadUInt32LittleEndian(trailer);
|
|
UncompressedSize = BinaryPrimitives.ReadUInt32LittleEndian(trailer.Slice(4));
|
|
}
|
|
|
|
private void ReadAndValidateGzipHeader()
|
|
{
|
|
// read the header on the first read
|
|
Span<byte> header = stackalloc byte[10];
|
|
var n = _stream.Read(header);
|
|
|
|
// 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.Slice(4));
|
|
DateModified = TarHeader.EPOCH.AddSeconds(timet);
|
|
if ((header[3] & 0x04) == 0x04)
|
|
{
|
|
// read and discard extra field
|
|
n = _stream.Read(header.Slice(0, 2)); // 2-byte length field
|
|
|
|
var extraLength = (short)(header[0] + (header[1] * 256));
|
|
var extra = new byte[extraLength];
|
|
|
|
if (!_stream.ReadFully(extra))
|
|
{
|
|
throw new ZlibException("Unexpected end-of-file reading GZIP header.");
|
|
}
|
|
n = extraLength;
|
|
}
|
|
if ((header[3] & 0x08) == 0x08)
|
|
{
|
|
_name = ReadZeroTerminatedString(_stream);
|
|
}
|
|
if ((header[3] & 0x10) == 0x010)
|
|
{
|
|
ReadZeroTerminatedString(_stream);
|
|
}
|
|
if ((header[3] & 0x02) == 0x02)
|
|
{
|
|
_stream.ReadByte(); // CRC16, ignore
|
|
}
|
|
}
|
|
|
|
private string ReadZeroTerminatedString(Stream stream)
|
|
{
|
|
Span<byte> buf1 = stackalloc byte[1];
|
|
var list = new List<byte>();
|
|
var done = false;
|
|
do
|
|
{
|
|
// workitem 7740
|
|
var n = stream.Read(buf1);
|
|
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);
|
|
}
|
|
}
|