Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 9bfb10d

Browse files
authored
chore: extracting compressed files to correct location (#1823)
1 parent 0a5ea37 commit 9bfb10d

File tree

1 file changed

+250
-9
lines changed

1 file changed

+250
-9
lines changed

component/updater/update_ui.go

+250-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package updater
22

33
import (
4+
"archive/tar"
45
"archive/zip"
6+
"compress/gzip"
57
"fmt"
68
"io"
79
"os"
@@ -22,6 +24,14 @@ type UIUpdater struct {
2224
mutex sync.Mutex
2325
}
2426

27+
type compressionType int
28+
29+
const (
30+
typeUnknown compressionType = iota
31+
typeZip
32+
typeTarGzip
33+
)
34+
2535
var DefaultUiUpdater = &UIUpdater{}
2636

2737
func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater {
@@ -70,6 +80,24 @@ func (u *UIUpdater) DownloadUI() error {
7080
return u.downloadUI()
7181
}
7282

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+
73101
func (u *UIUpdater) downloadUI() error {
74102
err := u.prepareUIPath()
75103
if err != nil {
@@ -78,12 +106,23 @@ func (u *UIUpdater) downloadUI() error {
78106

79107
data, err := downloadForBytes(u.externalUIURL)
80108
if err != nil {
81-
return fmt.Errorf("can't download file: %w", err)
109+
return fmt.Errorf("can't download file: %w", err)
110+
}
111+
112+
fileType := detectFileType(data)
113+
if fileType == typeUnknown {
114+
return fmt.Errorf("unknown or unsupported file type")
82115
}
83116

84-
saved := path.Join(C.Path.HomeDir(), "download.zip")
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)
85124
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)
87126
}
88127
defer os.Remove(saved)
89128

@@ -94,12 +133,12 @@ func (u *UIUpdater) downloadUI() error {
94133
}
95134
}
96135

97-
unzipFolder, err := unzip(saved, C.Path.HomeDir())
136+
extractedFolder, err := extract(saved, C.Path.HomeDir())
98137
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)
100139
}
101140

