Skip to content

Commit db37853

Browse files
ForestEckhardtryanmoran
authored andcommitted
Adds sorting functionality for symlinks to ensure that symlinks of symlinks have their base link created before themselves
1 parent de38ba1 commit db37853

5 files changed

+182
-3
lines changed

vacation/init_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
func TestVacation(t *testing.T) {
1111
suite := spec.New("vacation", spec.Report(report.Terminal{}))
1212
suite("VacationArchive", testVacationArchive)
13+
suite("VacationSymlinkSorting", testVacationSymlinkSorting)
1314
suite("VacationTar", testVacationTar)
1415
suite("VacationTarGzip", testVacationTarGzip)
1516
suite("VacationTarXZ", testVacationTarXZ)

vacation/vacation.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"io"
1515
"os"
1616
"path/filepath"
17+
"sort"
1718
"strings"
1819

1920
"github.com/gabriel-vasile/mimetype"
@@ -165,11 +166,15 @@ func (ta TarArchive) Decompress(destination string) error {
165166
}
166167
}
167168

169+
sort.Slice(symlinkHeaders, func(i, j int) bool {
170+
return filepath.Clean(symlinkHeaders[i].name) < filepath.Clean(filepath.Join(filepath.Dir(symlinkHeaders[j].name), symlinkHeaders[j].linkname))
171+
})
172+
168173
for _, h := range symlinkHeaders {
169174
// Check to see if the file that will be linked to is valid for symlinking
170175
_, err := filepath.EvalSymlinks(filepath.Join(filepath.Dir(h.path), h.linkname))
171176
if err != nil {
172-
return err
177+
return fmt.Errorf("failed to evaluate symlink %s: %w", h.path, err)
173178
}
174179

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

403+
sort.Slice(symlinkHeaders, func(i, j int) bool {
404+
return filepath.Clean(symlinkHeaders[i].name) < filepath.Clean(filepath.Join(filepath.Dir(symlinkHeaders[j].name), symlinkHeaders[j].linkname))
405+
})
406+
398407
for _, h := range symlinkHeaders {
399408
// Check to see if the file that will be linked to is valid for symlinking
400409
_, err := filepath.EvalSymlinks(filepath.Join(filepath.Dir(h.path), h.linkname))
401410
if err != nil {
402-
return err
411+
return fmt.Errorf("failed to evaluate symlink %s: %w", h.path, err)
403412
}
404413

405414
// Check that the file being symlinked to is inside the destination
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package vacation_test
2+
3+
import (
4+
"archive/tar"
5+
"archive/zip"
6+
"bytes"
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/paketo-buildpacks/packit/vacation"
13+
"github.com/sclevine/spec"
14+
15+
. "github.com/onsi/gomega"
16+
)
17+
18+
func testVacationSymlinkSorting(t *testing.T, context spec.G, it spec.S) {
19+
var (
20+
Expect = NewWithT(t).Expect
21+
)
22+
23+
context("TarArchive test that symlinks are sorted so that symlink to other symlinks are created after the initial symlink", func() {
24+
var (
25+
tempDir string
26+
tarArchive vacation.TarArchive
27+
)
28+
29+
it.Before(func() {
30+
var err error
31+
tempDir, err = os.MkdirTemp("", "vacation")
32+
Expect(err).NotTo(HaveOccurred())
33+
34+
buffer := bytes.NewBuffer(nil)
35+
tw := tar.NewWriter(buffer)
36+
37+
Expect(tw.WriteHeader(&tar.Header{Name: "b-symlink", Mode: 0755, Size: int64(0), Typeflag: tar.TypeSymlink, Linkname: filepath.Join("a-symlink", "x")})).To(Succeed())
38+
_, err = tw.Write([]byte{})
39+
Expect(err).NotTo(HaveOccurred())
40+
41+
Expect(tw.WriteHeader(&tar.Header{Name: "a-symlink", Mode: 0755, Size: int64(0), Typeflag: tar.TypeSymlink, Linkname: "z"})).To(Succeed())
42+
_, err = tw.Write([]byte{})
43+
Expect(err).NotTo(HaveOccurred())
44+
45+
Expect(tw.WriteHeader(&tar.Header{Name: "z", Mode: 0755, Typeflag: tar.TypeDir})).To(Succeed())
46+
_, err = tw.Write(nil)
47+
Expect(err).NotTo(HaveOccurred())
48+
49+
xFile := filepath.Join("z", "x")
50+
Expect(tw.WriteHeader(&tar.Header{Name: xFile, Mode: 0755, Size: int64(len(xFile))})).To(Succeed())
51+
_, err = tw.Write([]byte(xFile))
52+
Expect(err).NotTo(HaveOccurred())
53+
54+
Expect(tw.Close()).To(Succeed())
55+
56+
tarArchive = vacation.NewTarArchive(bytes.NewReader(buffer.Bytes()))
57+
})
58+
59+
it.After(func() {
60+
Expect(os.RemoveAll(tempDir)).To(Succeed())
61+
})
62+
63+
it("unpackages the archive into the path", func() {
64+
var err error
65+
err = tarArchive.Decompress(tempDir)
66+
Expect(err).ToNot(HaveOccurred())
67+
68+
files, err := filepath.Glob(fmt.Sprintf("%s/*", tempDir))
69+
Expect(err).NotTo(HaveOccurred())
70+
Expect(files).To(ConsistOf([]string{
71+
filepath.Join(tempDir, "a-symlink"),
72+
filepath.Join(tempDir, "b-symlink"),
73+
filepath.Join(tempDir, "z"),
74+
}))
75+
76+
Expect(filepath.Join(tempDir, "z")).To(BeADirectory())
77+
Expect(filepath.Join(tempDir, "z", "x")).To(BeARegularFile())
78+
79+
link, err := os.Readlink(filepath.Join(tempDir, "a-symlink"))
80+
Expect(err).NotTo(HaveOccurred())
81+
Expect(link).To(Equal("z"))
82+
83+
data, err := os.ReadFile(filepath.Join(tempDir, "b-symlink"))
84+
Expect(err).NotTo(HaveOccurred())
85+
Expect(data).To(Equal([]byte(filepath.Join("z", "x"))))
86+
})
87+
})
88+
89+
context("ZipArchive test that symlinks are sorted so that symlink to other symlinks are created after the initial symlink", func() {
90+
var (
91+
tempDir string
92+
zipArchive vacation.ZipArchive
93+
)
94+
95+
it.Before(func() {
96+
var err error
97+
tempDir, err = os.MkdirTemp("", "vacation")
98+
Expect(err).NotTo(HaveOccurred())
99+
100+
buffer := bytes.NewBuffer(nil)
101+
zw := zip.NewWriter(buffer)
102+
103+
fileHeader := &zip.FileHeader{Name: "b-symlink"}
104+
fileHeader.SetMode(0755 | os.ModeSymlink)
105+
106+
bSymlink, err := zw.CreateHeader(fileHeader)
107+
Expect(err).NotTo(HaveOccurred())
108+
109+
_, err = bSymlink.Write([]byte(filepath.Join("a-symlink", "x")))
110+
Expect(err).NotTo(HaveOccurred())
111+
112+
fileHeader = &zip.FileHeader{Name: "a-symlink"}
113+
fileHeader.SetMode(0755 | os.ModeSymlink)
114+
115+
aSymlink, err := zw.CreateHeader(fileHeader)
116+
Expect(err).NotTo(HaveOccurred())
117+
118+
_, err = aSymlink.Write([]byte(`z`))
119+
Expect(err).NotTo(HaveOccurred())
120+
121+
_, err = zw.Create("z" + string(filepath.Separator))
122+
Expect(err).NotTo(HaveOccurred())
123+
124+
fileHeader = &zip.FileHeader{Name: filepath.Join("z", "x")}
125+
fileHeader.SetMode(0644)
126+
127+
xFile, err := zw.CreateHeader(fileHeader)
128+
Expect(err).NotTo(HaveOccurred())
129+
130+
_, err = xFile.Write([]byte("x file"))
131+
Expect(err).NotTo(HaveOccurred())
132+
133+
Expect(zw.Close()).To(Succeed())
134+
135+
zipArchive = vacation.NewZipArchive(bytes.NewReader(buffer.Bytes()))
136+
})
137+
138+
it.After(func() {
139+
Expect(os.RemoveAll(tempDir)).To(Succeed())
140+
})
141+
142+
it("unpackages the archive into the path", func() {
143+
var err error
144+
err = zipArchive.Decompress(tempDir)
145+
Expect(err).ToNot(HaveOccurred())
146+
147+
files, err := filepath.Glob(fmt.Sprintf("%s/*", tempDir))
148+
Expect(err).NotTo(HaveOccurred())
149+
Expect(files).To(ConsistOf([]string{
150+
filepath.Join(tempDir, "a-symlink"),
151+
filepath.Join(tempDir, "b-symlink"),
152+
filepath.Join(tempDir, "z"),
153+
}))
154+
155+
Expect(filepath.Join(tempDir, "z")).To(BeADirectory())
156+
Expect(filepath.Join(tempDir, "z", "x")).To(BeARegularFile())
157+
158+
link, err := os.Readlink(filepath.Join(tempDir, "a-symlink"))
159+
Expect(err).NotTo(HaveOccurred())
160+
Expect(link).To(Equal("z"))
161+
162+
data, err := os.ReadFile(filepath.Join(tempDir, "b-symlink"))
163+
Expect(err).NotTo(HaveOccurred())
164+
Expect(data).To(Equal([]byte(`x file`)))
165+
})
166+
})
167+
}

vacation/vacation_tar_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ func testVacationTar(t *testing.T, context spec.G, it spec.S) {
255255

256256
it("returns an error", func() {
257257
err := zipSlipSymlinkTar.Decompress(tempDir)
258+
Expect(err).To(MatchError(ContainSubstring("failed to evaluate symlink")))
258259
Expect(err).To(MatchError(ContainSubstring("no such file or directory")))
259260
})
260261
})

vacation/vacation_zip_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ func testVacationZip(t *testing.T, context spec.G, it spec.S) {
8282
Expect(os.RemoveAll(tempDir)).To(Succeed())
8383
})
8484

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

225225
err := readyArchive.Decompress(tempDir)
226+
Expect(err).To(MatchError(ContainSubstring("failed to evaluate symlink")))
226227
Expect(err).To(MatchError(ContainSubstring("no such file or directory")))
227228
})
228229
})

0 commit comments

Comments
 (0)