@@ -6,14 +6,30 @@ import (
6
6
"io"
7
7
"os"
8
8
"path/filepath"
9
+ "runtime"
9
10
"strings"
11
+ "time"
10
12
11
13
"github.com/charmbracelet/bubbles/progress"
12
14
"github.com/lithammer/shortuuid"
13
15
"github.com/yorukot/superfile/src/config/icon"
14
16
"golift.io/xtractr"
15
17
)
16
18
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
+
17
33
func extractCompressFile (src , dest string ) error {
18
34
id := shortuuid .New ()
19
35
@@ -26,14 +42,17 @@ func extractCompressFile(src, dest string) error {
26
42
state : inOperation ,
27
43
total : 1 ,
28
44
done : 0 ,
45
+ doneTime : time.Time {},
29
46
}
30
47
message := channelMessage {
31
48
messageId : id ,
32
49
messageType : sendProcess ,
33
50
processNewState : p ,
34
51
}
35
52
53
+ if len (channel ) < 5 {
36
54
channel <- message
55
+ }
37
56
38
57
x := & xtractr.XFile {
39
58
FilePath : src ,
@@ -43,18 +62,24 @@ func extractCompressFile(src, dest string) error {
43
62
_ , _ , _ , err := xtractr .ExtractFile (x )
44
63
45
64
if err != nil {
46
- p .state = successful
65
+ p .state = failure
66
+ p .doneTime = time .Now ()
47
67
message .processNewState = p
68
+ if len (channel ) < 5 {
48
69
channel <- message
70
+ }
71
+ outPutLog (fmt .Sprintf ("Error extracting %s: %v" , src , err ))
49
72
return err
50
73
}
51
74
52
75
p .state = successful
53
76
p .done = 1
54
-
77
+ p . doneTime = time . Now ()
55
78
message .processNewState = p
79
+ if len (channel ) < 5 {
56
80
channel <- message
57
-
81
+ }
82
+
58
83
return nil
59
84
}
60
85
@@ -63,13 +88,14 @@ func unzip(src, dest string) error {
63
88
id := shortuuid .New ()
64
89
r , err := zip .OpenReader (src )
65
90
if err != nil {
66
- return err
91
+ return fmt . Errorf ( "failed to open zip: %w" , err )
67
92
}
68
93
defer func () {
69
94
if err := r .Close (); err != nil {
70
- panic ( err )
95
+ outPutLog ( fmt . Sprintf ( "Error closing zip reader: %v" , err ) )
71
96
}
72
97
}()
98
+
73
99
totalFiles := len (r .File )
74
100
// progressbar
75
101
prog := progress .New (generateGradientColor ())
@@ -81,6 +107,7 @@ func unzip(src, dest string) error {
81
107
state : inOperation ,
82
108
total : totalFiles ,
83
109
done : 0 ,
110
+ doneTime : time.Time {},
84
111
}
85
112
86
113
message := channelMessage {
@@ -94,68 +121,96 @@ func unzip(src, dest string) error {
94
121
95
122
rc , err := f .Open ()
96
123
if err != nil {
97
- return err
124
+ return fmt . Errorf ( "failed to open file in zip: %w" , err )
98
125
}
99
126
defer func () {
100
127
if err := rc .Close (); err != nil {
101
- panic ( err )
128
+ outPutLog ( fmt . Sprintf ( "Error closing file reader: %v" , err ) )
102
129
}
103
130
}()
104
131
105
132
path := filepath .Join (dest , f .Name )
106
133
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 )) {
109
136
return fmt .Errorf ("illegal file path: %s" , path )
110
137
}
111
138
139
+ fileMode := f .Mode ()
112
140
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 )
117
142
if err != nil {
118
- return fmt .Errorf ("error open file : %s " , err )
143
+ return fmt .Errorf ("failed to create directory : %w " , err )
119
144
}
120
- defer func () {
121
- if err := f .Close (); err != nil {
122
- panic (err )
123
- }
124
- }()
145
+ return nil
146
+ }
125
147
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
+ }
127
152
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 )
128
158
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 ))
130
165
}
166
+ }()
167
+
168
+ if _ , err := io .Copy (outFile , rc ); err != nil {
169
+ return fmt .Errorf ("failed to write file content: %w" , err )
131
170
}
171
+
132
172
return nil
133
173
}
134
174
135
175
for _ , f := range r .File {
136
176
p .name = icon .ExtractFile + icon .Space + f .Name
137
- if len (channel ) < 3 {
177
+ if len (channel ) < 5 {
138
178
message .processNewState = p
139
179
channel <- message
140
180
}
181
+
182
+ if shouldSkipFile (f .Name ) {
183
+ p .done ++
184
+ continue
185
+ }
186
+
141
187
err := extractAndWriteFile (f )
142
188
if err != nil {
143
189
p .state = failure
144
190
message .processNewState = p
145
191
channel <- message
146
- return err
192
+ outPutLog (fmt .Sprintf ("Error extracting %s: %v" , f .Name , err ))
193
+ p .done ++
194
+ continue
147
195
}
148
196
p .done ++
149
- if len (channel ) < 3 {
197
+ if len (channel ) < 5 {
150
198
message .processNewState = p
151
- channel <- message
199
+ channel <- message
152
200
}
153
201
}
154
202
155
203
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
+ }
157
210
message .processNewState = p
211
+ if len (channel ) < 5 {
158
212
channel <- message
213
+ }
159
214
160
215
return nil
161
216
}
0 commit comments