@@ -17,145 +17,35 @@ package download
17
17
import (
18
18
"archive/tar"
19
19
"archive/zip"
20
- "bytes"
21
- "compress/gzip"
22
20
"io"
23
21
"io/ioutil"
24
22
"net/http"
25
23
"os"
26
- "path/filepath"
27
24
"strings"
28
25
29
26
"github.com/golang/glog"
27
+ "github.com/mholt/archiver"
30
28
"github.com/pkg/errors"
31
29
)
32
30
33
31
// download gets a file from the internet in memory and writes it content
34
32
// to a Verifier.
35
- func download (url string , verifier Verifier , fetcher Fetcher ) (io. ReaderAt , int64 , error ) {
33
+ func download (url string , verifier Verifier , fetcher Fetcher ) ([] byte , error ) {
36
34
glog .V (2 ).Infof ("Fetching %q" , url )
37
35
body , err := fetcher .Get (url )
38
36
if err != nil {
39
- return nil , 0 , errors .Wrapf (err , "could not download %q" , url )
37
+ return nil , errors .Wrapf (err , "could not download %q" , url )
40
38
}
41
39
defer body .Close ()
42
40
43
41
glog .V (3 ).Infof ("Reading download data into memory" )
44
42
data , err := ioutil .ReadAll (io .TeeReader (body , verifier ))
45
43
if err != nil {
46
- return nil , 0 , errors .Wrap (err , "could not read download content" )
44
+ return nil , errors .Wrap (err , "could not read download content" )
47
45
}
48
46
glog .V (2 ).Infof ("Read %d bytes of download data into memory" , len (data ))
49
47
50
- return bytes .NewReader (data ), int64 (len (data )), verifier .Verify ()
51
- }
52
-
53
- // extractZIP extracts a zip file into the target directory.
54
- func extractZIP (targetDir string , read io.ReaderAt , size int64 ) error {
55
- glog .V (4 ).Infof ("Extracting download zip to %q" , targetDir )
56
- zipReader , err := zip .NewReader (read , size )
57
- if err != nil {
58
- return err
59
- }
60
-
61
- for _ , f := range zipReader .File {
62
- if err := suspiciousPath (f .Name ); err != nil {
63
- return err
64
- }
65
-
66
- path := filepath .Join (targetDir , filepath .FromSlash (f .Name ))
67
- if f .FileInfo ().IsDir () {
68
- if err := os .MkdirAll (path , f .Mode ()); err != nil {
69
- return errors .Wrap (err , "can't create directory tree" )
70
- }
71
- continue
72
- }
73
-
74
- src , err := f .Open ()
75
- if err != nil {
76
- return errors .Wrap (err , "could not open inflating zip file" )
77
- }
78
-
79
- dst , err := os .OpenFile (path , os .O_CREATE | os .O_WRONLY | os .O_TRUNC , f .Mode ())
80
- if err != nil {
81
- src .Close ()
82
- return errors .Wrap (err , "can't create file in zip destination dir" )
83
- }
84
- close := func () {
85
- src .Close ()
86
- dst .Close ()
87
- }
88
-
89
- if _ , err := io .Copy (dst , src ); err != nil {
90
- close ()
91
- return errors .Wrap (err , "can't copy content to zip destination file" )
92
- }
93
- close ()
94
- }
95
-
96
- return nil
97
- }
98
-
99
- // extractTARGZ extracts a gzipped tar file into the target directory.
100
- func extractTARGZ (targetDir string , at io.ReaderAt , size int64 ) error {
101
- glog .V (4 ).Infof ("tar: extracting to %q" , targetDir )
102
- in := io .NewSectionReader (at , 0 , size )
103
-
104
- gzr , err := gzip .NewReader (in )
105
- if err != nil {
106
- return errors .Wrap (err , "failed to create gzip reader" )
107
- }
108
- defer gzr .Close ()
109
-
110
- tr := tar .NewReader (gzr )
111
- for {
112
- hdr , err := tr .Next ()
113
- if err == io .EOF {
114
- break
115
- }
116
- if err != nil {
117
- return errors .Wrap (err , "tar extraction error" )
118
- }
119
- glog .V (4 ).Infof ("tar: processing %q (type=%d, mode=%s)" , hdr .Name , hdr .Typeflag , os .FileMode (hdr .Mode ))
120
- // see https://golang.org/cl/78355 for handling pax_global_header
121
- if hdr .Name == "pax_global_header" {
122
- glog .V (4 ).Infof ("tar: skipping pax_global_header file" )
123
- continue
124
- }
125
-
126
- if err := suspiciousPath (hdr .Name ); err != nil {
127
- return err
128
- }
129
-
130
- path := filepath .Join (targetDir , filepath .FromSlash (hdr .Name ))
131
- switch hdr .Typeflag {
132
- case tar .TypeDir :
133
- if err := os .MkdirAll (path , os .FileMode (hdr .Mode )); err != nil {
134
- return errors .Wrap (err , "failed to create directory from tar" )
135
- }
136
- case tar .TypeReg :
137
- dir := filepath .Dir (path )
138
- glog .V (4 ).Infof ("tar: ensuring parent dirs exist for regular file, dir=%s" , dir )
139
- if err := os .MkdirAll (dir , 0755 ); err != nil {
140
- return errors .Wrap (err , "failed to create directory for tar" )
141
- }
142
- f , err := os .OpenFile (path , os .O_CREATE | os .O_WRONLY , os .FileMode (hdr .Mode ))
143
- if err != nil {
144
- return errors .Wrapf (err , "failed to create file %q" , path )
145
- }
146
- close := func () { f .Close () }
147
- if _ , err := io .Copy (f , tr ); err != nil {
148
- close ()
149
- return errors .Wrapf (err , "failed to copy %q from tar into file" , hdr .Name )
150
- }
151
- close ()
152
- default :
153
- return errors .Errorf ("unable to handle file type %d for %q in tar" , hdr .Typeflag , hdr .Name )
154
- }
155
- glog .V (4 ).Infof ("tar: processed %q" , hdr .Name )
156
- }
157
- glog .V (4 ).Infof ("tar extraction to %s complete" , targetDir )
158
- return nil
48
+ return data , verifier .Verify ()
159
49
}
160
50
161
51
func suspiciousPath (path string ) error {
@@ -170,44 +60,38 @@ func suspiciousPath(path string) error {
170
60
return nil
171
61
}
172
62
173
- func detectMIMEType (at io.ReaderAt ) (string , error ) {
174
- buf := make ([]byte , 512 )
175
- n , err := at .ReadAt (buf , 0 )
176
- if err != nil && err != io .EOF {
177
- return "" , errors .Wrap (err , "failed to read first 512 bytes" )
178
- }
179
- if n < 512 {
180
- glog .V (5 ).Infof ("Did only read %d of 512 bytes to determine the file type" , n )
181
- }
63
+ func isSuspiciousArchive (path string ) error {
64
+ return archiver .Walk (path , func (f archiver.File ) error {
65
+ switch h := f .Header .(type ) {
66
+ case * tar.Header :
67
+ return suspiciousPath (h .Name )
68
+ case zip.FileHeader :
69
+ return suspiciousPath (h .Name )
70
+ default :
71
+ return errors .Errorf ("Unknow header type: %T" , h )
72
+ }
73
+ })
74
+ }
182
75
76
+ func detectMIMEType (data []byte ) string {
77
+ n := 512
78
+ if l := len (data ); l < n {
79
+ n = l
80
+ }
183
81
// Cut off mime extra info beginning with ';' i.e:
184
82
// "text/plain; charset=utf-8" should result in "text/plain".
185
- return strings .Split (http .DetectContentType (buf [:n ]), ";" )[0 ], nil
186
- }
187
-
188
- type extractor func (targetDir string , read io.ReaderAt , size int64 ) error
189
-
190
- var defaultExtractors = map [string ]extractor {
191
- "application/zip" : extractZIP ,
192
- "application/x-gzip" : extractTARGZ ,
83
+ return strings .Split (http .DetectContentType (data [:n ]), ";" )[0 ]
193
84
}
194
85
195
- func extractArchive ( dst string , at io. ReaderAt , size int64 ) error {
196
- // TODO(ahmetb) This package is not architected well, this method should not
197
- // be receiving this many args. Primary problem is at GetInsecure and
198
- // GetWithSha256 methods that embed extraction in them, which is orthogonal.
199
-
200
- t , err := detectMIMEType ( at )
201
- if err != nil {
202
- return errors .Wrap ( err , "failed to determine content type" )
86
+ func extensionFromMIME ( mime string ) ( string , error ) {
87
+ switch mime {
88
+ case "application/zip" :
89
+ return "zip" , nil
90
+ case "application/x-gzip" :
91
+ return "tar.gz" , nil
92
+ default :
93
+ return "" , errors .Errorf ( "unknown mime type to extract: %q" , mime )
203
94
}
204
- glog .V (4 ).Infof ("detected %q file type" , t )
205
- exf , ok := defaultExtractors [t ]
206
- if ! ok {
207
- return errors .Errorf ("mime type %q for downloaded file is not a supported archive format" , t )
208
- }
209
- return errors .Wrap (exf (dst , at , size ), "failed to extract file" )
210
-
211
95
}
212
96
213
97
// Downloader is responsible for fetching, verifying and extracting a binary.
@@ -227,9 +111,28 @@ func NewDownloader(v Verifier, f Fetcher) Downloader {
227
111
// Get pulls the uri and verifies it. On success, the download gets extracted
228
112
// into dst.
229
113
func (d Downloader ) Get (uri , dst string ) error {
230
- body , size , err := download (uri , d .verifier , d .fetcher )
114
+ data , err := download (uri , d .verifier , d .fetcher )
115
+ if err != nil {
116
+ return err
117
+ }
118
+ extension , err := extensionFromMIME (detectMIMEType (data ))
231
119
if err != nil {
232
120
return err
233
121
}
234
- return extractArchive (dst , body , size )
122
+
123
+ f , err := ioutil .TempFile ("" , "plugin.*." + extension )
124
+ if err != nil {
125
+ return errors .Wrap (err , "failed to create temp file to write" )
126
+ }
127
+ defer os .Remove (f .Name ())
128
+ if n , err := f .Write (data ); err != nil {
129
+ return errors .Wrap (err , "failed to write temp download file" )
130
+ } else if n != len (data ) {
131
+ return errors .Errorf ("failed to write whole download archive" )
132
+ }
133
+
134
+ if err := isSuspiciousArchive (f .Name ()); err != nil {
135
+ return err
136
+ }
137
+ return archiver .Unarchive (f .Name (), dst )
235
138
}
0 commit comments