1
1
package updater
2
2
3
3
import (
4
+ "archive/tar"
4
5
"archive/zip"
6
+ "compress/gzip"
5
7
"fmt"
6
8
"io"
7
9
"os"
@@ -22,6 +24,14 @@ type UIUpdater struct {
22
24
mutex sync.Mutex
23
25
}
24
26
27
+ type compressionType int
28
+
29
+ const (
30
+ typeUnknown compressionType = iota
31
+ typeZip
32
+ typeTarGzip
33
+ )
34
+
25
35
var DefaultUiUpdater = & UIUpdater {}
26
36
27
37
func NewUiUpdater (externalUI , externalUIURL , externalUIName string ) * UIUpdater {
@@ -70,6 +80,24 @@ func (u *UIUpdater) DownloadUI() error {
70
80
return u .downloadUI ()
71
81
}
72
82
83
+ func detectFileType (data []byte ) compressionType {
84
+ if len (data ) < 4 {
85
+ return typeUnknown
86
+ }
87
+
88
+ // Zip: 0x50 0x4B 0x03 0x04
89
+ if data [0 ] == 0x50 && data [1 ] == 0x4B && data [2 ] == 0x03 && data [3 ] == 0x04 {
90
+ return typeZip
91
+ }
92
+
93
+ // GZip: 0x1F 0x8B
94
+ if data [0 ] == 0x1F && data [1 ] == 0x8B {
95
+ return typeTarGzip
96
+ }
97
+
98
+ return typeUnknown
99
+ }
100
+
73
101
func (u * UIUpdater ) downloadUI () error {
74
102
err := u .prepareUIPath ()
75
103
if err != nil {
@@ -78,12 +106,23 @@ func (u *UIUpdater) downloadUI() error {
78
106
79
107
data , err := downloadForBytes (u .externalUIURL )
80
108
if err != nil {
81
- return fmt .Errorf ("can't download file: %w" , err )
109
+ return fmt .Errorf ("can't download file: %w" , err )
82
110
}
83
111
84
- saved := path .Join (C .Path .HomeDir (), "download.zip" )
112
+ fileType := detectFileType (data )
113
+ if fileType == typeUnknown {
114
+ return fmt .Errorf ("unknown or unsupported file type" )
115
+ }
116
+
117
+ ext := ".zip"
118
+ if fileType == typeTarGzip {
119
+ ext = ".tgz"
120
+ }
121
+
122
+ saved := path .Join (C .Path .HomeDir (), "download" + ext )
123
+ log .Debugln ("compression Type: %s" , ext )
85
124
if err = saveFile (data , saved ); err != nil {
86
- return fmt .Errorf ("can't save zip file: %w" , err )
125
+ return fmt .Errorf ("can't save compressed file: %w" , err )
87
126
}
88
127
defer os .Remove (saved )
89
128
@@ -94,12 +133,12 @@ func (u *UIUpdater) downloadUI() error {
94
133
}
95
134
}
96
135
97
- unzipFolder , err := unzip (saved , C .Path .HomeDir ())
136
+ extractedFolder , err := extract (saved , C .Path .HomeDir ())
98
137
if err != nil {
99
- return fmt .Errorf ("can't extract zip file: %w" , err )
138
+ return fmt .Errorf ("can't extract compressed file: %w" , err )
100
139
}
101
140
102
- err = os .Rename (unzipFolder , u .externalUIPath )
141
+ err = os .Rename (extractedFolder , u .externalUIPath )
103
142
if err != nil {
104
143
return fmt .Errorf ("rename UI folder failed: %w" , err )
105
144
}
@@ -122,9 +161,56 @@ func unzip(src, dest string) (string, error) {
122
161
return "" , err
123
162
}
124
163
defer r .Close ()
164
+
165
+ // check whether or not only exists singleRoot dir
166
+ rootDir := ""
167
+ isSingleRoot := true
168
+ for _ , f := range r .File {
169
+ parts := strings .Split (strings .Trim (f .Name , "/" ), "/" )
170
+ if len (parts ) == 0 {
171
+ continue
172
+ }
173
+
174
+ if len (parts ) == 1 {
175
+ isSingleRoot = false
176
+ break
177
+ }
178
+
179
+ if rootDir == "" {
180
+ rootDir = parts [0 ]
181
+ } else if parts [0 ] != rootDir {
182
+ isSingleRoot = false
183
+ break
184
+ }
185
+ }
186
+
187
+ // build the dir of extraction
125
188
var extractedFolder string
189
+ if isSingleRoot && rootDir != "" {
190
+ // if the singleRoot, use it directly
191
+ extractedFolder = filepath .Join (dest , rootDir )
192
+ } else {
193
+ // or put the files/dirs into new dir
194
+ baseName := filepath .Base (src )
195
+ baseName = strings .TrimSuffix (baseName , filepath .Ext (baseName ))
196
+ extractedFolder = filepath .Join (dest , baseName )
197
+
198
+ for i := 1 ; ; i ++ {
199
+ if _ , err := os .Stat (extractedFolder ); os .IsNotExist (err ) {
200
+ break
201
+ }
202
+ extractedFolder = filepath .Join (dest , fmt .Sprintf ("%s_%d" , baseName , i ))
203
+ }
204
+ }
205
+
126
206
for _ , f := range r .File {
127
- fpath := filepath .Join (dest , f .Name )
207
+ var fpath string
208
+ if isSingleRoot && rootDir != "" {
209
+ fpath = filepath .Join (dest , f .Name )
210
+ } else {
211
+ fpath = filepath .Join (extractedFolder , f .Name )
212
+ }
213
+
128
214
if ! strings .HasPrefix (fpath , filepath .Clean (dest )+ string (os .PathSeparator )) {
129
215
return "" , fmt .Errorf ("invalid file path: %s" , fpath )
130
216
}
@@ -149,13 +235,131 @@ func unzip(src, dest string) (string, error) {
149
235
if err != nil {
150
236
return "" , err
151
237
}
152
- if extractedFolder == "" {
153
- extractedFolder = filepath .Dir (fpath )
238
+ }
239
+ return extractedFolder , nil
240
+ }
241
+
242
+ func untgz (src , dest string ) (string , error ) {
243
+ file , err := os .Open (src )
244
+ if err != nil {
245
+ return "" , err
246
+ }
247
+ defer file .Close ()
248
+
249
+ gzr , err := gzip .NewReader (file )
250
+ if err != nil {
251
+ return "" , err
252
+ }
253
+ defer gzr .Close ()
254
+
255
+ tr := tar .NewReader (gzr )
256
+
257
+ rootDir := ""
258
+ isSingleRoot := true
259
+ for {
260
+ header , err := tr .Next ()
261
+ if err == io .EOF {
262
+ break
263
+ }
264
+ if err != nil {
265
+ return "" , err
266
+ }
267
+
268
+ parts := strings .Split (strings .Trim (header .Name , "/" ), "/" )
269
+ if len (parts ) == 0 {
270
+ continue
271
+ }
272
+
273
+ if len (parts ) == 1 {
274
+ isSingleRoot = false
275
+ break
276
+ }
277
+
278
+ if rootDir == "" {
279
+ rootDir = parts [0 ]
280
+ } else if parts [0 ] != rootDir {
281
+ isSingleRoot = false
282
+ break
283
+ }
284
+ }
285
+
286
+ file .Seek (0 , 0 )
287
+ gzr , _ = gzip .NewReader (file )
288
+ tr = tar .NewReader (gzr )
289
+
290
+ var extractedFolder string
291
+ if isSingleRoot && rootDir != "" {
292
+ extractedFolder = filepath .Join (dest , rootDir )
293
+ } else {
294
+ baseName := filepath .Base (src )
295
+ baseName = strings .TrimSuffix (baseName , filepath .Ext (baseName ))
296
+ baseName = strings .TrimSuffix (baseName , ".tar" )
297
+ extractedFolder = filepath .Join (dest , baseName )
298
+
299
+ for i := 1 ; ; i ++ {
300
+ if _ , err := os .Stat (extractedFolder ); os .IsNotExist (err ) {
301
+ break
302
+ }
303
+ extractedFolder = filepath .Join (dest , fmt .Sprintf ("%s_%d" , baseName , i ))
304
+ }
305
+ }
306
+
307
+ for {
308
+ header , err := tr .Next ()
309
+ if err == io .EOF {
310
+ break
311
+ }
312
+ if err != nil {
313
+ return "" , err
314
+ }
315
+
316
+ var fpath string
317
+ if isSingleRoot && rootDir != "" {
318
+ fpath = filepath .Join (dest , header .Name )
319
+ } else {
320
+ fpath = filepath .Join (extractedFolder , header .Name )
321
+ }
322
+
323
+ if ! strings .HasPrefix (fpath , filepath .Clean (dest )+ string (os .PathSeparator )) {
324
+ return "" , fmt .Errorf ("invalid file path: %s" , fpath )
325
+ }
326
+
327
+ switch header .Typeflag {
328
+ case tar .TypeDir :
329
+ if err = os .MkdirAll (fpath , os .FileMode (header .Mode )); err != nil {
330
+ return "" , err
331
+ }
332
+ case tar .TypeReg :
333
+ if err = os .MkdirAll (filepath .Dir (fpath ), os .ModePerm ); err != nil {
334
+ return "" , err
335
+ }
336
+ outFile , err := os .OpenFile (fpath , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , os .FileMode (header .Mode ))
337
+ if err != nil {
338
+ return "" , err
339
+ }
340
+ if _ , err := io .Copy (outFile , tr ); err != nil {
341
+ outFile .Close ()
342
+ return "" , err
343
+ }
344
+ outFile .Close ()
154
345
}
155
346
}
156
347
return extractedFolder , nil
157
348
}
158
349
350
+ func extract (src , dest string ) (string , error ) {
351
+ srcLower := strings .ToLower (src )
352
+ switch {
353
+ case strings .HasSuffix (srcLower , ".tar.gz" ) ||
354
+ strings .HasSuffix (srcLower , ".tgz" ):
355
+ return untgz (src , dest )
356
+ case strings .HasSuffix (srcLower , ".zip" ):
357
+ return unzip (src , dest )
358
+ default :
359
+ return "" , fmt .Errorf ("unsupported file format: %s" , src )
360
+ }
361
+ }
362
+
159
363
func cleanup (root string ) error {
160
364
if _ , err := os .Stat (root ); os .IsNotExist (err ) {
161
365
return nil
0 commit comments