diff --git a/src/SharpCompress/Common/GZip/GZipEntry.cs b/src/SharpCompress/Common/GZip/GZipEntry.cs index f5985534..1204cfd7 100644 --- a/src/SharpCompress/Common/GZip/GZipEntry.cs +++ b/src/SharpCompress/Common/GZip/GZipEntry.cs @@ -15,7 +15,7 @@ namespace SharpCompress.Common.GZip public override CompressionType CompressionType => CompressionType.GZip; - public override long Crc => 0; + public override long Crc => _filePart.Crc ?? 0; public override string Key => _filePart.FilePartName; @@ -23,7 +23,7 @@ namespace SharpCompress.Common.GZip public override long CompressedSize => 0; - public override long Size => 0; + public override long Size => _filePart.UncompressedSize ?? 0; public override DateTime? LastModifiedTime => _filePart.DateModified; diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.cs b/src/SharpCompress/Common/GZip/GZipFilePart.cs index 2ac205e1..03b5b9ae 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.cs @@ -16,14 +16,23 @@ namespace SharpCompress.Common.GZip internal GZipFilePart(Stream stream, ArchiveEncoding archiveEncoding) : base(archiveEncoding) { - ReadAndValidateGzipHeader(stream); - EntryStartPosition = stream.Position; _stream = stream; + ReadAndValidateGzipHeader(); + if (stream.CanSeek) + { + long position = stream.Position; + stream.Position = stream.Length - 8; + ReadTrailer(); + stream.Position = position; + } + EntryStartPosition = stream.Position; } internal long EntryStartPosition { get; } internal DateTime? DateModified { get; private set; } + internal int? Crc { get; private set; } + internal int? UncompressedSize { get; private set; } internal override string FilePartName => _name!; @@ -37,11 +46,21 @@ namespace SharpCompress.Common.GZip return _stream; } - private void ReadAndValidateGzipHeader(Stream stream) + private void ReadTrailer() + { + // Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32 + Span trailer = stackalloc byte[8]; + int n = _stream.Read(trailer); + + Crc = BinaryPrimitives.ReadInt32LittleEndian(trailer); + UncompressedSize = BinaryPrimitives.ReadInt32LittleEndian(trailer.Slice(4)); + } + + private void ReadAndValidateGzipHeader() { // read the header on the first read Span header = stackalloc byte[10]; - int n = stream.Read(header); + int n = _stream.Read(header); // workitem 8501: handle edge case (decompress empty stream) if (n == 0) @@ -64,12 +83,12 @@ namespace SharpCompress.Common.GZip if ((header[3] & 0x04) == 0x04) { // read and discard extra field - n = stream.Read(header.Slice(0, 2)); // 2-byte length field + n = _stream.Read(header.Slice(0, 2)); // 2-byte length field short extraLength = (short)(header[0] + header[1] * 256); byte[] extra = new byte[extraLength]; - if (!stream.ReadFully(extra)) + if (!_stream.ReadFully(extra)) { throw new ZlibException("Unexpected end-of-file reading GZIP header."); } @@ -77,15 +96,15 @@ namespace SharpCompress.Common.GZip } if ((header[3] & 0x08) == 0x08) { - _name = ReadZeroTerminatedString(stream); + _name = ReadZeroTerminatedString(_stream); } if ((header[3] & 0x10) == 0x010) { - ReadZeroTerminatedString(stream); + ReadZeroTerminatedString(_stream); } if ((header[3] & 0x02) == 0x02) { - stream.ReadByte(); // CRC16, ignore + _stream.ReadByte(); // CRC16, ignore } } diff --git a/src/SharpCompress/Compressors/Deflate/GZipStream.cs b/src/SharpCompress/Compressors/Deflate/GZipStream.cs index 903cea2c..ffc4759d 100644 --- a/src/SharpCompress/Compressors/Deflate/GZipStream.cs +++ b/src/SharpCompress/Compressors/Deflate/GZipStream.cs @@ -37,10 +37,9 @@ namespace SharpCompress.Compressors.Deflate { internal static readonly DateTime UNIX_EPOCH = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - public DateTime? LastModified { get; set; } - private string? _comment; private string? _fileName; + private DateTime? _lastModified; internal ZlibBaseStream BaseStream; private bool _disposed; @@ -274,6 +273,7 @@ namespace SharpCompress.Compressors.Deflate _firstReadDone = true; FileName = BaseStream._GzipFileName; Comment = BaseStream._GzipComment; + LastModified = BaseStream._GzipMtime; } return n; } @@ -358,6 +358,20 @@ namespace SharpCompress.Compressors.Deflate } } + + public DateTime? LastModified + { + get => _lastModified; + set + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(GZipStream)); + } + _lastModified = value; + } + } + public string? FileName { get => _fileName; @@ -398,8 +412,8 @@ namespace SharpCompress.Compressors.Deflate byte[]? filenameBytes = (FileName is null) ? null : _encoding.GetBytes(FileName); - int cbLength = (commentBytes is null) ? 0 : commentBytes.Length + 1; - int fnLength = (filenameBytes is null) ? 0 : filenameBytes.Length + 1; + int cbLength = commentBytes?.Length + 1 ?? 0; + int fnLength = filenameBytes?.Length + 1 ?? 0; int bufferLength = 10 + cbLength + fnLength; var header = new byte[bufferLength]; @@ -425,7 +439,7 @@ namespace SharpCompress.Compressors.Deflate header[i++] = flag; // mtime - if (!LastModified.HasValue) + if (LastModified is null) { LastModified = DateTime.Now; } diff --git a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs index 8476724a..5f4a7c22 100644 --- a/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipArchiveTests.cs @@ -22,6 +22,13 @@ namespace SharpCompress.Test.GZip { var entry = archive.Entries.First(); entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key)); + + long size = entry.Size; + var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar")); + var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); + + Assert.Equal(size, scratch.Length); + Assert.Equal(size, test.Length); } CompareArchivesByPath(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"), Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); @@ -35,6 +42,13 @@ namespace SharpCompress.Test.GZip { var entry = archive.Entries.First(); entry.WriteToFile(Path.Combine(SCRATCH_FILES_PATH, entry.Key)); + + long size = entry.Size; + var scratch = new FileInfo(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar")); + var test = new FileInfo(Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar")); + + Assert.Equal(size, scratch.Length); + Assert.Equal(size, test.Length); } CompareArchivesByPath(Path.Combine(SCRATCH_FILES_PATH, "Tar.tar"), Path.Combine(TEST_ARCHIVES_PATH, "Tar.tar"));