forked from vmware/go-vcloud-director
/
tar.go
131 lines (105 loc) · 3.24 KB
/
tar.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
/*
* Copyright 2018 VMware, Inc. All rights reserved. Licensed under the Apache v2 License.
*/
package util
import (
"archive/tar"
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
const TmpDirPrefix = "govcd"
// Extract files to system tmp dir with name govcd+random number. Created folder with files isn't deleted.
// Returns extracted files paths in array and path where folder with files created.
func Unpack(tarFile string) ([]string, string, error) {
var filePaths []string
var dst string
reader, err := os.Open(tarFile)
if err != nil {
return filePaths, dst, err
}
defer reader.Close()
tarReader := tar.NewReader(reader)
dst, err = ioutil.TempDir("", TmpDirPrefix)
if err != nil {
return filePaths, dst, err
}
var expectedFileSize int64 = -1
for {
header, err := tarReader.Next()
switch {
// if no more files are found return
case err == io.EOF:
return filePaths, dst, nil
// return any other error
case err != nil:
return filePaths, dst, err
// if the header is nil, just skip it (not sure how this happens)
case header == nil:
continue
case header != nil:
expectedFileSize = header.Size
}
// the target location where the dir/newFile should be created
target := filepath.Join(dst, sanitizedName(header.Name))
Logger.Printf("[TRACE] extracting newFile: %s \n", target)
// check the newFile type
switch header.Typeflag {
// if its a dir and it doesn't exist create it
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return filePaths, dst, err
}
}
case tar.TypeSymlink:
if header.Linkname != "" {
err := os.Symlink(header.Linkname, target)
if err != nil {
return filePaths, dst, err
}
} else {
return filePaths, dst, errors.New("File %s is a symlink, but no link information was provided\n")
}
// if it's a newFile create it
case tar.TypeReg:
newFile, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return filePaths, dst, err
}
// copy over contents
if _, err := io.Copy(newFile, tarReader); err != nil {
return filePaths, dst, err
}
filePaths = append(filePaths, newFile.Name())
if err := isExtractedFileValid(newFile, expectedFileSize); err != nil {
newFile.Close()
return filePaths, dst, err
}
// manually close here after each newFile operation; defering would cause each newFile close
// to wait until all operations have completed.
newFile.Close()
}
}
}
func isExtractedFileValid(file *os.File, expectedFileSize int64) error {
if fInfo, err := file.Stat(); err == nil {
Logger.Printf("[TRACE] isExtractedFileValid: created file size %#v, size from header %#v.\n", fInfo.Size(), expectedFileSize)
if fInfo.Size() != expectedFileSize && expectedFileSize != -1 {
return errors.New("extracted file didn't match defined file size")
}
}
return nil
}
func sanitizedName(filename string) string {
if len(filename) > 1 && filename[1] == ':' {
filename = filename[2:]
}
filename = strings.TrimLeft(filename, "\\/.")
filename = strings.TrimLeft(filename, "../")
filename = strings.Replace(filename, "../../", "../", -1)
return strings.Replace(filename, "..\\", "", -1)
}