-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Add tests for exotic external tar asset archives, fix some more corner case bugs #74412
Changes from all commits
6dc5425
2da41d1
264fc18
d7ce734
f06a4c1
9e83b4c
121d20a
33d2461
457cf2d
37a3511
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -205,6 +205,9 @@ | |||||
<value>The entry is a symbolic link or a hard link but the LinkName field is null or empty.</value> | ||||||
</data> | ||||||
<data name="TarEntryTypeNotSupported" xml:space="preserve"> | ||||||
<value>Entry type '{0}' not supported.</value> | ||||||
</data> | ||||||
<data name="TarEntryTypeNotSupportedInFormat" xml:space="preserve"> | ||||||
<value>Entry type '{0}' not supported in format '{1}'.</value> | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
</data> | ||||||
<data name="TarEntryTypeNotSupportedForExtracting" xml:space="preserve"> | ||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -20,15 +20,15 @@ internal sealed partial class TarHeader | |||||
// Attempts to retrieve the next header from the specified tar archive stream. | ||||||
// Throws if end of stream is reached or if any data type conversion fails. | ||||||
// Returns a valid TarHeader object if the attributes were read successfully, null otherwise. | ||||||
internal static TarHeader? TryGetNextHeader(Stream archiveStream, bool copyData, TarEntryFormat initialFormat) | ||||||
internal static TarHeader? TryGetNextHeader(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, bool processDataBlock) | ||||||
{ | ||||||
// The four supported formats have a header that fits in the default record size | ||||||
Span<byte> buffer = stackalloc byte[TarHelpers.RecordSize]; | ||||||
|
||||||
archiveStream.ReadExactly(buffer); | ||||||
|
||||||
TarHeader? header = TryReadAttributes(initialFormat, buffer); | ||||||
if (header != null) | ||||||
if (header != null && processDataBlock) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this better than one extra param?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good question. That was my initial approach, but then I opted for the extra boolean for these reasons:
|
||||||
{ | ||||||
header.ProcessDataBlock(archiveStream, copyData); | ||||||
} | ||||||
|
@@ -39,7 +39,7 @@ internal sealed partial class TarHeader | |||||
// Asynchronously attempts read all the fields of the next header. | ||||||
// Throws if end of stream is reached or if any data type conversion fails. | ||||||
// Returns true if all the attributes were read successfully, false otherwise. | ||||||
internal static async ValueTask<TarHeader?> TryGetNextHeaderAsync(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, CancellationToken cancellationToken) | ||||||
internal static async ValueTask<TarHeader?> TryGetNextHeaderAsync(Stream archiveStream, bool copyData, TarEntryFormat initialFormat, bool processDataBlock, CancellationToken cancellationToken) | ||||||
{ | ||||||
cancellationToken.ThrowIfCancellationRequested(); | ||||||
|
||||||
|
@@ -50,7 +50,7 @@ internal sealed partial class TarHeader | |||||
await archiveStream.ReadExactlyAsync(buffer, cancellationToken).ConfigureAwait(false); | ||||||
|
||||||
TarHeader? header = TryReadAttributes(initialFormat, buffer.Span); | ||||||
if (header != null) | ||||||
if (header != null && processDataBlock) | ||||||
{ | ||||||
await header.ProcessDataBlockAsync(archiveStream, copyData, cancellationToken).ConfigureAwait(false); | ||||||
} | ||||||
|
@@ -180,7 +180,7 @@ internal void ReplaceNormalAttributesWithExtended(Dictionary<string, string>? di | |||||
// will get all the data section read and the stream pointer positioned at the beginning of the next header. | ||||||
// - Block, Character, Directory, Fifo, HardLink and SymbolicLink typeflag entries have no data section so the archive stream pointer will be positioned at the beginning of the next header. | ||||||
// - All other typeflag entries with a data section will generate a stream wrapping the data section: SeekableSubReadStream for seekable archive streams, and SubReadStream for unseekable archive streams. | ||||||
private void ProcessDataBlock(Stream archiveStream, bool copyData) | ||||||
internal void ProcessDataBlock(Stream archiveStream, bool copyData) | ||||||
{ | ||||||
bool skipBlockAlignmentPadding = true; | ||||||
|
||||||
|
@@ -199,6 +199,10 @@ private void ProcessDataBlock(Stream archiveStream, bool copyData) | |||||
case TarEntryType.HardLink: | ||||||
case TarEntryType.SymbolicLink: | ||||||
// No data section | ||||||
if (_size > 0) | ||||||
{ | ||||||
throw new FormatException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag)); | ||||||
} | ||||||
break; | ||||||
case TarEntryType.RegularFile: | ||||||
case TarEntryType.V7RegularFile: // Treated as regular file | ||||||
|
@@ -257,6 +261,10 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca | |||||
case TarEntryType.HardLink: | ||||||
case TarEntryType.SymbolicLink: | ||||||
// No data section | ||||||
if (_size > 0) | ||||||
{ | ||||||
throw new FormatException(string.Format(SR.TarSizeFieldTooLargeForEntryType, _typeFlag)); | ||||||
} | ||||||
break; | ||||||
case TarEntryType.RegularFile: | ||||||
case TarEntryType.V7RegularFile: // Treated as regular file | ||||||
|
@@ -311,6 +319,8 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca | |||||
{ | ||||||
MemoryStream copiedData = new MemoryStream(); | ||||||
TarHelpers.CopyBytes(archiveStream, copiedData, _size); | ||||||
// Reset position pointer so the user can do the first DataStream read from the beginning | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: slightly better wording (don't trigger CI for this suggestion)
Suggested change
also: do other usages of |
||||||
copiedData.Position = 0; | ||||||
return copiedData; | ||||||
} | ||||||
|
||||||
|
@@ -336,6 +346,8 @@ private async Task ProcessDataBlockAsync(Stream archiveStream, bool copyData, Ca | |||||
{ | ||||||
MemoryStream copiedData = new MemoryStream(); | ||||||
await TarHelpers.CopyBytesAsync(archiveStream, copiedData, size, cancellationToken).ConfigureAwait(false); | ||||||
// Reset position pointer so the user can do the first DataStream read from the beginning | ||||||
copiedData.Position = 0; | ||||||
return copiedData; | ||||||
} | ||||||
|
||||||
|
@@ -396,12 +408,13 @@ TarEntryType.LongLink or | |||||
TarEntryType.LongPath or | ||||||
TarEntryType.MultiVolume or | ||||||
TarEntryType.RenamedOrSymlinked or | ||||||
TarEntryType.SparseFile or | ||||||
TarEntryType.TapeVolume => TarEntryFormat.Gnu, | ||||||
|
||||||
// V7 is the only one that uses 'V7RegularFile'. | ||||||
TarEntryType.V7RegularFile => TarEntryFormat.V7, | ||||||
|
||||||
TarEntryType.SparseFile => throw new NotSupportedException(string.Format(SR.TarEntryTypeNotSupported, header._typeFlag)), | ||||||
|
||||||
// We can quickly determine the *minimum* possible format if the entry type | ||||||
// is the POSIX 'RegularFile', although later we could upgrade it to PAX or GNU | ||||||
_ => (header._typeFlag == TarEntryType.RegularFile) ? TarEntryFormat.Ustar : TarEntryFormat.V7 | ||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -203,6 +203,12 @@ internal static TarEntryType GetCorrectTypeFlagForFormat(TarEntryFormat format, | |||||||||||||||||||||
internal static T ParseOctal<T>(ReadOnlySpan<byte> buffer) where T : struct, INumber<T> | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
buffer = TrimEndingNullsAndSpaces(buffer); | ||||||||||||||||||||||
buffer = TrimLeadingNullsAndSpaces(buffer); | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: either {Ending,Starting} or {Trailing,Leading} pair There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine, most tools I have tried only ignore nulls and spaces. I haven't found a spec explicitly calling it out how lenient the implementation can or should be, but libarchive's behavior is that it ignores all non-octal characters. Maybe add a comment linking to formal specification which talks about it, or if there is none: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks @am11. Sure, I can add a comment. If I don't get any blocking feedback to address, I'll add the comment in a separate PR to not block this with a CI re-run. |
||||||||||||||||||||||
|
||||||||||||||||||||||
if (buffer.Length == 0) | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
return T.Zero; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
T octalFactor = T.CreateTruncating(8u); | ||||||||||||||||||||||
T value = T.Zero; | ||||||||||||||||||||||
|
@@ -243,6 +249,17 @@ internal static ReadOnlySpan<byte> TrimEndingNullsAndSpaces(ReadOnlySpan<byte> b | |||||||||||||||||||||
return buffer.Slice(0, trimmedLength); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
private static ReadOnlySpan<byte> TrimLeadingNullsAndSpaces(ReadOnlySpan<byte> buffer) | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
int newStart = 0; | ||||||||||||||||||||||
while (newStart < buffer.Length && buffer[newStart] is 0 or 32) | ||||||||||||||||||||||
{ | ||||||||||||||||||||||
newStart++; | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
return buffer.Slice(newStart); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
Comment on lines
+253
to
+261
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: you can use the new
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since it's a nit, I can address this in a separate PR. |
||||||||||||||||||||||
|
||||||||||||||||||||||
// Returns the ASCII string contained in the specified buffer of bytes, | ||||||||||||||||||||||
// removing the trailing null or space chars. | ||||||||||||||||||||||
internal static string GetTrimmedAsciiString(ReadOnlySpan<byte> buffer) => GetTrimmedString(buffer, Encoding.ASCII); | ||||||||||||||||||||||
|
@@ -351,7 +368,7 @@ TarEntryType.RegularFile or | |||||||||||||||||||||
throw new FormatException(string.Format(SR.TarInvalidFormat, archiveFormat)); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
|
||||||||||||||||||||||
throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupported, entryType, archiveFormat)); | ||||||||||||||||||||||
throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupportedInFormat, entryType, archiveFormat)); | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} | ||||||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And below