102-
err = os.Rename(unzipFolder, u.externalUIPath)
141+
err = os.Rename(extractedFolder, u.externalUIPath)
103142
if err != nil {
104143
return fmt.Errorf("rename UI folder failed: %w", err)
105144
}
@@ -122,9 +161,66 @@ func unzip(src, dest string) (string, error) {
122161
return "", err
123162
}
124163
defer r.Close()
164+
165+
// check whether or not only exists singleRoot dir
166+
rootDir := ""
167+
isSingleRoot := true
168+
rootItemCount := 0
169+
for _, f := range r.File {
170+
parts := strings.Split(strings.Trim(f.Name, "/"), "/")
171+
if len(parts) == 0 {
172+
continue
173+
}
174+
175+
if len(parts) == 1 {
176+
isDir := strings.HasSuffix(f.Name, "/")
177+
if !isDir {
178+
isSingleRoot = false
179+
break
180+
}
181+
182+
if rootDir == "" {
183+
rootDir = parts[0]
184+
}
185+
rootItemCount++
186+
}
187+
}
188+
189+
if rootItemCount != 1 {
190+
isSingleRoot = false
191+
}
192+
193+
// build the dir of extraction
125194
var extractedFolder string
195+
if isSingleRoot && rootDir != "" {
196+
// if the singleRoot, use it directly
197+
log.Debugln("Match the singleRoot")
198+
extractedFolder = filepath.Join(dest, rootDir)
199+
log.Debugln("extractedFolder: %s", extractedFolder)
200+
} else {
201+
log.Debugln("Match the multiRoot")
202+
// or put the files/dirs into new dir
203+
baseName := filepath.Base(src)
204+
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
205+
extractedFolder = filepath.Join(dest, baseName)
206+
207+
for i := 1; ; i++ {
208+
if _, err := os.Stat(extractedFolder); os.IsNotExist(err) {
209+
break
210+
}
211+
extractedFolder = filepath.Join(dest, fmt.Sprintf("%s_%d", baseName, i))
212+
}
213+
log.Debugln("extractedFolder: %s", extractedFolder)
214+
}
215+
126216
for _, f := range r.File {
127-
fpath := filepath.Join(dest, f.Name)
217+
var fpath string
218+
if isSingleRoot && rootDir != "" {
219+
fpath = filepath.Join(dest, f.Name)
220+
} else {
221+
fpath = filepath.Join(extractedFolder, f.Name)
222+
}
223+
128224
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
129225
return "", fmt.Errorf("invalid file path: %s", fpath)
130226
}
@@ -149,13 +245,158 @@ func unzip(src, dest string) (string, error) {
149245
if err != nil {
150246
return "", err
151247
}
152-
if extractedFolder == "" {
153-
extractedFolder = filepath.Dir(fpath)
248+
}
249+
return extractedFolder, nil
250+
}
251+
252+
func untgz(src, dest string) (string, error) {
253+
file, err := os.Open(src)
254+
if err != nil {
255+
return "", err
256+
}
257+
defer file.Close()
258+
259+
gzr, err := gzip.NewReader(file)
260+
if err != nil {
261+
return "", err
262+
}
263+
defer gzr.Close()
264+
265+
tr := tar.NewReader(gzr)
266+
267+
rootDir := ""
268+
isSingleRoot := true
269+
rootItemCount := 0
270+
for {
271+
header, err := tr.Next()
272+
if err == io.EOF {
273+
break
274+
}
275+
if err != nil {
276+
return "", err
277+
}
278+
279+
parts := strings.Split(cleanTarPath(header.Name), string(os.PathSeparator))
280+
if len(parts) == 0 {
281+
continue
282+
}
283+
284+
if len(parts) == 1 {
285+
isDir := header.Typeflag == tar.TypeDir
286+
if !isDir {
287+
isSingleRoot = false
288+
break
289+
}
290+
291+
if rootDir == "" {
292+
rootDir = parts[0]
293+
}
294+
rootItemCount++
295+
}
296+
}
297+
298+
if rootItemCount != 1 {
299+
isSingleRoot = false
300+
}
301+
302+
file.Seek(0, 0)
303+
gzr, _ = gzip.NewReader(file)
304+
tr = tar.NewReader(gzr)
305+
306+
var extractedFolder string
307+
if isSingleRoot && rootDir != "" {
308+
log.Debugln("Match the singleRoot")
309+
extractedFolder = filepath.Join(dest, rootDir)
310+
log.Debugln("extractedFolder: %s", extractedFolder)
311+
} else {
312+
log.Debugln("Match the multiRoot")
313+
baseName := filepath.Base(src)
314+
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
315+
baseName = strings.TrimSuffix(baseName, ".tar")
316+
extractedFolder = filepath.Join(dest, baseName)
317+
318+
for i := 1; ; i++ {
319+
if _, err := os.Stat(extractedFolder); os.IsNotExist(err) {
320+
break
321+
}
322+
extractedFolder = filepath.Join(dest, fmt.Sprintf("%s_%d", baseName, i))
323+
}
324+
log.Debugln("extractedFolder: %s", extractedFolder)
325+
}
326+
327+
for {
328+
header, err := tr.Next()
329+
if err == io.EOF {
330+
break
331+
}
332+
if err != nil {
333+
return "", err
334+
}
335+
336+
var fpath string
337+
if isSingleRoot && rootDir != "" {
338+
fpath = filepath.Join(dest, cleanTarPath(header.Name))
339+
} else {
340+
fpath = filepath.Join(extractedFolder, cleanTarPath(header.Name))
341+
}
342+
343+
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
344+
return "", fmt.Errorf("invalid file path: %s", fpath)
345+
}
346+
347+
switch header.Typeflag {
348+
case tar.TypeDir:
349+
if err = os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil {
350+
return "", err
351+
}
352+
case tar.TypeReg:
353+
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
354+
return "", err
355+
}
356+
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
357+
if err != nil {
358+
return "", err
359+
}
360+
if _, err := io.Copy(outFile, tr); err != nil {
361+
outFile.Close()
362+
return "", err
363+
}
364+
outFile.Close()
154365
}
155366
}
156367
return extractedFolder, nil
157368
}
158369

370+
func extract(src, dest string) (string, error) {
371+
srcLower := strings.ToLower(src)
372+
switch {
373+
case strings.HasSuffix(srcLower, ".tar.gz") ||
374+
strings.HasSuffix(srcLower, ".tgz"):
375+
return untgz(src, dest)
376+
case strings.HasSuffix(srcLower, ".zip"):
377+
return unzip(src, dest)
378+
default:
379+
return "", fmt.Errorf("unsupported file format: %s", src)
380+
}
381+
}
382+
383+
func cleanTarPath(path string) string {
384+
// remove prefix ./ or ../
385+
path = strings.TrimPrefix(path, "./")
386+
path = strings.TrimPrefix(path, "../")
387+
388+
// normalize path
389+
path = filepath.Clean(path)
390+
391+
// transfer delimiters to system std
392+
path = filepath.FromSlash(path)
393+
394+
// remove prefix path delimiters
395+
path = strings.TrimPrefix(path, string(os.PathSeparator))
396+
397+
return path
398+
}
399+
159400
func cleanup(root string) error {
160401
if _, err := os.Stat(root); os.IsNotExist(err) {
161402
return nil

0 commit comments

Comments
 (0)