Skip to content

Commit

Permalink
Adds sorting functionality for symlinks to ensure that symlinks of sy…
Browse files Browse the repository at this point in the history
…mlinks have their base link created before themselves
  • Loading branch information
ForestEckhardt authored and ryanmoran committed Apr 13, 2021
1 parent de38ba1 commit db37853
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 3 deletions.
1 change: 1 addition & 0 deletions vacation/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
func TestVacation(t *testing.T) {
suite := spec.New("vacation", spec.Report(report.Terminal{}))
suite("VacationArchive", testVacationArchive)
suite("VacationSymlinkSorting", testVacationSymlinkSorting)
suite("VacationTar", testVacationTar)
suite("VacationTarGzip", testVacationTarGzip)
suite("VacationTarXZ", testVacationTarXZ)
Expand Down
13 changes: 11 additions & 2 deletions vacation/vacation.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"io"
"os"
"path/filepath"
"sort"
"strings"

"github.com/gabriel-vasile/mimetype"
Expand Down Expand Up @@ -165,11 +166,15 @@ func (ta TarArchive) Decompress(destination string) error {
}
}

sort.Slice(symlinkHeaders, func(i, j int) bool {
return filepath.Clean(symlinkHeaders[i].name) < filepath.Clean(filepath.Join(filepath.Dir(symlinkHeaders[j].name), symlinkHeaders[j].linkname))
})

for _, h := range symlinkHeaders {
// Check to see if the file that will be linked to is valid for symlinking
_, err := filepath.EvalSymlinks(filepath.Join(filepath.Dir(h.path), h.linkname))
if err != nil {
return err
return fmt.Errorf("failed to evaluate symlink %s: %w", h.path, err)
}

// Check that the file being symlinked to is inside the destination
Expand Down Expand Up @@ -395,11 +400,15 @@ func (z ZipArchive) Decompress(destination string) error {
}
}

sort.Slice(symlinkHeaders, func(i, j int) bool {
return filepath.Clean(symlinkHeaders[i].name) < filepath.Clean(filepath.Join(filepath.Dir(symlinkHeaders[j].name), symlinkHeaders[j].linkname))
})

