diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarExtendedHeaderReader.cs b/src/ICSharpCode.SharpZipLib/Tar/TarExtendedHeaderReader.cs new file mode 100644 index 000000000..e1ab189dc --- /dev/null +++ b/src/ICSharpCode.SharpZipLib/Tar/TarExtendedHeaderReader.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace ICSharpCode.SharpZipLib.Tar +{ + public class TarExtendedHeaderReader + { + const byte LENGTH = 0; + const byte KEY = 1; + const byte VALUE = 2; + const byte END = 3; + + private readonly Dictionary headers = new Dictionary(); + + private string[] headerParts = new string[3]; + + int bbIndex; + private byte[] byteBuffer; + private char[] charBuffer; + + private readonly StringBuilder sb = new StringBuilder(); + private readonly Decoder decoder = Encoding.UTF8.GetDecoder(); + + private int state = LENGTH; + + private static readonly byte[] StateNext = new[] { (byte)' ', (byte)'=', (byte)'\n' }; + + public TarExtendedHeaderReader() + { + ResetBuffers(); + } + + public void Read(byte[] buffer, int length) + { + for (int i = 0; i < length; i++) + { + byte next = buffer[i]; + + if (next == StateNext[state]) + { + Flush(); + headerParts[state] = sb.ToString(); + sb.Clear(); + + if (++state == END) + { + headers.Add(headerParts[KEY], headerParts[VALUE]); + headerParts = new string[3]; + state = LENGTH; + } + } + else + { + byteBuffer[bbIndex++] = next; + if (bbIndex == 4) + Flush(); + } + } + } + + private void Flush() + { + decoder.Convert(byteBuffer, 0, bbIndex, charBuffer, 0, 4, false, out int bytesUsed, out int charsUsed, out bool completed); + + sb.Append(charBuffer, 0, charsUsed); + ResetBuffers(); + } + + private void ResetBuffers() + { + charBuffer = new char[4]; + byteBuffer = new byte[4]; + bbIndex = 0; + } + + + public Dictionary Headers + { + get + { + // TODO: Check for invalid state? -NM 2018-07-01 + return headers; + } + } + + } +} diff --git a/src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs b/src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs index c398be0d3..59dff4c0c 100644 --- a/src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs +++ b/src/ICSharpCode.SharpZipLib/Tar/TarInputStream.cs @@ -434,7 +434,30 @@ public TarEntry GetNextEntry() SkipToNextEntry(); headerBuf = this.tarBuffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_XHDR) { // POSIX extended header - // Ignore things we dont understand completely for now + + byte[] nameBuffer = new byte[TarBuffer.BlockSize]; + long numToRead = this.entrySize; + + var xhr = new TarExtendedHeaderReader(); + + while (numToRead > 0) + { + int numRead = this.Read(nameBuffer, 0, (numToRead > nameBuffer.Length ? nameBuffer.Length : (int)numToRead)); + + if (numRead == -1) + { + throw new InvalidHeaderException("Failed to read long name entry"); + } + + xhr.Read(nameBuffer, numRead); + numToRead -= numRead; + } + + if (xhr.Headers.TryGetValue("path", out string name)) + { + longName = new StringBuilder(name); + } + SkipToNextEntry(); headerBuf = this.tarBuffer.ReadBlock(); } else if (header.TypeFlag == TarHeader.LF_GNU_VOLHDR) { diff --git a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs index 5f4b2a7e5..4e0e5f73b 100644 --- a/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs +++ b/test/ICSharpCode.SharpZipLib.Tests/Tar/TarTests.cs @@ -231,6 +231,58 @@ public void LongNames() } } + [Test] + [Category("Tar")] + public void ExtendedHeaderLongName() + { + string expectedName = "lftest-0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"; + + var input64 = @"Li9QYXhIZWFkZXJzLjExOTY5L2xmdGVzdC0wMDAwMDAwMDAwMTExMTExMTExMTIyMjIyMjIyMjIz + MzMzMzMzMzMzNDQ0NDQ0NDQ0NDU1NTU1NTU1NTU2NjY2NjY2NjY2Nzc3NzAwMDA2NDQAMDAwMDAw + MAAwMDAwMDAwADAwMDAwMDAwMzE3ADEzMzE2MTYyMzMzADAyMTYwNgAgeAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAx + MTcgcGF0aD1sZnRlc3QtMDAwMDAwMDAwMDExMTExMTExMTEyMjIyMjIyMjIyMzMzMzMzMzMzMzQ0 + NDQ0NDQ0NDQ1NTU1NTU1NTU1NjY2NjY2NjY2Njc3Nzc3Nzc3Nzc4ODg4ODg4ODg4OTk5OTk5OTk5 + OQozMCBtdGltZT0xNTMwNDU1MjU5LjcwNjU0ODg4OAozMCBhdGltZT0xNTMwNDU1MjU5LjcwNjU0 + ODg4OAozMCBjdGltZT0xNTMwNDU1MjU5LjcwNjU0ODg4OAoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGxm + dGVzdC0wMDAwMDAwMDAwMTExMTExMTExMTIyMjIyMjIyMjIzMzMzMzMzMzMzNDQ0NDQ0NDQ0NDU1 + NTU1NTU1NTU2NjY2NjY2NjY2Nzc3Nzc3Nzc3Nzg4ODg4ODg4ODg5OTkwMDAwNjY0ADAwMDE3NTAA + MDAwMTc1MAAwMDAwMDAwMDAwMAAxMzMxNjE2MjMzMwAwMjM3MjcAIDAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDBuaWxzAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAG5pbHMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAw"; + + var buffer = new byte[2560]; + var truncated = Convert.FromBase64String(input64); + Array.Copy(truncated, buffer, truncated.Length); + truncated = null; + + using (var ms = new MemoryStream(buffer)) + using (var tis = new TarInputStream(ms)) + { + var entry = tis.GetNextEntry(); + Assert.IsNotNull(entry, "Entry is null"); + + Assert.IsNotNull(entry.Name, "Entry name is null"); + + Assert.AreEqual(expectedName.Length, entry.Name.Length, $"Entry name is truncated to {entry.Name.Length} bytes."); + + Assert.AreEqual(expectedName, entry.Name, "Entry name does not match expected value"); + } + + } + + /// /// Test equals function for tar headers. ///