// Reads the long path found in the data section of a GNU entry of type 'K' or 'L' // and replaces Name or LinkName, respectively, with the found string. // Throws if end of stream is reached. private void ReadGnuLongPathDataBlock(Stream archiveStream) { Debug.Assert(_typeFlag is TarEntryType.LongLink or TarEntryType.LongPath); if (_size == 0) { return; } byte[] buffer = new byte[(int)_size]; if (archiveStream.Read(buffer.AsSpan()) != _size) { throw new EndOfStreamException(); } string longPath = TarHelpers.GetTrimmedUtf8String(buffer); if (_typeFlag == TarEntryType.LongLink) { _linkName = longPath; } else if (_typeFlag == TarEntryType.LongPath) { _name = longPath; } }
// Reads the ustar prefix attribute. // Throws if a conversion to an expected data type fails. private void ReadUstarAttributes(Span <byte> buffer) { _prefix = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.Prefix, FieldLengths.Prefix)); // In ustar, Prefix is used to store the *leading* path segments of // Name, if the full path did not fit in the Name byte array. if (!string.IsNullOrEmpty(_prefix)) { // Prefix never has a leading separator, so we add it // it should always be a forward slash for compatibility _name = string.Format(UstarPrefixFormat, _prefix, _name); } }
// Collects the GNU long path info from the buffer and sets it in the right field depending on the type flag. private void ReadGnuLongPathDataFromBuffer(ReadOnlySpan <byte> buffer) { string longPath = TarHelpers.GetTrimmedUtf8String(buffer); if (_typeFlag == TarEntryType.LongLink) { _linkName = longPath; } else if (_typeFlag == TarEntryType.LongPath) { _name = longPath; } }
// Returns a dictionary containing the extended attributes collected from the provided byte buffer. private void ReadExtendedAttributesFromBuffer(ReadOnlySpan <byte> buffer, string name) { string dataAsString = TarHelpers.GetTrimmedUtf8String(buffer); using StringReader reader = new(dataAsString); while (TryGetNextExtendedAttribute(reader, out string?key, out string?value)) { if (ExtendedAttributes.ContainsKey(key)) { throw new FormatException(string.Format(SR.TarDuplicateExtendedAttribute, name)); } ExtendedAttributes.Add(key, value); } }
// Collects the extended attributes found in the data section of a PAX entry of type 'x' or 'g'. // Throws if end of stream is reached or if an attribute is malformed. private void ReadExtendedAttributesBlock(Stream archiveStream) { Debug.Assert(_typeFlag is TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes); // Regardless of the size, this entry should always have a valid dictionary object _extendedAttributes ??= new Dictionary <string, string>(); if (_size == 0) { return; } // It is not expected that the extended attributes data section will be longer than int.MaxValue, considering // 4096 is a common max path length, and also the size field is 12 bytes long, which is under int.MaxValue. if (_size > int.MaxValue) { throw new InvalidOperationException(string.Format(SR.TarSizeFieldTooLargeForExtendedAttribute, _typeFlag.ToString())); } byte[] buffer = new byte[(int)_size]; if (archiveStream.Read(buffer.AsSpan()) != _size) { throw new EndOfStreamException(); } string dataAsString = TarHelpers.GetTrimmedUtf8String(buffer); using StringReader reader = new(dataAsString); while (TryGetNextExtendedAttribute(reader, out string?key, out string?value)) { _extendedAttributes ??= new Dictionary <string, string>(); if (_extendedAttributes.ContainsKey(key)) { throw new FormatException(string.Format(SR.TarDuplicateExtendedAttribute, _name)); } _extendedAttributes.Add(key, value); } }
// Reads the long path found in the data section of a GNU entry of type 'K' or 'L' // and replaces Name or LinkName, respectively, with the found string. // Throws if end of stream is reached. private void ReadGnuLongPathDataBlock(Stream archiveStream) { Debug.Assert(_typeFlag is TarEntryType.LongLink or TarEntryType.LongPath); if (_size == 0) { return; } byte[] buffer = new byte[(int)_size]; archiveStream.ReadExactly(buffer); string longPath = TarHelpers.GetTrimmedUtf8String(buffer); if (_typeFlag == TarEntryType.LongLink) { _linkName = longPath; } else if (_typeFlag == TarEntryType.LongPath) { _name = longPath; } }
// Attempts to read the fields shared by all formats and stores them in their expected data type. // Throws if any data type conversion fails. // Returns true on success, false if checksum is zero. private static TarHeader?TryReadCommonAttributes(Span <byte> buffer, TarEntryFormat initialFormat) { // Start by collecting fields that need special checks that return early when data is wrong // Empty checksum means this is an invalid (all blank) entry, finish early Span <byte> spanChecksum = buffer.Slice(FieldLocations.Checksum, FieldLengths.Checksum); if (TarHelpers.IsAllNullBytes(spanChecksum)) { return(null); } int checksum = (int)TarHelpers.ParseOctal <uint>(spanChecksum); // Zero checksum means the whole header is empty if (checksum == 0) { return(null); } long size = (int)TarHelpers.ParseOctal <uint>(buffer.Slice(FieldLocations.Size, FieldLengths.Size)); if (size < 0) { throw new FormatException(string.Format(SR.TarSizeFieldNegative)); } // Continue with the rest of the fields that require no special checks TarHeader header = new(initialFormat, name : TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.Name, FieldLengths.Name)), mode : (int)TarHelpers.ParseOctal <uint>(buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)), mTime : TarHelpers.GetDateTimeOffsetFromSecondsSinceEpoch((long)TarHelpers.ParseOctal <ulong>(buffer.Slice(FieldLocations.MTime, FieldLengths.MTime))), typeFlag : (TarEntryType)buffer[FieldLocations.TypeFlag]) { _checksum = checksum, _size = size, _uid = (int)TarHelpers.ParseOctal <uint>(buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)), _gid = (int)TarHelpers.ParseOctal <uint>(buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)), _linkName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)) }; if (header._format == TarEntryFormat.Unknown) { header._format = header._typeFlag switch { TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes => TarEntryFormat.Pax, TarEntryType.DirectoryList or TarEntryType.LongLink or TarEntryType.LongPath or TarEntryType.MultiVolume or TarEntryType.RenamedOrSymlinked 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 }; } return(header); }
// Attempts to read the fields shared by all formats and stores them in their expected data type. // Throws if any data type conversion fails. // Returns true on success, false if checksum is zero. private bool TryReadCommonAttributes(Span <byte> buffer) { // Start by collecting fields that need special checks that return early when data is wrong // Empty checksum means this is an invalid (all blank) entry, finish early Span <byte> spanChecksum = buffer.Slice(FieldLocations.Checksum, FieldLengths.Checksum); if (TarHelpers.IsAllNullBytes(spanChecksum)) { return(false); } _checksum = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(spanChecksum); // Zero checksum means the whole header is empty if (_checksum == 0) { return(false); } _size = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Size, FieldLengths.Size)); if (_size < 0) { throw new FormatException(string.Format(SR.TarSizeFieldNegative, _name)); } // Continue with the rest of the fields that require no special checks _name = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.Name, FieldLengths.Name)); _mode = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Mode, FieldLengths.Mode)); _uid = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Uid, FieldLengths.Uid)); _gid = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.Gid, FieldLengths.Gid)); int mTime = TarHelpers.GetTenBaseNumberFromOctalAsciiChars(buffer.Slice(FieldLocations.MTime, FieldLengths.MTime)); _mTime = TarHelpers.GetDateTimeFromSecondsSinceEpoch(mTime); _typeFlag = (TarEntryType)buffer[FieldLocations.TypeFlag]; _linkName = TarHelpers.GetTrimmedUtf8String(buffer.Slice(FieldLocations.LinkName, FieldLengths.LinkName)); if (_format == TarFormat.Unknown) { _format = _typeFlag switch { TarEntryType.ExtendedAttributes or TarEntryType.GlobalExtendedAttributes => TarFormat.Pax, TarEntryType.DirectoryList or TarEntryType.LongLink or TarEntryType.LongPath or TarEntryType.MultiVolume or TarEntryType.RenamedOrSymlinked or TarEntryType.SparseFile or TarEntryType.TapeVolume => TarFormat.Gnu, // V7 is the only one that uses 'V7RegularFile'. TarEntryType.V7RegularFile => TarFormat.V7, // 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 _ => (_typeFlag == TarEntryType.RegularFile) ? TarFormat.Ustar : TarFormat.V7 }; } return(true); }