|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
namespace System.Formats.Tar
{
// Describes the header attributes from a tar archive entry.
// Supported formats:
// - 1979 Version 7 AT&T Unix Tar Command Format (v7).
// - POSIX IEEE 1003.1-1988 Unix Standard Tar Format (ustar).
// - POSIX IEEE 1003.1-2001 ("POSIX.1") Pax Interchange Tar Format (pax).
// - GNU Tar Format (gnu).
// Documentation: https://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5
internal sealed partial class TarHeader
{
// POSIX fields (shared by Ustar and PAX)
private const string UstarMagic = "ustar\0";
private const string UstarVersion = "00";
// GNU-specific fields
private const string GnuMagic = "ustar ";
private const string GnuVersion = " \0";
// Names of PAX extended attributes commonly found fields
private const string PaxEaName = "path";
private const string PaxEaLinkName = "linkpath";
private const string PaxEaMode = "mode";
private const string PaxEaGName = "gname";
private const string PaxEaUName = "uname";
private const string PaxEaGid = "gid";
private const string PaxEaUid = "uid";
internal const string PaxEaATime = "atime";
internal const string PaxEaCTime = "ctime";
private const string PaxEaMTime = "mtime";
private const string PaxEaSize = "size";
private const string PaxEaDevMajor = "devmajor";
private const string PaxEaDevMinor = "devminor";
internal Stream? _dataStream;
internal long _dataOffset;
// Position in the stream where the data ends in this header.
internal long _endOfHeaderAndDataAndBlockAlignment;
internal TarEntryFormat _format;
// Common attributes
internal string _name;
internal int _mode;
internal int _uid;
internal int _gid;
internal long _size;
internal DateTimeOffset _mTime;
internal int _checksum;
internal TarEntryType _typeFlag;
internal string? _linkName;
// POSIX and GNU shared attributes
internal string _magic;
internal string _version;
internal string? _gName;
internal string? _uName;
internal int _devMajor;
internal int _devMinor;
// POSIX attributes
internal string? _prefix;
// PAX attributes
private Dictionary<string, string>? _ea;
internal Dictionary<string, string> ExtendedAttributes => _ea ??= new Dictionary<string, string>();
// GNU attributes
internal DateTimeOffset _aTime;
internal DateTimeOffset _cTime;
// If the archive is GNU and the offset, longnames, unused, sparse, isextended and realsize
// fields have data, we store it to avoid data loss, but we don't yet expose it publicly.
internal byte[]? _gnuUnusedBytes;
// Constructor called when creating an entry with default common fields.
internal TarHeader(TarEntryFormat format, string name = "", int mode = 0, DateTimeOffset mTime = default, TarEntryType typeFlag = TarEntryType.RegularFile)
{
_format = format;
_name = name;
_mode = mode;
_mTime = mTime;
_typeFlag = typeFlag;
_magic = GetMagicForFormat(format);
_version = GetVersionForFormat(format);
_dataOffset = -1;
}
// Constructor called when creating an entry using the common fields from another entry.
// The *TarEntry constructor calling this should take care of setting any format-specific fields.
internal TarHeader(TarEntryFormat format, TarEntryType typeFlag, TarHeader other)
: this(format, other._name, other._mode, other._mTime, typeFlag)
{
_uid = other._uid;
_gid = other._gid;
_size = other._size;
_checksum = other._checksum;
_linkName = other._linkName;
_dataStream = other._dataStream;
}
internal void InitializeExtendedAttributesWithExisting(IEnumerable<KeyValuePair<string, string>> existing)
{
Debug.Assert(_ea == null);
Debug.Assert(existing != null);
using IEnumerator<KeyValuePair<string, string>> enumerator = existing.GetEnumerator();
while (enumerator.MoveNext())
{
KeyValuePair<string, string> kvp = enumerator.Current;
int index = kvp.Key.AsSpan().IndexOfAny('=', '\n');
if (index >= 0)
{
throw new ArgumentException(SR.Format(SR.TarExtAttrDisallowedKeyChar, kvp.Key, kvp.Key[index] == '\n' ? "\\n" : kvp.Key[index]));
}
if (kvp.Value.Contains('\n'))
{
throw new ArgumentException(SR.Format(SR.TarExtAttrDisallowedValueChar, kvp.Key, "\\n"));
}
_ea ??= new Dictionary<string, string>();
_ea.Add(kvp.Key, kvp.Value);
}
}
private static string GetMagicForFormat(TarEntryFormat format) => format switch
{
TarEntryFormat.Ustar or TarEntryFormat.Pax => UstarMagic,
TarEntryFormat.Gnu => GnuMagic,
_ => string.Empty,
};
private static string GetVersionForFormat(TarEntryFormat format) => format switch
{
TarEntryFormat.Ustar or TarEntryFormat.Pax => UstarVersion,
TarEntryFormat.Gnu => GnuVersion,
_ => string.Empty,
};
// Stores the archive stream's position where we know the current entry's data section begins,
// if the archive stream is seekable. Otherwise, -1.
private static void SetDataOffset(TarHeader header, Stream archiveStream) =>
header._dataOffset = archiveStream.CanSeek ? archiveStream.Position : -1;
}
}
|