Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose Last Modified time on GZipStream. Add CRC and Size to GZipEntries on Archive #560

Merged
merged 5 commits into from Jan 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/SharpCompress/Common/GZip/GZipEntry.cs
Expand Up @@ -15,15 +15,15 @@ internal GZipEntry(GZipFilePart filePart)

public override CompressionType CompressionType => CompressionType.GZip;

public override long Crc => 0;
public override long Crc => _filePart.Crc ?? 0;

public override string Key => _filePart.FilePartName;

public override string? LinkTarget => null;

public override long CompressedSize => 0;

public override long Size => 0;
public override long Size => _filePart.UncompressedSize ?? 0;

public override DateTime? LastModifiedTime => _filePart.DateModified;

Expand Down
37 changes: 28 additions & 9 deletions src/SharpCompress/Common/GZip/GZipFilePart.cs
Expand Up @@ -16,14 +16,23 @@ internal sealed class GZipFilePart : FilePart
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!;

Expand All @@ -37,11 +46,21 @@ internal override Stream GetRawStream()
return _stream;
}

private void ReadAndValidateGzipHeader(Stream stream)
private void ReadTrailer()
{
// Read and potentially verify the GZIP trailer: CRC32 and size mod 2^32
Span<byte> 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<byte> 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)
Expand All @@ -64,28 +83,28 @@ private void ReadAndValidateGzipHeader(Stream stream)
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.");
}
n = extraLength;
}
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
}
}

Expand Down
24 changes: 19 additions & 5 deletions src/SharpCompress/Compressors/Deflate/GZipStream.cs
Expand Up @@ -37,10 +37,9 @@ public class GZipStream : Stream
{
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;
Expand Down Expand Up @@ -274,6 +273,7 @@ public override int Read(byte[] buffer, int offset, int count)
_firstReadDone = true;
FileName = BaseStream._GzipFileName;
Comment = BaseStream._GzipComment;
LastModified = BaseStream._GzipMtime;
}
return n;
}
Expand Down Expand Up @@ -358,6 +358,20 @@ public override void Write(byte[] buffer, int offset, int count)
}
}


public DateTime? LastModified
{
get => _lastModified;
set
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(GZipStream));
}
_lastModified = value;
}
}

public string? FileName
{
get => _fileName;
Expand Down Expand Up @@ -398,8 +412,8 @@ private int EmitHeader()
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];
Expand All @@ -425,7 +439,7 @@ private int EmitHeader()
header[i++] = flag;

// mtime
if (!LastModified.HasValue)
if (LastModified is null)
{
LastModified = DateTime.Now;
}
Expand Down
14 changes: 14 additions & 0 deletions tests/SharpCompress.Test/GZip/GZipArchiveTests.cs
Expand Up @@ -22,6 +22,13 @@ public void GZip_Archive_Generic()
{
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"));
Expand All @@ -35,6 +42,13 @@ public void GZip_Archive()
{
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"));
Expand Down