Skip to content

Commit 0e308b0

Browse files
R22627R22627
authored andcommitted
fix zip.Unzip path traversal vulnerability and add some new file utility functions
1 parent 4c79264 commit 0e308b0

4 files changed

Lines changed: 104 additions & 45 deletions

File tree

README.md

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ gbkStr := []byte{0xC4, 0xE3, 0xBA, 0xC3} // 你好 in gbk
2121
utf8Str, _ := GbkToUtf8(gbkStr) // 你好 in utf8
2222
gbkStrRes, _ := Utf8ToGbk(utf8Str) // [196 227 186 195]
2323
```
24+
Some converting function to json.
25+
```go
26+
student := struct {
27+
Hobby string
28+
Age int32
29+
}{
30+
Hobby: "pingpopng",
31+
Age: 28,
32+
}
33+
encoding.ToIndentJSON(&student)
34+
/*
35+
output:
36+
{
37+
"Hobby": "pingpopng",
38+
"Age": 28
39+
}
40+
*/
41+
```
2442
# Net
2543
Some useful functions can be used to handle network. For example you can use `IPv4StrToU32()` transform ipv4 string to uint32 value.
2644

@@ -329,26 +347,38 @@ math.GetRandLowerStr(3) // lts
329347
math.GetRandUpperStr(3) // YUT
330348
```
331349

332-
# JSON
333-
334-
Some converting function to json.
350+
# File
335351

336352
```go
337-
student := struct {
338-
Hobby string
339-
Age int32
340-
}{
341-
Hobby: "pingpopng",
342-
Age: 28,
343-
}
344-
encoding.ToIndentJSON(&student)
345-
/*
346-
output:
347-
{
348-
"Hobby": "pingpopng",
349-
"Age": 28
350-
}
351-
*/
353+
// ListDir lists all the file or directory names in the specified directory.
354+
ListDir()
355+
356+
// IsExist checks whether a file/dir exists.
357+
IsExist()
358+
359+
// IsDir checks whether a path is a directory.
360+
IsDir()
361+
IsDirE()
362+
363+
// IsFile checks whether a path is a file.
364+
IsFile()
365+
IsFileE()
366+
367+
// IsSymlink checks a file whether is a symbolic link file on Linux.
368+
IsSymlink()
369+
IsSymlinkE()
370+
371+
// IsShortcut checks a file whether is a shortcut on Windows.
372+
IsShortcutFile()
373+
374+
// Create a file.
375+
Create()
376+
CreateFile()
377+
378+
// ClearFile clears a file content.
379+
ClearFile()
380+
381+
// ...
352382
```
353383

354384
# Comparison

file/file.go

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func ReadLinesV2(path string) ([]string, int, error) {
5555
}
5656
}
5757

