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() { diff --git a/SharpCompress/Common/Tar/Headers/TarHeader.cs b/SharpCompress/Common/Tar/Headers/TarHeader.cs index c0241ec5..8323fc3f 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) @@ -163,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)