fix(coderd): correctly handle tar dir entries with missing path separator (#12479)

* coderd: add test to reproduce trailing directory issue
* coderd: add trailing path separator to dir entries when converting to zip
* provisionersdk: add trailing path separator to directory entries
This commit is contained in:
Cian Johnston 2024-03-11 14:06:41 +00:00 committed by GitHub
parent 21d1873d97
commit bed61f7d2a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 14 deletions

View File

@ -7,6 +7,7 @@ import (
"errors"
"io"
"log"
"strings"
)
func CreateTarFromZip(zipReader *zip.Reader) ([]byte, error) {
@ -87,6 +88,12 @@ func WriteZipArchive(w io.Writer, tarReader *tar.Reader) error {
return err
}
zipHeader.Name = tarHeader.Name
// Some versions of unzip do not check the mode on a file entry and
// simply assume that entries with a trailing path separator (/) are
// directories, and that everything else is a file. Give them a hint.
if tarHeader.FileInfo().IsDir() && !strings.HasSuffix(tarHeader.Name, "/") {
zipHeader.Name += "/"
}
zipEntry, err := zipWriter.CreateHeader(zipHeader)
if err != nil {

View File

@ -58,26 +58,64 @@ func TestCreateZipFromTar(t *testing.T) {
if runtime.GOOS != "linux" {
t.Skip("skipping this test on non-Linux platform")
}
t.Run("OK", func(t *testing.T) {
t.Parallel()
tarBytes, err := os.ReadFile(filepath.Join(".", "testdata", "test.tar"))
require.NoError(t, err, "failed to read sample tar file")
tarBytes, err := os.ReadFile(filepath.Join("testdata", "test.tar"))
require.NoError(t, err, "failed to read sample tar file")
tr := tar.NewReader(bytes.NewReader(tarBytes))
zipBytes, err := coderd.CreateZipFromTar(tr)
require.NoError(t, err)
tr := tar.NewReader(bytes.NewReader(tarBytes))
zipBytes, err := coderd.CreateZipFromTar(tr)
require.NoError(t, err)
assertSampleZipFile(t, zipBytes)
assertSampleZipFile(t, zipBytes)
tempDir := t.TempDir()
tempFilePath := filepath.Join(tempDir, "test.zip")
err = os.WriteFile(tempFilePath, zipBytes, 0o600)
require.NoError(t, err, "failed to write converted zip file")
tempDir := t.TempDir()
tempFilePath := filepath.Join(tempDir, "test.zip")
err = os.WriteFile(tempFilePath, zipBytes, 0o600)
require.NoError(t, err, "failed to write converted zip file")
ctx := testutil.Context(t, testutil.WaitShort)
cmd := exec.CommandContext(ctx, "unzip", tempFilePath, "-d", tempDir)
require.NoError(t, cmd.Run(), "failed to extract converted zip file")
ctx := testutil.Context(t, testutil.WaitShort)
cmd := exec.CommandContext(ctx, "unzip", tempFilePath, "-d", tempDir)
require.NoError(t, cmd.Run(), "failed to extract converted zip file")
assertExtractedFiles(t, tempDir, false)
})
assertExtractedFiles(t, tempDir, false)
t.Run("MissingSlashInDirectoryHeader", func(t *testing.T) {
t.Parallel()
// Given: a tar archive containing a directory entry that has the directory
// mode bit set but the name is missing a trailing slash
var tarBytes bytes.Buffer
tw := tar.NewWriter(&tarBytes)
tw.WriteHeader(&tar.Header{
Name: "dir",
Typeflag: tar.TypeDir,
Size: 0,
})
require.NoError(t, tw.Flush())
require.NoError(t, tw.Close())
// When: we convert this to a zip
tr := tar.NewReader(&tarBytes)
zipBytes, err := coderd.CreateZipFromTar(tr)
require.NoError(t, err)
// Then: the resulting zip should contain a corresponding directory
zr, err := zip.NewReader(bytes.NewReader(zipBytes), int64(len(zipBytes)))
require.NoError(t, err)
for _, zf := range zr.File {
switch zf.Name {
case "dir":
require.Fail(t, "missing trailing slash in directory name")
case "dir/":
require.True(t, zf.Mode().IsDir(), "should be a directory")
default:
require.Fail(t, "unexpected file in archive")
}
}
})
}
// nolint:revive // this is a control flag but it's in a unit test

View File

@ -107,6 +107,11 @@ func Tar(w io.Writer, logger slog.Logger, directory string, limit int64) error {
}
// Use unix paths in the tar archive.
header.Name = filepath.ToSlash(rel)
// tar.FileInfoHeader() will do this, but filepath.Rel() calls filepath.Clean()
// which strips trailing path separators for directories.
if fileInfo.IsDir() {
header.Name += "/"
}
if err := tarWriter.WriteHeader(header); err != nil {
return err
}