58-
// ListDir lists all the file or dir names in the specified directory.
58+
// ListDir lists all the file or directory names in the specified directory.
5959
// Note that ListDir don't traverse recursively.
6060
func ListDir(dirname string) ([]string, error) {
6161
infos, err := ioutil.ReadDir(dirname)
@@ -69,12 +69,12 @@ func ListDir(dirname string) ([]string, error) {
6969
return names, nil
7070
}
7171

72-
// IsPathExist checks whether a file/dir exists.
72+
// IsExist checks whether a file/dir exists.
7373
// Use os.Stat to get the info of the target file or dir to check whether exists.
7474
// If os.Stat returns nil err, the target exists.
7575
// If os.Stat returns a os.ErrNotExist err, the target does not exist.
7676
// If the error returned is another type, the target is uncertain whether exists.
77-
func IsPathExist(path string) (bool, error) {
77+
func IsExist(path string) (bool, error) {
7878
_, err := os.Stat(path)
7979
if err == nil {
8080
return true, nil
@@ -123,17 +123,19 @@ func IsFileE(path string) (bool, error) {
123123
return false, err
124124
}
125125

126-
// IsSymlink checks a file whether is a symbolic link.
126+
// IsSymlink checks a file whether is a symbolic link on Linux.
127127
// Note that this doesn't work for the shortcut file on windows.
128+
// If you want to check a file whether is a shortcut file on Windows please use IsShortcut function.
128129
func IsSymlink(path string) bool {
129130
if info, err := os.Lstat(path); err == nil && info.Mode()&os.ModeSymlink != 0 {
130131
return true
131132
}
132133
return false
133134
}
134135

135-
// IsSymlinkE checks a file whether is a symbolic link.
136+
// IsSymlinkE checks a file whether is a symbolic link on Linux.
136137
// Note that this doesn't work for the shortcut file on windows.
138+
// If you want to check a file whether is a shortcut file on Windows please use IsShortcut function.
137139
func IsSymlinkE(path string) (bool, error) {
138140
info, err := os.Lstat(path)
139141
if err == nil && info.Mode()&os.ModeSymlink != 0 {
@@ -142,27 +144,37 @@ func IsSymlinkE(path string) (bool, error) {
142144
return false, err
143145
}
144146

147+
// IsShortcut checks a file whether is a shortcut on Windows.
148+
func IsShortcut(path string) bool {
149+
ext := filepath.Ext(path)
150+
if ext == ".lnk" {
151+
return true
152+
}
153+
return false
154+
}
155+
145156
// RemoveFile removes the named file or empty directory.
146-
// https://gist.github.com/novalagung/13c5c8f4d30e0c4bff27
157+
// If there is an error, it will be of type *PathError.
147158
func RemoveFile(path string) error {
148-
err := os.Remove(path)
149-
return err
159+
return os.Remove(path)
150160
}
151161

152162
// Create creates or truncates the target file specified by path.
153-
// If the parent directory does not exist, it will be created with mode os.ModePerm.is cr truncated.
163+
// If the parent directory does not exist, it will be created with mode os.ModePerm.
154164
// If the file does not exist, it is created with mode 0666.
155-
// If successful, methods on the returned File can be used for I/O; the associated file descriptor has mode O_RDWR.
156-
func Create(filePath string) (*os.File, error) {
157-
if exist, err := IsPathExist(filePath); err != nil {
165+
// If successful, methods on the returned file can be used for I/O; the associated file descriptor has mode O_RDWR.
166+
func Create(path string) (*os.File, error) {
167+
exist, err := IsExist(path)
168+
if err != nil {
158169
return nil, err
159-
} else if exist {
160-
return os.Create(filePath)
161170
}
162-
if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil {
171+
if exist {
172+
return os.Create(path)
173+
}
174+
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
163175
return nil, err
164176
}
165-
return os.Create(filePath)
177+
return os.Create(path)
166178
}
167179

168180
// CreateFile creates a file specified by path.
@@ -181,15 +193,16 @@ func FileToBytes(path string) []byte {
181193
return byteStream
182194
}
183195

184-
// BytesToFile writes data to a file. If the file does not exist it will be created with permission mode 0644.
185-
func BytesToFile(filePath string, data []byte) error {
186-
exist, _ := IsPathExist(filePath)
196+
// BytesToFile writes data to a file.
197+
// If the file does not exist it will be created with permission mode 0644.
198+
func BytesToFile(path string, data []byte) error {
199+
exist, _ := IsExist(path)
187200
if !exist {
188-
if err := CreateFile(filePath); err != nil {
201+
if err := CreateFile(path); err != nil {
189202
return err
190203
}
191204
}
192-
return ioutil.WriteFile(filePath, data, 0644)
205+
return ioutil.WriteFile(path, data, 0644)
193206
}
194207

195208
// GetDirAllEntryPaths gets all the file or dir paths in the specified directory recursively.
@@ -260,3 +273,13 @@ func GetDirAllEntryPathsFollowSymlink(dirname string, incl bool) ([]string, erro
260273
}
261274
return paths, nil
262275
}
276+
277+
// ClearFile clears a file content.
278+
func ClearFile(path string) error {
279+
f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0777)
280+
if err != nil {
281+
return err
282+
}
283+
defer f.Close()
284+
return nil
285+
}

zip/unzip.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"path"
88
"path/filepath"
9+
"strings"
910
)
1011

1112
// Unzip decompresses a zip file to specified directory.
@@ -26,8 +27,12 @@ func Unzip(zipPath, dstDir string) error {
2627
}
2728

2829
func unzipFile(file *zip.File, dstDir string) error {
29-
// create the directory of file
30-
filePath := path.Join(dstDir, file.Name)
30+
// Prevent path traversal vulnerability.
31+
// Such as if the file name is "../../../path/to/file.txt" which will be cleaned to "path/to/file.txt".
32+
name := strings.TrimPrefix(filepath.Join(string(filepath.Separator), file.Name), string(filepath.Separator))
33+
filePath := path.Join(dstDir, name)
34+
35+
// Create the directory of file.
3136
if file.FileInfo().IsDir() {
3237
if err := os.MkdirAll(filePath, os.ModePerm); err != nil {
3338
return err
@@ -38,21 +43,21 @@ func unzipFile(file *zip.File, dstDir string) error {
3843
return err
3944
}
4045

41-
// open the file
46+
// Open the file.
4247
r, err := file.Open()
4348
if err != nil {
4449
return err
4550
}
4651
defer r.Close()
4752

48-
// create the file
53+
// Create the file.
4954
w, err := os.Create(filePath)
5055
if err != nil {
5156
return err
5257
}
5358
defer w.Close()
5459

55-
// save the decompressed file content
60+
// Save the decompressed file content.
5661
_, err = io.Copy(w, r)
5762
return err
5863
}

zip/zip.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ import (
2222
// dir
2323
// ├── bar.txt
2424
// └── foo.txt
25-
// Note that if a file is a symbolic link it will be skipped.
25+
// Note that if a file is a symbolic link on Linux it will be skipped.
26+
// If you want to follow a symbolic link please use the function ZipFollowSymlink.
2627
func Zip(zipPath string, paths ...string) error {
2728
// Create zip file and it's parent dir.
2829
if err := os.MkdirAll(filepath.Dir(zipPath), os.ModePerm); err != nil {

0 commit comments

Comments
 (0)