Skip to content

Commit

Permalink
fix: prevent duplicate folders from being created (#411)
Browse files Browse the repository at this point in the history
* fix: Prevent duplicate folders from being created.

* Fix apk filename convention.
  • Loading branch information
rtpt-erikgeiser committed Dec 1, 2021
1 parent 9570f52 commit e959f1d
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 7 deletions.
24 changes: 20 additions & 4 deletions apk/apk.go
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,14 @@ func createFilesInsideTarGz(info *nfpm.Info, tw *tar.Writer, created map[string]
continue
}

normalizedName := normalizePath(strings.Trim(file.Destination, "/")) + "/"

if created[normalizedName] {
return fmt.Errorf("duplicate directory: %q", normalizedName)
}

err = tw.WriteHeader(&tar.Header{
Name: files.ToNixPath(strings.Trim(file.Destination, "/") + "/"),
Name: normalizedName,
Mode: int64(file.FileInfo.Mode),
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
Expand All @@ -427,7 +433,7 @@ func createFilesInsideTarGz(info *nfpm.Info, tw *tar.Writer, created map[string]
return err
}

created[strings.TrimPrefix(file.Destination, "/")] = true
created[normalizedName] = true
}

for _, file := range info.Contents {
Expand Down Expand Up @@ -483,7 +489,7 @@ func copyToTarAndDigest(file *files.Content, tw *tar.Writer, sizep *int64) error
// tar.FileInfoHeader only uses file.Mode().Perm() which masks the mode with
// 0o777 which we don't want because we want to be able to set the suid bit.
header.Mode = int64(file.Mode())
header.Name = files.ToNixPath(file.Destination[1:])
header.Name = normalizePath(file.Destination)
header.Uname = file.FileInfo.Owner
header.Gname = file.FileInfo.Group
if err = newItemInsideTarGz(tw, contents, header); err != nil {
Expand All @@ -494,20 +500,30 @@ func copyToTarAndDigest(file *files.Content, tw *tar.Writer, sizep *int64) error
return nil
}

// normalizePath returns a path separated by slashes without a leading slash.
func normalizePath(src string) string {
return files.ToNixPath(strings.TrimLeft(src, "/"))
}

// this is needed because the data.tar.gz file should have the empty folders
// as well, so we walk through the dst and create all subfolders.
func createTree(tarw *tar.Writer, dst string, created map[string]bool) error {
for _, path := range pathsToCreate(dst) {
path = normalizePath(path) + "/"

if created[path] {
// skipping dir that was previously created inside the archive
// (eg: usr/)
continue
}

if err := tarw.WriteHeader(&tar.Header{
Name: files.ToNixPath(path + "/"),
Name: path,
Mode: 0o755,
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
Uname: "root",
Gname: "root",
}); err != nil {
return fmt.Errorf("failed to create folder %s: %w", path, err)
}
Expand Down
79 changes: 79 additions & 0 deletions apk/apk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,25 @@ func extractFromTar(t *testing.T, tarFile []byte, fileName string) []byte {
return nil
}

func tarContents(tb testing.TB, tarFile []byte) []string {
tb.Helper()

contents := []string{}

tr := tar.NewReader(bytes.NewReader(tarFile))
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break // End of archive
}
require.NoError(tb, err)

contents = append(contents, hdr.Name)
}

return contents
}

func TestAPKConventionalFileName(t *testing.T) {
apkName := "default"
testCases := []struct {
Expand Down Expand Up @@ -516,6 +535,66 @@ func TestDirectories(t *testing.T) {
require.Equal(t, h.Typeflag, byte(tar.TypeDir))
}

func TestNoDuplicateAutocreatedDirectories(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Contents = []*files.Content{
{
Source: "../testdata/fake",
Destination: "/etc/foo/bar",
},
{
Type: "dir",
Destination: "/etc/foo",
},
}
require.NoError(t, info.Validate())

expected := map[string]bool{
"etc/": true,
"etc/foo/": true,
"etc/foo/bar": true,
}

var buf bytes.Buffer
size := int64(0)
err := createFilesInsideTarGz(info, tar.NewWriter(&buf), make(map[string]bool), &size)
require.NoError(t, err)

contents := tarContents(t, buf.Bytes())

if len(expected) != len(contents) {
t.Fatalf("contents has %d entries instead of %d: %#v", len(contents), len(expected), contents)
}

for _, entry := range contents {
if !expected[entry] {
t.Fatalf("unexpected content: %q", entry)
}
}
}

func TestNoDuplicateDirectories(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Contents = []*files.Content{
{
Type: "dir",
Destination: "/etc/foo",
},
{
Type: "dir",
Destination: "/etc/foo/",
},
}
require.NoError(t, info.Validate())

var buf bytes.Buffer
size := int64(0)
err := createFilesInsideTarGz(info, tar.NewWriter(&buf), make(map[string]bool), &size)
require.Error(t, err)
}

func TestNoDuplicateContents(t *testing.T) {
info := exampleInfo()
info.Contents = []*files.Content{
Expand Down
13 changes: 11 additions & 2 deletions deb/deb.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,14 @@ func createFilesInsideDataTar(info *nfpm.Info, tw *tar.Writer,
continue
}

normalizedName := normalizePath(strings.Trim(file.Destination, "/")) + "/"

if created[normalizedName] {
return md5buf, 0, fmt.Errorf("duplicate directory: %q", normalizedName)
}

err = tw.WriteHeader(&tar.Header{
Name: normalizePath(strings.Trim(file.Destination, "/") + "/"),
Name: normalizedName,
Mode: int64(file.FileInfo.Mode),
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
Expand All @@ -293,7 +299,7 @@ func createFilesInsideDataTar(info *nfpm.Info, tw *tar.Writer,
return md5buf, 0, err
}

created[strings.TrimPrefix(file.Destination, "/")] = true
created[normalizedName] = true
}

// create files and implicit directories
Expand Down Expand Up @@ -588,12 +594,15 @@ func createTree(tarw *tar.Writer, dst string, created map[string]bool) error {
// (eg: usr/)
continue
}

if err := tarw.WriteHeader(&tar.Header{
Name: path,
Mode: 0o755,
Typeflag: tar.TypeDir,
Format: tar.FormatGNU,
ModTime: time.Now(),
Uname: "root",
Gname: "root",
}); err != nil {
return fmt.Errorf("failed to create folder: %w", err)
}
Expand Down
75 changes: 75 additions & 0 deletions deb/deb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,62 @@ func TestDisableGlobbing(t *testing.T) {
require.Equal(t, expectedContent, actualContent)
}

func TestNoDuplicateAutocreatedDirectories(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Contents = []*files.Content{
{
Source: "../testdata/fake",
Destination: "/etc/foo/bar",
},
{
Type: "dir",
Destination: "/etc/foo",
},
}
require.NoError(t, info.Validate())

expected := map[string]bool{
"./etc/": true,
"./etc/foo/": true,
"./etc/foo/bar": true,
}

dataTarball, _, _, tarballName, err := createDataTarball(info)
require.NoError(t, err)

contents := tarContents(t, inflate(t, tarballName, dataTarball))

if len(expected) != len(contents) {
t.Fatalf("contents has %d entries instead of %d: %#v", len(contents), len(expected), contents)
}

for _, entry := range contents {
if !expected[entry] {
t.Fatalf("unexpected content: %q", entry)
}
}
}

func TestNoDuplicateDirectories(t *testing.T) {
info := exampleInfo()
info.DisableGlobbing = true
info.Contents = []*files.Content{
{
Type: "dir",
Destination: "/etc/foo",
},
{
Type: "dir",
Destination: "/etc/foo/",
},
}
require.NoError(t, info.Validate())

_, _, _, _, err := createDataTarball(info)
require.Error(t, err)
}

func TestCompressionAlgorithms(t *testing.T) {
testCases := []struct {
algorithm string
Expand Down Expand Up @@ -1028,6 +1084,25 @@ func tarContains(tb testing.TB, tarFile []byte, filename string) bool {
return false
}

func tarContents(tb testing.TB, tarFile []byte) []string {
tb.Helper()

contents := []string{}

tr := tar.NewReader(bytes.NewReader(tarFile))
for {
hdr, err := tr.Next()
if errors.Is(err, io.EOF) {
break // End of archive
}
require.NoError(tb, err)

contents = append(contents, hdr.Name)
}

return contents
}

func extractFileHeaderFromTar(tb testing.TB, tarFile []byte, filename string) *tar.Header {
tb.Helper()

Expand Down
7 changes: 6 additions & 1 deletion files/files.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,13 @@ func checkNoCollisions(contents Contents) error {
for _, elem := range contents {
present, ok := alreadyPresent[elem.Destination]
if ok && (present.Packager == "" || elem.Packager == "" || present.Packager == elem.Packager) {
if elem.Type == "dir" {
return fmt.Errorf("cannot add directory %q because it is already present: %w",
elem.Destination, ErrContentCollision)
}

return fmt.Errorf(
"cannot add %s because %s is already present at the same destination (%s): %w",
"cannot add %q because %q is already present at the same destination (%s): %w",
elem.Source, present.Source, present.Destination, ErrContentCollision)
}

Expand Down

0 comments on commit e959f1d

Please sign in to comment.