-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
TarHelpers.cs
306 lines (272 loc) · 13.2 KB
/
TarHelpers.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace System.Formats.Tar
{
// Static class containing a variety of helper methods.
internal static class TarHelpers
{
internal const short RecordSize = 512;
internal const int MaxBufferLength = 4096;
internal const int ZeroChar = 0x30;
internal const byte SpaceChar = 0x20;
internal const byte EqualsChar = 0x3d;
internal const byte NewLineChar = 0xa;
internal const UnixFileMode DefaultMode = // 644 in octal
UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.GroupRead | UnixFileMode.OtherRead;
// Helps advance the stream a total number of bytes larger than int.MaxValue.
internal static void AdvanceStream(Stream archiveStream, long bytesToDiscard)
{
if (archiveStream.CanSeek)
{
archiveStream.Position += bytesToDiscard;
}
else if (bytesToDiscard > 0)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(minimumLength: MaxBufferLength);
while (bytesToDiscard > 0)
{
int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToDiscard);
if (archiveStream.Read(buffer.AsSpan(0, currentLengthToRead)) != currentLengthToRead)
{
throw new EndOfStreamException();
}
bytesToDiscard -= currentLengthToRead;
}
ArrayPool<byte>.Shared.Return(buffer);
}
}
// Helps copy a specific number of bytes from one stream into another.
internal static void CopyBytes(Stream origin, Stream destination, long bytesToCopy)
{
byte[] buffer = ArrayPool<byte>.Shared.Rent(minimumLength: MaxBufferLength);
while (bytesToCopy > 0)
{
int currentLengthToRead = (int)Math.Min(MaxBufferLength, bytesToCopy);
if (origin.Read(buffer.AsSpan(0, currentLengthToRead)) != currentLengthToRead)
{
throw new EndOfStreamException();
}
destination.Write(buffer.AsSpan(0, currentLengthToRead));
bytesToCopy -= currentLengthToRead;
}
ArrayPool<byte>.Shared.Return(buffer);
}
// Returns the number of bytes until the next multiple of the record size.
internal static int CalculatePadding(long size)
{
long ceilingMultipleOfRecordSize = ((RecordSize - 1) | (size - 1)) + 1;
int padding = (int)(ceilingMultipleOfRecordSize - size);
return padding;
}
// Returns the specified 8-base number as a 10-base number.
internal static int ConvertDecimalToOctal(int value)
{
int multiplier = 1;
int accum = value;
int actual = 0;
while (accum != 0)
{
actual += (accum % 8) * multiplier;
accum /= 8;
multiplier *= 10;
}
return actual;
}
// Returns the specified 10-base number as an 8-base number.
internal static long ConvertDecimalToOctal(long value)
{
long multiplier = 1;
long accum = value;
long actual = 0;
while (accum != 0)
{
actual += (accum % 8) * multiplier;
accum /= 8;
multiplier *= 10;
}
return actual;
}
// Returns true if all the bytes in the specified array are nulls, false otherwise.
internal static bool IsAllNullBytes(Span<byte> buffer)
{
for (int i = 0; i < buffer.Length; i++)
{
if (buffer[i] != 0)
{
return false;
}
}
return true;
}
// Converts the specified number of seconds that have passed since the Unix Epoch to a DateTimeOffset.
internal static DateTimeOffset GetDateTimeOffsetFromSecondsSinceEpoch(long secondsSinceUnixEpoch) =>
new DateTimeOffset((secondsSinceUnixEpoch * TimeSpan.TicksPerSecond) + DateTime.UnixEpoch.Ticks, TimeSpan.Zero);
// Converts the specified number of seconds that have passed since the Unix Epoch to a DateTimeOffset.
private static DateTimeOffset GetDateTimeOffsetFromSecondsSinceEpoch(decimal secondsSinceUnixEpoch) =>
new DateTimeOffset((long)(secondsSinceUnixEpoch * TimeSpan.TicksPerSecond) + DateTime.UnixEpoch.Ticks, TimeSpan.Zero);
// Converts the specified DateTimeOffset to the number of seconds that have passed since the Unix Epoch.
private static decimal GetSecondsSinceEpochFromDateTimeOffset(DateTimeOffset dateTimeOffset) =>
((decimal)(dateTimeOffset.UtcDateTime - DateTime.UnixEpoch).Ticks) / TimeSpan.TicksPerSecond;
// If the specified fieldName is found in the provided dictionary and it is a valid decimal number, returns true and sets the value in 'dateTimeOffset'.
internal static bool TryGetDateTimeOffsetFromTimestampString(Dictionary<string, string>? dict, string fieldName, out DateTimeOffset dateTimeOffset)
{
dateTimeOffset = default;
if (dict != null &&
dict.TryGetValue(fieldName, out string? value) &&
decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal secondsSinceEpoch))
{
dateTimeOffset = GetDateTimeOffsetFromSecondsSinceEpoch(secondsSinceEpoch);
return true;
}
return false;
}
// Converts the specified DateTimeOffset to the string representation of seconds since the Unix Epoch.
internal static string GetTimestampStringFromDateTimeOffset(DateTimeOffset timestamp)
{
decimal secondsSinceEpoch = GetSecondsSinceEpochFromDateTimeOffset(timestamp);
// Use 'G' to ensure the decimals get preserved (avoid losing precision).
return secondsSinceEpoch.ToString("G", CultureInfo.InvariantCulture);
}
// If the specified fieldName is found in the provided dictionary and is a valid string representation of a number, returns true and sets the value in 'baseTenInteger'.
internal static bool TryGetStringAsBaseTenInteger(IReadOnlyDictionary<string, string> dict, string fieldName, out int baseTenInteger)
{
if (dict.TryGetValue(fieldName, out string? strNumber) && !string.IsNullOrEmpty(strNumber))
{
baseTenInteger = Convert.ToInt32(strNumber);
return true;
}
baseTenInteger = 0;
return false;
}
// If the specified fieldName is found in the provided dictionary and is a valid string representation of a number, returns true and sets the value in 'baseTenLong'.
internal static bool TryGetStringAsBaseTenLong(IReadOnlyDictionary<string, string> dict, string fieldName, out long baseTenLong)
{
if (dict.TryGetValue(fieldName, out string? strNumber) && !string.IsNullOrEmpty(strNumber))
{
baseTenLong = Convert.ToInt64(strNumber);
return true;
}
baseTenLong = 0;
return false;
}
// Receives a byte array that represents an ASCII string containing a number in octal base.
// Converts the array to an octal base number, then transforms it to ten base and returns it.
internal static int GetTenBaseNumberFromOctalAsciiChars(Span<byte> buffer)
{
string str = GetTrimmedAsciiString(buffer);
return string.IsNullOrEmpty(str) ? 0 : Convert.ToInt32(str, fromBase: 8);
}
// Receives a byte array that represents an ASCII string containing a number in octal base.
// Converts the array to an octal base number, then transforms it to ten base and returns it.
internal static long GetTenBaseLongFromOctalAsciiChars(Span<byte> buffer)
{
string str = GetTrimmedAsciiString(buffer);
return string.IsNullOrEmpty(str) ? 0 : Convert.ToInt64(str, fromBase: 8);
}
// Returns the string contained in the specified buffer of bytes,
// in the specified encoding, removing the trailing null or space chars.
private static string GetTrimmedString(ReadOnlySpan<byte> buffer, Encoding encoding)
{
int trimmedLength = buffer.Length;
while (trimmedLength > 0 && IsByteNullOrSpace(buffer[trimmedLength - 1]))
{
trimmedLength--;
}
return trimmedLength == 0 ? string.Empty : encoding.GetString(buffer.Slice(0, trimmedLength));
static bool IsByteNullOrSpace(byte c) => c is 0 or 32;
}
// 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);
// Returns the UTF8 string contained in the specified buffer of bytes,
// removing the trailing null or space chars.
internal static string GetTrimmedUtf8String(ReadOnlySpan<byte> buffer) => GetTrimmedString(buffer, Encoding.UTF8);
// After the file contents, there may be zero or more null characters,
// which exist to ensure the data is aligned to the record size. Skip them and
// set the stream position to the first byte of the next entry.
internal static int SkipBlockAlignmentPadding(Stream archiveStream, long size)
{
int bytesToSkip = CalculatePadding(size);
AdvanceStream(archiveStream, bytesToSkip);
return bytesToSkip;
}
// Throws if the specified entry type is not supported for the specified format.
internal static void ThrowIfEntryTypeNotSupported(TarEntryType entryType, TarEntryFormat archiveFormat)
{
switch (archiveFormat)
{
case TarEntryFormat.V7:
if (entryType is
TarEntryType.Directory or
TarEntryType.HardLink or
TarEntryType.V7RegularFile or
TarEntryType.SymbolicLink)
{
return;
}
break;
case TarEntryFormat.Ustar:
if (entryType is
TarEntryType.BlockDevice or
TarEntryType.CharacterDevice or
TarEntryType.Directory or
TarEntryType.Fifo or
TarEntryType.HardLink or
TarEntryType.RegularFile or
TarEntryType.SymbolicLink)
{
return;
}
break;
case TarEntryFormat.Pax:
if (entryType is
TarEntryType.BlockDevice or
TarEntryType.CharacterDevice or
TarEntryType.Directory or
TarEntryType.Fifo or
TarEntryType.HardLink or
TarEntryType.RegularFile or
TarEntryType.SymbolicLink)
{
// GlobalExtendedAttributes is handled via PaxGlobalExtendedAttributesEntry
// Not supported for writing - internally autogenerated:
// - ExtendedAttributes
return;
}
break;
case TarEntryFormat.Gnu:
if (entryType is
TarEntryType.BlockDevice or
TarEntryType.CharacterDevice or
TarEntryType.Directory or
TarEntryType.Fifo or
TarEntryType.HardLink or
TarEntryType.RegularFile or
TarEntryType.SymbolicLink)
{
// Not supported for writing:
// - ContiguousFile
// - DirectoryList
// - MultiVolume
// - RenamedOrSymlinked
// - SparseFile
// - TapeVolume
// Also not supported for writing - internally autogenerated:
// - LongLink
// - LongPath
return;
}
break;
case TarEntryFormat.Unknown:
default:
throw new FormatException(string.Format(SR.TarInvalidFormat, archiveFormat));
}
throw new InvalidOperationException(string.Format(SR.TarEntryTypeNotSupported, entryType, archiveFormat));
}
}
}