Skip to content

Commit

Permalink
Merge pull request paketo-buildpacks#9 from cloudfoundry/zip
Browse files Browse the repository at this point in the history
Adds zip functionality to vacation
  • Loading branch information
dwillist authored May 7, 2020
2 parents 1790e3b + fe22e39 commit b9da674
Show file tree
Hide file tree
Showing 2 changed files with 296 additions and 2 deletions.
80 changes: 80 additions & 0 deletions vacation/vacation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package vacation

import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -124,3 +127,80 @@ func (txz TarXZArchive) StripComponents(components int) TarXZArchive {
txz.components = components
return txz
}

type ZipArchive struct {
reader io.Reader
}

func NewZipArchive(inputReader io.Reader) ZipArchive {
return ZipArchive{reader: inputReader}
}

func (z ZipArchive) Decompress(destination string) error {
// Have to convert and io.Reader into a bytes.Reader which
// implements the ReadAt function making it compatible with
// the io.ReaderAt inteface which required for zip.NewReader
buff := bytes.NewBuffer(nil)
size, err := io.Copy(buff, z.reader)
if err != nil {
return err
}

readerAt := bytes.NewReader(buff.Bytes())

zr, err := zip.NewReader(readerAt, size)
if err != nil {
return fmt.Errorf("failed to create zip reader: %w", err)
}

for _, f := range zr.File {
path := filepath.Join(destination, f.Name)

switch {
case f.FileInfo().IsDir():
err = os.MkdirAll(path, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to unzip directory: %w", err)
}
case f.FileInfo().Mode()&os.ModeSymlink != 0:
fd, err := f.Open()
if err != nil {
return err
}

content, err := ioutil.ReadAll(fd)
if err != nil {
return err
}

err = os.Symlink(string(content), path)
if err != nil {
return fmt.Errorf("failed to unzip symlink: %w", err)
}
default:
err = os.MkdirAll(filepath.Dir(path), os.ModePerm)
if err != nil {
return fmt.Errorf("failed to unzip directory that was part of file path: %w", err)
}

dst, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return fmt.Errorf("failed to unzip file: %w", err)
}
defer dst.Close()

src, err := f.Open()
if err != nil {
return err
}
defer src.Close()

_, err = io.Copy(dst, src)
if err != nil {
return err
}
}
}

return nil
}
218 changes: 216 additions & 2 deletions vacation/vacation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package vacation_test

import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"fmt"
Expand Down Expand Up @@ -62,7 +63,6 @@ func testVacation(t *testing.T, context spec.G, it spec.S) {
Expect(tw.Close()).To(Succeed())

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

})

it.After(func() {
Expand Down Expand Up @@ -119,7 +119,6 @@ func testVacation(t *testing.T, context spec.G, it spec.S) {

err := readyArchive.Decompress(tempDir)
Expect(err).To(MatchError(ContainSubstring("failed to read tar response")))

})
})

Expand Down Expand Up @@ -389,4 +388,219 @@ func testVacation(t *testing.T, context spec.G, it spec.S) {
})
})
})

context("ZipArchive.Decompress", func() {
var (
tempDir string
zipArchive vacation.ZipArchive
)

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

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

_, err = zw.Create("some-dir/")
Expect(err).NotTo(HaveOccurred())

_, err = zw.Create(fmt.Sprintf("%s/", filepath.Join("some-dir", "some-other-dir")))
Expect(err).NotTo(HaveOccurred())

fileHeader := &zip.FileHeader{Name: filepath.Join("some-dir", "some-other-dir", "some-file")}
fileHeader.SetMode(0644)

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

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

for _, name := range []string{"first", "second", "third"} {
fileHeader := &zip.FileHeader{Name: name}
fileHeader.SetMode(0755)

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

_, err = f.Write([]byte(name))
Expect(err).NotTo(HaveOccurred())
}

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

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

_, err = symlink.Write([]byte(filepath.Join("some-dir", "some-other-dir", "some-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("downloads the dependency and unpackages it 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, "first"),
filepath.Join(tempDir, "second"),
filepath.Join(tempDir, "third"),
filepath.Join(tempDir, "some-dir"),
filepath.Join(tempDir, "symlink"),
}))

info, err := os.Stat(filepath.Join(tempDir, "first"))
Expect(err).NotTo(HaveOccurred())
Expect(info.Mode()).To(Equal(os.FileMode(0755)))

Expect(filepath.Join(tempDir, "some-dir", "some-other-dir")).To(BeADirectory())
Expect(filepath.Join(tempDir, "some-dir", "some-other-dir", "some-file")).To(BeARegularFile())

link, err := os.Readlink(filepath.Join(tempDir, "symlink"))
Expect(err).NotTo(HaveOccurred())
Expect(link).To(Equal("some-dir/some-other-dir/some-file"))

data, err := ioutil.ReadFile(filepath.Join(tempDir, "symlink"))
Expect(err).NotTo(HaveOccurred())
Expect(data).To(Equal([]byte("nested file")))
})

