1
16
17 package tar
18
19 import (
20 "archive/tar"
21 "compress/gzip"
22 "fmt"
23 "io"
24 "os"
25 "path/filepath"
26 "regexp"
27 "strings"
28
29 "github.com/sirupsen/logrus"
30 )
31
32
33
34
35
36 func Compress(tarFilePath, tarContentsPath string, excludes ...*regexp.Regexp) error {
37 return compress(true, tarFilePath, tarContentsPath, excludes...)
38 }
39
40
41
42
43 func CompressWithoutPreservingPath(tarFilePath, tarContentsPath string, excludes ...*regexp.Regexp) error {
44 return compress(false, tarFilePath, tarContentsPath, excludes...)
45 }
46
47 func compress(preserveRootDirStructure bool, tarFilePath, tarContentsPath string, excludes ...*regexp.Regexp) error {
48 tarFile, err := os.Create(tarFilePath)
49 if err != nil {
50 return fmt.Errorf("create tar file %q: %w", tarFilePath, err)
51 }
52 defer tarFile.Close()
53
54 gzipWriter := gzip.NewWriter(tarFile)
55 defer gzipWriter.Close()
56
57 tarWriter := tar.NewWriter(gzipWriter)
58 defer tarWriter.Close()
59
60 if err := filepath.Walk(tarContentsPath, func(filePath string, fileInfo os.FileInfo, err error) error {
61 if err != nil {
62 return err
63 }
64
65 var link string
66 isLink := fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink
67 if isLink {
68 link, err = os.Readlink(filePath)
69 if err != nil {
70 return fmt.Errorf("read file link of %s: %w", filePath, err)
71 }
72 }
73
74 header, err := tar.FileInfoHeader(fileInfo, link)
75 if err != nil {
76 return fmt.Errorf("create file info header for %q: %w", filePath, err)
77 }
78
79 if fileInfo.IsDir() || filePath == tarFilePath {
80 logrus.Tracef("Skipping: %s", filePath)
81 return nil
82 }
83
84 for _, re := range excludes {
85 if re != nil && re.MatchString(filePath) {
86 logrus.Tracef("Excluding: %s", filePath)
87 return nil
88 }
89 }
90
91
92
93
94
95
96
97
98
99
100
101 dropPath := filepath.Dir(tarFilePath)
102 if !preserveRootDirStructure {
103 dropPath = tarContentsPath
104 }
105 header.Name = strings.TrimLeft(
106 strings.TrimPrefix(filePath, dropPath),
107 string(filepath.Separator),
108 )
109 header.Linkname = filepath.ToSlash(header.Linkname)
110
111 if err := tarWriter.WriteHeader(header); err != nil {
112 return fmt.Errorf("writing tar header: %w", err)
113 }
114
115 if !isLink {
116 file, err := os.Open(filePath)
117 if err != nil {
118 return fmt.Errorf("open file %q: %w", filePath, err)
119 }
120
121 if _, err := io.Copy(tarWriter, file); err != nil {
122 return fmt.Errorf("writing file to tar writer: %w", err)
123 }
124
125 file.Close()
126 }
127
128 return nil
129 }); err != nil {
130 return fmt.Errorf("walking tree in %q: %w", tarContentsPath, err)
131 }
132
133 return nil
134 }
135
136
137
138 func Extract(tarFilePath, destinationPath string) error {
139 return iterateTarball(
140 tarFilePath,
141 func(reader *tar.Reader, header *tar.Header) (stop bool, err error) {
142 switch header.Typeflag {
143 case tar.TypeDir:
144 targetDir, err := SanitizeArchivePath(destinationPath, header.Name)
145 if err != nil {
146 return false, fmt.Errorf("SanitizeArchivePath: %w", err)
147 }
148 logrus.Tracef("Creating directory %s", targetDir)
149 if err := os.MkdirAll(targetDir, os.FileMode(0o755)); err != nil {
150 return false, fmt.Errorf("create target directory: %w", err)
151 }
152 case tar.TypeSymlink:
153 targetFile, err := SanitizeArchivePath(destinationPath, header.Name)
154 if err != nil {
155 return false, fmt.Errorf("SanitizeArchivePath: %w", err)
156 }
157 logrus.Tracef(
158 "Creating symlink %s -> %s", header.Linkname, targetFile,
159 )
160 if err := os.MkdirAll(
161 filepath.Dir(targetFile), os.FileMode(0o755),
162 ); err != nil {
163 return false, fmt.Errorf("create target directory: %w", err)
164 }
165 if err := os.Symlink(header.Linkname, targetFile); err != nil {
166 return false, fmt.Errorf("create symlink: %w", err)
167 }
168
169
170 case tar.TypeReg:
171 targetFile, err := SanitizeArchivePath(destinationPath, header.Name)
172 if err != nil {
173 return false, fmt.Errorf("SanitizeArchivePath: %w", err)
174 }
175 logrus.Tracef("Creating file %s", targetFile)
176
177 if err := os.MkdirAll(
178 filepath.Dir(targetFile), os.FileMode(0o755),
179 ); err != nil {
180 return false, fmt.Errorf("create target directory: %w", err)
181 }
182
183 outFile, err := os.Create(targetFile)
184 if err != nil {
185 return false, fmt.Errorf("create target file: %w", err)
186 }
187 if err := outFile.Chmod(os.FileMode(header.Mode)); err != nil {
188 return false, fmt.Errorf("chmod target file: %w", err)
189 }
190
191 if _, err := io.Copy(outFile, reader); err != nil {
192 return false, fmt.Errorf("copy file contents %s: %w", targetFile, err)
193 }
194 outFile.Close()
195
196 default:
197 logrus.Warnf(
198 "File %s has unknown type %s",
199 header.Name, string(header.Typeflag),
200 )
201 }
202
203 return false, nil
204 },
205 )
206 }
207
208
209
210 func SanitizeArchivePath(d, t string) (v string, err error) {
211 v = filepath.Join(d, t)
212 if strings.HasPrefix(v, filepath.Clean(d)) {
213 return v, nil
214 }
215
216 return "", fmt.Errorf("%s: %s", "content filepath is tainted", t)
217 }
218
219
220 func ReadFileFromGzippedTar(
221 tarPath, filePath string,
222 ) (res io.Reader, err error) {
223 if err := iterateTarball(
224 tarPath,
225 func(reader *tar.Reader, header *tar.Header) (stop bool, err error) {
226 if header.Name == filePath {
227 res = reader
228 return true, nil
229 }
230 return false, nil
231 },
232 ); err != nil {
233 return nil, err
234 }
235
236 if res == nil {
237 return nil, fmt.Errorf("unable to find file %q in tarball %q: %w", tarPath, filePath, err)
238 }
239
240 return res, nil
241 }
242
243
244
245 func iterateTarball(
246 tarPath string,
247 callback func(*tar.Reader, *tar.Header) (stop bool, err error),
248 ) error {
249 file, err := os.Open(tarPath)
250 if err != nil {
251 return fmt.Errorf("opening tar file %q: %w", tarPath, err)
252 }
253
254 gzipReader, err := gzip.NewReader(file)
255 if err != nil {
256 return fmt.Errorf("creating gzip reader for file %q: %w", tarPath, err)
257 }
258 tarReader := tar.NewReader(gzipReader)
259
260 for {
261 tarHeader, err := tarReader.Next()
262 if err == io.EOF {
263 break
264 }
265
266 stop, err := callback(tarReader, tarHeader)
267 if err != nil {
268 return err
269 }
270 if stop {
271
272 break
273 }
274 }
275
276 return nil
277 }
278
View as plain text