1
16
17 package loader
18
19 import (
20 "archive/tar"
21 "bytes"
22 "compress/gzip"
23 "fmt"
24 "io"
25 "net/http"
26 "os"
27 "path"
28 "regexp"
29 "strings"
30
31 "github.com/pkg/errors"
32
33 "helm.sh/helm/v3/pkg/chart"
34 )
35
36 var drivePathPattern = regexp.MustCompile(`^[a-zA-Z]:/`)
37
38
39 type FileLoader string
40
41
42 func (l FileLoader) Load() (*chart.Chart, error) {
43 return LoadFile(string(l))
44 }
45
46
47 func LoadFile(name string) (*chart.Chart, error) {
48 if fi, err := os.Stat(name); err != nil {
49 return nil, err
50 } else if fi.IsDir() {
51 return nil, errors.New("cannot load a directory")
52 }
53
54 raw, err := os.Open(name)
55 if err != nil {
56 return nil, err
57 }
58 defer raw.Close()
59
60 err = ensureArchive(name, raw)
61 if err != nil {
62 return nil, err
63 }
64
65 c, err := LoadArchive(raw)
66 if err != nil {
67 if err == gzip.ErrHeader {
68 return nil, fmt.Errorf("file '%s' does not appear to be a valid chart file (details: %s)", name, err)
69 }
70 }
71 return c, err
72 }
73
74
75
76
77
78
79 func ensureArchive(name string, raw *os.File) error {
80 defer raw.Seek(0, 0)
81
82
83 buffer := make([]byte, 512)
84 _, err := raw.Read(buffer)
85 if err != nil && err != io.EOF {
86 return fmt.Errorf("file '%s' cannot be read: %s", name, err)
87 }
88
89
90
91 if contentType := http.DetectContentType(buffer); contentType != "application/x-gzip" && !isGZipApplication(buffer) {
92
93
94
95
96 if strings.HasSuffix(name, ".yml") || strings.HasSuffix(name, ".yaml") {
97 return fmt.Errorf("file '%s' seems to be a YAML file, but expected a gzipped archive", name)
98 }
99 return fmt.Errorf("file '%s' does not appear to be a gzipped archive; got '%s'", name, contentType)
100 }
101 return nil
102 }
103
104
105 func isGZipApplication(data []byte) bool {
106 sig := []byte("\x1F\x8B\x08")
107 return bytes.HasPrefix(data, sig)
108 }
109
110
111
112
113 func LoadArchiveFiles(in io.Reader) ([]*BufferedFile, error) {
114 unzipped, err := gzip.NewReader(in)
115 if err != nil {
116 return nil, err
117 }
118 defer unzipped.Close()
119
120 files := []*BufferedFile{}
121 tr := tar.NewReader(unzipped)
122 for {
123 b := bytes.NewBuffer(nil)
124 hd, err := tr.Next()
125 if err == io.EOF {
126 break
127 }
128 if err != nil {
129 return nil, err
130 }
131
132 if hd.FileInfo().IsDir() {
133
134
135 continue
136 }
137
138 switch hd.Typeflag {
139
140 case tar.TypeXGlobalHeader, tar.TypeXHeader:
141 continue
142 }
143
144
145 delimiter := "/"
146 if strings.ContainsRune(hd.Name, '\\') {
147 delimiter = "\\"
148 }
149
150 parts := strings.Split(hd.Name, delimiter)
151 n := strings.Join(parts[1:], delimiter)
152
153
154 n = strings.ReplaceAll(n, delimiter, "/")
155
156 if path.IsAbs(n) {
157 return nil, errors.New("chart illegally contains absolute paths")
158 }
159
160 n = path.Clean(n)
161 if n == "." {
162
163 return nil, errors.Errorf("chart illegally contains content outside the base directory: %q", hd.Name)
164 }
165 if strings.HasPrefix(n, "..") {
166 return nil, errors.New("chart illegally references parent directory")
167 }
168
169
170
171
172
173 if drivePathPattern.MatchString(n) {
174 return nil, errors.New("chart contains illegally named files")
175 }
176
177 if parts[0] == "Chart.yaml" {
178 return nil, errors.New("chart yaml not in base directory")
179 }
180
181 if _, err := io.Copy(b, tr); err != nil {
182 return nil, err
183 }
184
185 data := bytes.TrimPrefix(b.Bytes(), utf8bom)
186
187 files = append(files, &BufferedFile{Name: n, Data: data})
188 b.Reset()
189 }
190
191 if len(files) == 0 {
192 return nil, errors.New("no files in chart archive")
193 }
194 return files, nil
195 }
196
197
198 func LoadArchive(in io.Reader) (*chart.Chart, error) {
199 files, err := LoadArchiveFiles(in)
200 if err != nil {
201 return nil, err
202 }
203
204 return LoadFiles(files)
205 }
206
View as plain text