Skip to content

Commit 4ee3b24

Browse files
authored
Merge PR #240, Add support for POSIX Extended Headers
Fixes #121 Only "path" keyword supported as it's used for non-GNU long file names.
1 parent c162f19 commit 4ee3b24

File tree

3 files changed

+165
-1
lines changed

3 files changed

+165
-1
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Text;
5+
6+
namespace ICSharpCode.SharpZipLib.Tar
7+
{
8+
public class TarExtendedHeaderReader
9+
{
10+
const byte LENGTH = 0;
11+
const byte KEY = 1;
12+
const byte VALUE = 2;
13+
const byte END = 3;
14+
15+
private readonly Dictionary<string, string> headers = new Dictionary<string, string>();
16+
17+
private string[] headerParts = new string[3];
18+
19+
int bbIndex;
20+
private byte[] byteBuffer;
21+
private char[] charBuffer;
22+
23+
private readonly StringBuilder sb = new StringBuilder();
24+
private readonly Decoder decoder = Encoding.UTF8.GetDecoder();
25+
26+
private int state = LENGTH;
27+
28+
private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' };
29+
30+
public TarExtendedHeaderReader()
31+
{
32+
ResetBuffers();
33+
}
34+
35+
public void Read(byte[] buffer, int length)
36+
{
37+
for (int i = 0; i < length; i++)
38+
{
39+
byte next = buffer[i];
40+
41+
if (next == StateNext[state])
42+
{
43+
Flush();
44+
headerParts[state] = sb.ToString();
45+
sb.Clear();
46+
47+
if (++state == END)
48+
{
49+
headers.Add(headerParts[KEY], headerParts[VALUE]);
50+
headerParts = new string[3];
51+
state = LENGTH;
52+
}
53+
}
54+
else
55+
{
56+
byteBuffer[bbIndex++] = next;
57+
if (bbIndex == 4)
58+
Flush();
59+
}
60+
}
61+
}
62+
63+
private void Flush()
64+
{
65+
decoder.Convert(byteBuffer, 0, bbIndex, charBuffer, 0, 4, false, out int bytesUsed, out int charsUsed, out bool completed);
66+
67+
sb.Append(charBuffer, 0, charsUsed);
68+
ResetBuffers();
69+
}
70+
71+
private void ResetBuffers()
72+
{
73+
charBuffer = new char[4];
74+
byteBuffer = new byte[4];
75+
bbIndex = 0;
76+
}
77+
78+
79+
public Dictionary<string, string> Headers
80+
{
81+
get
82+
{
83+
// TODO: Check for invalid state? -NM 2018-07-01
84+
return headers;
85+
}
86+
}
87+
88+
}
89+
}

src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,30 @@ public TarEntry GetNextEntry()
434434
SkipToNextEntry();
435435
headerBuf = this.tarBuffer.ReadBlock();
436436
} else if (header.TypeFlag == TarHeader.LF_XHDR) { // POSIX extended header
437-
// Ignore things we dont understand completely for now
437+
438+
byte[] nameBuffer = new byte[TarBuffer.BlockSize];
439+
long numToRead = this.entrySize;
440+
441+
var xhr = new TarExtendedHeaderReader();
442+
443+
while (numToRead > 0)
444+
{
445+
int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead));
446+
447+
if (numRead == -1)
448+
{
449+
throw new InvalidHeaderException("Failed to read long name entry");
450+
}
451+
452+
xhr.Read(nameBuffer, numRead);
453+
numToRead -= numRead;
454+
}
455+
456+
if (xhr.Headers.TryGetValue("path", out string name))
457+
{
458+
longName = new StringBuilder(name);
459+
}
460+
438461
SkipToNextEntry();
439462
headerBuf = this.tarBuffer.ReadBlock();
440463
} else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) {

test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,58 @@ public void LongNames()
231231
}
232232
}
233233

234+
[Test]
235+
[Category("Tar")]
236+
public void ExtendedHeaderLongName()
237+
{
238+
string expectedName = "lftest-0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999";
239+
240+
var input64 = @"Li9QYXhIZWFkZXJzLjExOTY5L2xmdGVzdC0wMDAwMDAwMDAwMTExMTExMTExMTIyMjIyMjIyMjIz
241+
MzMzMzMzMzMzNDQ0NDQ0NDQ0NDU1NTU1NTU1NTU2NjY2NjY2NjY2Nzc3NzAwMDA2NDQAMDAwMDAw
242+
MAAwMDAwMDAwADAwMDAwMDAwMzE3ADEzMzE2MTYyMzMzADAyMTYwNgAgeAAAAAAAAAAAAAAAAAAA
243+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
244+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMAAAAAAAAAAAAAAAAAAAAAAAAAAA
245+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
246+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
247+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
248+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAx
249+
MTcgcGF0aD1sZnRlc3QtMDAwMDAwMDAwMDExMTExMTExMTEyMjIyMjIyMjIyMzMzMzMzMzMzMzQ0
250+
NDQ0NDQ0NDQ1NTU1NTU1NTU1NjY2NjY2NjY2Njc3Nzc3Nzc3Nzc4ODg4ODg4ODg4OTk5OTk5OTk5
251+
OQozMCBtdGltZT0xNTMwNDU1MjU5LjcwNjU0ODg4OAozMCBhdGltZT0xNTMwNDU1MjU5LjcwNjU0
252+
ODg4OAozMCBjdGltZT0xNTMwNDU1MjU5LjcwNjU0ODg4OAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
253+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
254+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
255+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
256+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
257+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGxm
258+
dGVzdC0wMDAwMDAwMDAwMTExMTExMTExMTIyMjIyMjIyMjIzMzMzMzMzMzMzNDQ0NDQ0NDQ0NDU1
259+
NTU1NTU1NTU2NjY2NjY2NjY2Nzc3Nzc3Nzc3Nzg4ODg4ODg4ODg5OTkwMDAwNjY0ADAwMDE3NTAA
260+
MDAwMTc1MAAwMDAwMDAwMDAwMAAxMzMxNjE2MjMzMwAwMjM3MjcAIDAAAAAAAAAAAAAAAAAAAAAA
261+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
262+
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDBuaWxzAAAAAAAAAAAAAAAAAAAAAAAA
263+
AAAAAAAAAAAAAG5pbHMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAw";
264+
265+
var buffer = new byte[2560];
266+
var truncated = Convert.FromBase64String(input64);
267+
Array.Copy(truncated, buffer, truncated.Length);
268+
truncated = null;
269+
270+
using (var ms = new MemoryStream(buffer))
271+
using (var tis = new TarInputStream(ms))
272+
{
273+
var entry = tis.GetNextEntry();
274+
Assert.IsNotNull(entry, "Entry is null");
275+
276+
Assert.IsNotNull(entry.Name, "Entry name is null");
277+
278+
Assert.AreEqual(expectedName.Length, entry.Name.Length, $"Entry name is truncated to {entry.Name.Length} bytes.");
279+
280+
Assert.AreEqual(expectedName, entry.Name, "Entry name does not match expected value");
281+
}
282+
283+
}
284+
285+
234286
/// <summary>
235287
/// Test equals function for tar headers.
236288
/// </summary>

0 commit comments

Comments
 (0)