From 0a90423a0fd645faf8aff4a9a8c7741f25735e4e Mon Sep 17 00:00:00 2001 From: Forest Eckhardt Date: Mon, 6 Jul 2020 11:09:13 -0400 Subject: [PATCH] Enables the tar decompression the ability to decompress a file that has no directory headers - Previously vacation was given a tar file that had no directory headers, which can be generated by handing the tar command a list of all the files that should be tared, vacation would not be able to handle it because it would try and create files in directories that did not exist. - We now log all of the directories that have been created previously and check to see that any file we create is within one of those directories, if it is not then we create the directory for the file before we try and write the file. --- vacation/vacation.go | 28 ++++++++++++++++++- vacation/vacation_test.go | 57 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/vacation/vacation.go b/vacation/vacation.go index b182a037..8023a040 100644 --- a/vacation/vacation.go +++ b/vacation/vacation.go @@ -55,6 +55,13 @@ func NewTarXZArchive(inputReader io.Reader) TarXZArchive { // Decompress reads from TarArchive and writes files into the // destination specified. func (ta TarArchive) Decompress(destination string) error { + // This map keeps track of what directories have been made already + // so that we only attempt to make them once for a cleaner interaction. + // This map is only necessary in cases where there are no directory headers + // in the tarball, which can be seen in the test around there being + // no directory metadata. + directories := map[string]interface{}{} + tarReader := tar.NewReader(ta.reader) for { hdr, err := tarReader.Next() @@ -72,6 +79,9 @@ func (ta TarArchive) Decompress(destination string) error { } path := filepath.Join(append([]string{destination}, fileNames[ta.components:]...)...) + + // This switch case handles all cases for creating the directory structure + // this logic is needed to handle tarballs with no directory headers. switch hdr.Typeflag { case tar.TypeDir: err = os.MkdirAll(path, os.ModePerm) @@ -79,6 +89,22 @@ func (ta TarArchive) Decompress(destination string) error { return fmt.Errorf("failed to create archived directory: %s", err) } + directories[path] = nil + + default: + dir := filepath.Dir(path) + _, ok := directories[dir] + if !ok { + err = os.MkdirAll(dir, os.ModePerm) + if err != nil { + return fmt.Errorf("failed to create archived directory from file path: %s", err) + } + directories[dir] = nil + } + } + + // This switch case handles the creation of files during the untaring process. + switch hdr.Typeflag { case tar.TypeReg: file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, hdr.FileInfo().Mode()) if err != nil { @@ -163,7 +189,7 @@ func NewZipArchive(inputReader io.Reader) ZipArchive { // Decompress reads from ZipArchive and writes files into the // destination specified. func (z ZipArchive) Decompress(destination string) error { - // Have to convert and io.Reader into a bytes.Reader which + // Have to convert an io.Reader into a bytes.Reader which // implements the ReadAt function making it compatible with // the io.ReaderAt inteface which required for zip.NewReader buff := bytes.NewBuffer(nil) diff --git a/vacation/vacation_test.go b/vacation/vacation_test.go index 582e8eab..5f8f7c2b 100644 --- a/vacation/vacation_test.go +++ b/vacation/vacation_test.go @@ -112,6 +112,40 @@ func testVacation(t *testing.T, context spec.G, it spec.S) { }) + context("there is no directory metadata", func() { + it.Before(func() { + var err error + + buffer := bytes.NewBuffer(nil) + tw := tar.NewWriter(buffer) + + nestedFile := filepath.Join("some-dir", "some-other-dir", "some-file") + Expect(tw.WriteHeader(&tar.Header{Name: nestedFile, Mode: 0755, Size: int64(len(nestedFile))})).To(Succeed()) + _, err = tw.Write([]byte(nestedFile)) + Expect(err).NotTo(HaveOccurred()) + + Expect(tw.WriteHeader(&tar.Header{Name: filepath.Join("sym-dir", "symlink"), Mode: 0755, Size: int64(0), Typeflag: tar.TypeSymlink, Linkname: filepath.Join("..", nestedFile)})).To(Succeed()) + _, err = tw.Write([]byte{}) + Expect(err).NotTo(HaveOccurred()) + + Expect(tw.Close()).To(Succeed()) + + tarArchive = vacation.NewTarArchive(bytes.NewReader(buffer.Bytes())) + }) + + it("unpackages the archive into the path", func() { + err := tarArchive.Decompress(tempDir) + Expect(err).ToNot(HaveOccurred()) + + Expect(filepath.Join(tempDir, "some-dir", "some-other-dir")).To(BeADirectory()) + Expect(filepath.Join(tempDir, "some-dir", "some-other-dir", "some-file")).To(BeARegularFile()) + + data, err := ioutil.ReadFile(filepath.Join(tempDir, "sym-dir", "symlink")) + Expect(err).NotTo(HaveOccurred()) + Expect(data).To(Equal([]byte(filepath.Join("some-dir", "some-other-dir", "some-file")))) + }) + }) + context("failure cases", func() { context("when it fails to read the tar response", func() { it("returns an error", func() { @@ -135,6 +169,29 @@ func testVacation(t *testing.T, context spec.G, it spec.S) { err := tarArchive.Decompress(tempDir) Expect(err).To(MatchError(ContainSubstring("failed to create archived directory"))) }) + + context("there are no directory headers", func() { + it.Before(func() { + var err error + + buffer := bytes.NewBuffer(nil) + tw := tar.NewWriter(buffer) + + nestedFile := filepath.Join("some-dir", "some-other-dir", "some-file") + Expect(tw.WriteHeader(&tar.Header{Name: nestedFile, Mode: 0755, Size: int64(len(nestedFile))})).To(Succeed()) + _, err = tw.Write([]byte(nestedFile)) + Expect(err).NotTo(HaveOccurred()) + + Expect(tw.Close()).To(Succeed()) + + tarArchive = vacation.NewTarArchive(bytes.NewReader(buffer.Bytes())) + }) + + it("returns an error", func() { + err := tarArchive.Decompress(tempDir) + Expect(err).To(MatchError(ContainSubstring("failed to create archived directory from file path"))) + }) + }) }) context("when it is unable to create an archived file", func() {