diff --git a/fileutil/compressor_interface.go b/fileutil/compressor_interface.go index 3ec51cd3..62ea0033 100644 --- a/fileutil/compressor_interface.go +++ b/fileutil/compressor_interface.go @@ -15,6 +15,8 @@ type Compressor interface { DecompressFileToDir(path string, dir string, options CompressorOptions) (err error) + IsNonCompressedTarball(path string) bool + // CleanUp cleans up compressed file after it was used CleanUp(path string) error } diff --git a/fileutil/fakes/fake_compressor.go b/fileutil/fakes/fake_compressor.go index 7f5aafae..8b40ae07 100644 --- a/fileutil/fakes/fake_compressor.go +++ b/fileutil/fakes/fake_compressor.go @@ -24,6 +24,9 @@ type FakeCompressor struct { DecompressFileToDirErr error DecompressFileToDirCallBack func() + IsNonCompressedTarballPath string + IsNonCompressedTarballReturns bool + CleanUpTarballPath string CleanUpErr error } @@ -65,6 +68,11 @@ func (fc *FakeCompressor) DecompressFileToDir(tarballPath string, dir string, op return fc.DecompressFileToDirErr } +func (fc *FakeCompressor) IsNonCompressedTarball(path string) bool { + fc.IsNonCompressedTarballPath = path + return fc.IsNonCompressedTarballReturns +} + func (fc *FakeCompressor) CleanUp(tarballPath string) error { fc.CleanUpTarballPath = tarballPath return fc.CleanUpErr diff --git a/fileutil/tarball_compressor.go b/fileutil/tarball_compressor.go index 037eb7d6..84a7d662 100644 --- a/fileutil/tarball_compressor.go +++ b/fileutil/tarball_compressor.go @@ -1,13 +1,24 @@ package fileutil import ( + "bytes" "fmt" + "os" "runtime" bosherr "github.com/cloudfoundry/bosh-utils/errors" boshsys "github.com/cloudfoundry/bosh-utils/system" ) +var ( + gzipMagic = []byte{0x1f, 0x8b} + bzip2Magic = []byte{0x42, 0x5a, 0x68} // "BZh" + xzMagic = []byte{0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00} + zstdMagic = []byte{0x28, 0xb5, 0x2f, 0xfd} + ustarMagic = []byte("ustar") + ustarOffset = 257 // Offset of the TAR magic string in the file +) + type tarballCompressor struct { cmdRunner boshsys.CmdRunner fs boshsys.FileSystem @@ -80,6 +91,39 @@ func (c tarballCompressor) DecompressFileToDir(tarballPath string, dir string, o return nil } +func (c tarballCompressor) IsNonCompressedTarball(path string) bool { + f, err := c.fs.OpenFile(path, os.O_RDONLY, 0644) + if err != nil { + // If we cannot open the file, we assume it is not compressed + return false + } + defer f.Close() //nolint:errcheck + + // Read the first 512 bytes to check both compression headers and the TAR header. + // Ignore the error from reading a partial buffer, which is fine for short files. + buffer := make([]byte, 512) + _, _ = f.Read(buffer) //nolint:errcheck + + // 1. Check for compression first. + if bytes.HasPrefix(buffer, gzipMagic) || + bytes.HasPrefix(buffer, bzip2Magic) || + bytes.HasPrefix(buffer, xzMagic) || + bytes.HasPrefix(buffer, zstdMagic) { + return false + } + + // 2. If NOT compressed, check for the TAR magic string at its specific offset. + // Ensure the buffer is long enough to contain the TAR header magic string. + if len(buffer) > ustarOffset+len(ustarMagic) { + magicBytes := buffer[ustarOffset : ustarOffset+len(ustarMagic)] + if bytes.Equal(magicBytes, ustarMagic) { + return true + } + } + + return false +} + func (c tarballCompressor) CleanUp(tarballPath string) error { return c.fs.RemoveAll(tarballPath) } diff --git a/fileutil/tarball_compressor_test.go b/fileutil/tarball_compressor_test.go index c3a74bff..a875ff29 100644 --- a/fileutil/tarball_compressor_test.go +++ b/fileutil/tarball_compressor_test.go @@ -273,6 +273,26 @@ var _ = Describe("tarballCompressor", func() { }) }) + Describe("IsNonCompressedTarball", func() { + It("returns false for compressed tarball", func() { + tgzName, err := compressor.CompressFilesInDir(testAssetsFixtureDir, CompressorOptions{NoCompression: false}) + Expect(err).ToNot(HaveOccurred()) + defer os.Remove(tgzName) + + result := compressor.IsNonCompressedTarball(tgzName) + Expect(result).To(BeFalse()) + }) + + It("returns true for uncompressed tarball", func() { + tgzName, err := compressor.CompressFilesInDir(testAssetsFixtureDir, CompressorOptions{NoCompression: true}) + Expect(err).ToNot(HaveOccurred()) + defer os.Remove(tgzName) + + result := compressor.IsNonCompressedTarball(tgzName) + Expect(result).To(BeTrue()) + }) + }) + Describe("CleanUp", func() { It("removes tarball path", func() { fs := fakesys.NewFakeFileSystem()