forked from paketo-buildpacks/packit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
tar_archive.go
166 lines (140 loc) · 4.2 KB
/
tar_archive.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package vacation
import (
"archive/tar"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
// A TarArchive decompresses tar files from an input stream.
type TarArchive struct {
reader io.Reader
components int
}
// NewTarArchive returns a new TarArchive that reads from inputReader.
func NewTarArchive(inputReader io.Reader) TarArchive {
return TarArchive{reader: inputReader}
}
// Decompress reads from TarArchive and writes files into the
// destination specified.
func (ta TarArchive) Decompress(destination string) error {
// This map keeps track of what directories have been made already so that we
// only attempt to make them once for a cleaner interaction. This map is
// only necessary in cases where there are no directory headers in the
// tarball, which can be seen in the test around there being no directory
// metadata.
directories := map[string]interface{}{}
var symlinks []link
var links []link
tarReader := tar.NewReader(ta.reader)
for {
hdr, err := tarReader.Next()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("failed to read tar response: %s", err)
}
// Clean the name in the header to prevent './filename' being stripped to
// 'filename' also to skip if the destination it the destination directory
// itself i.e. './'
var name string
if name = filepath.Clean(hdr.Name); name == "." {
continue
}
err = checkExtractPath(name, destination)
if err != nil {
return err
}
fileNames := strings.Split(name, "/")
// Checks to see if file should be written when stripping components
if len(fileNames) <= ta.components {
continue
}
// Constructs the path that conforms to the stripped components.
path := filepath.Join(append([]string{destination}, fileNames[ta.components:]...)...)
// This switch case handles all cases for creating the directory structure
// this logic is needed to handle tarballs with no directory headers.
switch hdr.Typeflag {
case tar.TypeDir:
err = os.MkdirAll(path, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create archived directory: %s", err)
}
directories[path] = nil
default:
dir := filepath.Dir(path)
_, ok := directories[dir]
if !ok {
err = os.MkdirAll(dir, os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create archived directory from file path: %s", err)
}
directories[dir] = nil
}
}
// This switch case handles the creation of files during the untaring process.
switch hdr.Typeflag {
case tar.TypeReg:
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, hdr.FileInfo().Mode())
if err != nil {
return fmt.Errorf("failed to create archived file: %s", err)
}
_, err = io.Copy(file, tarReader)
if err != nil {
return err
}
err = file.Close()
if err != nil {
return err
}
case tar.TypeLink:
// Collect all of the headers for links so that they can be verified
// after all other files are written
links = append(links, link{
name: hdr.Linkname,
path: path,
})
case tar.TypeSymlink:
// Collect all of the headers for symlinks so that they can be verified
// after all other files are written
symlinks = append(symlinks, link{
name: hdr.Linkname,
path: path,
})
}
}
symlinks, err := sortLinks(symlinks)
if err != nil {
return err
}
for _, link := range symlinks {
// Check to see if the file that will be linked to is valid for symlinking
_, err := filepath.EvalSymlinks(linknameFullPath(link.path, link.name))
if err != nil {
return fmt.Errorf("failed to evaluate symlink %s: %w", link.path, err)
}
err = os.Symlink(link.name, link.path)
if err != nil {
return fmt.Errorf("failed to extract symlink: %s", err)
}
}
links, err = sortLinks(links)
if err != nil {
return err
}
for _, link := range links {
err := os.Link(filepath.Join(destination, link.name), link.path)
if err != nil {
return fmt.Errorf("failed to extract link: %s", err)
}
}
return nil
}
// StripComponents behaves like the --strip-components flag on tar command
// removing the first n levels from the final decompression destination.
func (ta TarArchive) StripComponents(components int) TarArchive {
ta.components = components
return ta
}