Skip to content

Commit c7fce5c

Browse files
authored
Merge pull request #524 from sahinfalcon/fix-file-extract
Fix file extraction
2 parents a24093b + a99e468 commit c7fce5c

File tree

1 file changed

+81
-26
lines changed

1 file changed

+81
-26
lines changed

src/internal/file_operations_extract.go

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,30 @@ import (
66
"io"
77
"os"
88
"path/filepath"
9+
"runtime"
910
"strings"
11+
"time"
1012

1113
"github.com/charmbracelet/bubbles/progress"
1214
"github.com/lithammer/shortuuid"
1315
"github.com/yorukot/superfile/src/config/icon"
1416
"golift.io/xtractr"
1517
)
1618

19+
func getDefaultFileMode() os.FileMode {
20+
if runtime.GOOS == "windows" {
21+
return 0666
22+
}
23+
return 0644
24+
}
25+
26+
func shouldSkipFile(name string) bool {
27+
// Skip system files across platforms
28+
return strings.HasPrefix(name, "__MACOSX/") ||
29+
strings.EqualFold(name, "Thumbs.db") ||
30+
strings.EqualFold(name, "desktop.ini")
31+
}
32+
1733
func extractCompressFile(src, dest string) error {
1834
id := shortuuid.New()
1935

@@ -26,14 +42,17 @@ func extractCompressFile(src, dest string) error {
2642
state: inOperation,
2743
total: 1,
2844
done: 0,
45+
doneTime: time.Time{},
2946
}
3047
message := channelMessage{
3148
messageId: id,
3249
messageType: sendProcess,
3350
processNewState: p,
3451
}
3552

53+
if len(channel) < 5 {
3654
channel <- message
55+
}
3756

3857
x := &xtractr.XFile{
3958
FilePath: src,
@@ -43,18 +62,24 @@ func extractCompressFile(src, dest string) error {
4362
_, _, _, err := xtractr.ExtractFile(x)
4463

4564
if err != nil {
46-
p.state = successful
65+
p.state = failure
66+
p.doneTime = time.Now()
4767
message.processNewState = p
68+
if len(channel) < 5 {
4869
channel <- message
70+
}
71+
outPutLog(fmt.Sprintf("Error extracting %s: %v", src, err))
4972
return err
5073
}
5174

5275
p.state = successful
5376
p.done = 1
54-
77+
p.doneTime = time.Now()
5578
message.processNewState = p
79+
if len(channel) < 5 {
5680
channel <- message
57-
81+
}
82+
5883
return nil
5984
}
6085

@@ -63,13 +88,14 @@ func unzip(src, dest string) error {
6388
id := shortuuid.New()
6489
r, err := zip.OpenReader(src)
6590
if err != nil {
66-
return err
91+
return fmt.Errorf("failed to open zip: %w", err)
6792
}
6893
defer func() {
6994
if err := r.Close(); err != nil {
70-
panic(err)
95+
outPutLog(fmt.Sprintf("Error closing zip reader: %v", err))
7196
}
7297
}()
98+
7399
totalFiles := len(r.File)
74100
// progressbar
75101
prog := progress.New(generateGradientColor())
@@ -81,6 +107,7 @@ func unzip(src, dest string) error {
81107
state: inOperation,
82108
total: totalFiles,
83109
done: 0,
110+
doneTime: time.Time{},
84111
}
85112

86113
message := channelMessage{
@@ -94,68 +121,96 @@ func unzip(src, dest string) error {
94121

95122
rc, err := f.Open()
96123
if err != nil {
97-
return err
124+
return fmt.Errorf("failed to open file in zip: %w", err)
98125
}
99126
defer func() {
100127
if err := rc.Close(); err != nil {
101-
panic(err)
128+
outPutLog(fmt.Sprintf("Error closing file reader: %v", err))
102129
}
103130
}()
104131

105132
path := filepath.Join(dest, f.Name)
106133

107-
// Check for ZipSlip (Directory traversal)
108-
if !strings.HasPrefix(path, filepath.Clean(dest)+string(os.PathSeparator)) {
134+
// Cross-platform path security check
135+
if !strings.HasPrefix(filepath.Clean(path), filepath.Clean(dest)+string(os.PathSeparator)) {
109136
return fmt.Errorf("illegal file path: %s", path)
110137
}
111138

139+
fileMode := f.Mode()
112140
if f.FileInfo().IsDir() {
113-
os.MkdirAll(path, f.Mode())
114-
} else {
115-
os.MkdirAll(filepath.Dir(path), f.Mode())
116-
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
141+
err := os.MkdirAll(path, fileMode)
117142
if err != nil {
118-
return fmt.Errorf("error open file: %s", err)
143+
return fmt.Errorf("failed to create directory: %w", err)
119144
}
120-
defer func() {
121-
if err := f.Close(); err != nil {
122-
panic(err)
123-
}
124-
}()
145+
return nil
146+
}
125147

126-
_, err = io.Copy(f, rc)
148+
// Create directory structure
149+
if err := os.MkdirAll(filepath.Dir(path), fileMode); err != nil {
150+
return fmt.Errorf("failed to create parent directory: %w", err)
151+
}
127152

153+
// Try default permissions first
154+
outFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, getDefaultFileMode())
155+
if err != nil {
156+
// Fall back to original file permissions
157+
outFile, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileMode)
128158
if err != nil {
129-
return fmt.Errorf("error copy file: %s", err)
159+
return fmt.Errorf("failed to create file: %w", err)
160+
}
161+
}
162+
defer func() {
163+
if err := outFile.Close(); err != nil {
164+
outPutLog(fmt.Sprintf("Error closing output file %s: %v", path, err))
130165
}
166+
}()
167+
168+
if _, err := io.Copy(outFile, rc); err != nil {
169+
return fmt.Errorf("failed to write file content: %w", err)
131170
}
171+
132172
return nil
133173
}
134174

135175
for _, f := range r.File {
136176
p.name = icon.ExtractFile + icon.Space + f.Name
137-
if len(channel) < 3 {
177+
if len(channel) < 5 {
138178
message.processNewState = p
139179
channel <- message
140180
}
181+
182+
if shouldSkipFile(f.Name) {
183+
p.done++
184+
continue
185+
}
186+
141187
err := extractAndWriteFile(f)
142188
if err != nil {
143189
p.state = failure
144190
message.processNewState = p
145191
channel <- message
146-
return err
192+
outPutLog(fmt.Sprintf("Error extracting %s: %v", f.Name, err))
193+
p.done++
194+
continue
147195
}
148196
p.done++
149-
if len(channel) < 3 {
197+
if len(channel) < 5 {
150198
message.processNewState = p
151-
channel <- message
199+
channel <- message
152200
}
153201
}
154202

155203
p.total = totalFiles
156-
p.state = successful
204+
p.doneTime = time.Now()
205+
if p.done == totalFiles {
206+
p.state = successful
207+
} else {
208+
p.state = failure
209+
}
157210
message.processNewState = p
211+
if len(channel) < 5 {
158212
channel <- message
213+
}
159214

160215
return nil
161216
}

0 commit comments

Comments
 (0)