for _, h := range symlinkHeaders {
// Check to see if the file that will be linked to is valid for symlinking
_, err := filepath.EvalSymlinks(filepath.Join(filepath.Dir(h.path), h.linkname))
if err != nil {
return err
return fmt.Errorf("failed to evaluate symlink %s: %w", h.path, err)
}

// Check that the file being symlinked to is inside the destination
Expand Down
167 changes: 167 additions & 0 deletions vacation/vacation_symlink_sorting_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package vacation_test

import (
"archive/tar"
"archive/zip"
"bytes"
"fmt"
"os"
"path/filepath"
"testing"

"github.com/paketo-buildpacks/packit/vacation"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
)

func testVacationSymlinkSorting(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect
)

context("TarArchive test that symlinks are sorted so that symlink to other symlinks are created after the initial symlink", func() {
var (
tempDir string
tarArchive vacation.TarArchive
)

it.Before(func() {
var err error
tempDir, err = os.MkdirTemp("", "vacation")
Expect(err).NotTo(HaveOccurred())

buffer := bytes.NewBuffer(nil)
tw := tar.NewWriter(buffer)

Expect(tw.WriteHeader(&tar.Header{Name: "b-symlink", Mode: 0755, Size: int64(0), Typeflag: tar.TypeSymlink, Linkname: filepath.Join("a-symlink", "x")})).To(Succeed())
_, err = tw.Write([]byte{})
Expect(err).NotTo(HaveOccurred())

Expect(tw.WriteHeader(&tar.Header{Name: "a-symlink", Mode: 0755, Size: int64(0), Typeflag: tar.TypeSymlink, Linkname: "z"})).To(Succeed())
_, err = tw.Write([]byte{})
Expect(err).NotTo(HaveOccurred())

Expect(tw.WriteHeader(&tar.Header{Name: "z", Mode: 0755, Typeflag: tar.TypeDir})).To(Succeed())
_, err = tw.Write(nil)
Expect(err).NotTo(HaveOccurred())

xFile := filepath.Join("z", "x")
Expect(tw.WriteHeader(&tar.Header{Name: xFile, Mode: 0755, Size: int64(len(xFile))})).To(Succeed())
_, err = tw.Write([]byte(xFile))
Expect(err).NotTo(HaveOccurred())

Expect(tw.Close()).To(Succeed())

tarArchive = vacation.NewTarArchive(bytes.NewReader(buffer.Bytes()))
})

it.After(func() {
Expect(os.RemoveAll(tempDir)).To(Succeed())
})

it("unpackages the archive into the path", func() {
var err error
err = tarArchive.Decompress(tempDir)
Expect(err).ToNot(HaveOccurred())

files, err := filepath.Glob(fmt.Sprintf("%s/*", tempDir))
Expect(err).NotTo(HaveOccurred())
Expect(files).To(ConsistOf([]string{
filepath.Join(tempDir, "a-symlink"),
filepath.Join(tempDir, "b-symlink"),
filepath.Join(tempDir, "z"),
}))

Expect(filepath.Join(tempDir, "z")).To(BeADirectory())
Expect(filepath.Join(tempDir, "z", "x")).To(BeARegularFile())

link, err := os.Readlink(filepath.Join(tempDir, "a-symlink"))
Expect(err).NotTo(HaveOccurred())
Expect(link).To(Equal("z"))

data, err := os.ReadFile(filepath.Join(tempDir, "b-symlink"))
Expect(err).NotTo(HaveOccurred())
Expect(data).To(Equal([]byte(filepath.Join("z", "x"))))
})
})

context("ZipArchive test that symlinks are sorted so that symlink to other symlinks are created after the initial symlink", func() {
var (
tempDir string
zipArchive vacation.ZipArchive
)

it.Before(func() {
var err error
tempDir, err = os.MkdirTemp("", "vacation")
Expect(err).NotTo(HaveOccurred())

buffer := bytes.NewBuffer(nil)
zw := zip.NewWriter(buffer)

fileHeader := &zip.FileHeader{Name: "b-symlink"}
fileHeader.SetMode(0755 | os.ModeSymlink)

bSymlink, err := zw.CreateHeader(fileHeader)
Expect(err).NotTo(HaveOccurred())

_, err = bSymlink.Write([]byte(filepath.Join("a-symlink", "x")))
Expect(err).NotTo(HaveOccurred())

fileHeader = &zip.FileHeader{Name: "a-symlink"}
fileHeader.SetMode(0755 | os.ModeSymlink)

aSymlink, err := zw.CreateHeader(fileHeader)
Expect(err).NotTo(HaveOccurred())

_, err = aSymlink.Write([]byte(`z`))
Expect(err).NotTo(HaveOccurred())

_, err = zw.Create("z" + string(filepath.Separator))
Expect(err).NotTo(HaveOccurred())

fileHeader = &zip.FileHeader{Name: filepath.Join("z", "x")}
fileHeader.SetMode(0644)

xFile, err := zw.CreateHeader(fileHeader)
Expect(err).NotTo(HaveOccurred())

_, err = xFile.Write([]byte("x file"))
Expect(err).NotTo(HaveOccurred())

Expect(zw.Close()).To(Succeed())

zipArchive = vacation.NewZipArchive(bytes.NewReader(buffer.Bytes()))
})

it.After(func() {
Expect(os.RemoveAll(tempDir)).To(Succeed())
})

it("unpackages the archive into the path", func() {
var err error
err = zipArchive.Decompress(tempDir)
Expect(err).ToNot(HaveOccurred())

files, err := filepath.Glob(fmt.Sprintf("%s/*", tempDir))
Expect(err).NotTo(HaveOccurred())
Expect(files).To(ConsistOf([]string{
filepath.Join(tempDir, "a-symlink"),
filepath.Join(tempDir, "b-symlink"),
filepath.Join(tempDir, "z"),
}))

Expect(filepath.Join(tempDir, "z")).To(BeADirectory())
Expect(filepath.Join(tempDir, "z", "x")).To(BeARegularFile())

link, err := os.Readlink(filepath.Join(tempDir, "a-symlink"))
Expect(err).NotTo(HaveOccurred())
Expect(link).To(Equal("z"))

data, err := os.ReadFile(filepath.Join(tempDir, "b-symlink"))
Expect(err).NotTo(HaveOccurred())
Expect(data).To(Equal([]byte(`x file`)))
})
})
}
1 change: 1 addition & 0 deletions vacation/vacation_tar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ func testVacationTar(t *testing.T, context spec.G, it spec.S) {

it("returns an error", func() {
err := zipSlipSymlinkTar.Decompress(tempDir)
Expect(err).To(MatchError(ContainSubstring("failed to evaluate symlink")))
Expect(err).To(MatchError(ContainSubstring("no such file or directory")))
})
})
Expand Down
3 changes: 2 additions & 1 deletion vacation/vacation_zip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func testVacationZip(t *testing.T, context spec.G, it spec.S) {
Expect(os.RemoveAll(tempDir)).To(Succeed())
})

it("downloads the dependency and unpackages it into the path", func() {
it("unpackages the archive into the path", func() {
var err error
err = zipArchive.Decompress(tempDir)
Expect(err).ToNot(HaveOccurred())
Expand Down Expand Up @@ -223,6 +223,7 @@ func testVacationZip(t *testing.T, context spec.G, it spec.S) {
readyArchive := vacation.NewZipArchive(buffer)

err := readyArchive.Decompress(tempDir)
Expect(err).To(MatchError(ContainSubstring("failed to evaluate symlink")))
Expect(err).To(MatchError(ContainSubstring("no such file or directory")))
})
})
Expand Down

0 comments on commit db37853

Please sign in to comment.