From 088644240af9246c19db7261071f1b4d0f763a61 Mon Sep 17 00:00:00 2001 From: Sander Bosma Date: Mon, 6 Apr 2015 14:38:57 +0200 Subject: [PATCH 1/3] Tar: Support for writing long filenames (>100 chars) using longlink --- SharpCompress/Common/Tar/Headers/TarHeader.cs | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/SharpCompress/Common/Tar/Headers/TarHeader.cs b/SharpCompress/Common/Tar/Headers/TarHeader.cs index c0241ec5..7582094c 100644 --- a/SharpCompress/Common/Tar/Headers/TarHeader.cs +++ b/SharpCompress/Common/Tar/Headers/TarHeader.cs @@ -40,48 +40,64 @@ namespace SharpCompress.Common.Tar.Headers internal void Write(Stream output) { - if (Name.Length > 255) - { - throw new InvalidFormatException("UsTar fileName can not be longer than 255 chars"); - } byte[] buffer = new byte[512]; - string name = Name; - if (name.Length > 100) - { - name = Name.Substring(0, 100); - } - WriteStringBytes(name, buffer, 0, 100); - WriteOctalBytes(511, buffer, 100, 8); - WriteOctalBytes(0, buffer, 108, 8); - WriteOctalBytes(0, buffer, 116, 8); - WriteOctalBytes(Size, buffer, 124, 12); - var time = (long) (LastModifiedTime.ToUniversalTime() - Epoch).TotalSeconds; - WriteOctalBytes(time, buffer, 136, 12); - - buffer[156] = (byte) EntryType; + WriteOctalBytes(511, buffer, 100, 8); // file mode + WriteOctalBytes(0, buffer, 108, 8); // owner ID + WriteOctalBytes(0, buffer, 116, 8); // group ID //Encoding.UTF8.GetBytes("magic").CopyTo(buffer, 257); if (Name.Length > 100) { - name = Name.Substring(101, Name.Length); - ArchiveEncoding.Default.GetBytes(name).CopyTo(buffer, 345); + // Set mock filename and filetype to indicate the next block is the actual name of the file + WriteStringBytes("././@LongLink", buffer, 0, 100); + buffer[156] = (byte)EntryType.LongName; + WriteOctalBytes(Name.Length + 1, buffer, 124, 12); } - if (Size >= 0x1FFFFFFFF) + else { + WriteStringBytes(Name, buffer, 0, 100); + WriteOctalBytes(Size, buffer, 124, 12); + var time = (long)(LastModifiedTime.ToUniversalTime() - Epoch).TotalSeconds; + WriteOctalBytes(time, buffer, 136, 12); + buffer[156] = (byte)EntryType; + + if (Size >= 0x1FFFFFFFF) + { #if PORTABLE || NETFX_CORE byte[] bytes = BitConverter.GetBytes(Utility.HostToNetworkOrder(Size)); #else - byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Size)); + byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Size)); #endif - var bytes12 = new byte[12]; - bytes.CopyTo(bytes12, 12 - bytes.Length); - bytes12[0] |= 0x80; - bytes12.CopyTo(buffer, 124); + var bytes12 = new byte[12]; + bytes.CopyTo(bytes12, 12 - bytes.Length); + bytes12[0] |= 0x80; + bytes12.CopyTo(buffer, 124); + } } + int crc = RecalculateChecksum(buffer); WriteOctalBytes(crc, buffer, 148, 8); + output.Write(buffer, 0, buffer.Length); + + if (Name.Length > 100) + { + WriteLongFilenameHeader(output); + Name = Name.Substring(0, 100); + Write(output); + } + } + private void WriteLongFilenameHeader(Stream output) + { + byte[] nameBytes = ArchiveEncoding.Default.GetBytes(Name); + output.Write(nameBytes, 0, nameBytes.Length); + + // pad to multiple of 512 bytes, and make sure a terminating null is added + int numPaddingBytes = 512 - (nameBytes.Length % 512); + if (numPaddingBytes == 0) + numPaddingBytes = 512; + output.Write(new byte[numPaddingBytes], 0, numPaddingBytes); } internal bool Read(BinaryReader reader) From 3113500229346b01d34097d8c7c6cf6dff3f9f31 Mon Sep 17 00:00:00 2001 From: Sander Bosma Date: Tue, 7 Apr 2015 21:22:56 +0200 Subject: [PATCH 2/3] don't write trailing zero in WriteOctalBytes this fixes a bug where the trailing zero of the CRC overwrote the entrytype --- SharpCompress/Common/Tar/Headers/TarHeader.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/SharpCompress/Common/Tar/Headers/TarHeader.cs b/SharpCompress/Common/Tar/Headers/TarHeader.cs index 7582094c..8323fc3f 100644 --- a/SharpCompress/Common/Tar/Headers/TarHeader.cs +++ b/SharpCompress/Common/Tar/Headers/TarHeader.cs @@ -179,7 +179,6 @@ namespace SharpCompress.Common.Tar.Headers { buffer[offset + i + shift] = (byte) val[i]; } - buffer[offset + length] = 0; } private static int ReadASCIIInt32Base8(byte[] buffer, int offset, int count) From 26ddc09c6a9483391bc91e001dc5703040d13265 Mon Sep 17 00:00:00 2001 From: Sander Bosma Date: Tue, 7 Apr 2015 21:27:39 +0200 Subject: [PATCH 3/3] add unit test for writing tar archives containing long filenames --- SharpCompress.Test/Tar/TarArchiveTests.cs | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/SharpCompress.Test/Tar/TarArchiveTests.cs b/SharpCompress.Test/Tar/TarArchiveTests.cs index 71a11e97..b7a8124c 100644 --- a/SharpCompress.Test/Tar/TarArchiveTests.cs +++ b/SharpCompress.Test/Tar/TarArchiveTests.cs @@ -44,6 +44,44 @@ namespace SharpCompress.Test } } + [TestMethod] + public void Tar_VeryLongFilepathReadback() + { + string archive = "Tar_VeryLongFilepathReadback.tar"; + + ResetScratch(); + + // create a very long filename + string longFilename = ""; + for (int i = 0; i < 600; i = longFilename.Length) + longFilename += i.ToString("D10") + "-"; + longFilename += ".txt"; + + // Step 1: create a tar file containing a file with a long name + using (Stream stream = File.OpenWrite(Path.Combine(SCRATCH2_FILES_PATH, archive))) + using (var writer = SharpCompress.Writer.WriterFactory.Open(stream, ArchiveType.Tar, CompressionType.None)) + using (Stream inputStream = new MemoryStream()) + { + StreamWriter sw = new StreamWriter(inputStream); + sw.Write("dummy filecontent"); + sw.Flush(); + + inputStream.Position = 0; + writer.Write(longFilename, inputStream, null); + } + + // Step 2: check if the written tar file can be read correctly + string unmodified = Path.Combine(SCRATCH2_FILES_PATH, archive); + using (var archive2 = TarArchive.Open(unmodified)) + { + Assert.AreEqual(1, archive2.Entries.Count); + Assert.IsTrue(archive2.Entries.Any(entry => entry.Key == longFilename)); + + foreach (var entry in archive2.Entries) + Assert.AreEqual("dummy filecontent", new StreamReader(entry.OpenEntryStream()).ReadLine()); + } + } + [TestMethod] public void Tar_UstarArchivePathReadLongName() {