Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/libraries/System.Formats.Tar/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,10 @@
<data name="ExtHeaderInvalidRecords" xml:space="preserve">
<value>The extended header contains invalid records.</value>
</data>
<data name="TarInvalidArchiveFormat" xml:space="preserve">
<value>The file is not a valid TAR archive format.</value>
</data>
<data name="TarCompressionArchiveDetected" xml:space="preserve">
<value>The file appears to be a {0} archive. TAR format expected.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,20 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca
{
return null;
}
int checksum = (int)TarHelpers.ParseOctal<uint>(spanChecksum);

int checksum;
try
{
checksum = (int)TarHelpers.ParseOctal<uint>(spanChecksum);
}
catch (InvalidDataException)
{
// This is likely not a TAR file
// Check for compression magic numbers to provide better error message
ThrowInvalidFormatWithBetterMessage(buffer);
throw; // Ensure control flow never reaches code using 'checksum'
}

// Zero checksum means the whole header is empty
if (checksum == 0)
{
Expand Down Expand Up @@ -789,5 +802,86 @@ private static bool TryGetNextExtendedAttribute(
buffer = buffer.Slice(newlinePos + 1);
return true;
}


/// Analyzes the buffer for known file format magic numbers and throws an InvalidDataException
/// with a specific error message. This provides better user experience by identifying the
/// actual file type when users pass non-TAR files to the TAR reader.
/// <exception cref="InvalidDataException">Always thrown with either a format-specific or generic message</exception>
[DoesNotReturn]
private static void ThrowInvalidFormatWithBetterMessage(ReadOnlySpan<byte> buffer)
{
if (buffer.Length < 2)
{
throw new InvalidDataException(SR.TarInvalidArchiveFormat);
}

byte firstByte = buffer[0];
switch (firstByte)
{
case 0x28: // Zstandard
if (buffer.Length >= 4 &&
buffer[1] == 0xB5 && buffer[2] == 0x2F && buffer[3] == 0xFD)
{
throw new InvalidDataException(SR.Format(SR.TarCompressionArchiveDetected, "Zstandard"));
}
break;

case 0x37: // 7-Zip
if (buffer.Length >= 6 &&
buffer[1] == 0x7A && buffer[2] == 0xBC &&
buffer[3] == 0xAF && buffer[4] == 0x27 && buffer[5] == 0x1C)
{
throw new InvalidDataException(SR.Format(SR.TarCompressionArchiveDetected, "7-Zip"));
}
break;

case 0x50: // ZIP files start with "PK"
if (buffer.Length >= 2 && buffer[1] == 0x4B)
{
throw new InvalidDataException(SR.Format(SR.TarCompressionArchiveDetected, "ZIP"));
}
break;

case 0x1F: // GZIP
if (buffer.Length >= 2 && buffer[1] == 0x8B)
{
throw new InvalidDataException(SR.Format(SR.TarCompressionArchiveDetected, "GZIP"));
}
break;

case 0x42: // BZIP2 - "BZh"
if (buffer.Length >= 3 && buffer[1] == 0x5A && buffer[2] == 0x68)
{
throw new InvalidDataException(SR.Format(SR.TarCompressionArchiveDetected, "BZIP2"));
}
break;

case 0xFD: // XZ
if (buffer.Length >= 6 &&
buffer[1] == 0x37 && buffer[2] == 0x7A &&
buffer[3] == 0x58 && buffer[4] == 0x5A && buffer[5] == 0x00)
{
throw new InvalidDataException(SR.Format(SR.TarCompressionArchiveDetected, "XZ"));
}
break;

case 0x78: // ZLIB (deflate compression)
if (buffer.Length >= 2)
{
byte secondByte = buffer[1];
if (secondByte == 0x01 || secondByte == 0x5E || secondByte == 0x9C ||
secondByte == 0xDA || secondByte == 0x20 || secondByte == 0x7D ||
secondByte == 0xBB || secondByte == 0xF9)
{
throw new InvalidDataException(SR.Format(SR.TarCompressionArchiveDetected, "ZLIB"));
}
}
break;
}

// If we can't identify the specific format, provide a generic message
throw new InvalidDataException(SR.TarInvalidArchiveFormat);
}
}
}
Loading