Skip to content

Commit

Permalink
Enables the tar decompression the ability to decompress a file that has
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ForestEckhardt authored and ryanmoran committed Jul 7, 2020
1 parent f099442 commit 0a90423
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 1 deletion.
28 changes: 27 additions & 1 deletion vacation/vacation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -72,13 +79,32 @@ 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)
if err != nil {
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 {
Expand Down Expand Up @@ -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)
Expand Down
57 changes: 57 additions & 0 deletions vacation/vacation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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() {
Expand Down

0 comments on commit 0a90423

Please sign in to comment.