context("failure cases", func() {
context("when it fails to create a zip reader", func() {
it("returns an error", func() {
readyArchive := vacation.NewZipArchive(bytes.NewBuffer([]byte(`something`)))

err := readyArchive.Decompress(tempDir)
Expect(err).To(MatchError(ContainSubstring("failed to create zip reader")))
})
})

context("when it fails to unzip a directory", func() {
var buffer *bytes.Buffer
it.Before(func() {
var err error
buffer = bytes.NewBuffer(nil)
zw := zip.NewWriter(buffer)

_, err = zw.Create("some-dir/")
Expect(err).NotTo(HaveOccurred())

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

Expect(os.Chmod(tempDir, 0000)).To(Succeed())
})

it.After(func() {
Expect(os.Chmod(tempDir, os.ModePerm)).To(Succeed())
})

it("returns an error", func() {
readyArchive := vacation.NewZipArchive(buffer)

err := readyArchive.Decompress(tempDir)
Expect(err).To(MatchError(ContainSubstring("failed to unzip directory")))
})
})

context("when it fails to unzip a directory that is part of a file base", func() {
var buffer *bytes.Buffer
it.Before(func() {
var err error
buffer = bytes.NewBuffer(nil)
zw := zip.NewWriter(buffer)

_, err = zw.Create("some-dir/some-file")
Expect(err).NotTo(HaveOccurred())

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

Expect(os.Chmod(tempDir, 0000)).To(Succeed())
})

it.After(func() {
Expect(os.Chmod(tempDir, os.ModePerm)).To(Succeed())
})

it("returns an error", func() {
readyArchive := vacation.NewZipArchive(buffer)

err := readyArchive.Decompress(tempDir)
Expect(err).To(MatchError(ContainSubstring("failed to unzip directory that was part of file path")))
})
})

context("when it fails to unzip a symlink", func() {
var buffer *bytes.Buffer
it.Before(func() {
var err error
buffer = bytes.NewBuffer(nil)
zw := zip.NewWriter(buffer)

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

symlink, err := zw.CreateHeader(header)
Expect(err).NotTo(HaveOccurred())

_, err = symlink.Write([]byte(filepath.Join("some", "path", "to", "a", "target")))
Expect(err).NotTo(HaveOccurred())

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

Expect(os.Chmod(tempDir, 0000)).To(Succeed())
})

it.After(func() {
Expect(os.Chmod(tempDir, os.ModePerm)).To(Succeed())
})

it("returns an error", func() {
readyArchive := vacation.NewZipArchive(buffer)

err := readyArchive.Decompress(tempDir)
Expect(err).To(MatchError(ContainSubstring("failed to unzip symlink")))
})
})

context("when it fails to unzip a file", func() {
var buffer *bytes.Buffer
it.Before(func() {
var err error
buffer = bytes.NewBuffer(nil)
zw := zip.NewWriter(buffer)

_, err = zw.Create("some-file")
Expect(err).NotTo(HaveOccurred())

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

Expect(os.Chmod(tempDir, 0000)).To(Succeed())
})

it.After(func() {
Expect(os.Chmod(tempDir, os.ModePerm)).To(Succeed())
})

it("returns an error", func() {
readyArchive := vacation.NewZipArchive(buffer)

err := readyArchive.Decompress(tempDir)
Expect(err).To(MatchError(ContainSubstring("failed to unzip file")))
})
})
})
})
}

0 comments on commit b9da674

Please sign in to comment.